├── favicon.ico
├── README.md
├── src
├── audio
│ ├── song.mp3
│ ├── song.webm
│ ├── test.mp3
│ ├── test.webm
│ ├── doorOpen.mp3
│ ├── knocking.mp3
│ ├── lightHum.mp3
│ ├── outside.mp3
│ ├── outside.webm
│ ├── doorOpen.webm
│ ├── footstep1.mp3
│ ├── footstep1.webm
│ ├── footstep2.mp3
│ ├── footstep2.webm
│ ├── footstep3.mp3
│ ├── footstep3.webm
│ ├── footstep4.mp3
│ ├── footstep4.webm
│ ├── footstep5.mp3
│ ├── footstep5.webm
│ ├── itemPickup.mp3
│ ├── itemPickup.webm
│ ├── knocking.webm
│ ├── lightHum.webm
│ ├── slidingDoorClose.mp3
│ ├── slidingDoorOpen.mp3
│ ├── slidingDoorOpen.webm
│ └── slidingDoorClose.webm
├── assets
│ ├── items
│ │ ├── note.png
│ │ ├── apple.png
│ │ ├── bread.png
│ │ ├── stand.png
│ │ ├── pickaxe.png
│ │ ├── redbull.png
│ │ └── barrelItem.png
│ ├── objects
│ │ ├── elmo.png
│ │ ├── barrel.png
│ │ ├── radio.png
│ │ ├── redbull.png
│ │ └── table.png
│ ├── skyBoxes
│ │ └── sky.jpg
│ ├── walls
│ │ ├── wall1.png
│ │ ├── wall2.png
│ │ ├── wall3.png
│ │ ├── wall4.png
│ │ ├── doubleDoorOpen.png
│ │ ├── doubleDoor2Open.png
│ │ ├── doubleDoorClosed.png
│ │ └── doubleDoor2Closed.png
│ ├── floors
│ │ ├── floor1.png
│ │ ├── floor2.png
│ │ ├── floor3.png
│ │ ├── floor4.png
│ │ ├── floor5.png
│ │ └── floor6.png
│ ├── thinWalls
│ │ ├── fence.png
│ │ └── slidingDoor.png
│ ├── ceilings
│ │ ├── ceiling1.png
│ │ └── ceiling2.png
│ ├── paintings
│ │ ├── shield.png
│ │ ├── painting1.png
│ │ ├── painting10.png
│ │ ├── painting11.png
│ │ ├── painting12.png
│ │ ├── painting13.png
│ │ ├── painting14.png
│ │ ├── painting15.png
│ │ ├── painting8.png
│ │ ├── painting9.png
│ │ ├── painting2one.png
│ │ ├── painting2two.png
│ │ ├── painting3one.png
│ │ ├── painting3two.png
│ │ ├── painting4one.png
│ │ ├── painting4two.png
│ │ ├── painting5one.png
│ │ ├── painting5two.png
│ │ ├── painting6one.png
│ │ ├── painting6two.png
│ │ ├── painting7one.png
│ │ ├── painting7two.png
│ │ └── bloodyHandprint.png
│ └── lightSources
│ │ ├── chandelier.png
│ │ ├── hangingLight.png
│ │ ├── hangingLight2.png
│ │ └── standingLight.png
├── index.js
├── audio.js
├── hud.js
├── actions.js
├── maps.js
└── engine.js
├── package.json
├── utils
└── calc.js
├── index.html
└── index.css
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/favicon.ico
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Note
2 | If trying on Chrome, make sure "vulkan" is disabled in chrome://flags
3 |
--------------------------------------------------------------------------------
/src/audio/song.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/song.mp3
--------------------------------------------------------------------------------
/src/audio/song.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/song.webm
--------------------------------------------------------------------------------
/src/audio/test.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/test.mp3
--------------------------------------------------------------------------------
/src/audio/test.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/test.webm
--------------------------------------------------------------------------------
/src/audio/doorOpen.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/doorOpen.mp3
--------------------------------------------------------------------------------
/src/audio/knocking.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/knocking.mp3
--------------------------------------------------------------------------------
/src/audio/lightHum.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/lightHum.mp3
--------------------------------------------------------------------------------
/src/audio/outside.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/outside.mp3
--------------------------------------------------------------------------------
/src/audio/outside.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/outside.webm
--------------------------------------------------------------------------------
/src/assets/items/note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/items/note.png
--------------------------------------------------------------------------------
/src/audio/doorOpen.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/doorOpen.webm
--------------------------------------------------------------------------------
/src/audio/footstep1.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep1.mp3
--------------------------------------------------------------------------------
/src/audio/footstep1.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep1.webm
--------------------------------------------------------------------------------
/src/audio/footstep2.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep2.mp3
--------------------------------------------------------------------------------
/src/audio/footstep2.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep2.webm
--------------------------------------------------------------------------------
/src/audio/footstep3.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep3.mp3
--------------------------------------------------------------------------------
/src/audio/footstep3.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep3.webm
--------------------------------------------------------------------------------
/src/audio/footstep4.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep4.mp3
--------------------------------------------------------------------------------
/src/audio/footstep4.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep4.webm
--------------------------------------------------------------------------------
/src/audio/footstep5.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep5.mp3
--------------------------------------------------------------------------------
/src/audio/footstep5.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/footstep5.webm
--------------------------------------------------------------------------------
/src/audio/itemPickup.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/itemPickup.mp3
--------------------------------------------------------------------------------
/src/audio/itemPickup.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/itemPickup.webm
--------------------------------------------------------------------------------
/src/audio/knocking.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/knocking.webm
--------------------------------------------------------------------------------
/src/audio/lightHum.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/lightHum.webm
--------------------------------------------------------------------------------
/src/assets/items/apple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/items/apple.png
--------------------------------------------------------------------------------
/src/assets/items/bread.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/items/bread.png
--------------------------------------------------------------------------------
/src/assets/items/stand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/items/stand.png
--------------------------------------------------------------------------------
/src/assets/objects/elmo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/objects/elmo.png
--------------------------------------------------------------------------------
/src/assets/skyBoxes/sky.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/skyBoxes/sky.jpg
--------------------------------------------------------------------------------
/src/assets/walls/wall1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/walls/wall1.png
--------------------------------------------------------------------------------
/src/assets/walls/wall2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/walls/wall2.png
--------------------------------------------------------------------------------
/src/assets/walls/wall3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/walls/wall3.png
--------------------------------------------------------------------------------
/src/assets/walls/wall4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/walls/wall4.png
--------------------------------------------------------------------------------
/src/assets/floors/floor1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/floors/floor1.png
--------------------------------------------------------------------------------
/src/assets/floors/floor2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/floors/floor2.png
--------------------------------------------------------------------------------
/src/assets/floors/floor3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/floors/floor3.png
--------------------------------------------------------------------------------
/src/assets/floors/floor4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/floors/floor4.png
--------------------------------------------------------------------------------
/src/assets/floors/floor5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/floors/floor5.png
--------------------------------------------------------------------------------
/src/assets/floors/floor6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/floors/floor6.png
--------------------------------------------------------------------------------
/src/assets/items/pickaxe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/items/pickaxe.png
--------------------------------------------------------------------------------
/src/assets/items/redbull.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/items/redbull.png
--------------------------------------------------------------------------------
/src/assets/objects/barrel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/objects/barrel.png
--------------------------------------------------------------------------------
/src/assets/objects/radio.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/objects/radio.png
--------------------------------------------------------------------------------
/src/assets/objects/redbull.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/objects/redbull.png
--------------------------------------------------------------------------------
/src/assets/objects/table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/objects/table.png
--------------------------------------------------------------------------------
/src/assets/thinWalls/fence.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/thinWalls/fence.png
--------------------------------------------------------------------------------
/src/audio/slidingDoorClose.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/slidingDoorClose.mp3
--------------------------------------------------------------------------------
/src/audio/slidingDoorOpen.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/slidingDoorOpen.mp3
--------------------------------------------------------------------------------
/src/audio/slidingDoorOpen.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/slidingDoorOpen.webm
--------------------------------------------------------------------------------
/src/assets/ceilings/ceiling1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/ceilings/ceiling1.png
--------------------------------------------------------------------------------
/src/assets/ceilings/ceiling2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/ceilings/ceiling2.png
--------------------------------------------------------------------------------
/src/assets/items/barrelItem.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/items/barrelItem.png
--------------------------------------------------------------------------------
/src/assets/paintings/shield.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/shield.png
--------------------------------------------------------------------------------
/src/audio/slidingDoorClose.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/audio/slidingDoorClose.webm
--------------------------------------------------------------------------------
/src/assets/paintings/painting1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting1.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting10.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting11.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting12.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting12.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting13.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting13.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting14.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting15.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting8.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting9.png
--------------------------------------------------------------------------------
/src/assets/walls/doubleDoorOpen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/walls/doubleDoorOpen.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting2one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting2one.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting2two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting2two.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting3one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting3one.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting3two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting3two.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting4one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting4one.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting4two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting4two.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting5one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting5one.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting5two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting5two.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting6one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting6one.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting6two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting6two.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting7one.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting7one.png
--------------------------------------------------------------------------------
/src/assets/paintings/painting7two.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/painting7two.png
--------------------------------------------------------------------------------
/src/assets/thinWalls/slidingDoor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/thinWalls/slidingDoor.png
--------------------------------------------------------------------------------
/src/assets/walls/doubleDoor2Open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/walls/doubleDoor2Open.png
--------------------------------------------------------------------------------
/src/assets/walls/doubleDoorClosed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/walls/doubleDoorClosed.png
--------------------------------------------------------------------------------
/src/assets/lightSources/chandelier.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/lightSources/chandelier.png
--------------------------------------------------------------------------------
/src/assets/lightSources/hangingLight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/lightSources/hangingLight.png
--------------------------------------------------------------------------------
/src/assets/paintings/bloodyHandprint.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/paintings/bloodyHandprint.png
--------------------------------------------------------------------------------
/src/assets/walls/doubleDoor2Closed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/walls/doubleDoor2Closed.png
--------------------------------------------------------------------------------
/src/assets/lightSources/hangingLight2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/lightSources/hangingLight2.png
--------------------------------------------------------------------------------
/src/assets/lightSources/standingLight.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtkyber/raycaster-game/HEAD/src/assets/lightSources/standingLight.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "raycaster-game",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "howler": "^2.2.4"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/utils/calc.js:
--------------------------------------------------------------------------------
1 | export function degToRad(deg) {
2 | return deg * (Math.PI / 180);
3 | }
4 |
5 | export function radToDeg(rad) {
6 | return rad * (180 / Math.PI);
7 | }
8 |
9 | export function getIntersection(x, y, r, theta, x1, y1, x2, y2, p4) {
10 | const x3 = x;
11 | const y3 = y;
12 | let x4;
13 | let y4;
14 | let uMax = Infinity;
15 |
16 | if (p4?.x && p4?.y) {
17 | x4 = p4.x;
18 | y4 = p4.y;
19 | uMax = 1;
20 | } else {
21 | x4 = x + r * Math.cos(theta);
22 | y4 = y + r * Math.sin(theta);
23 | }
24 |
25 | const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
26 |
27 | if (denom == 0) {
28 | return;
29 | }
30 |
31 | const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom;
32 | const u = ((x1 - x3) * (y1 - y2) - (y1 - y3) * (x1 - x2)) / denom;
33 |
34 | if (t > 0 && t < 1 && u > 0 && u <= uMax) {
35 | const px = x3 + u * (x4 - x3);
36 | const py = y3 + u * (y4 - y3);
37 | return [px, py];
38 | } else {
39 | return;
40 | }
41 | }
42 |
43 | export function convertDeg0To360(deg) {
44 | return ((deg % 360) + 360) % 360;
45 | }
46 |
47 | export function getPerpCoords(playerX, playerY, x, y, halfLen) {
48 | const slope = (y - playerY) / (x - playerX);
49 | const perpSlope = -(1 / slope);
50 | const angle = Math.atan(perpSlope);
51 | const x1 = x + halfLen * Math.cos(angle);
52 | const y1 = y + halfLen * Math.sin(angle);
53 | const x2 = x - halfLen * Math.cos(angle);
54 | const y2 = y - halfLen * Math.sin(angle);
55 | return [x1, y1, x2, y2];
56 | }
57 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Ray Casting Engine
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Processing Lighting Effects
18 |
This data will be saved until the next update
19 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | -webkit-user-select: none;
6 | -moz-user-select: none;
7 | -ms-user-select: none;
8 | user-select: none;
9 | font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
10 | }
11 |
12 | body {
13 | width: 100vw;
14 | height: 100vh;
15 | background-color: rgba(0, 0, 0, 1);
16 | overflow: hidden;
17 | }
18 |
19 | .container {
20 | width: 100%;
21 | height: 100%;
22 | display: flex;
23 | flex-flow: column nowrap;
24 | justify-content: center;
25 | align-items: center;
26 | gap: 1rem;
27 | }
28 |
29 | .loadingContainer {
30 | position: fixed;
31 | top: 0;
32 | left: 0;
33 | z-index: 1000;
34 | width: 100vw;
35 | height: 100vh;
36 | display: none;
37 | justify-content: center;
38 | align-items: center;
39 | flex-flow: column nowrap;
40 | }
41 | .loadingText {
42 | color: white;
43 | font-weight: 200;
44 | }
45 | .loadingTextSmall {
46 | color: white;
47 | font-weight: 800;
48 | }
49 | .loadingBar {
50 | position: relative;
51 | margin-top: 1rem;
52 | border: 1px solid white;
53 | background-color: rgba(255, 255, 255, 0.4);
54 | width: 10rem;
55 | height: 1.5rem;
56 | border-radius: 0.3rem;
57 | overflow: hidden;
58 | }
59 | .loadingFill {
60 | position: absolute;
61 | left: 0;
62 | top: 0;
63 | background-color: white;
64 | width: 0;
65 | height: 100%;
66 | z-index: -1;
67 | }
68 | .loadingValue {
69 | width: 100%;
70 | height: 100%;
71 | display: flex;
72 | justify-content: center;
73 | align-items: center;
74 | color: black;
75 | }
76 |
77 | #canvas {
78 | position: relative;
79 | /* border: 1px solid rgba(255, 255, 255, 0.4); */
80 | height: 50%;
81 | width: auto;
82 | background-color: black;
83 | box-shadow: 0.5rem 0.5rem 1.5rem 0 rgba(0, 0, 0, 0.5);
84 | z-index: 2;
85 | margin: 0rem;
86 | aspect-ratio: 16/9;
87 | display: block;
88 | }
89 | #canvas.fullscreen {
90 | border: none;
91 | box-shadow: none;
92 | max-width: 1920px;
93 | max-height: 1080px;
94 | }
95 |
96 | #debugCanvas {
97 | position: relative;
98 | background-color: rgba(0, 0, 0, 1);
99 | box-shadow: 0.5rem 0.5rem 1.5rem 0 rgba(0, 0, 0, 0.5);
100 | z-index: 1;
101 | display: block;
102 | height: 50%;
103 | width: auto;
104 | }
105 |
106 | @media (min-aspect-ratio: 16/9) {
107 | #canvas.fullscreen {
108 | width: auto;
109 | height: 100%;
110 | }
111 | }
112 | @media (max-aspect-ratio: 16/9) {
113 | #canvas.fullscreen {
114 | width: 100%;
115 | height: auto;
116 | }
117 | }
118 | @media (min-aspect-ratio: 2/1) {
119 | .container {
120 | flex-flow: row nowrap;
121 | justify-content: center;
122 | margin-top: 0;
123 | }
124 |
125 | #canvas {
126 | width: 70%;
127 | height: auto;
128 | }
129 |
130 | #debugCanvas {
131 | width: 30%;
132 | height: auto;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Actions from './actions.js';
2 | import Sound from './audio.js';
3 | import Engine from './engine.js';
4 | import Hud from './hud.js';
5 |
6 | const request = window.indexedDB.open('RaycasterDB', 3);
7 |
8 | let audio;
9 | let engine;
10 | let hud;
11 | let actions;
12 |
13 | const fpsInterval = 1000 / 60;
14 | let animationFrameId;
15 | let deltaTime = 0;
16 | let timestamp = 0;
17 |
18 | const alterOffscreenCanvasPixels = () => {
19 | engine.update();
20 | engine.ctx.putImageData(engine.offscreenCanvasPixels, 0, 0);
21 | };
22 |
23 | const drawOntoCanvas = () => {
24 | hud.drawReticle();
25 | engine.fade();
26 | hud.drawFps();
27 | if (engine.inventoryOpen) {
28 | hud.drawInventory();
29 | if (hud.inventoryIndexSelected !== null) hud.drawSelectedInventoryItem();
30 | }
31 |
32 | if (engine.consoleValues.length) hud.drawEngineConsole(engine.consoleValues);
33 | };
34 |
35 | const gameLoop = () => {
36 | timestamp = Date.now();
37 |
38 | if (engine.isCrouching) engine.fPlayerMoveSpeed *= 0.5;
39 |
40 | alterOffscreenCanvasPixels();
41 | drawOntoCanvas();
42 | actions.runNextFunction();
43 | if (engine.inventoryOpen) hud.drawCursor();
44 | else {
45 | hud.cursorX = engine.canvasWidth / 2;
46 | hud.cursorY = engine.canvasHeight / 2;
47 | hud.inventoryIndexSelected = null;
48 | hud.itemCanBePlaced = false;
49 | hud.itemOutsideOfInventory = false;
50 | }
51 | deltaTime = Date.now() - timestamp;
52 |
53 | engine.fGameSpeed = (deltaTime / fpsInterval) * 2.5;
54 | engine.fPlayerMoveSpeed = (deltaTime / fpsInterval) * 4;
55 |
56 | animationFrameId = requestAnimationFrame(gameLoop);
57 | };
58 |
59 | const beginLoop = () => {
60 | gameLoop();
61 |
62 | setInterval(() => {
63 | hud.framesCounted = ~~(1000 / deltaTime);
64 | }, 100);
65 | };
66 |
67 | const setUp = async db => {
68 | audio = new Sound(db);
69 | engine = new Engine(audio, db);
70 | hud = new Hud(engine, audio, db);
71 | actions = new Actions(engine, audio, db);
72 |
73 | const promise = engine.init();
74 | Promise.resolve(promise).then(() => {
75 | document.querySelector('.loadingContainer').remove();
76 | actions.init();
77 | // audio.init();
78 |
79 | hud.frameRate = 0;
80 |
81 | beginLoop();
82 | });
83 | };
84 |
85 | document.onmousemove = e => {
86 | if (engine?.inventoryOpen && engine?.userIsInTab) {
87 | const newX = hud.cursorX + e.movementX / 4;
88 | const newY = hud.cursorY + e.movementY / 4;
89 |
90 | if (newX >= 0 && newX <= engine.canvasWidth && newY >= 0 && newY <= engine.canvasHeight) {
91 | hud.cursorX += e.movementX / 3;
92 | hud.cursorY += e.movementY / 3;
93 | }
94 | }
95 | };
96 |
97 | document.onmousedown = e => {
98 | if (engine?.inventoryOpen && engine?.userIsInTab) {
99 | if (hud.inventoryIndexSelected === null) hud.setItemSelected();
100 | }
101 | };
102 |
103 | document.onmouseup = e => {
104 | if (engine?.inventoryOpen && engine?.userIsInTab) {
105 | if (hud.inventoryIndexSelected !== null) hud.placeSelectedInventoryItem();
106 | }
107 | };
108 |
109 | request.onerror = e => {
110 | console.log(e.target.error);
111 | };
112 |
113 | request.onupgradeneeded = e => {
114 | if (!e.target.result.objectStoreNames.contains('lighting')) {
115 | e.target.result.createObjectStore('lighting', { autoIncrement: true });
116 | }
117 | if (!e.target.result.objectStoreNames.contains('lightingVersion')) {
118 | e.target.result.createObjectStore('lightingVersion', { autoIncrement: true });
119 | }
120 | };
121 |
122 | request.onsuccess = e => {
123 | setUp(e.target.result);
124 | };
125 |
--------------------------------------------------------------------------------
/src/audio.js:
--------------------------------------------------------------------------------
1 | import { convertDeg0To360, degToRad, radToDeg } from '../utils/calc.js';
2 | import { maps } from './maps.js';
3 |
4 | export default class Sound {
5 | constructor(db) {
6 | this.db = db;
7 | this.soundPaths = [
8 | // './src/audio/song.mp3',
9 | './src/audio/song',
10 | './src/audio/outside',
11 | './src/audio/test',
12 | './src/audio/slidingDoorOpen',
13 | './src/audio/slidingDoorClose',
14 | './src/audio/footstep1',
15 | './src/audio/footstep2',
16 | './src/audio/footstep3',
17 | './src/audio/footstep4',
18 | './src/audio/footstep5',
19 | './src/audio/itemPickup',
20 | './src/audio/doorOpen',
21 | './src/audio/knocking',
22 | './src/audio/lightHum',
23 | ];
24 | this.sounds = {};
25 | this.soundsPlaying = [];
26 | }
27 |
28 | updateSoundPositions(pAng, px, py) {
29 | for (let i = 0; i < this.soundsPlaying.length; i++) {
30 | const name = this.soundsPlaying[i].name;
31 | if (this.soundsPlaying[i].hasPanning) {
32 | const x = this.soundsPlaying[i].x;
33 | const y = this.soundsPlaying[i].y;
34 | const dx = Math.abs(px - x);
35 | const dy = Math.abs(py - y);
36 | const d = Math.sqrt(dx * dx + dy * dy);
37 |
38 | let playerAng = convertDeg0To360(pAng);
39 | let sourceAngle = radToDeg(Math.atan2(py - y, px - x)) + 180;
40 |
41 | let angleDiff = ((sourceAngle - playerAng + 180 + 360) % 360) - 180;
42 | const angleDiffAbs = Math.abs(angleDiff);
43 |
44 | let newX = d * Math.cos(Math.PI / 2 - degToRad(angleDiffAbs));
45 | if (angleDiff < 0) newX *= -1;
46 | newX;
47 | let newY = -30;
48 | let newZ = d;
49 | if (angleDiffAbs > 90) newZ = newZ + (newZ * angleDiffAbs) / 500;
50 | this.sounds[name].pos(newX, newY, newZ);
51 | }
52 | }
53 | }
54 |
55 | stopSound(name) {
56 | this.sounds[name].stop();
57 | for (let i = 0; i < this.soundsPlaying.length; i++) {
58 | if (this.soundsPlaying[name] === name) this.soundsPlaying.splice(i, 1);
59 | }
60 | }
61 |
62 | playSound(name, x, y, hasPanning, i = '') {
63 | this.sounds[name + i].play();
64 | this.soundsPlaying.push({
65 | name: name + i,
66 | x,
67 | y,
68 | hasPanning,
69 | });
70 | }
71 |
72 | init(i = 0) {
73 | Howler.unload();
74 | this.sounds = {};
75 | this.soundsPlaying = [];
76 | for (let j = 0; j < this.soundPaths.length; j++) {
77 | const name = this.soundPaths[j].split('/').pop()?.split('.')[0];
78 | let rate = 1;
79 | let html5 = false;
80 | let autoplay = false;
81 | let volume = 1;
82 | let loop = false;
83 |
84 | if (name.includes('footstep')) {
85 | volume = 1.1;
86 | } else if (name.includes('itemPickup')) {
87 | volume = 0.4;
88 | } else if (name.includes('doorOpen')) {
89 | volume = 0.5;
90 | } else continue;
91 |
92 | this.sounds[name] = new Howl({
93 | src: [`${this.soundPaths[j]}.webm`, `${this.soundPaths[j]}.mp3`],
94 | preload: true,
95 | html5: html5,
96 | autoplay: autoplay,
97 | loop: loop,
98 | volume: volume,
99 | rate: rate,
100 | maxDistance: 1000000,
101 | onend: () => {
102 | for (let k = 0; k < this.soundsPlaying.length; k++) {
103 | if (!loop && this.soundsPlaying[k].name === name) this.soundsPlaying.splice(k, 1);
104 | }
105 | },
106 | });
107 | this.sounds[name].pannerAttr({
108 | ...this.sounds[name].pannerAttr(),
109 | distanceModel: 'exponential',
110 | rolloffFactor: 2,
111 | refDistance: 200,
112 | });
113 | }
114 |
115 | for (let j = 0; j < maps[i].ambientAudio?.length; j++) {
116 | const name = maps[i].ambientAudio[j].name;
117 | let rate = 1;
118 | let html5 = false;
119 | let autoplay = true;
120 | let volume = 1;
121 | let loop = true;
122 |
123 | switch (name) {
124 | case 'outside':
125 | volume = 0.2;
126 | break;
127 | }
128 |
129 | this.sounds[name] = new Howl({
130 | src: [`./src/audio/${name}.webm`, `./src/audio/${name}.mp3`],
131 | preload: true,
132 | html5: html5,
133 | autoplay: autoplay,
134 | loop: loop,
135 | volume: volume,
136 | rate: rate,
137 | maxDistance: 1000000,
138 | onend: () => {
139 | for (let k = 0; k < this.soundsPlaying.length; k++) {
140 | if (!loop && this.soundsPlaying[k].name === name) this.soundsPlaying.splice(k, 1);
141 | }
142 | },
143 | });
144 | this.sounds[name].pannerAttr({
145 | ...this.sounds[name].pannerAttr(),
146 | distanceModel: 'exponential',
147 | rolloffFactor: 2,
148 | refDistance: 200,
149 | });
150 | }
151 |
152 | for (
153 | let j = 0;
154 | j < maps[i]?.objects.length + maps[i]?.thinWalls.length + maps[i]?.lightSources.length;
155 | j++
156 | ) {
157 | let obj;
158 | let index;
159 | if (j < maps[i].objects.length) {
160 | index = j;
161 | obj = maps[i].objects[index];
162 | } else if (j < maps[i].objects.length + maps[i].thinWalls.length) {
163 | index = j - maps[i]?.objects.length;
164 | obj = maps[i].thinWalls[index];
165 | } else {
166 | index = j - maps[i]?.objects.length - maps[i]?.thinWalls.length;
167 | obj = maps[i].lightSources[index];
168 | }
169 |
170 | if (obj?.sounds) {
171 | obj.sounds.forEach(fileName => {
172 | const name = fileName + index;
173 | let rate = 1;
174 | let html5 = false;
175 | let autoplay = false;
176 | let volume = 1;
177 | let loop = false;
178 |
179 | switch (fileName) {
180 | case 'slidingDoorOpen':
181 | rate = 1.7;
182 | break;
183 | case 'slidingDoorClose':
184 | rate = 1.7;
185 | break;
186 | case 'song':
187 | loop = true;
188 | volume = 0.5;
189 | break;
190 | case 'outside':
191 | loop = true;
192 | volume = 1;
193 | break;
194 | case 'knocking':
195 | loop = true;
196 | break;
197 | case 'lightHum':
198 | loop = true;
199 | volume = 0.1;
200 | break;
201 | default:
202 | break;
203 | }
204 |
205 | this.sounds[name] = new Howl({
206 | src: [`./src/audio/${fileName}.webm`, `./src/audio/${fileName}.mp3`],
207 | preload: true,
208 | html5: html5,
209 | autoplay: autoplay,
210 | loop: loop,
211 | volume: volume,
212 | rate: rate,
213 | maxDistance: 1000000,
214 | onend: () => {
215 | for (let k = 0; k < this.soundsPlaying.length; k++) {
216 | if (!loop && this.soundsPlaying[k].name === name) this.soundsPlaying.splice(k, 1);
217 | }
218 | },
219 | });
220 | this.sounds[name].pannerAttr({
221 | ...this.sounds[name].pannerAttr(),
222 | distanceModel: 'exponential',
223 | rolloffFactor: 2,
224 | refDistance: 200,
225 | });
226 | });
227 | }
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/hud.js:
--------------------------------------------------------------------------------
1 | export default class Hud {
2 | constructor(engine, audio, db) {
3 | this.db = db;
4 | this.audio = audio;
5 | this.engine = engine;
6 | this.ctx = engine.ctx;
7 | this.canvasWidth = engine.canvasWidth;
8 | this.canvasHeight = engine.canvasHeight;
9 | this.canvasPixels = engine.ctx.getImageData(0, 0, engine.canvasWidth, engine.canvasWidth);
10 | this.showFps = true;
11 | this.framesCounted = 0;
12 | this.cursorX = this.canvasWidth / 2;
13 | this.cursorY = this.canvasHeight / 2;
14 | this.inventorySlotSize = ~~(this.canvasWidth / 20);
15 | this.inventoryIndexSelected = null;
16 | this.itemCanBePlaced = false;
17 | this.itemOutsideOfInventory = false;
18 | this.itemPlacementCol = 0;
19 | this.itemPlacementRow = 0;
20 | }
21 |
22 | drawCursor() {
23 | if (this.inventoryIndexSelected !== null) return;
24 | this.ctx.lineWidth = 2;
25 | this.ctx.strokeStyle = 'rgb(0, 0, 0)';
26 | this.ctx.fillStyle = 'rgb(255, 255, 255)';
27 | this.ctx.beginPath();
28 | this.ctx.ellipse(
29 | this.cursorX,
30 | this.cursorY,
31 | this.canvasHeight / 200,
32 | this.canvasHeight / 200,
33 | 2 * Math.PI,
34 | 0,
35 | 2 * Math.PI
36 | );
37 | this.ctx.stroke();
38 | this.ctx.fill();
39 | }
40 |
41 | drawFillRectangle(x, y, width, height, red, green, blue, alpha) {
42 | const bytesPerPixel = 4;
43 | let targetIndex = bytesPerPixel * this.engine.offscreenCanvasPixels.width * y + bytesPerPixel * x;
44 | for (let h = 0; h < height; h++) {
45 | for (let w = 0; w < width; w++) {
46 | this.engine.offscreenCanvasPixels.data[targetIndex] = red;
47 | this.engine.offscreenCanvasPixels.data[targetIndex + 1] = green;
48 | this.engine.offscreenCanvasPixels.data[targetIndex + 2] = blue;
49 | this.engine.offscreenCanvasPixels.data[targetIndex + 3] = alpha;
50 | targetIndex += bytesPerPixel;
51 | }
52 | targetIndex += bytesPerPixel * (this.engine.offscreenCanvasPixels.width - width);
53 | }
54 | }
55 |
56 | setItemSelected() {
57 | const inventory = this.engine.inventory;
58 | const inventoryW = this.engine.inventorySlotCols * this.inventorySlotSize;
59 | const inventoryH = this.engine.inventorySlotRows * this.inventorySlotSize;
60 |
61 | const inventoryStartX = this.canvasWidth / 2 - inventoryW / 2;
62 | const inventoryStartY = this.canvasHeight / 2 - inventoryH / 2;
63 | if (
64 | this.cursorX < inventoryStartX ||
65 | this.cursorX > inventoryStartX + inventoryW ||
66 | this.cursorY < inventoryStartY ||
67 | this.cursorY > inventoryStartY + inventoryH
68 | ) {
69 | return;
70 | }
71 |
72 | for (let i = 0; i < inventory.length; i++) {
73 | const xStart = inventoryStartX + inventory[i].slotIdStartCol * this.inventorySlotSize;
74 | const yStart = inventoryStartY + inventory[i].slotIdStartRow * this.inventorySlotSize;
75 | const xEnd = xStart + inventory[i].slotCols * this.inventorySlotSize;
76 | const yEnd = yStart + inventory[i].slotRows * this.inventorySlotSize;
77 |
78 | if (this.cursorX >= xStart && this.cursorX <= xEnd && this.cursorY >= yStart && this.cursorY <= yEnd) {
79 | this.inventoryIndexSelected = i;
80 | }
81 | }
82 | }
83 |
84 | drawSelectedInventoryItem() {
85 | const inventory = this.engine.inventory;
86 | const item = inventory[this.inventoryIndexSelected];
87 | const img = this.engine.textures[item.name];
88 |
89 | const inventoryW = this.engine.inventorySlotCols * this.inventorySlotSize;
90 | const inventoryH = this.engine.inventorySlotRows * this.inventorySlotSize;
91 | const inventoryStartX = this.canvasWidth / 2 - inventoryW / 2;
92 | const inventoryStartY = this.canvasHeight / 2 - inventoryH / 2;
93 |
94 | const slotCols = item.slotCols;
95 | const slotRows = item.slotRows;
96 | const scaleFactor = Math.min(
97 | (this.inventorySlotSize * slotCols) / img.width,
98 | (this.inventorySlotSize * slotRows) / img.height
99 | );
100 | const newW = img.width * scaleFactor - 4;
101 | const newH = img.height * scaleFactor - 4;
102 |
103 | const colStartNew = Math.round((this.cursorX - inventoryStartX - newW / 2) / this.inventorySlotSize);
104 | const rowStartNew = Math.round((this.cursorY - inventoryStartY - newH / 2) / this.inventorySlotSize);
105 | const colEndNew = colStartNew + (item.slotCols - 1);
106 | const rowEndNew = rowStartNew + (item.slotRows - 1);
107 |
108 | let spaceFound = true;
109 | for (let i = 0; i < inventory.length; i++) {
110 | const endCol = inventory[i].slotIdStartCol + (inventory[i].slotCols - 1);
111 | const endRow = inventory[i].slotIdStartRow + (inventory[i].slotRows - 1);
112 |
113 | if (
114 | i !== this.inventoryIndexSelected &&
115 | colStartNew <= endCol &&
116 | colEndNew >= inventory[i].slotIdStartCol &&
117 | rowStartNew <= endRow &&
118 | rowEndNew >= inventory[i].slotIdStartRow
119 | ) {
120 | spaceFound = false;
121 | }
122 | }
123 |
124 | const slotX = colStartNew * this.inventorySlotSize + inventoryStartX;
125 | const slotY = rowStartNew * this.inventorySlotSize + inventoryStartY;
126 | const slotXEnd = colEndNew * this.inventorySlotSize + inventoryStartX;
127 | const slotYEnd = rowEndNew * this.inventorySlotSize + inventoryStartY;
128 |
129 | this.itemCanBePlaced = false;
130 | if (spaceFound) {
131 | if (
132 | slotX >= inventoryStartX &&
133 | slotXEnd <= inventoryStartX + (inventoryW - this.inventorySlotSize) &&
134 | slotY >= inventoryStartY &&
135 | slotYEnd <= inventoryStartY + (inventoryH - this.inventorySlotSize)
136 | ) {
137 | this.itemPlacementCol = colStartNew;
138 | this.itemPlacementRow = rowStartNew;
139 | this.itemCanBePlaced = true;
140 | this.ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
141 | this.ctx.beginPath();
142 | this.ctx.fillRect(slotX, slotY, this.inventorySlotSize * slotCols, this.inventorySlotSize * slotRows);
143 | } else {
144 | this.itemOutsideOfInventory = true;
145 |
146 | this.ctx.font = `400 8px arial`;
147 | this.ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
148 | this.ctx.textAlign = 'center';
149 | this.ctx.textBaseline = 'center';
150 | this.ctx.fillText('Drop', this.cursorX, this.cursorY - newH / 2 - 10);
151 | }
152 | }
153 |
154 | this.ctx.beginPath();
155 | this.ctx.drawImage(img, this.cursorX - newW / 2, this.cursorY - newH / 2, newW, newH);
156 | }
157 |
158 | placeSelectedInventoryItem() {
159 | if (this.itemCanBePlaced) {
160 | this.engine.inventory[this.inventoryIndexSelected].slotIdStartCol = this.itemPlacementCol;
161 | this.engine.inventory[this.inventoryIndexSelected].slotIdStartRow = this.itemPlacementRow;
162 | this.itemCanBePlaced = false;
163 | } else if (this.itemOutsideOfInventory) {
164 | this.itemOutsideOfInventory = false;
165 | this.engine.items.push({
166 | name: this.engine.inventory[this.inventoryIndexSelected].name,
167 | x: this.engine.fPlayerX,
168 | y: this.engine.fPlayerY,
169 | category: this.engine.inventory[this.inventoryIndexSelected].category,
170 | inReticle: this.engine.inventory[this.inventoryIndexSelected].inReticle,
171 | inventoryCols: this.engine.inventory[this.inventoryIndexSelected].slotCols,
172 | inventoryRows: this.engine.inventory[this.inventoryIndexSelected].slotRows,
173 | });
174 |
175 | const img = this.engine.textures[this.engine.inventory[this.inventoryIndexSelected].name];
176 | this.engine.fItemTextureBufferList.push(new OffscreenCanvas(img.width, img.height));
177 | let lastIndex = this.engine.fItemTextureBufferList.length - 1;
178 | this.engine.fItemTextureBufferList[lastIndex].getContext('2d', { alpha: true }).drawImage(img, 0, 0);
179 | const imgData = this.engine.fItemTextureBufferList[lastIndex]
180 | .getContext('2d', { alpha: false })
181 | .getImageData(
182 | 0,
183 | 0,
184 | this.engine.fItemTextureBufferList[lastIndex].width,
185 | this.engine.fItemTextureBufferList[lastIndex].height
186 | );
187 | this.engine.fItemTexturePixelsList[lastIndex] = imgData.data;
188 |
189 | this.engine.inventory.splice(this.inventoryIndexSelected, 1);
190 | }
191 | this.inventoryIndexSelected = null;
192 | }
193 |
194 | drawInventory() {
195 | const inventory = this.engine.inventory;
196 | const ctx = this.ctx;
197 | const inventoryW = this.engine.inventorySlotCols * this.inventorySlotSize;
198 | const inventoryH = this.engine.inventorySlotRows * this.inventorySlotSize;
199 | const inventoryStartX = this.canvasWidth / 2 - inventoryW / 2;
200 | const inventoryStartY = this.canvasHeight / 2 - inventoryH / 2;
201 |
202 | ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
203 | ctx.lineWidth = 1;
204 | ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
205 | ctx.beginPath();
206 | ctx.fillRect(inventoryStartX, inventoryStartY, inventoryW, inventoryH);
207 | for (let i = 0; i < this.engine.inventorySlotCols; i++) {
208 | for (let j = 0; j < this.engine.inventorySlotRows; j++) {
209 | ctx.beginPath();
210 | const slotX = i * this.inventorySlotSize + inventoryStartX;
211 | const slotY = j * this.inventorySlotSize + inventoryStartY;
212 |
213 | let slotFilled = false;
214 | for (let k = 0; k < inventory.length; k++) {
215 | if (k === this.inventoryIndexSelected) continue;
216 | if (inventory[k].slotIdStartCol === i && inventory[k].slotIdStartRow === j) {
217 | const img = this.engine.textures[inventory[k].name];
218 |
219 | const slotCols = inventory[k].slotCols;
220 | const slotRows = inventory[k].slotRows;
221 | const scaleFactor = Math.min(
222 | (this.inventorySlotSize * slotCols) / img.width,
223 | (this.inventorySlotSize * slotRows) / img.height
224 | );
225 | const newW = img.width * scaleFactor - 4;
226 | const newH = img.height * scaleFactor - 4;
227 | const x = slotX + (this.inventorySlotSize * slotCols) / 2 - newW / 2;
228 | const y = slotY + (this.inventorySlotSize * slotRows) / 2 - newH / 2;
229 | ctx.rect(slotX, slotY, this.inventorySlotSize * slotCols, this.inventorySlotSize * slotRows);
230 | ctx.stroke();
231 | ctx.drawImage(img, x, y, newW, newH);
232 | if (
233 | this.inventoryIndexSelected === null &&
234 | this.cursorX >= slotX &&
235 | this.cursorX <= slotX + this.inventorySlotSize * slotCols &&
236 | this.cursorY >= slotY &&
237 | this.cursorY <= slotY + this.inventorySlotSize * slotRows
238 | ) {
239 | ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
240 | ctx.beginPath();
241 | ctx.fillRect(
242 | slotX,
243 | slotY,
244 | this.inventorySlotSize * slotCols,
245 | this.inventorySlotSize * slotRows
246 | );
247 | }
248 | }
249 |
250 | if (
251 | i >= inventory[k].slotIdStartCol &&
252 | i < inventory[k].slotIdStartCol + inventory[k].slotCols &&
253 | j >= inventory[k].slotIdStartRow &&
254 | j < inventory[k].slotIdStartRow + inventory[k].slotRows
255 | ) {
256 | slotFilled = true;
257 | }
258 | }
259 |
260 | if (!slotFilled) {
261 | ctx.beginPath();
262 | ctx.rect(slotX, slotY, this.inventorySlotSize, this.inventorySlotSize);
263 | ctx.stroke();
264 | }
265 | }
266 | }
267 |
268 | ctx.lineWidth = 1;
269 | ctx.beginPath();
270 | ctx.rect(inventoryStartX, inventoryStartY, inventoryW, inventoryH);
271 | ctx.stroke();
272 | }
273 |
274 | drawEngineConsole(values) {
275 | const fontSize = this.canvasHeight / 34;
276 | const xOffset = this.canvasWidth / 90;
277 | const h = 10 * values.length;
278 |
279 | this.ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
280 | this.ctx.fillRect(0, this.canvasHeight - h - 2, this.canvasWidth, h + 2);
281 |
282 | for (let i = 0; i < values.length; i++) {
283 | const yOffset = this.canvasHeight - h + 10 * i;
284 |
285 | this.ctx.font = `500 ${fontSize}px arial`;
286 | this.ctx.fillStyle = 'white';
287 | this.ctx.strokeStyle = 'black';
288 | this.ctx.textAlign = 'left';
289 | this.ctx.textBaseline = 'top';
290 | this.ctx.fillText(`${i}) ${values[i]}`, xOffset, yOffset);
291 | }
292 | }
293 |
294 | drawReticle() {
295 | this.ctx.fillStyle = 'rgb(255, 255, 255)';
296 | this.ctx.beginPath();
297 | this.ctx.ellipse(
298 | ~~(this.canvasWidth / 2),
299 | ~~(this.canvasHeight / 2),
300 | this.canvasHeight / 300,
301 | this.canvasHeight / 300,
302 | 2 * Math.PI,
303 | 0,
304 | 2 * Math.PI
305 | );
306 | this.ctx.fill();
307 | }
308 |
309 | drawFps() {
310 | if (!this.showFps) return;
311 | const fontSize = this.canvasHeight / 28;
312 | const xOffset = this.canvasWidth / 90;
313 | const yOffset = this.canvasHeight / 90;
314 |
315 | this.ctx.font = `600 ${fontSize}px arial`;
316 | this.ctx.fillStyle = this.framesCounted < 60 ? 'red' : 'green';
317 | this.ctx.strokeStyle = 'black';
318 | this.ctx.textAlign = 'left';
319 | this.ctx.textBaseline = 'top';
320 | this.ctx.fillText(this.framesCounted, xOffset, yOffset);
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/src/actions.js:
--------------------------------------------------------------------------------
1 | import { degToRad, getIntersection } from '../utils/calc.js';
2 | import { maps } from './maps.js';
3 |
4 | export default class Actions {
5 | constructor(engine, audio, db) {
6 | this.db = db;
7 | this.audio = audio;
8 | this.engine = engine;
9 | this.minUseDist = engine.minUseDist;
10 | this.keysPressed = [];
11 | this.functionToRun = null;
12 | }
13 |
14 | openDoor(rowFound, colFound, tileIndex) {
15 | const engine = this.engine;
16 | engine.map[rowFound * engine.mapCols + colFound] = 2;
17 | engine.levelTransition = true;
18 |
19 | const newTileIndex = engine.doorMap[tileIndex].indexTo;
20 | const newTileSide = engine.doorMap[tileIndex].side;
21 | const newMapNum = engine.doorMap[tileIndex].mapTo;
22 | const newMapCols = maps[newMapNum].map[0].length;
23 | let x = engine.TILE_SIZE * (newTileIndex % newMapCols);
24 | let y = engine.TILE_SIZE * Math.floor(newTileIndex / newMapCols);
25 | let newPlayerAngle = 0;
26 | const offset = 20;
27 |
28 | switch (newTileSide) {
29 | case 0:
30 | x = x + engine.TILE_SIZE / 2;
31 | y = y - offset;
32 | newPlayerAngle = 270;
33 | break;
34 | case 1:
35 | x = x + engine.TILE_SIZE + offset;
36 | y = y + engine.TILE_SIZE / 2;
37 | newPlayerAngle = 0;
38 | break;
39 | case 2:
40 | x = x + engine.TILE_SIZE / 2;
41 | y = y + engine.TILE_SIZE + offset;
42 | newPlayerAngle = 90;
43 | break;
44 | case 3:
45 | x = x - engine.TILE_SIZE - offset;
46 | y = y + engine.TILE_SIZE / 2;
47 | newPlayerAngle = 180;
48 | break;
49 | }
50 |
51 | const interval = setInterval(() => {
52 | if (!engine.levelTransition) {
53 | engine.fPlayerX = x;
54 | engine.fPlayerY = y;
55 | engine.fPlayerAngle = newPlayerAngle;
56 | engine.setNewMapData(newMapNum);
57 | clearInterval(interval);
58 | }
59 | }, 50);
60 | }
61 |
62 | checkThinWalls() {
63 | const engine = this.engine;
64 | if (!engine.reticleOnWall) return;
65 |
66 | let record = Infinity;
67 | let thinWallIndex = null;
68 |
69 | for (let i = 0; i < engine.thinWalls.length; i++) {
70 | if (engine.thinWalls[i].function !== 'door') continue;
71 | const intersection = getIntersection(
72 | engine.fPlayerX,
73 | engine.fPlayerY,
74 | 1,
75 | degToRad(engine.fPlayerAngle),
76 | engine.thinWalls[i].xStart,
77 | engine.thinWalls[i].yStart,
78 | engine.thinWalls[i].xEnd,
79 | engine.thinWalls[i].yEnd
80 | );
81 |
82 | if (intersection?.[0]) {
83 | const dx = Math.abs(engine.fPlayerX - intersection[0]);
84 | const dy = Math.abs(engine.fPlayerY - intersection[1]);
85 | const d = Math.sqrt(dx * dx + dy * dy);
86 | record = Math.min(d, record);
87 |
88 | if (d <= record) {
89 | record = d;
90 | thinWallIndex = i;
91 | }
92 | }
93 | }
94 |
95 | if (record < this.minUseDist) {
96 | return {
97 | record: record,
98 | index: thinWallIndex,
99 | };
100 | }
101 | }
102 |
103 | checkDoors() {
104 | const engine = this.engine;
105 | if (!engine.reticleOnWall) return;
106 | let record = Infinity;
107 | let rowFound = null;
108 | let colFound = null;
109 | let tileIndex = null;
110 |
111 | let adjustedAngle = engine.fPlayerAngle;
112 | if (adjustedAngle < 0) adjustedAngle += 360;
113 | const playerQuadrant = Math.floor(adjustedAngle / 90);
114 | const sidesToCheck = engine.getSidesToCheck(playerQuadrant);
115 |
116 | for (let row = 0; row < engine.mapRows; row++) {
117 | for (let col = 0; col < engine.mapCols; col++) {
118 | const tile = engine.map[row * engine.mapCols + col];
119 | if (tile > 5) continue;
120 |
121 | const tileIntersection = engine.getIntersectionOfTile(
122 | engine.fPlayerX,
123 | engine.fPlayerY,
124 | row,
125 | col,
126 | degToRad(engine.fPlayerAngle),
127 | sidesToCheck
128 | );
129 |
130 | if (tileIntersection.record < record) {
131 | tileIndex = row * engine.mapCols + col;
132 | record = tileIntersection.record;
133 | rowFound = row;
134 | colFound = col;
135 | }
136 | }
137 | }
138 |
139 | if (engine.doorMap[tileIndex] && record < this.minUseDist) {
140 | return {
141 | record: record,
142 | rowFound: rowFound,
143 | colFound: colFound,
144 | tileIndex: tileIndex,
145 | };
146 | }
147 | return;
148 | }
149 |
150 | checkItems() {
151 | const engine = this.engine;
152 | let record = Infinity;
153 | let ItemIndex = null;
154 |
155 | for (let i = 0; i < engine.items.length; i++) {
156 | // Get perpendicular line coords
157 | const deltaY = engine.items[i].y - engine.fPlayerY;
158 | const deltaX = engine.items[i].x - engine.fPlayerX;
159 | const slope = deltaY / deltaX;
160 | const perpSlope = -(1 / slope);
161 | const angle = Math.atan(perpSlope);
162 | let x1;
163 | let y1;
164 | let x2;
165 | let y2;
166 | x1 = engine.items[i].x - (engine.fItemTextureBufferList[i].width / 2) * Math.cos(angle);
167 | y1 = engine.items[i].y - (engine.fItemTextureBufferList[i].width / 2) * Math.sin(angle);
168 | x2 = engine.items[i].x + (engine.fItemTextureBufferList[i].width / 2) * Math.cos(angle);
169 | y2 = engine.items[i].y + (engine.fItemTextureBufferList[i].width / 2) * Math.sin(angle);
170 |
171 | const intersection = getIntersection(
172 | engine.fPlayerX,
173 | engine.fPlayerY,
174 | 1,
175 | degToRad(engine.fPlayerAngle),
176 | x1,
177 | y1,
178 | x2,
179 | y2
180 | );
181 |
182 | if (intersection?.[0]) {
183 | const dx = Math.abs(engine.fPlayerX - intersection[0]);
184 | const dy = Math.abs(engine.fPlayerY - intersection[1]);
185 | const d = Math.sqrt(dx * dx + dy * dy);
186 |
187 | if (d <= record && engine.items[i].inReticle) {
188 | record = Math.min(d, record);
189 | record = d;
190 | ItemIndex = i;
191 | }
192 | }
193 | }
194 |
195 | if (record < this.minUseDist) {
196 | return {
197 | record: record,
198 | index: ItemIndex,
199 | };
200 | }
201 | }
202 |
203 | findSpotForItem(newItemIndex) {
204 | const inventory = this.engine.inventory;
205 | const newItemCols = this.engine.items[newItemIndex].inventoryCols;
206 | const newItemRows = this.engine.items[newItemIndex].inventoryRows;
207 |
208 | for (let j = 0; j < this.engine.inventorySlotRows; j++) {
209 | loop: for (let i = 0; i < this.engine.inventorySlotCols; i++) {
210 | let spaceFound = true;
211 | const newItemEndCol = i + (newItemCols - 1);
212 | const newItemEndRow = j + (newItemRows - 1);
213 |
214 | if (
215 | newItemEndCol > this.engine.inventorySlotCols - 1 ||
216 | newItemEndRow > this.engine.inventorySlotRows - 1
217 | ) {
218 | break loop;
219 | }
220 |
221 | for (let k = 0; k < inventory.length; k++) {
222 | const endCol = inventory[k].slotIdStartCol + (inventory[k].slotCols - 1);
223 | const endRow = inventory[k].slotIdStartRow + (inventory[k].slotRows - 1);
224 |
225 | if (
226 | i <= endCol &&
227 | newItemEndCol >= inventory[k].slotIdStartCol &&
228 | j <= endRow &&
229 | newItemEndRow >= inventory[k].slotIdStartRow
230 | ) {
231 | spaceFound = false;
232 | }
233 | }
234 |
235 | if (spaceFound) {
236 | this.engine.inventory.push({
237 | name: this.engine.items[newItemIndex].name,
238 | slotIdStartCol: i,
239 | slotIdStartRow: j,
240 | slotCols: newItemCols,
241 | slotRows: newItemRows,
242 | category: this.engine.items[newItemIndex].category,
243 | });
244 | this.engine.items.splice(newItemIndex, 1);
245 | this.engine.fItemTextureBufferList.splice(newItemIndex, 1);
246 | this.engine.fItemTexturePixelsList.splice(newItemIndex, 1);
247 | return;
248 | }
249 | }
250 | }
251 | }
252 |
253 | handleUseBtn() {
254 | let record = Infinity;
255 | let action = '';
256 |
257 | const doorData = this.checkDoors();
258 | if (doorData?.record < record) {
259 | record = doorData.record;
260 | action = 'openDoor';
261 | }
262 |
263 | const thinWallData = this.checkThinWalls();
264 | if (thinWallData?.record < record) {
265 | record = thinWallData.record;
266 | action = 'operateThinWall';
267 | }
268 |
269 | const itemData = this.checkItems();
270 | if (itemData?.record < record) {
271 | record = itemData.record;
272 | action = 'grabItem';
273 | }
274 |
275 | switch (action) {
276 | case 'openDoor':
277 | this.openDoor(doorData.rowFound, doorData.colFound, doorData.tileIndex);
278 | this.audio.playSound('doorOpen', this.engine.fPlayerX, this.engine.fPlayerY, false);
279 | break;
280 | case 'operateThinWall':
281 | this.engine.activeThinWallId = thinWallData.index;
282 | const x =
283 | this.engine.thinWalls[thinWallData.index].xStartOriginal +
284 | Math.abs(
285 | this.engine.thinWalls[thinWallData.index].xEnd - this.engine.thinWalls[thinWallData.index].xStart
286 | ) /
287 | 2;
288 | const y =
289 | this.engine.thinWalls[thinWallData.index].yStartOriginal +
290 | Math.abs(
291 | this.engine.thinWalls[thinWallData.index].yEnd - this.engine.thinWalls[thinWallData.index].yStart
292 | ) /
293 | 2;
294 |
295 | if (this.engine.thinWalls[thinWallData.index].isOpen) {
296 | this.audio.playSound('slidingDoorClose', x, y, true, thinWallData.index);
297 | } else this.audio.playSound('slidingDoorOpen', x, y, true, thinWallData.index);
298 | break;
299 | case 'grabItem':
300 | this.findSpotForItem(itemData.index);
301 | this.audio.playSound('itemPickup', this.engine.fPlayerX, this.engine.fPlayerY, false);
302 | break;
303 | }
304 | }
305 |
306 | runNextFunction() {
307 | if (!this.functionToRun) return;
308 | this.functionToRun();
309 | this.functionToRun = null;
310 | }
311 |
312 | init() {
313 | const engine = this.engine;
314 | document.addEventListener('mousedown', e => {
315 | if (!engine.userIsInTab || engine.DEBUG) return;
316 | });
317 |
318 | document.addEventListener('mousemove', e => {
319 | if (!engine.userIsInTab) return;
320 | if (!engine.DEBUG) {
321 | if (!engine.inventoryOpen) {
322 | engine.fPlayerAngle += e.movementX / 20;
323 | engine.fProjectionPlaneYCenter -= e.movementY / 4;
324 | if (engine.fProjectionPlaneYCenter < -engine.PROJECTIONPLANEHEIGHT / 2) {
325 | engine.fProjectionPlaneYCenter = -engine.PROJECTIONPLANEHEIGHT / 2;
326 | } else if (
327 | engine.fProjectionPlaneYCenter >
328 | engine.PROJECTIONPLANEHEIGHT + engine.PROJECTIONPLANEHEIGHT / 2
329 | ) {
330 | engine.fProjectionPlaneYCenter = engine.PROJECTIONPLANEHEIGHT + engine.PROJECTIONPLANEHEIGHT / 2;
331 | }
332 | }
333 | }
334 | });
335 |
336 | document.addEventListener('keydown', e => {
337 | if (!this.keysPressed.includes(e.code)) {
338 | this.keysPressed.push(e.code);
339 | if (
340 | !(
341 | this.keysPressed.includes('ControlLeft') &&
342 | this.keysPressed.includes('ShiftLeft') &&
343 | this.keysPressed.includes('KeyI')
344 | ) &&
345 | !this.keysPressed.includes('F5')
346 | ) {
347 | e.preventDefault();
348 | }
349 | }
350 |
351 | if (e.code === 'KeyW') {
352 | engine.fKeyForward = true;
353 | engine.fKeyBack = false;
354 | } else if (e.code === 'KeyS') {
355 | engine.fKeyBack = true;
356 | engine.fKeyForward = false;
357 | }
358 |
359 | if (e.code === 'KeyA') {
360 | if (engine.DEBUG) engine.fRotationDir = 'left';
361 | else {
362 | engine.fKeyLeft = true;
363 | engine.fKeyRight = false;
364 | }
365 | } else if (e.code === 'KeyD') {
366 | if (engine.DEBUG) engine.fRotationDir = 'right';
367 | else {
368 | engine.fKeyRight = true;
369 | engine.fKeyLeft = false;
370 | }
371 | }
372 |
373 | if (e.code === 'Space' && !engine.isJumping && !engine.isCrouching && !engine.isStanding) {
374 | engine.isJumping = true;
375 | }
376 |
377 | if (e.code === 'ShiftLeft' && !engine.isCrouching && !engine.isJumping && !engine.isStanding) {
378 | engine.isCrouching = true;
379 | }
380 | });
381 |
382 | document.addEventListener('keyup', e => {
383 | e.preventDefault();
384 |
385 | const index = this.keysPressed.indexOf(e.code);
386 | if (index > -1) this.keysPressed.splice(index, 1);
387 |
388 | if (e.code === 'Tab') {
389 | if (this.engine.inventoryOpen) this.engine.inventoryOpen = false;
390 | else this.engine.inventoryOpen = true;
391 | }
392 |
393 | if (!engine.userIsInTab && !engine.DEBUG) return;
394 |
395 | if (e.code === 'KeyW') {
396 | engine.fKeyForward = false;
397 | } else if (e.code === 'KeyS') {
398 | engine.fKeyBack = false;
399 | }
400 |
401 | if (e.code === 'KeyA') {
402 | if (engine.DEBUG) engine.fRotationDir = '';
403 | else engine.fKeyLeft = false;
404 | } else if (e.code === 'KeyD') {
405 | if (engine.DEBUG) engine.fRotationDir = '';
406 | else engine.fKeyRight = false;
407 | }
408 |
409 | if (e.code === 'ShiftLeft') {
410 | engine.isCrouching = false;
411 | engine.isStanding = true;
412 | }
413 |
414 | if (e.code === 'KeyE') {
415 | this.functionToRun = this.handleUseBtn;
416 | }
417 | });
418 | }
419 | }
420 |
--------------------------------------------------------------------------------
/src/maps.js:
--------------------------------------------------------------------------------
1 | // prettier-ignore
2 |
3 | // Walls: 0 - 5
4 | // Floors: 6 - 9
5 | // Closed Door --> 1
6 | // Open Door --> 2
7 | export const maps = [
8 | {
9 | // 24 x 23
10 | map: [
11 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
12 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 7, 7, 7, 7, 7, 7, 7, 0],
13 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 0, 0],
14 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 7, 7, 7, 7, 7, 7, 0, 0],
15 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 7, 7, 7, 7, 7, 7, 7, 0],
16 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 0, 7, 7, 0, 0, 0, 0],
17 | [0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 0, 7, 7, 7, 7, 7, 7, 7, 0],
18 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 0, 7, 7, 7, 7, 7, 0, 7, 0],
19 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 0, 7, 7, 7, 7, 7, 0, 7, 0],
20 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 0, 7, 7, 7, 7, 7, 7, 7, 0],
21 | [0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 0, 7, 7, 7, 7, 7, 7, 7, 0],
22 | [0, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0, 6, 0, 6, 6, 6, 7, 7, 7, 7, 7, 0, 7, 0],
23 | [0, 6, 6, 6, 6, 0, 6, 6, 6, 0, 6, 6, 0, 6, 6, 0, 7, 7, 7, 7, 7, 0, 7, 0],
24 | [0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 7, 7, 7, 7, 7, 0],
25 | [4, 3, 3, 3, 3, 3, 3, 6, 3, 3, 3, 3, 3, 3, 3, 0, 7, 7, 7, 7, 7, 7, 7, 0],
26 | [3, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 3, 0, 7, 7, 7, 7, 7, 0, 7, 0],
27 | [3, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 3, 0, 7, 7, 7, 7, 7, 0, 7, 0],
28 | [3, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 3, 0, 7, 7, 7, 7, 7, 7, 7, 0],
29 | [1, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 3, 0, 7, 7, 7, 7, 7, 7, 7, 0],
30 | [3, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 3, 0, 7, 7, 7, 7, 7, 0, 7, 0],
31 | [3, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 3, 0, 7, 7, 7, 7, 7, 0, 7, 0],
32 | [3, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 9, 8, 3, 0, 7, 7, 7, 7, 7, 7, 7, 0],
33 | [3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0],
34 | ],
35 | noCeilingIndeces: [
36 | 40, 41, 42, 43, 44, 45, 46,
37 | 64, 65, 66, 67, 68, 69, 70,
38 | 88, 89, 90, 91, 92, 93, 94,
39 | 112, 113, 114, 115, 116, 117, 118
40 | ],
41 | lightSources: [
42 | {
43 | surface: 'floor',
44 | texture: 'standingLight',
45 | col: 14,
46 | row: 1,
47 | side: null,
48 | lightOffsetFromTexture: 10,
49 | strength: 0.5
50 | },
51 | {
52 | surface: 'ceiling',
53 | texture: 'chandelier',
54 | col: 19,
55 | row: 11,
56 | side: null,
57 | lightOffsetFromTexture: 10,
58 | strength: 0.8
59 | },
60 | {
61 | surface: 'ceiling',
62 | texture: 'hangingLight2',
63 | col: 7,
64 | row: 18,
65 | side: null,
66 | lightOffsetFromTexture: 10,
67 | strength: 1
68 | }
69 | ],
70 | thinWalls: [
71 | {
72 | texture: 'slidingDoor',
73 | rowStart: 2,
74 | colStart: 15,
75 | rowEnd: 3,
76 | colEnd: 15,
77 | isOpen: false,
78 | function: 'door',
79 | sounds: ['slidingDoorOpen', 'slidingDoorClose'],
80 | transparent: true,
81 | vaultable: false
82 | },
83 | {
84 | texture: 'slidingDoor',
85 | rowStart: 11,
86 | colStart: 15,
87 | rowEnd: 12,
88 | colEnd: 15,
89 | isOpen: false,
90 | function: 'door',
91 | sounds: ['slidingDoorOpen', 'slidingDoorClose'],
92 | transparent: true,
93 | vaultable: false
94 | }
95 | ],
96 | items: [
97 | {
98 | name: 'apple',
99 | x: 500,
100 | y: 150,
101 | category: 'food',
102 | inReticle: false,
103 | inventoryCols: 1,
104 | inventoryRows: 1,
105 | },
106 | {
107 | name: 'note',
108 | x: 940,
109 | y: 80,
110 | category: 'note',
111 | inReticle: false,
112 | inventoryCols: 1,
113 | inventoryRows: 1,
114 | },
115 | {
116 | name: 'pickaxe',
117 | x: 1450,
118 | y: 80,
119 | category: 'tool',
120 | inReticle: false,
121 | inventoryCols: 2,
122 | inventoryRows: 3,
123 | },
124 | {
125 | name: 'bread',
126 | x: 700,
127 | y: 600,
128 | category: 'food',
129 | inReticle: false,
130 | inventoryCols: 2,
131 | inventoryRows: 1,
132 | },
133 | {
134 | name: 'pickaxe',
135 | x: 730,
136 | y: 530,
137 | category: 'tool',
138 | inReticle: false,
139 | inventoryCols: 2,
140 | inventoryRows: 3,
141 | },
142 | {
143 | name: 'bread',
144 | x: 750,
145 | y: 480,
146 | category: 'food',
147 | inReticle: false,
148 | inventoryCols: 2,
149 | inventoryRows: 1,
150 | },
151 | {
152 | name: 'bread',
153 | x: 710,
154 | y: 500,
155 | category: 'food',
156 | inReticle: false,
157 | inventoryCols: 2,
158 | inventoryRows: 1,
159 | },
160 | {
161 | name: 'bread',
162 | x: 100,
163 | y: 1380,
164 | category: 'food',
165 | inReticle: false,
166 | inventoryCols: 2,
167 | inventoryRows: 1,
168 | },
169 | ],
170 | objects: [
171 | {
172 | name: 'barrel',
173 | x: 1420,
174 | y: 100,
175 | hFromGround: 0,
176 | sounds: []
177 | },
178 | {
179 | name: 'barrel',
180 | x: 1420,
181 | y: 280,
182 | hFromGround: 0,
183 | sounds: []
184 | },
185 | {
186 | name: 'table',
187 | x: 1370,
188 | y: 640,
189 | hFromGround: 0,
190 | sounds: []
191 | },
192 | {
193 | name: 'table',
194 | x: 1370,
195 | y: 900,
196 | hFromGround: 0,
197 | sounds: []
198 | },
199 | {
200 | name: 'table',
201 | x: 1370,
202 | y: 1160,
203 | hFromGround: 0,
204 | sounds: []
205 | },
206 | {
207 | name: 'radio',
208 | x: 800,
209 | y: 1350,
210 | hFromGround: 0,
211 | sounds: ['song']
212 | },
213 | ],
214 | wallTextures: ['wall1', 'doubleDoorClosed', 'doubleDoorOpen', 'wall3'],
215 | doorMap: {
216 | 432: {
217 | mapTo: 1,
218 | indexTo: 198,
219 | side: 1
220 | }
221 | },
222 | floorTextures: ['floor1', 'floor3', 'floor4', 'floor5'],
223 | ceilingTexture: 'ceiling1',
224 | skyTexture: 'sky',
225 | paintings: ['painting3two', 'painting3one', 'painting1', 'painting4one', 'painting4two', 'painting2one', 'painting2two', 'painting5one', 'painting5two', 'painting6one', 'painting6two', 'painting7one', 'painting7two', 'shield', 'shield'],
226 | paintingDetails: [
227 | {
228 | row: 4,
229 | col: 6,
230 | side: 0
231 | },
232 | {
233 | row: 4,
234 | col: 7,
235 | side: 0
236 | },
237 | {
238 | row: 5,
239 | col: 0,
240 | side: 1
241 | },
242 | {
243 | row: 2,
244 | col: 22,
245 | side: 3
246 | },
247 | {
248 | row: 3,
249 | col: 22,
250 | side: 3
251 | },
252 | {
253 | row: 7,
254 | col: 21,
255 | side: 3
256 | },
257 | {
258 | row: 8,
259 | col: 21,
260 | side: 3
261 | },
262 | {
263 | row: 11,
264 | col: 21,
265 | side: 3
266 | },
267 | {
268 | row: 12,
269 | col: 21,
270 | side: 3
271 | },
272 | {
273 | row: 15,
274 | col: 21,
275 | side: 3
276 | },
277 | {
278 | row: 16,
279 | col: 21,
280 | side: 3
281 | },
282 | {
283 | row: 19,
284 | col: 21,
285 | side: 3
286 | },
287 | {
288 | row: 20,
289 | col: 21,
290 | side: 3
291 | },
292 | {
293 | row: 13,
294 | col: 6,
295 | side: 0
296 | },
297 | {
298 | row: 13,
299 | col: 8,
300 | side: 0
301 | }
302 | ]
303 | },
304 | {
305 | // 33 x 13
306 | map: [
307 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
308 | [0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
309 | [0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
310 | [0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
311 | [0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
312 | [0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 6, 6, 6, 6, 6, 6, 0],
313 | [1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0],
314 | [0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 6, 6, 6, 6, 6, 6, 0],
315 | [0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
316 | [0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
317 | [0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
318 | [0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
319 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
320 | ],
321 | lightSources: [
322 | {
323 | surface: 'ceiling',
324 | texture: 'hangingLight2',
325 | col: 8,
326 | row: 6,
327 | side: null,
328 | lightOffsetFromTexture: 10,
329 | strength: 0.5
330 | },
331 | {
332 | surface: 'floor',
333 | texture: 'standingLight',
334 | col: 16,
335 | row: 11,
336 | side: null,
337 | lightOffsetFromTexture: 10,
338 | strength: 0.3
339 | }
340 | ],
341 | thinWalls: [
342 | {
343 | texture: 'slidingDoor',
344 | rowStart: 9,
345 | colStart: 20,
346 | rowEnd: 10,
347 | colEnd: 20,
348 | isOpen: false,
349 | function: 'door',
350 | sounds: ['slidingDoorOpen', 'slidingDoorClose'],
351 | transparent: true,
352 | vaultable: false
353 | }
354 | ],
355 | objects: [
356 | {
357 | name: 'elmo',
358 | x: 1042,
359 | y: 86,
360 | hFromGround: 0,
361 | sounds: ['knocking']
362 | }
363 | ],
364 | wallTextures: ['wall1', 'doubleDoorClosed', 'doubleDoorOpen'],
365 | doorMap: {
366 | 198: {
367 | mapTo: 0,
368 | indexTo: 432,
369 | side: 1
370 | },
371 | 413: {
372 | mapTo: 2,
373 | indexTo: 1,
374 | side: 2
375 | }
376 | },
377 | noCeilingIndeces: [],
378 | floorTextures: ['floor1'],
379 | ceilingTexture: 'ceiling1',
380 | paintings: ['painting1', 'painting8', 'painting9', 'painting10', 'painting11', 'painting12', 'painting13', 'painting14', 'painting15', 'bloodyHandprint'],
381 | paintingDetails: [
382 | {
383 | row: 5,
384 | col: 5,
385 | side: 2
386 | },
387 | {
388 | row: 7,
389 | col: 10,
390 | side: 0
391 | },
392 | {
393 | row: 5,
394 | col: 15,
395 | side: 2
396 | },
397 | {
398 | row: 7,
399 | col: 20,
400 | side: 0
401 | },
402 | {
403 | row: 12,
404 | col: 3,
405 | side: 0
406 | },
407 | {
408 | row: 0,
409 | col: 8,
410 | side: 2
411 | },
412 | {
413 | row: 12,
414 | col: 13,
415 | side: 0
416 | },
417 | {
418 | row: 0,
419 | col: 18,
420 | side: 2
421 | },
422 | {
423 | row: 12,
424 | col: 22,
425 | side: 0
426 | },
427 | {
428 | row: 0,
429 | col: 16,
430 | side: 2
431 | },
432 | ]
433 | },
434 | {
435 | // 20 x 20
436 | map: [
437 | [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
438 | [0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0],
439 | [0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0],
440 | [0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0],
441 | [0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0],
442 | [0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
443 | [0, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 0],
444 | [0, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0],
445 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0],
446 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 6, 6, 0],
447 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 0],
448 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 6, 0],
449 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0],
450 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0],
451 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0],
452 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0],
453 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0],
454 | [1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0],
455 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 0],
456 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
457 | ],
458 | lightSources: [
459 | {
460 | surface: 'ceiling',
461 | texture: 'hangingLight',
462 | col: 8,
463 | row: 8,
464 | side: null,
465 | lightOffsetFromTexture: 10,
466 | strength: 0.8,
467 | sounds: ['lightHum']
468 | },
469 | {
470 | surface: 'ceiling',
471 | texture: 'hangingLight',
472 | col: 6,
473 | row: 15,
474 | side: null,
475 | lightOffsetFromTexture: 10,
476 | strength: 0.8,
477 | sounds: ['lightHum']
478 | }
479 | ],
480 | thinWalls: [
481 |
482 | ],
483 | items: [
484 |
485 | ],
486 | objects: [
487 |
488 | ],
489 | noCeilingIndeces: [],
490 | wallTextures: ['wall2', 'doubleDoor2Closed', 'doubleDoor2Open'],
491 | doorMap: {
492 | 1: {
493 | mapTo: 1,
494 | indexTo: 413,
495 | side: 0
496 | },
497 | 340: {
498 | mapTo: 3,
499 | indexTo: 79,
500 | side: 3
501 | }
502 | },
503 | floorTextures: ['floor2'],
504 | skyTexture: 'sky',
505 | ceilingTexture: 'ceiling2',
506 | paintings: [],
507 | paintingDetails: []
508 | },
509 | {
510 | // 20 x 5
511 | map: [
512 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
513 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0],
514 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0],
515 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 1],
516 | [0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0],
517 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
518 | ],
519 | lightSources: [
520 | {
521 | surface: 'wall',
522 | texture: 'hangingLight',
523 | col: 0,
524 | row: 4,
525 | side: 1,
526 | lightOffsetFromTexture: 10,
527 | strength: 1
528 | },
529 | ],
530 | thinWalls: [
531 | {
532 | texture: 'fence',
533 | rowStart: 2,
534 | colStart: 1,
535 | rowEnd: 2,
536 | colEnd: 2,
537 | isOpen: false,
538 | function: null,
539 | sounds: [],
540 | transparent: true,
541 | vaultable: true
542 | },
543 | {
544 | texture: 'fence',
545 | rowStart: 2,
546 | colStart: 2,
547 | rowEnd: 2,
548 | colEnd: 3,
549 | isOpen: false,
550 | function: null,
551 | sounds: [],
552 | transparent: true,
553 | vaultable: true
554 | },
555 | {
556 | texture: 'fence',
557 | rowStart: 2,
558 | colStart: 3,
559 | rowEnd: 2,
560 | colEnd: 4,
561 | isOpen: false,
562 | function: null,
563 | sounds: [],
564 | transparent: true,
565 | vaultable: true
566 | },
567 | {
568 | texture: 'fence',
569 | rowStart: 2,
570 | colStart: 5,
571 | rowEnd: 3,
572 | colEnd: 5,
573 | isOpen: false,
574 | function: null,
575 | sounds: [],
576 | transparent: true,
577 | vaultable: true
578 | },
579 | {
580 | texture: 'fence',
581 | rowStart: 3,
582 | colStart: 5,
583 | rowEnd: 4,
584 | colEnd: 5,
585 | isOpen: false,
586 | function: null,
587 | sounds: [],
588 | transparent: true,
589 | vaultable: true
590 | },
591 | {
592 | texture: 'fence',
593 | rowStart: 4,
594 | colStart: 5,
595 | rowEnd: 5,
596 | colEnd: 5,
597 | isOpen: false,
598 | function: null,
599 | sounds: [],
600 | transparent: true,
601 | vaultable: true
602 | },
603 | ],
604 | items: [
605 |
606 | ],
607 | objects: [
608 |
609 | ],
610 | wallTextures: ['wall4', 'doubleDoorClosed', 'doubleDoorOpen'],
611 | doorMap: {
612 | 79: {
613 | mapTo: 2,
614 | indexTo: 340,
615 | side: 1
616 | }
617 | },
618 | noCeilingIndeces: [],
619 | floorTextures: ['floor6'],
620 | skyTexture: 'sky',
621 | ceilingTexture: null,
622 | paintings: [],
623 | paintingDetails: [],
624 | ambientAudio: [
625 | {
626 | name: 'outside',
627 | x: null,
628 | y: null,
629 | }
630 | ]
631 | },
632 | ]
633 |
634 | export const texturePaths = [
635 | // Walls
636 | 'src/assets/walls/wall1.png',
637 | 'src/assets/walls/wall2.png',
638 | 'src/assets/walls/wall3.png',
639 | 'src/assets/walls/wall4.png',
640 | 'src/assets/walls/doubleDoorClosed.png',
641 | 'src/assets/walls/doubleDoorOpen.png',
642 | 'src/assets/walls/doubleDoor2Closed.png',
643 | 'src/assets/walls/doubleDoor2Open.png',
644 | // Floors
645 | 'src/assets/floors/floor1.png',
646 | 'src/assets/floors/floor2.png',
647 | 'src/assets/floors/floor3.png',
648 | 'src/assets/floors/floor4.png',
649 | 'src/assets/floors/floor5.png',
650 | 'src/assets/floors/floor6.png',
651 | // Ceilings
652 | 'src/assets/ceilings/ceiling1.png',
653 | 'src/assets/ceilings/ceiling2.png',
654 | // Paintings
655 | 'src/assets/paintings/painting1.png',
656 | 'src/assets/paintings/painting2one.png',
657 | 'src/assets/paintings/painting2two.png',
658 | 'src/assets/paintings/painting3one.png',
659 | 'src/assets/paintings/painting3two.png',
660 | 'src/assets/paintings/painting4one.png',
661 | 'src/assets/paintings/painting4two.png',
662 | 'src/assets/paintings/painting5one.png',
663 | 'src/assets/paintings/painting5two.png',
664 | 'src/assets/paintings/painting6one.png',
665 | 'src/assets/paintings/painting6two.png',
666 | 'src/assets/paintings/painting7one.png',
667 | 'src/assets/paintings/painting7two.png',
668 | 'src/assets/paintings/painting8.png',
669 | 'src/assets/paintings/painting9.png',
670 | 'src/assets/paintings/painting10.png',
671 | 'src/assets/paintings/painting11.png',
672 | 'src/assets/paintings/painting12.png',
673 | 'src/assets/paintings/painting13.png',
674 | 'src/assets/paintings/painting14.png',
675 | 'src/assets/paintings/painting15.png',
676 | 'src/assets/paintings/shield.png',
677 | 'src/assets/paintings/bloodyHandprint.png',
678 | // Objects
679 | 'src/assets/objects/barrel.png',
680 | 'src/assets/objects/redbull.png',
681 | 'src/assets/objects/elmo.png',
682 | 'src/assets/objects/table.png',
683 | 'src/assets/objects/radio.png',
684 | // Thin Walls
685 | 'src/assets/thinWalls/slidingDoor.png',
686 | 'src/assets/thinWalls/fence.png',
687 | // Items
688 | 'src/assets/items/apple.png',
689 | 'src/assets/items/pickaxe.png',
690 | 'src/assets/items/note.png',
691 | 'src/assets/items/redbull.png',
692 | 'src/assets/items/bread.png',
693 | // Light Sources
694 | 'src/assets/lightSources/hangingLight.png',
695 | 'src/assets/lightSources/hangingLight2.png',
696 | 'src/assets/lightSources/standingLight.png',
697 | 'src/assets/lightSources/chandelier.png',
698 | // Skys
699 | 'src/assets/skyBoxes/sky.jpg',
700 | ];
701 |
--------------------------------------------------------------------------------
/src/engine.js:
--------------------------------------------------------------------------------
1 | import { convertDeg0To360, degToRad, getIntersection, radToDeg } from '../utils/calc.js';
2 | import { maps, texturePaths } from './maps.js';
3 |
4 | export default class Engine {
5 | constructor(audio, db) {
6 | this.db = db;
7 | this.audio = audio;
8 | this.canvas = document.getElementById('canvas');
9 | this.canvasWidth = this.canvas.width;
10 | this.canvasHeight = this.canvas.height;
11 | this.ctx = this.canvas.getContext('2d', { alpha: true });
12 |
13 | this.offscreenCanvas = new OffscreenCanvas(canvas.width, canvas.height);
14 | this.offscreenCanvasContext = this.offscreenCanvas.getContext('2d', { alpha: false });
15 | this.offscreenCanvasPixels = this.offscreenCanvasContext.getImageData(
16 | 0,
17 | 0,
18 | this.canvasWidth,
19 | this.canvasHeight
20 | );
21 |
22 | this.PROJECTIONPLANEWIDTH = this.canvasWidth;
23 | this.PROJECTIONPLANEHEIGHT = this.canvasHeight;
24 |
25 | this.mapLightValues = [null, null, null];
26 | this.mapLightRefs = [null, null, null];
27 | this.mapLightSides = [null, null, null];
28 | this.currentLightValues = null;
29 | this.currentLightRefs = null;
30 | this.currentThinWallLightSides = null;
31 |
32 | this.minBrightness = 0.12;
33 | this.maxBrightness = 1.3;
34 |
35 | this.fWallTextureBufferList;
36 | this.fWallTexturePixelsList;
37 |
38 | this.fPaintingTextureBufferList;
39 | this.fPaintingTexturePixelsList;
40 | this.fPaintingDetails;
41 |
42 | this.fFloorTextureBufferList;
43 | this.fFloorTexturePixelsList;
44 |
45 | this.fCeilingTextureBuffer;
46 | this.fCeilingTexturePixels;
47 |
48 | this.fSkyTextureBuffer;
49 | this.fSkyTexturePixels;
50 |
51 | this.fObjectTextureBufferList;
52 | this.fObjectTexturePixelsList;
53 |
54 | this.fThinWallTextureBufferList;
55 | this.fThinWallTexturePixelsList;
56 |
57 | this.fItemTextureBufferList;
58 | this.fItemTexturePixelsList;
59 |
60 | this.fLightTextureBufferList;
61 | this.fLightTexturePixelList;
62 |
63 | this.noCeilingIndeces;
64 |
65 | this.items = [];
66 | this.objects = [];
67 | this.lightSources = [];
68 | this.objectRefs = new Array(this.PROJECTIONPLANEWIDTH);
69 | this.objectOffsets = new Array(this.PROJECTIONPLANEWIDTH);
70 | this.objectRayLengths = new Array(this.PROJECTIONPLANEWIDTH);
71 | this.objectCollisionsX = new Array(this.PROJECTIONPLANEWIDTH);
72 | this.objectCollisionsY = new Array(this.PROJECTIONPLANEWIDTH);
73 | this.isItemRay = new Array(this.PROJECTIONPLANEWIDTH);
74 | this.isLightsourceRay = new Array(this.PROJECTIONPLANEWIDTH);
75 |
76 | this.minUseDist = 140;
77 |
78 | this.objectTargetIndecesForStrip = [];
79 | this.objectDistsForStrip = [];
80 |
81 | this.thinWalls = [];
82 | this.thinWallRefs = new Array(this.PROJECTIONPLANEWIDTH);
83 | this.thinWallOffsets = new Float32Array(this.PROJECTIONPLANEWIDTH);
84 | this.thinWallRayLengths = new Uint16Array(this.PROJECTIONPLANEWIDTH);
85 | this.thinWallCollisionsX = new Float32Array(this.PROJECTIONPLANEWIDTH);
86 | this.thinWallCollisionsY = new Float32Array(this.PROJECTIONPLANEWIDTH);
87 |
88 | this.activeThinWallId = null;
89 |
90 | this.bytesPerPixel = 4;
91 | this.pi = Math.PI;
92 |
93 | this.texturePaths = texturePaths;
94 | this.textures = {};
95 |
96 | this.TILE_SIZE = 64;
97 | this.WALL_HEIGHT = 64;
98 |
99 | this.mapCols = maps[0].map[0].length;
100 | this.mapRows = maps[0].map.length;
101 | this.mapWidth = this.TILE_SIZE * this.mapCols;
102 | this.mapHeight = this.TILE_SIZE * this.mapRows;
103 | this.TILE_SIZE - 1;
104 |
105 | this.mapNum = 0;
106 | this.map = maps[this.mapNum].map;
107 |
108 | this.debugCanvas;
109 | this.debugCanvasWidth;
110 | this.debugCanvasHeight;
111 | this.debugCtx;
112 |
113 | this.fProjectionPlaneYCenter = this.PROJECTIONPLANEHEIGHT / 2;
114 |
115 | this.fPlayerX = 100;
116 | this.fPlayerY = 100;
117 | this.fPlayerAngle = 10;
118 | this.fPlayerMoveDir = 0;
119 | this.fPlayerFov = 70;
120 | this.fPlayerHeight = this.TILE_SIZE / 2;
121 | this.fGameSpeed = 0;
122 | this.fPlayerMoveSpeed = 0;
123 | this.fPlayerDistanceToProjectionPlane = Math.floor(
124 | this.PROJECTIONPLANEWIDTH / 2 / Math.tan(degToRad(this.fPlayerFov) / 2)
125 | );
126 |
127 | this.fKeyForward = false;
128 | this.fKeyBack = false;
129 | this.fKeyLeft = false;
130 | this.fKeyRight = false;
131 |
132 | this.fRotationDir = '';
133 |
134 | this.fWallTextureCanvas;
135 | this.fWallTexturePixels;
136 |
137 | this.fFishTable = new Float32Array(this.PROJECTIONPLANEWIDTH);
138 |
139 | this.RAD0 = 0;
140 | this.RAD90 = (3 * this.pi) / 2;
141 | this.RAD180 = this.pi;
142 | this.RAD270 = this.pi / 2;
143 | this.RAD360 = 2 * this.pi;
144 |
145 | this.rayLengths = new Uint16Array(this.PROJECTIONPLANEWIDTH);
146 | this.rayAngles = new Float32Array(this.PROJECTIONPLANEWIDTH);
147 | this.rayAngleQuadrants = new Uint8Array(this.PROJECTIONPLANEWIDTH);
148 | this.tileCollisionsX = new Float32Array(this.PROJECTIONPLANEWIDTH);
149 | this.tileCollisionsY = new Float32Array(this.PROJECTIONPLANEWIDTH);
150 | this.tileTypes = new Uint8Array(this.PROJECTIONPLANEWIDTH);
151 | this.tileSides = new Uint8Array(this.PROJECTIONPLANEWIDTH);
152 | this.tileIndeces = new Uint16Array(this.PROJECTIONPLANEWIDTH);
153 |
154 | this.userIsInTab = false;
155 | this.reticleOnWall = false;
156 | this.isJumping = false;
157 | this.jumpSpeedStart = 1.2;
158 | this.jumpSpeed = this.jumpSpeedStart;
159 | this.gravityValue = 0.035;
160 |
161 | this.isCrouching = false;
162 | this.isStanding = false;
163 | this.crouchAmt = 10;
164 | this.crouchSpeedStart = 0.4;
165 | this.crouchSpeed = this.crouchSpeedStart;
166 | this.crouchGravity = 0.007;
167 | this.standSpeedStart = 0.4;
168 | this.standSpeed = this.standSpeedStart;
169 | this.standGravity = 0.007;
170 |
171 | this.redTint = 1.2;
172 | this.greenTint = 1.1;
173 | this.blueTint = 1;
174 |
175 | this.levelTransition = false;
176 | this.levelTransitionFadeAmt = 0;
177 | this.doorMap = {};
178 |
179 | this.inventorySlotCols = 5;
180 | this.inventorySlotRows = 6;
181 | this.inventoryOpen = false;
182 | this.inventory = [
183 | {
184 | name: 'apple',
185 | slotIdStartCol: 0,
186 | slotIdStartRow: 0,
187 | slotCols: 1,
188 | slotRows: 1,
189 | category: 'food',
190 | },
191 | {
192 | name: 'bread',
193 | slotIdStartCol: 0,
194 | slotIdStartRow: 1,
195 | slotCols: 2,
196 | slotRows: 1,
197 | category: 'food',
198 | },
199 | ];
200 |
201 | this.itemInUseIndex = null;
202 |
203 | this.timeOfLastFootstep = 10000;
204 |
205 | this.DEBUG = false;
206 | this.preventPageReloadDialog = false;
207 | this.consoleValues = [];
208 | this.lightingVersionNum = 3;
209 | }
210 |
211 | getSidesToCheck(quadrant) {
212 | switch (quadrant) {
213 | case 0:
214 | return [0, 3];
215 | case 1:
216 | return [0, 1];
217 | case 2:
218 | return [1, 2];
219 | case 3:
220 | return [2, 3];
221 | }
222 | }
223 |
224 | drawFillRectangle(x, y, width, height, red, green, blue, alpha) {
225 | const bytesPerPixel = 4;
226 | let targetIndex = bytesPerPixel * this.canvasWidth * y + bytesPerPixel * x;
227 | for (let h = 0; h < height; h++) {
228 | for (let w = 0; w < width; w++) {
229 | this.offscreenCanvasPixels.data[targetIndex] = red;
230 | this.offscreenCanvasPixels.data[targetIndex + 1] = green;
231 | this.offscreenCanvasPixels.data[targetIndex + 2] = blue;
232 | this.offscreenCanvasPixels.data[targetIndex + 3] = alpha;
233 | targetIndex += bytesPerPixel;
234 | }
235 | targetIndex += bytesPerPixel * (this.canvasWidth - width);
236 | }
237 | }
238 |
239 | drawSky(x, angle) {
240 | const xRatio = convertDeg0To360(radToDeg(angle)) / 360;
241 | const sourceCol = ~~(this.fSkyTextureBuffer.width * xRatio);
242 | let sourceIndex = ~~(
243 | this.fSkyTextureBuffer.height * (this.fSkyTextureBuffer.width * this.bytesPerPixel) +
244 | sourceCol * this.bytesPerPixel
245 | );
246 | for (let row = ~~this.fProjectionPlaneYCenter; row >= 0; row--) {
247 | if (row <= this.canvasHeight) {
248 | const targetIndex = row * (this.canvasWidth * this.bytesPerPixel) + this.bytesPerPixel * x;
249 | const red = this.fSkyTexturePixels[sourceIndex];
250 | const green = this.fSkyTexturePixels[sourceIndex + 1];
251 | const blue = this.fSkyTexturePixels[sourceIndex + 2];
252 | this.offscreenCanvasPixels.data[targetIndex] = red;
253 | this.offscreenCanvasPixels.data[targetIndex + 1] = green;
254 | this.offscreenCanvasPixels.data[targetIndex + 2] = blue;
255 | this.offscreenCanvasPixels.data[targetIndex + 3] = 255;
256 | }
257 | sourceIndex -= this.bytesPerPixel * this.fSkyTextureBuffer.width;
258 | }
259 | }
260 |
261 | drawCeiling(wallTop, castColumn, rayAng) {
262 | let targetIndex = wallTop * (this.canvasWidth * this.bytesPerPixel) + this.bytesPerPixel * castColumn;
263 |
264 | for (let row = wallTop; row >= 0; row--) {
265 | const ratio = (this.WALL_HEIGHT - this.fPlayerHeight) / (this.fProjectionPlaneYCenter - row);
266 | const diagDist = ~~(this.fPlayerDistanceToProjectionPlane * ratio * this.fFishTable[castColumn]);
267 |
268 | const xEnd = ~~(diagDist * Math.cos(rayAng) + this.fPlayerX);
269 | const yEnd = ~~(diagDist * Math.sin(rayAng) + this.fPlayerY);
270 |
271 | let brightnessLevel = this.currentLightValues?.[yEnd * this.mapWidth + xEnd] || this.minBrightness;
272 | if (brightnessLevel > this.maxBrightness) brightnessLevel = this.maxBrightness;
273 |
274 | const cellX = ~~(xEnd / this.TILE_SIZE);
275 | const cellY = ~~(yEnd / this.TILE_SIZE);
276 | const cellIndex = cellY * this.mapCols + cellX;
277 |
278 | if (
279 | this.fCeilingTextureBuffer &&
280 | cellX < this.mapCols &&
281 | cellY < this.mapRows &&
282 | cellX >= 0 &&
283 | cellY >= 0
284 | ) {
285 | if (!this.noCeilingIndeces.includes(cellIndex)) {
286 | const tileRow = xEnd & (this.TILE_SIZE - 1);
287 | const tileCol = yEnd & (this.TILE_SIZE - 1);
288 |
289 | const sourceIndex =
290 | tileRow * this.fCeilingTextureBuffer.width * this.bytesPerPixel + this.bytesPerPixel * tileCol;
291 |
292 | const red = this.fCeilingTexturePixels[sourceIndex] * (brightnessLevel * this.redTint);
293 | const green = this.fCeilingTexturePixels[sourceIndex + 1] * (brightnessLevel * this.greenTint);
294 | const blue = this.fCeilingTexturePixels[sourceIndex + 2] * (brightnessLevel * this.blueTint);
295 |
296 | this.offscreenCanvasPixels.data[targetIndex] = ~~red;
297 | this.offscreenCanvasPixels.data[targetIndex + 1] = ~~green;
298 | this.offscreenCanvasPixels.data[targetIndex + 2] = ~~blue;
299 | this.offscreenCanvasPixels.data[targetIndex + 3] = 255;
300 | }
301 | targetIndex -= this.bytesPerPixel * this.canvasWidth;
302 | }
303 | }
304 | }
305 |
306 | drawFloor(wallBottom, castColumn, rayAng) {
307 | let targetIndex = wallBottom * (this.canvasWidth * this.bytesPerPixel) + this.bytesPerPixel * castColumn;
308 |
309 | for (let row = wallBottom + 1; row <= this.PROJECTIONPLANEHEIGHT; row++) {
310 | const straightDistance =
311 | (this.fPlayerHeight / (row - this.fProjectionPlaneYCenter)) * this.fPlayerDistanceToProjectionPlane;
312 |
313 | const diagDist = straightDistance * this.fFishTable[castColumn];
314 |
315 | const xEnd = ~~(diagDist * Math.cos(rayAng) + this.fPlayerX);
316 | const yEnd = ~~(diagDist * Math.sin(rayAng) + this.fPlayerY);
317 |
318 | let brightnessLevel = this.currentLightValues?.[yEnd * this.mapWidth + xEnd] || this.minBrightness;
319 | if (brightnessLevel > this.maxBrightness) brightnessLevel = this.maxBrightness;
320 |
321 | let cellX = ~~(xEnd / this.TILE_SIZE);
322 | let cellY = ~~(yEnd / this.TILE_SIZE);
323 |
324 | let fIndex = 0;
325 |
326 | const tileIndex = cellY * this.mapCols + cellX;
327 | let type = this.map[tileIndex];
328 | if (type >= 6) fIndex = type - 6;
329 |
330 | if (cellX < this.mapCols && cellY < this.mapRows && cellX >= 0 && cellY >= 0) {
331 | const tileRow = xEnd & (this.TILE_SIZE - 1);
332 | const tileCol = yEnd & (this.TILE_SIZE - 1);
333 |
334 | const sourceIndex =
335 | tileRow * this.fFloorTextureBufferList[fIndex].width * this.bytesPerPixel +
336 | this.bytesPerPixel * tileCol;
337 |
338 | const red = this.fFloorTexturePixelsList[fIndex][sourceIndex] * (brightnessLevel * this.redTint);
339 | const green =
340 | this.fFloorTexturePixelsList[fIndex][sourceIndex + 1] * (brightnessLevel * this.greenTint);
341 | const blue =
342 | this.fFloorTexturePixelsList[fIndex][sourceIndex + 2] * (brightnessLevel * this.blueTint);
343 |
344 | this.offscreenCanvasPixels.data[targetIndex] = ~~red;
345 | this.offscreenCanvasPixels.data[targetIndex + 1] = ~~green;
346 | this.offscreenCanvasPixels.data[targetIndex + 2] = ~~blue;
347 | this.offscreenCanvasPixels.data[targetIndex + 3] = 255;
348 |
349 | targetIndex += this.bytesPerPixel * this.canvasWidth;
350 | }
351 | }
352 | }
353 |
354 | drawWallSliceRectangleTinted(
355 | x,
356 | rectTop,
357 | height,
358 | xOffset,
359 | brightnessLevel,
360 | textureBuffer,
361 | texturePixels,
362 | textureBufferPainting,
363 | texturePixelsPainting
364 | ) {
365 | rectTop = Math.floor(rectTop);
366 |
367 | let sourceIndex = this.bytesPerPixel * xOffset;
368 | const lastSourceIndex = sourceIndex + textureBuffer.width * textureBuffer.height * this.bytesPerPixel;
369 |
370 | let targetIndex = this.canvasWidth * this.bytesPerPixel * rectTop + this.bytesPerPixel * x;
371 |
372 | let heightToDraw = height;
373 |
374 | if (rectTop + heightToDraw > this.canvasHeight) heightToDraw = this.canvasHeight - rectTop;
375 |
376 | if (heightToDraw < 0) return;
377 |
378 | let red;
379 | let green;
380 | let blue;
381 | let alpha;
382 |
383 | let yError = 0;
384 | let sourceRow;
385 |
386 | sourceRow = ~~(sourceIndex / (this.bytesPerPixel * textureBuffer.width));
387 |
388 | let paintingSourceTop = null;
389 | let paintingSourceBottom = null;
390 | let paintingSourceLeft = null;
391 | let paintingSourceRight = null;
392 |
393 | let sourceIndexPainting = null;
394 |
395 | if (textureBufferPainting) {
396 | // Painting is present on column
397 | paintingSourceTop = textureBuffer.height / 2 - textureBufferPainting.height / 2;
398 | paintingSourceBottom = textureBuffer.height / 2 + textureBufferPainting.height / 2;
399 | paintingSourceLeft = textureBuffer.width / 2 - textureBufferPainting.width / 2;
400 | paintingSourceRight = textureBuffer.width / 2 + textureBufferPainting.width / 2;
401 |
402 | sourceIndexPainting = this.bytesPerPixel * (xOffset - paintingSourceLeft);
403 | }
404 |
405 | while (true) {
406 | yError += height;
407 | if (texturePixelsPainting) alpha = texturePixelsPainting[sourceIndexPainting + 3];
408 |
409 | if (
410 | textureBufferPainting &&
411 | sourceRow > paintingSourceTop - 1 &&
412 | sourceRow < paintingSourceBottom &&
413 | xOffset > paintingSourceLeft - 1 &&
414 | xOffset < paintingSourceRight
415 | ) {
416 | // Painting on column and within size of painting source
417 | if (alpha > 0) {
418 | red = texturePixelsPainting[sourceIndexPainting] * (brightnessLevel * this.redTint);
419 | green = texturePixelsPainting[sourceIndexPainting + 1] * (brightnessLevel * this.greenTint);
420 | blue = texturePixelsPainting[sourceIndexPainting + 2] * (brightnessLevel * this.blueTint);
421 | } else {
422 | red = texturePixels[sourceIndex] * (brightnessLevel * this.redTint);
423 | green = texturePixels[sourceIndex + 1] * (brightnessLevel * this.greenTint);
424 | blue = texturePixels[sourceIndex + 2] * (brightnessLevel * this.blueTint);
425 | }
426 |
427 | sourceIndexPainting += this.bytesPerPixel * textureBufferPainting.width;
428 | } else {
429 | red = texturePixels[sourceIndex] * (brightnessLevel * this.redTint);
430 | green = texturePixels[sourceIndex + 1] * (brightnessLevel * this.greenTint);
431 | blue = texturePixels[sourceIndex + 2] * (brightnessLevel * this.blueTint);
432 | }
433 |
434 | while (yError >= textureBuffer.height) {
435 | yError -= textureBuffer.height;
436 | this.offscreenCanvasPixels.data[targetIndex] = ~~red;
437 | this.offscreenCanvasPixels.data[targetIndex + 1] = ~~green;
438 | this.offscreenCanvasPixels.data[targetIndex + 2] = ~~blue;
439 | this.offscreenCanvasPixels.data[targetIndex + 3] = 255;
440 | targetIndex += this.bytesPerPixel * this.canvasWidth;
441 |
442 | heightToDraw--;
443 | if (heightToDraw < 1) return;
444 | }
445 |
446 | sourceIndex += this.bytesPerPixel * textureBuffer.width;
447 | if (sourceIndex > lastSourceIndex) sourceIndex = lastSourceIndex;
448 | sourceRow = ~~(sourceIndex / (this.bytesPerPixel * textureBuffer.width));
449 | }
450 | }
451 |
452 | drawObjectStrip(x, y, height, brightness, xOffset, textureBuffer, texturePixels, inReticle, dist) {
453 | if (textureBuffer == undefined) return;
454 | const bytesPerPixel = 4;
455 |
456 | let sourceIndex = bytesPerPixel * xOffset;
457 | const lastSourceIndex = sourceIndex + textureBuffer.width * textureBuffer.height * bytesPerPixel;
458 |
459 | let targetIndex = this.canvasWidth * bytesPerPixel * y + bytesPerPixel * x;
460 |
461 | let heightToDraw = height;
462 |
463 | if (y + heightToDraw > this.canvasHeight) heightToDraw = this.canvasHeight - y;
464 |
465 | let yError = 0;
466 |
467 | if (heightToDraw < 0) return;
468 |
469 | while (true) {
470 | yError += height;
471 |
472 | let red = texturePixels[sourceIndex] * brightness * this.redTint;
473 | let green = texturePixels[sourceIndex + 1] * brightness * this.greenTint;
474 | let blue = texturePixels[sourceIndex + 2] * brightness * this.blueTint;
475 | let alpha = texturePixels[sourceIndex + 3];
476 | if (inReticle) {
477 | red += 70;
478 | green += 70;
479 | blue += 70;
480 | }
481 |
482 | while (yError >= textureBuffer.height) {
483 | if (alpha > 0) {
484 | this.offscreenCanvasPixels.data[targetIndex] = ~~red;
485 | this.offscreenCanvasPixels.data[targetIndex + 1] = ~~green;
486 | this.offscreenCanvasPixels.data[targetIndex + 2] = ~~blue;
487 | this.offscreenCanvasPixels.data[targetIndex + 3] = 255;
488 | this.objectTargetIndecesForStrip.push(targetIndex);
489 | this.objectDistsForStrip.push(dist);
490 | }
491 | yError -= textureBuffer.height;
492 | targetIndex += bytesPerPixel * this.canvasWidth;
493 |
494 | heightToDraw--;
495 | if (heightToDraw < 1) return;
496 | }
497 | sourceIndex += bytesPerPixel * textureBuffer.width;
498 | if (sourceIndex > lastSourceIndex) sourceIndex = lastSourceIndex;
499 | }
500 | }
501 |
502 | drawThinWallStrip(x, y, height, brightness, thinWallRef, xOffset, dist) {
503 | if (this.fThinWallTextureBufferList[thinWallRef] == undefined) return;
504 | const bytesPerPixel = 4;
505 |
506 | let sourceIndex = bytesPerPixel * xOffset;
507 | const lastSourceIndex =
508 | sourceIndex +
509 | this.fThinWallTextureBufferList[thinWallRef].width *
510 | this.fThinWallTextureBufferList[thinWallRef].height *
511 | bytesPerPixel;
512 |
513 | let targetIndex = this.canvasWidth * bytesPerPixel * y + bytesPerPixel * x;
514 |
515 | let heightToDraw = height;
516 |
517 | if (y + heightToDraw > this.canvasHeight) heightToDraw = this.canvasHeight - y;
518 |
519 | let yError = 0;
520 |
521 | if (heightToDraw < 0) return;
522 |
523 | while (true) {
524 | yError += height;
525 |
526 | let red = this.fThinWallTexturePixelsList[thinWallRef][sourceIndex] * brightness * this.redTint;
527 | let green = this.fThinWallTexturePixelsList[thinWallRef][sourceIndex + 1] * brightness * this.greenTint;
528 | let blue = this.fThinWallTexturePixelsList[thinWallRef][sourceIndex + 2] * brightness * this.blueTint;
529 | let alpha = this.fThinWallTexturePixelsList[thinWallRef][sourceIndex + 3];
530 |
531 | while (yError >= this.fThinWallTextureBufferList[thinWallRef].height) {
532 | yError -= this.fThinWallTextureBufferList[thinWallRef].height;
533 | if (
534 | !this.objectTargetIndecesForStrip.includes(targetIndex) ||
535 | this.objectDistsForStrip[this.objectTargetIndecesForStrip.indexOf(targetIndex)] > dist
536 | ) {
537 | // Blend pixel color values with transparent thin wall color values
538 | let redBlend;
539 | let greenBlend;
540 | let blueBlend;
541 | if (alpha > 0) {
542 | redBlend = (alpha / 255) * red + (1 - alpha / 255) * this.offscreenCanvasPixels.data[targetIndex];
543 | greenBlend =
544 | (alpha / 255) * green + (1 - alpha / 255) * this.offscreenCanvasPixels.data[targetIndex + 1];
545 | blueBlend =
546 | (alpha / 255) * blue + (1 - alpha / 255) * this.offscreenCanvasPixels.data[targetIndex + 2];
547 |
548 | this.offscreenCanvasPixels.data[targetIndex] = ~~redBlend;
549 | this.offscreenCanvasPixels.data[targetIndex + 1] = ~~greenBlend;
550 | this.offscreenCanvasPixels.data[targetIndex + 2] = ~~blueBlend;
551 | this.offscreenCanvasPixels.data[targetIndex + 3] = 255;
552 | }
553 | }
554 |
555 | targetIndex += bytesPerPixel * this.canvasWidth;
556 |
557 | heightToDraw--;
558 | if (heightToDraw < 1) return;
559 | }
560 | sourceIndex += bytesPerPixel * this.fThinWallTextureBufferList[thinWallRef].width;
561 | if (sourceIndex > lastSourceIndex) sourceIndex = lastSourceIndex;
562 | }
563 | }
564 |
565 | draw3d() {
566 | for (let i = 0; i < this.items.length; i++) {
567 | // Get perpendicular line coords
568 | const deltaY = this.items[i].y - this.fPlayerY;
569 | const deltaX = this.items[i].x - this.fPlayerX;
570 | const slope = deltaY / deltaX;
571 | const perpSlope = -(1 / slope);
572 | const angle = Math.atan(perpSlope);
573 | let x1;
574 | let y1;
575 | let x2;
576 | let y2;
577 | x1 = this.items[i].x - (this.fItemTextureBufferList[i].width / 2) * Math.cos(angle);
578 | y1 = this.items[i].y - (this.fItemTextureBufferList[i].width / 2) * Math.sin(angle);
579 | x2 = this.items[i].x + (this.fItemTextureBufferList[i].width / 2) * Math.cos(angle);
580 | y2 = this.items[i].y + (this.fItemTextureBufferList[i].width / 2) * Math.sin(angle);
581 |
582 | const intersection = getIntersection(
583 | this.fPlayerX,
584 | this.fPlayerY,
585 | 1,
586 | degToRad(this.fPlayerAngle),
587 | x1,
588 | y1,
589 | x2,
590 | y2
591 | );
592 |
593 | if (intersection?.[0]) {
594 | const dx = Math.abs(this.fPlayerX - intersection[0]);
595 | const dy = Math.abs(this.fPlayerY - intersection[1]);
596 | const d = Math.sqrt(dx * dx + dy * dy);
597 |
598 | if (d <= this.minUseDist) this.items[i].inReticle = true;
599 | else this.items[i].inReticle = false;
600 | } else this.items[i].inReticle = false;
601 | }
602 |
603 | for (let i = 0; i < this.rayLengths.length; i++) {
604 | if (this.rayLengths[i] === 0) return;
605 | let dist = this.rayLengths[i] / this.fFishTable[i];
606 |
607 | const ratio = this.fPlayerDistanceToProjectionPlane / dist;
608 | const scale = (this.fPlayerDistanceToProjectionPlane * this.WALL_HEIGHT) / dist;
609 | const wallBottom = ratio * this.fPlayerHeight + this.fProjectionPlaneYCenter;
610 | const wallTop = wallBottom - scale;
611 | const wallHeight = wallBottom - wallTop;
612 |
613 | let textureBufferPainting = null;
614 | let texturePixelsPainting = null;
615 |
616 | loop: for (let j = 0; j < this.fPaintingDetails.length; j++) {
617 | const tileIndexPainting = this.fPaintingDetails[j].row * this.mapCols + this.fPaintingDetails[j].col;
618 | if (
619 | tileIndexPainting === this.tileIndeces[i] &&
620 | this.fPaintingDetails[j].side === this.tileSides[i]
621 | ) {
622 | textureBufferPainting = this.fPaintingTextureBufferList[j];
623 | texturePixelsPainting = this.fPaintingTexturePixelsList[j];
624 | break loop;
625 | }
626 | }
627 |
628 | let adjustedAngle = this.rayAngles[i] + degToRad(this.fPlayerAngle);
629 | if (adjustedAngle < 0) adjustedAngle += 2 * this.pi;
630 |
631 | if (
632 | i === this.PROJECTIONPLANEWIDTH / 2 &&
633 | wallTop <= this.PROJECTIONPLANEHEIGHT / 2 &&
634 | wallBottom >= this.PROJECTIONPLANEHEIGHT / 2
635 | ) {
636 | this.reticleOnWall = true;
637 | } else if (i === this.PROJECTIONPLANEWIDTH / 2) this.reticleOnWall = false;
638 |
639 | let offset =
640 | this.tileSides?.[i] === 0 || this.tileSides?.[i] === 2
641 | ? this.tileCollisionsX[i] & (this.TILE_SIZE - 1)
642 | : this.tileCollisionsY[i] & (this.TILE_SIZE - 1);
643 |
644 | if (this.tileSides?.[i] === 0 || this.tileSides?.[i] === 1) offset = this.TILE_SIZE - offset - 1;
645 |
646 | let textureBuffer = this.fWallTextureBufferList[this.tileTypes?.[i]];
647 | let texturePixels = this.fWallTexturePixelsList[this.tileTypes?.[i]];
648 |
649 | let brightnessLevel =
650 | this.currentLightValues?.[~~this.tileCollisionsY[i] * this.mapWidth + ~~this.tileCollisionsX[i]];
651 | if (brightnessLevel > this.maxBrightness) brightnessLevel = this.maxBrightness;
652 | if (!brightnessLevel || brightnessLevel < this.minBrightness) brightnessLevel = this.minBrightness;
653 | if (this.tileSides?.[i] === 1 || this.tileSides?.[i] === 3) brightnessLevel *= 0.85;
654 |
655 | if (this?.fSkyTextureBuffer) this.drawSky(i, adjustedAngle);
656 |
657 | this.drawFloor(Math.floor(wallBottom), i, adjustedAngle);
658 |
659 | this.drawCeiling(Math.floor(wallTop), i, adjustedAngle);
660 |
661 | this.drawWallSliceRectangleTinted(
662 | i,
663 | wallTop,
664 | wallHeight + 1,
665 | offset,
666 | brightnessLevel,
667 | textureBuffer,
668 | texturePixels,
669 | textureBufferPainting,
670 | texturePixelsPainting
671 | );
672 |
673 | this.objectTargetIndecesForStrip = [];
674 | this.objectDistsForStrip = [];
675 | // Objects
676 | for (let j = 0; j < this.objectRayLengths[i].length; j++) {
677 | let textureBuffer;
678 | let texturePixels;
679 |
680 | let objDist =
681 | this.objectRayLengths[i][j] > 0 ? this.objectRayLengths[i][j] / this.fFishTable[i] : null;
682 |
683 | if (objDist) {
684 | let objRatio;
685 | let objScale;
686 | let objBottom;
687 | let objTop;
688 | let objHeight;
689 | if (this.isItemRay[i][j]) {
690 | textureBuffer = this.fItemTextureBufferList[this.objectRefs[i][j]];
691 | texturePixels = this.fItemTexturePixelsList[this.objectRefs[i][j]];
692 | } else if (this.isLightsourceRay[i][j]) {
693 | textureBuffer = this.fLightTextureBufferList[this.objectRefs[i][j]];
694 | texturePixels = this.fLightTexturePixelsList[this.objectRefs[i][j]];
695 | } else {
696 | textureBuffer = this.fObjectTextureBufferList[this.objectRefs[i][j]];
697 | texturePixels = this.fObjectTexturePixelsList[this.objectRefs[i][j]];
698 | }
699 |
700 | objRatio = this.fPlayerDistanceToProjectionPlane / objDist;
701 | objScale = (this.fPlayerDistanceToProjectionPlane * textureBuffer.height) / objDist;
702 | objBottom = objRatio * this.fPlayerHeight + this.fProjectionPlaneYCenter;
703 | objTop = objBottom - objScale;
704 | objHeight = objBottom - objTop;
705 |
706 | if (this.isLightsourceRay[i][j]) {
707 | switch (this.lightSources[this.objectRefs[i][j]]?.surface) {
708 | case 'wall':
709 | objScale =
710 | (this.fPlayerDistanceToProjectionPlane *
711 | (this.WALL_HEIGHT / 2 + textureBuffer.height / 2)) /
712 | objDist;
713 | break;
714 | case 'ceiling':
715 | objScale = (this.fPlayerDistanceToProjectionPlane * this.WALL_HEIGHT) / objDist;
716 | break;
717 | }
718 | objTop = objBottom - objScale;
719 | }
720 |
721 | if (
722 | this.isItemRay[i][j] &&
723 | this.items[this.objectRefs[i][j]].inReticle &&
724 | objTop <= this.canvasHeight / 2 &&
725 | objBottom >= this.canvasHeight / 2
726 | ) {
727 | this.items[this.objectRefs[i][j]].inReticle = true;
728 | } else if (this.isItemRay[i][j]) this.items[this.objectRefs[i][j]].inReticle = false;
729 |
730 | let objBrightnessLevel =
731 | this.currentLightValues?.[
732 | ~~this.objectCollisionsY[i][j] * this.mapWidth + ~~this.objectCollisionsX[i][j]
733 | ] || this.minBrightness;
734 | if (objBrightnessLevel > this.maxBrightness) objBrightnessLevel = this.maxBrightness;
735 |
736 | this.drawObjectStrip(
737 | i,
738 | Math.floor(objTop),
739 | objHeight,
740 | objBrightnessLevel,
741 | this.objectOffsets[i][j],
742 | textureBuffer,
743 | texturePixels,
744 | this.isItemRay[i][j] ? this.items[this.objectRefs[i][j]].inReticle : false,
745 | objDist
746 | );
747 | }
748 | }
749 |
750 | let thinWallDist =
751 | this.thinWallRayLengths[i] > 0 ? this.thinWallRayLengths[i] / this.fFishTable[i] : null;
752 |
753 | if (thinWallDist) {
754 | let thinWallBrightnessLevel =
755 | this.currentLightValues?.[
756 | ~~this.thinWallCollisionsY[i] * this.mapWidth + ~~this.thinWallCollisionsX[i]
757 | ] || this.minBrightness;
758 | if (thinWallBrightnessLevel > this.maxBrightness) thinWallBrightnessLevel = this.maxBrightness;
759 |
760 | const thinWallRatio = this.fPlayerDistanceToProjectionPlane / thinWallDist;
761 | const thinWallScale = (this.fPlayerDistanceToProjectionPlane * this.WALL_HEIGHT) / thinWallDist;
762 | const thinWallBottom = thinWallRatio * this.fPlayerHeight + this.fProjectionPlaneYCenter;
763 | const thinWallTop = thinWallBottom - thinWallScale;
764 | let thinWallHeight = thinWallBottom - thinWallTop;
765 |
766 | if (
767 | i === this.PROJECTIONPLANEWIDTH / 2 &&
768 | thinWallTop <= this.PROJECTIONPLANEHEIGHT / 2 &&
769 | thinWallBottom >= this.PROJECTIONPLANEHEIGHT / 2
770 | ) {
771 | this.reticleOnWall = true;
772 | } else if (i === this.PROJECTIONPLANEWIDTH / 2) this.reticleOnWall = false;
773 |
774 | this.drawThinWallStrip(
775 | i,
776 | Math.floor(thinWallTop),
777 | thinWallHeight,
778 | thinWallBrightnessLevel,
779 | this.thinWallRefs[i],
780 | this.thinWallOffsets[i],
781 | thinWallDist
782 | );
783 | }
784 | }
785 | }
786 |
787 | draw2d() {
788 | for (let i = 0; i < this.mapRows; i++) {
789 | for (let j = 0; j < this.mapCols; j++) {
790 | const tile = this.map[i * this.mapCols + j];
791 |
792 | this.debugCtx.fillStyle = `rgb(${(tile + 1) / 0.1}, ${(tile + 1) / 0.1}, ${(tile + 1) / 0.1})`;
793 | this.debugCtx.beginPath();
794 | this.debugCtx.fillRect(j * this.TILE_SIZE, i * this.TILE_SIZE, this.TILE_SIZE, this.TILE_SIZE);
795 | }
796 | }
797 |
798 | for (let i = 0; i < this.objects.length; i++) {
799 | this.debugCtx.fillStyle = `rgb(0, 100, 255)`;
800 | this.debugCtx.beginPath();
801 | this.debugCtx.ellipse(
802 | this.objects[i].x,
803 | this.objects[i].y,
804 | this.fObjectTextureBufferList[i].width / 2,
805 | this.fObjectTextureBufferList[i].width / 2,
806 | 2 * this.pi,
807 | 0,
808 | 2 * this.pi
809 | );
810 | this.debugCtx.fill();
811 | }
812 |
813 | for (let i = 0; i < this.items.length; i++) {
814 | this.debugCtx.fillStyle = `rgb(0, 255, 0)`;
815 | this.debugCtx.beginPath();
816 | this.debugCtx.ellipse(
817 | this.items[i].x,
818 | this.items[i].y,
819 | this.fItemTextureBufferList[i].width / 2,
820 | this.fItemTextureBufferList[i].width / 2,
821 | 2 * this.pi,
822 | 0,
823 | 2 * this.pi
824 | );
825 | this.debugCtx.fill();
826 | }
827 |
828 | for (let i = 0; i < this.thinWalls.length; i++) {
829 | const wall = this.thinWalls[i];
830 |
831 | this.debugCtx.strokeStyle = `rgb(255, 0, 255)`;
832 | this.debugCtx.lineWidth = 10;
833 | this.debugCtx.beginPath();
834 | this.debugCtx.moveTo(wall.xStart, wall.yStart);
835 | this.debugCtx.lineTo(wall.xEnd, wall.yEnd);
836 | this.debugCtx.stroke();
837 | }
838 | }
839 |
840 | getIntersectionOfTile(x, y, row, col, theta, sides = [0, 1, 2, 3], p4 = null) {
841 | const x1 = col * this.TILE_SIZE;
842 | const y1 = row * this.TILE_SIZE;
843 |
844 | const x2 = x1 + this.TILE_SIZE;
845 | const y2 = y1;
846 |
847 | const x3 = x2;
848 | const y3 = y1 + this.TILE_SIZE;
849 |
850 | const x4 = x1;
851 | const y4 = y3;
852 |
853 | let record = Infinity;
854 | let closest = null;
855 | let dir = 0;
856 |
857 | let tX1 = 0;
858 | let tY1 = 0;
859 | let tX2 = 0;
860 | let tY2 = 0;
861 |
862 | for (let i = 0; i < sides.length; i++) {
863 | switch (sides[i]) {
864 | case 0:
865 | tX1 = x1;
866 | tY1 = y1;
867 | tX2 = x2;
868 | tY2 = y2;
869 | break;
870 | case 1:
871 | tX1 = x2;
872 | tY1 = y2;
873 | tX2 = x3;
874 | tY2 = y3;
875 | break;
876 | case 2:
877 | tX1 = x3;
878 | tY1 = y3;
879 | tX2 = x4;
880 | tY2 = y4;
881 | break;
882 | case 3:
883 | tX1 = x4;
884 | tY1 = y4;
885 | tX2 = x1;
886 | tY2 = y1;
887 | break;
888 | }
889 |
890 | const intersection = getIntersection(x, y, 1, theta, tX1, tY1, tX2, tY2, p4);
891 | if (intersection?.[0]) {
892 | const dx = Math.abs(x - intersection[0]);
893 | const dy = Math.abs(y - intersection[1]);
894 | const d = Math.sqrt(dx * dx + dy * dy);
895 | record = Math.min(d, record);
896 | if (d <= record) {
897 | record = d;
898 | closest = intersection;
899 | dir = sides[i];
900 | }
901 | }
902 | }
903 |
904 | return {
905 | record,
906 | closest,
907 | dir,
908 | };
909 | }
910 |
911 | raycaster() {
912 | let tileTypeTemp = 0;
913 | let tileSideDirTemp = 0;
914 |
915 | for (let i = 0; i < this.rayAngles.length; i++) {
916 | let adjustedAngle;
917 | adjustedAngle = this.rayAngles[i] + degToRad(this.fPlayerAngle);
918 | if (adjustedAngle < 0) adjustedAngle += 2 * this.pi;
919 |
920 | let closest = null;
921 | let record = Infinity;
922 | let thinWallRecord = Infinity;
923 | let thinWallClosest = null;
924 |
925 | // Filter through thin walls for each ray --------------------------------------------------------
926 | this.thinWallRayLengths[i] = 0;
927 | this.thinWallCollisionsX[i] = 0;
928 | this.thinWallCollisionsY[i] = 0;
929 | this.thinWallRefs[i] = 0;
930 | this.thinWallOffsets[i] = 0;
931 |
932 | for (let j = 0; j < this.thinWalls.length; j++) {
933 | const intersection = getIntersection(
934 | this.fPlayerX,
935 | this.fPlayerY,
936 | 1,
937 | adjustedAngle,
938 | this.thinWalls[j].xStart,
939 | this.thinWalls[j].yStart,
940 | this.thinWalls[j].xEnd,
941 | this.thinWalls[j].yEnd
942 | );
943 |
944 | if (intersection?.[0]) {
945 | const dx = Math.abs(this.fPlayerX - intersection[0]);
946 | const dy = Math.abs(this.fPlayerY - intersection[1]);
947 | const d = Math.sqrt(dx * dx + dy * dy);
948 |
949 | if (d < thinWallRecord) {
950 | thinWallRecord = d;
951 | thinWallClosest = intersection;
952 | this.thinWallRayLengths[i] = d;
953 | this.thinWallCollisionsX[i] = intersection[0];
954 | this.thinWallCollisionsY[i] = intersection[1];
955 | this.thinWallRefs[i] = j;
956 | this.thinWallOffsets[i] = ~~Math.sqrt(
957 | (intersection[0] - this.thinWalls[j].xStart) * (intersection[0] - this.thinWalls[j].xStart) +
958 | (intersection[1] - this.thinWalls[j].yStart) * (intersection[1] - this.thinWalls[j].yStart)
959 | );
960 | }
961 | }
962 | }
963 |
964 | //-------------------------------------------------------------------------------------------
965 |
966 | this.rayAngleQuadrants[i] = Math.floor(adjustedAngle / (this.pi / 2));
967 |
968 | let sidesToCheck = [0, 1, 2, 3];
969 | if (this.rayAngleQuadrants[i] === 0) sidesToCheck = [0, 3];
970 | else if (this.rayAngleQuadrants[i] === 1) sidesToCheck = [0, 1];
971 | else if (this.rayAngleQuadrants[i] === 2) sidesToCheck = [1, 2];
972 | else if (this.rayAngleQuadrants[i] === 3) sidesToCheck = [2, 3];
973 |
974 | let tileIndex = 0;
975 | for (let row = 0; row < this.mapRows; row++) {
976 | for (let col = 0; col < this.mapCols; col++) {
977 | const tile = this.map[row * this.mapCols + col];
978 | if (tile > 5) continue;
979 |
980 | const tileIntersection = this.getIntersectionOfTile(
981 | this.fPlayerX,
982 | this.fPlayerY,
983 | row,
984 | col,
985 | adjustedAngle,
986 | sidesToCheck
987 | );
988 |
989 | if (tileIntersection.record < record) {
990 | record = tileIntersection.record;
991 | closest = tileIntersection.closest;
992 | tileIndex = row * this.mapCols + col;
993 | tileTypeTemp = tile;
994 | tileSideDirTemp = tileIntersection.dir;
995 |
996 | if (record < thinWallRecord) this.thinWallRayLengths[i] = 0;
997 | }
998 | }
999 | }
1000 |
1001 | if (closest) {
1002 | this.rayLengths[i] = ~~record;
1003 | this.tileCollisionsX[i] = closest[0];
1004 | this.tileCollisionsY[i] = closest[1];
1005 | this.tileTypes[i] = tileTypeTemp;
1006 | this.tileSides[i] = tileSideDirTemp;
1007 | this.tileIndeces[i] = tileIndex;
1008 | } else this.rayLengths[i] = 0;
1009 |
1010 | // Draw rays on debug canvas ----------------------------------------------------------------
1011 | if (this.DEBUG) {
1012 | if (record < thinWallRecord) {
1013 | this.debugCtx.strokeStyle =
1014 | i === this.rayAngles.length ? `rgba(0,255,0,0.7)` : `rgba(255,255,255,0.3)`;
1015 | this.debugCtx.beginPath();
1016 | this.debugCtx.moveTo(this.fPlayerX, this.fPlayerY);
1017 | this.debugCtx.lineTo(closest[0], closest[1]);
1018 | this.debugCtx.lineWidth = 1;
1019 | this.debugCtx.stroke();
1020 | } else {
1021 | if (this.DEBUG) {
1022 | this.debugCtx.strokeStyle = `rgba(255,0,255,0.3)`;
1023 | this.debugCtx.beginPath();
1024 | this.debugCtx.moveTo(this.fPlayerX, this.fPlayerY);
1025 | this.debugCtx.lineTo(thinWallClosest[0], thinWallClosest[1]);
1026 | this.debugCtx.lineWidth = 1;
1027 | this.debugCtx.stroke();
1028 | }
1029 | }
1030 | }
1031 |
1032 | // Filter through objects and items for each ray -----------------------------------------------------------
1033 | this.objectRayLengths[i] = [];
1034 | this.objectCollisionsX[i] = [];
1035 | this.objectCollisionsY[i] = [];
1036 | this.objectRefs[i] = [];
1037 | this.objectOffsets[i] = [];
1038 | this.isItemRay[i] = [];
1039 | this.isLightsourceRay[i] = [];
1040 |
1041 | const rayObjData = [
1042 | {
1043 | rayLength: 0,
1044 | collisionX: 0,
1045 | collisionY: 0,
1046 | ref: 0,
1047 | offset: 0,
1048 | isItemRay: 0,
1049 | isLightsourceRay: 0,
1050 | },
1051 | ];
1052 |
1053 | for (let j = 0; j < this.objects.length + this.items.length + this.lightSources.length; j++) {
1054 | let object;
1055 | let textureBuffer;
1056 | let ref;
1057 | let isItem = false;
1058 | let isLightsource = false;
1059 | if (j < this.objects.length) {
1060 | object = this.objects[j];
1061 | textureBuffer = this.fObjectTextureBufferList[j];
1062 | ref = j;
1063 | } else if (j >= this.objects.length && j < this.objects.length + this.items.length) {
1064 | ref = j - this.objects.length;
1065 | object = this.items[ref];
1066 | textureBuffer = this.fItemTextureBufferList[ref];
1067 | isItem = true;
1068 | } else {
1069 | ref = j - this.objects.length - this.items.length;
1070 | object = this.lightSources[ref];
1071 | textureBuffer = this.fLightTextureBufferList[ref];
1072 | isLightsource = true;
1073 | }
1074 | // Get perpendicular line coords
1075 | const deltaY = object.y - this.fPlayerY;
1076 | const deltaX = object.x - this.fPlayerX;
1077 | const slope = deltaY / deltaX;
1078 | const perpSlope = -(1 / slope);
1079 | const angle = Math.atan(perpSlope);
1080 | const angle2 = Math.atan2(deltaY, deltaX);
1081 | let x1;
1082 | let y1;
1083 | let x2;
1084 | let y2;
1085 | if (angle2 < 0) {
1086 | x1 = object.x - (textureBuffer.width / 2) * Math.cos(angle);
1087 | y1 = object.y - (textureBuffer.width / 2) * Math.sin(angle);
1088 | x2 = object.x + (textureBuffer.width / 2) * Math.cos(angle);
1089 | y2 = object.y + (textureBuffer.width / 2) * Math.sin(angle);
1090 | } else {
1091 | x1 = object.x + (textureBuffer.width / 2) * Math.cos(angle);
1092 | y1 = object.y + (textureBuffer.width / 2) * Math.sin(angle);
1093 | x2 = object.x - (textureBuffer.width / 2) * Math.cos(angle);
1094 | y2 = object.y - (textureBuffer.width / 2) * Math.sin(angle);
1095 | }
1096 |
1097 | const intersection = getIntersection(this.fPlayerX, this.fPlayerY, 1, adjustedAngle, x1, y1, x2, y2);
1098 |
1099 | if (intersection?.[0]) {
1100 | const dx = Math.abs(this.fPlayerX - intersection[0]);
1101 | const dy = Math.abs(this.fPlayerY - intersection[1]);
1102 | const d = Math.sqrt(dx * dx + dy * dy);
1103 |
1104 | if (d < record) {
1105 | rayObjData.push({
1106 | rayLength: ~~d,
1107 | collisionX: intersection[0],
1108 | collisionY: intersection[1],
1109 | ref: ref,
1110 | offset: ~~Math.sqrt(
1111 | (intersection[0] - x1) * (intersection[0] - x1) +
1112 | (intersection[1] - y1) * (intersection[1] - y1)
1113 | ),
1114 | isItemRay: isItem ? 1 : 0,
1115 | isLightsourceRay: isLightsource ? 1 : 0,
1116 | });
1117 |
1118 | if (this.DEBUG && d < thinWallRecord) {
1119 | this.debugCtx.strokeStyle = isItem ? `rgba(0,255,0,0.3)` : `rgba(0,100,255,0.3)`;
1120 | this.debugCtx.beginPath();
1121 | this.debugCtx.moveTo(this.fPlayerX, this.fPlayerY);
1122 | this.debugCtx.lineTo(intersection[0], intersection[1]);
1123 | this.debugCtx.lineWidth = 1;
1124 | this.debugCtx.stroke();
1125 | }
1126 | }
1127 | }
1128 | }
1129 |
1130 | rayObjData.sort((a, b) => b.rayLength - a.rayLength);
1131 |
1132 | for (let j = 0; j < rayObjData.length; j++) {
1133 | this.objectRayLengths[i].push(rayObjData[j].rayLength);
1134 | this.objectCollisionsX[i].push(rayObjData[j].collisionX);
1135 | this.objectCollisionsY[i].push(rayObjData[j].collisionY);
1136 | this.objectRefs[i].push(rayObjData[j].ref);
1137 | this.objectOffsets[i].push(rayObjData[j].offset);
1138 | this.isItemRay[i].push(rayObjData[j].isItemRay);
1139 | this.isLightsourceRay[i].push(rayObjData[j].isLightsourceRay);
1140 | }
1141 | // if (this.isLightsourceRay[i]) console.log(this.lightSources[0]);
1142 | }
1143 | }
1144 |
1145 | rotate() {
1146 | if (this.fRotationDir === 'left') {
1147 | this.fPlayerAngle -= 4;
1148 | } else if (this.fRotationDir === 'right') {
1149 | this.fPlayerAngle += 4;
1150 | }
1151 | }
1152 |
1153 | setMoveDir() {
1154 | if (this.fKeyForward && !this.fKeyRight && !this.fKeyLeft) {
1155 | // forward
1156 | this.fPlayerMoveDir = this.fPlayerAngle;
1157 | } else if (this.fKeyBack && !this.fKeyRight && !this.fKeyLeft) {
1158 | // backwards
1159 | this.fPlayerMoveDir = this.fPlayerAngle + 180;
1160 | } else if (this.fKeyRight && !this.fKeyForward && !this.fKeyBack) {
1161 | // right
1162 | this.fPlayerMoveDir = this.fPlayerAngle + 90;
1163 | } else if (this.fKeyLeft && !this.fKeyForward && !this.fKeyBack) {
1164 | // left
1165 | this.fPlayerMoveDir = this.fPlayerAngle - 90;
1166 | } else if (this.fKeyForward && this.fKeyRight) {
1167 | // forward-right
1168 | this.fPlayerMoveDir = this.fPlayerAngle + 45;
1169 | } else if (this.fKeyForward && this.fKeyLeft) {
1170 | // forward-left
1171 | this.fPlayerMoveDir = this.fPlayerAngle - 45;
1172 | } else if (this.fKeyBack && this.fKeyRight) {
1173 | // backwards-right
1174 | this.fPlayerMoveDir = this.fPlayerAngle + 135;
1175 | } else if (this.fKeyBack && this.fKeyLeft) {
1176 | // backwards-left
1177 | this.fPlayerMoveDir = this.fPlayerAngle - 135;
1178 | }
1179 | }
1180 |
1181 | playerTooCloseToWall(row, col, minDist) {
1182 | const tileMidX = col * this.TILE_SIZE + this.TILE_SIZE / 2;
1183 | const tileMidY = row * this.TILE_SIZE + this.TILE_SIZE / 2;
1184 |
1185 | const dx = this.fPlayerX - tileMidX;
1186 | const dy = this.fPlayerY - tileMidY;
1187 | const d = Math.sqrt(dx * dx + dy * dy);
1188 |
1189 | if (d <= minDist) return [dx, dy];
1190 | return;
1191 | }
1192 |
1193 | getXspeed = () => this.fPlayerMoveSpeed * Math.cos(degToRad(this.fPlayerMoveDir));
1194 |
1195 | getYspeed = () => this.fPlayerMoveSpeed * Math.sin(degToRad(this.fPlayerMoveDir));
1196 |
1197 | move() {
1198 | if (this.levelTransition) return;
1199 | this.rotate();
1200 |
1201 | const playerTileCol = ~~(this.fPlayerX / this.TILE_SIZE);
1202 | const playerTileRow = ~~(this.fPlayerY / this.TILE_SIZE);
1203 |
1204 | this.setMoveDir();
1205 | let moveDir = convertDeg0To360(this.fPlayerMoveDir);
1206 | let newPlayerX = null;
1207 | let newPlayerY = null;
1208 |
1209 | const minDist = (this.TILE_SIZE * Math.sqrt(2)) / 1.5;
1210 | if (this.fKeyForward || this.fKeyBack || this.fKeyLeft || this.fKeyRight) {
1211 | if (!this.isJumping && Date.now() - this.timeOfLastFootstep > (this.isCrouching ? 1000 : 500)) {
1212 | const footstepNum = ~~(Math.random() * (5 - 1) + 1);
1213 | this.audio.playSound(`footstep${footstepNum}`, this.fPlayerX, this.fPlayerY, false);
1214 | this.timeOfLastFootstep = Date.now();
1215 | }
1216 | for (let i = 0; i < this.thinWalls.length; i++) {
1217 | const intersection = getIntersection(
1218 | this.fPlayerX,
1219 | this.fPlayerY,
1220 | 1,
1221 | degToRad(moveDir),
1222 | this.thinWalls[i].xStart,
1223 | this.thinWalls[i].yStart,
1224 | this.thinWalls[i].xEnd,
1225 | this.thinWalls[i].yEnd
1226 | );
1227 |
1228 | if (intersection?.[0]) {
1229 | const dx = this.fPlayerX - intersection[0];
1230 | const dy = this.fPlayerY - intersection[1];
1231 | const d = Math.sqrt(dx * dx + dy * dy);
1232 |
1233 | if (d <= minDist && !(this.thinWalls[i].vaultable && this.isJumping)) return;
1234 | }
1235 | }
1236 |
1237 | for (let row = 0; row < this.mapRows; row++) {
1238 | loop1: for (let col = 0; col < this.mapCols; col++) {
1239 | const tileIndex = row * this.mapCols + col;
1240 | const tile = this.map[tileIndex];
1241 | if (tile > 5 || (row === playerTileRow && col === playerTileCol)) continue;
1242 |
1243 | if (Math.abs(col - playerTileCol) <= 1 && Math.abs(row - playerTileRow) <= 1) {
1244 | const closeDistToTile = this.playerTooCloseToWall(row, col, minDist);
1245 |
1246 | if (closeDistToTile) {
1247 | const angleToWallCenter = radToDeg(
1248 | Math.atan2(Math.abs(closeDistToTile[1]), Math.abs(closeDistToTile[0]))
1249 | );
1250 |
1251 | if (angleToWallCenter >= 0 && angleToWallCenter < 45) {
1252 | // On left or right of wall
1253 | const playerWallTileDiffCol = playerTileCol - col;
1254 | if (playerWallTileDiffCol > 0 && (moveDir > 270 || moveDir < 90)) break loop1;
1255 | else if (playerWallTileDiffCol < 0 && moveDir < 270 && moveDir > 90) break loop1;
1256 |
1257 | newPlayerY = this.fPlayerY + this.getYspeed();
1258 | } else if (angleToWallCenter >= 45 && angleToWallCenter < 90) {
1259 | // On top or bottom of wall
1260 | const playerWallTileDiffRow = playerTileRow - row;
1261 | if (playerWallTileDiffRow > 0 && moveDir > 0 && moveDir < 180) break loop1;
1262 | else if (playerWallTileDiffRow < 0 && moveDir > 180 && moveDir < 360) break loop1;
1263 |
1264 | newPlayerX = this.fPlayerX + this.getXspeed();
1265 | }
1266 | }
1267 | }
1268 | }
1269 | }
1270 |
1271 | if (newPlayerX && newPlayerY) return;
1272 | else if (newPlayerX) {
1273 | this.fPlayerX = newPlayerX;
1274 | return;
1275 | } else if (newPlayerY) {
1276 | this.fPlayerY = newPlayerY;
1277 | return;
1278 | }
1279 | }
1280 |
1281 | moveDir = degToRad(moveDir);
1282 |
1283 | if (this.fKeyForward || this.fKeyBack || this.fKeyLeft || this.fKeyRight) {
1284 | this.fPlayerX += this.fPlayerMoveSpeed * Math.cos(moveDir);
1285 | this.fPlayerY += this.fPlayerMoveSpeed * Math.sin(moveDir);
1286 | }
1287 | }
1288 |
1289 | onWallTextureLoaded(imgNames) {
1290 | this.fWallTextureBufferList = new Array(imgNames.length);
1291 | this.fWallTexturePixelsList = new Array(imgNames.length);
1292 | for (let i = 0; i < imgNames.length; i++) {
1293 | const img = this.textures[imgNames[i]];
1294 | this.fWallTextureBufferList[i] = new OffscreenCanvas(img.width, img.height);
1295 | this.fWallTextureBufferList[i].getContext('2d', { alpha: false }).drawImage(img, 0, 0);
1296 |
1297 | const imgData = this.fWallTextureBufferList[i]
1298 | .getContext('2d', { alpha: false })
1299 | .getImageData(0, 0, this.fWallTextureBufferList[i].width, this.fWallTextureBufferList[i].height);
1300 | this.fWallTexturePixelsList[i] = imgData.data;
1301 | }
1302 | }
1303 |
1304 | onCeilingTextureLoaded(imgName) {
1305 | const img = this.textures[imgName];
1306 | this.fCeilingTextureBuffer = new OffscreenCanvas(img.width, img.height);
1307 | this.fCeilingTextureBuffer.getContext('2d', { alpha: false }).drawImage(img, 0, 0);
1308 |
1309 | const imgData = this.fCeilingTextureBuffer
1310 | .getContext('2d', { alpha: false })
1311 | .getImageData(0, 0, this.fCeilingTextureBuffer.width, this.fCeilingTextureBuffer.height);
1312 | this.fCeilingTexturePixels = imgData.data;
1313 | }
1314 |
1315 | onSkyTextureLoaded(imgName) {
1316 | const img = this.textures[imgName];
1317 | this.fSkyTextureBuffer = new OffscreenCanvas(img.width, img.height);
1318 | this.fSkyTextureBuffer.getContext('2d', { alpha: false }).drawImage(img, 0, 0);
1319 |
1320 | const imgData = this.fSkyTextureBuffer
1321 | .getContext('2d', { alpha: false })
1322 | .getImageData(0, 0, this.fSkyTextureBuffer.width, this.fSkyTextureBuffer.height);
1323 | this.fSkyTexturePixels = imgData.data;
1324 | }
1325 |
1326 | onFloorTextureLoaded(imgNames) {
1327 | this.fFloorTextureBufferList = new Array(imgNames.length);
1328 | this.fFloorTexturePixelsList = new Array(imgNames.length);
1329 | for (let i = 0; i < imgNames.length; i++) {
1330 | const img = this.textures[imgNames[i]];
1331 | this.fFloorTextureBufferList[i] = new OffscreenCanvas(img.width, img.height);
1332 | this.fFloorTextureBufferList[i].getContext('2d', { alpha: false }).drawImage(img, 0, 0);
1333 |
1334 | const imgData = this.fFloorTextureBufferList[i]
1335 | .getContext('2d', { alpha: false })
1336 | .getImageData(0, 0, this.fFloorTextureBufferList[i].width, this.fFloorTextureBufferList[i].height);
1337 | this.fFloorTexturePixelsList[i] = imgData.data;
1338 | }
1339 | }
1340 |
1341 | onPaintingTexturesLoaded(imgNames) {
1342 | this.fPaintingTextureBufferList = new Array(imgNames.length);
1343 | this.fPaintingTexturePixelsList = new Array(imgNames.length);
1344 |
1345 | for (let i = 0; i < imgNames.length; i++) {
1346 | const img = this.textures[imgNames[i]];
1347 | this.fPaintingTextureBufferList[i] = new OffscreenCanvas(img.width, img.height);
1348 | this.fPaintingTextureBufferList[i].getContext('2d', { alpha: true }).drawImage(img, 0, 0);
1349 |
1350 | const imgData = this.fPaintingTextureBufferList[i]
1351 | .getContext('2d', { alpha: false })
1352 | .getImageData(
1353 | 0,
1354 | 0,
1355 | this.fPaintingTextureBufferList[i].width,
1356 | this.fPaintingTextureBufferList[i].height
1357 | );
1358 | this.fPaintingTexturePixelsList[i] = imgData.data;
1359 | }
1360 | }
1361 |
1362 | onObjectTexturesLoaded(imgNames) {
1363 | this.fObjectTextureBufferList = new Array(imgNames.length);
1364 | this.fObjectTexturePixelsList = new Array(imgNames.length);
1365 |
1366 | for (let i = 0; i < imgNames.length; i++) {
1367 | const img = this.textures[imgNames[i]];
1368 | this.fObjectTextureBufferList[i] = new OffscreenCanvas(img.width, img.height);
1369 | this.fObjectTextureBufferList[i].getContext('2d', { alpha: true }).drawImage(img, 0, 0);
1370 |
1371 | const imgData = this.fObjectTextureBufferList[i]
1372 | .getContext('2d', { alpha: false })
1373 | .getImageData(0, 0, this.fObjectTextureBufferList[i].width, this.fObjectTextureBufferList[i].height);
1374 | this.fObjectTexturePixelsList[i] = imgData.data;
1375 | }
1376 | }
1377 |
1378 | onThinWallTexturesLoaded(imgNames) {
1379 | this.fThinWallTextureBufferList = new Array(imgNames.length);
1380 | this.fThinWallTexturePixelsList = new Array(imgNames.length);
1381 |
1382 | for (let i = 0; i < imgNames.length; i++) {
1383 | const img = this.textures[imgNames[i]];
1384 | this.fThinWallTextureBufferList[i] = new OffscreenCanvas(img.width, img.height);
1385 | this.fThinWallTextureBufferList[i].getContext('2d', { alpha: true }).drawImage(img, 0, 0);
1386 |
1387 | const imgData = this.fThinWallTextureBufferList[i]
1388 | .getContext('2d', { alpha: false })
1389 | .getImageData(
1390 | 0,
1391 | 0,
1392 | this.fThinWallTextureBufferList[i].width,
1393 | this.fThinWallTextureBufferList[i].height
1394 | );
1395 | this.fThinWallTexturePixelsList[i] = imgData.data;
1396 | }
1397 | }
1398 |
1399 | onItemTexturesLoaded(imgNames) {
1400 | this.fItemTextureBufferList = new Array(imgNames.length);
1401 | this.fItemTexturePixelsList = new Array(imgNames.length);
1402 |
1403 | for (let i = 0; i < imgNames.length; i++) {
1404 | const img = this.textures[imgNames[i]];
1405 | this.fItemTextureBufferList[i] = new OffscreenCanvas(img.width, img.height);
1406 | this.fItemTextureBufferList[i].getContext('2d', { alpha: true }).drawImage(img, 0, 0);
1407 |
1408 | const imgData = this.fItemTextureBufferList[i]
1409 | .getContext('2d', { alpha: false })
1410 | .getImageData(0, 0, this.fItemTextureBufferList[i].width, this.fItemTextureBufferList[i].height);
1411 | this.fItemTexturePixelsList[i] = imgData.data;
1412 | }
1413 | }
1414 |
1415 | onLightTexturesLoaded(imgNames) {
1416 | this.fLightTextureBufferList = new Array(imgNames.length);
1417 | this.fLightTexturePixelsList = new Array(imgNames.length);
1418 |
1419 | for (let i = 0; i < imgNames.length; i++) {
1420 | const img = this.textures[imgNames[i]];
1421 | this.fLightTextureBufferList[i] = new OffscreenCanvas(img.width, img.height);
1422 | this.fLightTextureBufferList[i].getContext('2d', { alpha: true }).drawImage(img, 0, 0);
1423 |
1424 | const imgData = this.fLightTextureBufferList[i]
1425 | .getContext('2d', { alpha: false })
1426 | .getImageData(0, 0, this.fLightTextureBufferList[i].width, this.fLightTextureBufferList[i].height);
1427 | this.fLightTexturePixelsList[i] = imgData.data;
1428 | }
1429 | }
1430 |
1431 | jump() {
1432 | if (this.isCrouching || this.isStanding) return;
1433 | this.fPlayerHeight += this.jumpSpeed * this.fGameSpeed;
1434 | this.jumpSpeed -= this.gravityValue * this.fGameSpeed;
1435 |
1436 | if (this.fPlayerHeight <= this.TILE_SIZE / 2) {
1437 | this.jumpSpeed = this.jumpSpeedStart;
1438 | this.fPlayerHeight = this.TILE_SIZE / 2;
1439 | this.isJumping = false;
1440 | }
1441 | }
1442 |
1443 | crouch() {
1444 | if (this.isJumping || this.isStanding) return;
1445 | this.fPlayerHeight -= this.crouchSpeed * this.fGameSpeed;
1446 | this.crouchSpeed -= this.crouchGravity * this.fGameSpeed;
1447 |
1448 | if (this.fPlayerHeight <= this.TILE_SIZE / 2 - this.crouchAmt) {
1449 | this.crouchSpeed = this.crouchSpeedStart;
1450 | this.fPlayerHeight = this.TILE_SIZE / 2 - this.crouchAmt;
1451 | }
1452 | }
1453 |
1454 | stand() {
1455 | if (this.isJumping || this.isCrouching) return;
1456 | this.fPlayerHeight += this.crouchSpeed * this.fGameSpeed;
1457 | this.crouchSpeed -= this.crouchGravity * this.fGameSpeed;
1458 |
1459 | if (this.fPlayerHeight >= this.TILE_SIZE / 2 || this.crouchSpeed <= 0) {
1460 | this.crouchSpeed = this.crouchSpeedStart;
1461 | this.fPlayerHeight = this.TILE_SIZE / 2;
1462 | this.isStanding = false;
1463 | }
1464 | }
1465 |
1466 | fade() {
1467 | if (this.levelTransition) {
1468 | if (this.levelTransitionFadeAmt < 1) this.levelTransitionFadeAmt += this.fGameSpeed / 80;
1469 | else this.levelTransition = false;
1470 | this.ctx.fillStyle = `rgba(0, 0, 0, ${this.levelTransitionFadeAmt})`;
1471 | this.ctx.fillRect(0, 0, this.PROJECTIONPLANEWIDTH, this.PROJECTIONPLANEHEIGHT);
1472 | } else if (this.levelTransitionFadeAmt > 0) {
1473 | this.levelTransitionFadeAmt -= this.fGameSpeed / 80;
1474 | this.ctx.fillStyle = `rgba(0, 0, 0, ${this.levelTransitionFadeAmt})`;
1475 | this.ctx.fillRect(0, 0, this.PROJECTIONPLANEWIDTH, this.PROJECTIONPLANEHEIGHT);
1476 | }
1477 | }
1478 |
1479 | operateThinWall(i) {
1480 | const thinWall = this.thinWalls[i];
1481 | const slideSpeed = this.fGameSpeed / 2.5;
1482 | const stopGap = 8;
1483 |
1484 | if (thinWall.xEnd - thinWall.xStart !== 0) {
1485 | const length = Math.abs(thinWall.xEnd - thinWall.xStart);
1486 | const moveDir = thinWall.xEnd > thinWall.xStart ? 1 : -1;
1487 |
1488 | if (!thinWall.isOpen) {
1489 | this.thinWalls[i].xStart -= slideSpeed * moveDir;
1490 | this.thinWalls[i].xEnd -= slideSpeed * moveDir;
1491 | if (Math.abs(this.thinWalls[i].xStart - this.thinWalls[i].xStartOriginal) >= length - stopGap) {
1492 | this.activeThinWallId = null;
1493 | this.thinWalls[i].isOpen = true;
1494 | this.thinWalls[i].xStart = this.thinWalls[i].xStartOriginal - (length - stopGap) * moveDir;
1495 | }
1496 | } else {
1497 | this.thinWalls[i].xStart += slideSpeed * moveDir;
1498 | this.thinWalls[i].xEnd += slideSpeed * moveDir;
1499 | if (Math.abs(this.thinWalls[i].xEnd - this.thinWalls[i].xStartOriginal) >= length) {
1500 | this.activeThinWallId = null;
1501 | this.thinWalls[i].isOpen = false;
1502 | this.thinWalls[i].xStart = this.thinWalls[i].xStartOriginal;
1503 | }
1504 | }
1505 | } else {
1506 | const length = Math.abs(thinWall.yEnd - thinWall.yStart);
1507 | const moveDir = thinWall.yEnd > thinWall.yStart ? 1 : -1;
1508 |
1509 | if (!thinWall.isOpen) {
1510 | this.thinWalls[i].yStart -= slideSpeed * moveDir;
1511 | this.thinWalls[i].yEnd -= slideSpeed * moveDir;
1512 | if (Math.abs(this.thinWalls[i].yStart - this.thinWalls[i].yStartOriginal) >= length - stopGap) {
1513 | this.activeThinWallId = null;
1514 | this.thinWalls[i].isOpen = true;
1515 | this.thinWalls[i].yStart = this.thinWalls[i].yStartOriginal - (length - stopGap) * moveDir;
1516 | }
1517 | } else {
1518 | this.thinWalls[i].yStart += slideSpeed * moveDir;
1519 | this.thinWalls[i].yEnd += slideSpeed * moveDir;
1520 | if (Math.abs(this.thinWalls[i].yEnd - this.thinWalls[i].yStartOriginal) >= length) {
1521 | this.activeThinWallId = null;
1522 | this.thinWalls[i].isOpen = false;
1523 | this.thinWalls[i].yStart = this.thinWalls[i].yStartOriginal;
1524 | }
1525 | }
1526 | }
1527 | }
1528 |
1529 | update() {
1530 | if (this.DEBUG && this.debugCtx)
1531 | this.debugCtx.clearRect(0, 0, this.debugCanvasWidth, this.debugCanvasHeight);
1532 |
1533 | if (this.isJumping) this.jump();
1534 | if (this.isCrouching) this.crouch();
1535 | if (this.isStanding) this.stand();
1536 | if (this.activeThinWallId !== null) this.operateThinWall(this.activeThinWallId);
1537 |
1538 | this.move();
1539 | this.audio.updateSoundPositions(this.fPlayerAngle, this.fPlayerX, this.fPlayerY);
1540 |
1541 | if (this.DEBUG) this.draw2d();
1542 | this.raycaster();
1543 | this.draw3d();
1544 |
1545 | if (this.DEBUG && this.debugCtx) {
1546 | this.debugCtx.fillStyle = `rgba(255,255,255,1)`;
1547 | this.debugCtx.beginPath();
1548 | this.debugCtx.ellipse(this.fPlayerX, this.fPlayerY, 4, 4, 0, 0, 2 * this.pi);
1549 | this.debugCtx.fill();
1550 | }
1551 | }
1552 |
1553 | setAngles() {
1554 | const rayInc = this.fPlayerFov / this.PROJECTIONPLANEWIDTH;
1555 | let ang = 0;
1556 |
1557 | for (let i = 0; i < this.rayAngles.length; i++) {
1558 | this.rayAngles[i] = degToRad(ang - this.fPlayerFov / 2);
1559 | ang += rayInc;
1560 | }
1561 |
1562 | for (let i = -this.PROJECTIONPLANEWIDTH / 2; i < this.PROJECTIONPLANEWIDTH / 2; i++) {
1563 | const radian = (i * this.pi) / (this.PROJECTIONPLANEWIDTH * 3);
1564 | this.fFishTable[i + this.PROJECTIONPLANEWIDTH / 2] = 1 / Math.cos(radian);
1565 | }
1566 | }
1567 |
1568 | async preloadTextures() {
1569 | const promises = this.texturePaths.map(path => {
1570 | return new Promise((resolve, reject) => {
1571 | const name = path.split('/').pop()?.split('.')[0];
1572 | const image = new Image();
1573 |
1574 | image.src = path;
1575 | image.onload = () => resolve([name, image]);
1576 | image.onerror = () => reject(`Image failed to load: ${path}`);
1577 | });
1578 | });
1579 |
1580 | const imgArraytemp = await Promise.all(promises);
1581 | this.textures = Object.fromEntries(imgArraytemp);
1582 | }
1583 |
1584 | lockPointer() {
1585 | if (!this.userIsInTab && !this.DEBUG) {
1586 | this.canvas.requestPointerLock =
1587 | this.canvas.requestPointerLock ||
1588 | this.canvas.mozRequestPointerLock ||
1589 | this.canvas.webkitRequestPointerLock;
1590 |
1591 | const promise = this.canvas.requestPointerLock({ unadjustedMovement: true });
1592 |
1593 | if (!promise) {
1594 | console.log('Disabling mouse acceleration is not supported');
1595 | return this.canvas.requestPointerLock();
1596 | }
1597 |
1598 | return promise
1599 | .then(() => console.log('Pointer is locked'))
1600 | .catch(err => {
1601 | if (err.name === 'NotSupportedError') {
1602 | return this.canvas.requestPointerLock();
1603 | }
1604 | });
1605 | }
1606 | }
1607 |
1608 | resetTextures() {
1609 | this.fCeilingTextureBuffer = undefined;
1610 | this.fCeilingTexturePixels = undefined;
1611 | this.fFloorTextureBufferList = undefined;
1612 | this.fFloorTexturePixelsList = undefined;
1613 | this.fWallTextureBufferList = undefined;
1614 | this.fWallTexturePixelsList = undefined;
1615 | this.fSkyTextureBuffer = undefined;
1616 | this.fSkyTexturePixels = undefined;
1617 | this.fPaintingTextureBufferList = undefined;
1618 | this.fPaintingTexturePixelsList = undefined;
1619 | this.fThinWallTextureBufferList = undefined;
1620 | this.fThinWallTexturePixelsList = undefined;
1621 | this.fObjectTextureBufferList = undefined;
1622 | this.fObjectTexturePixelsList = undefined;
1623 | this.fItemTextureBufferList = [];
1624 | this.fItemTexturePixelsList = [];
1625 | this.fLightTextureBufferList = undefined;
1626 | this.fLightTexturePixelsList = undefined;
1627 | }
1628 |
1629 | setNewMapData(i = this.mapNum) {
1630 | this.resetTextures();
1631 | this.audio.init(i);
1632 | this.currentLightValues = this.mapLightValues[i];
1633 | this.currentLightRefs = this.mapLightRefs[i];
1634 | this.noCeilingIndeces = maps[i].noCeilingIndeces;
1635 |
1636 | this.onWallTextureLoaded(maps[i].wallTextures);
1637 | this.onPaintingTexturesLoaded(maps[i].paintings);
1638 | this.fPaintingDetails = maps[i].paintingDetails;
1639 |
1640 | if (maps[i].ceilingTexture) this.onCeilingTextureLoaded(maps[i].ceilingTexture);
1641 | if (maps[i].skyTexture) this.onSkyTextureLoaded(maps[i].skyTexture);
1642 | this.onFloorTextureLoaded(maps[i].floorTextures);
1643 |
1644 | if (maps[i]?.lightSources?.length) {
1645 | this.onLightTexturesLoaded(maps[i].lightSources.map(light => light.texture));
1646 | this.lightSources = maps[i].lightSources;
1647 | for (let i = 0; i < this.lightSources.length; i++) {
1648 | const textureWidth = this.textures[this.lightSources[i].texture].width;
1649 | switch (this.lightSources[i].side) {
1650 | case 0:
1651 | this.lightSources[i].x = this.lightSources[i].col * this.TILE_SIZE + this.TILE_SIZE / 2;
1652 | this.lightSources[i].y = this.lightSources[i].row * this.TILE_SIZE - textureWidth / 2;
1653 | break;
1654 | case 1:
1655 | this.lightSources[i].x =
1656 | this.lightSources[i].col * this.TILE_SIZE + this.TILE_SIZE + textureWidth / 2;
1657 | this.lightSources[i].y = this.lightSources[i].row * this.TILE_SIZE + this.TILE_SIZE / 2;
1658 | break;
1659 | case 2:
1660 | this.lightSources[i].x = this.lightSources[i].col * this.TILE_SIZE + this.TILE_SIZE / 2;
1661 | this.lightSources[i].y =
1662 | this.lightSources[i].row * this.TILE_SIZE + this.TILE_SIZE + textureWidth / 2;
1663 | break;
1664 | case 3:
1665 | this.lightSources[i].x = this.lightSources[i].col * this.TILE_SIZE - textureWidth / 2;
1666 | this.lightSources[i].y = this.lightSources[i].row * this.TILE_SIZE + this.TILE_SIZE / 2;
1667 | break;
1668 | default:
1669 | this.lightSources[i].x = this.lightSources[i].col * this.TILE_SIZE + this.TILE_SIZE / 2;
1670 | this.lightSources[i].y = this.lightSources[i].row * this.TILE_SIZE + this.TILE_SIZE / 2;
1671 | }
1672 | }
1673 | } else this.lightSources = [];
1674 |
1675 | if (maps[i]?.items?.length) {
1676 | this.onItemTexturesLoaded(maps[i].items.map(item => item.name));
1677 | this.items = maps[i].items;
1678 | } else this.items = [];
1679 |
1680 | if (maps[i]?.objects?.length) {
1681 | this.onObjectTexturesLoaded(maps[i].objects.map(obj => obj.name));
1682 | this.objects = maps[i].objects;
1683 | } else this.objects = [];
1684 |
1685 | if (maps[i]?.thinWalls?.length) {
1686 | this.onThinWallTexturesLoaded(maps[i].thinWalls.map(wall => wall.texture));
1687 | this.thinWalls = maps[i].thinWalls.map(wall => {
1688 | let xStartTemp = wall.colStart * this.TILE_SIZE;
1689 | let yStartTemp = wall.rowStart * this.TILE_SIZE;
1690 | let xEndTemp = wall.colEnd * this.TILE_SIZE;
1691 | let yEndTemp = wall.rowEnd * this.TILE_SIZE;
1692 |
1693 | if (yStartTemp < yEndTemp) {
1694 | xStartTemp += this.TILE_SIZE / 2;
1695 | xEndTemp += this.TILE_SIZE / 2;
1696 | } else if (yEndTemp < yStartTemp) {
1697 | xStartTemp += this.TILE_SIZE / 2;
1698 | xEndTemp += this.TILE_SIZE / 2;
1699 | } else if (xStartTemp < xEndTemp) {
1700 | yStartTemp += this.TILE_SIZE / 2;
1701 | yEndTemp += this.TILE_SIZE / 2;
1702 | } else if (xEndTemp < xStartTemp) {
1703 | yStartTemp += this.TILE_SIZE / 2;
1704 | yEndTemp += this.TILE_SIZE / 2;
1705 | }
1706 |
1707 | return {
1708 | texture: wall.texture,
1709 | xStartOriginal: xStartTemp,
1710 | yStartOriginal: yStartTemp,
1711 | xStart: xStartTemp,
1712 | yStart: yStartTemp,
1713 | xEnd: xEndTemp,
1714 | yEnd: yEndTemp,
1715 | isOpen: wall.isOpen,
1716 | sounds: wall.sounds,
1717 | function: wall.function,
1718 | transparent: wall.transparent,
1719 | vaultable: wall.vaultable,
1720 | };
1721 | });
1722 | } else this.thinWalls = [];
1723 |
1724 | this.map = new Uint8Array(maps[i].map.flat());
1725 | this.mapNum = i;
1726 | this.doorMap = maps[i].doorMap;
1727 | this.mapCols = maps[i].map[0].length;
1728 | this.mapRows = maps[i].map.length;
1729 | this.mapWidth = this.TILE_SIZE * this.mapCols;
1730 | this.mapHeight = this.TILE_SIZE * this.mapRows;
1731 | this.fPlayerX = this.fPlayerX;
1732 | this.fPlayerY = this.fPlayerY;
1733 |
1734 | if (this.DEBUG && this.debugCanvas) {
1735 | this.debugCanvas.width = this.mapWidth;
1736 | this.debugCanvas.height = this.mapHeight;
1737 | this.debugCanvasWidth = this.debugCanvas.width;
1738 | this.debugCanvasHeight = this.debugCanvas.height;
1739 | this.debugCtx = this.debugCanvas.getContext('2d', { alpha: false });
1740 |
1741 | this.debugCanvas.style.aspectRatio = this.debugCanvasWidth / this.debugCanvasHeight;
1742 | }
1743 |
1744 | for (let j = 0; j < this.objects.length; j++) {
1745 | const obj = this.objects[j];
1746 | if (obj?.sounds?.length) {
1747 | obj.sounds.forEach(sound => {
1748 | if (this.audio.sounds[sound + j]?.loop()) {
1749 | this.audio.playSound(sound, obj.x, obj.y, true, j);
1750 | }
1751 | });
1752 | }
1753 | }
1754 |
1755 | for (let j = 0; j < this.lightSources.length; j++) {
1756 | const light = this.lightSources[j];
1757 | if (light?.sounds?.length) {
1758 | light.sounds.forEach(sound => {
1759 | if (this.audio.sounds[sound + j]?.loop()) {
1760 | this.audio.playSound(sound, light.x, light.y, true, j);
1761 | }
1762 | });
1763 | }
1764 | }
1765 | }
1766 |
1767 | calculateLightValues() {
1768 | return new Promise((resolve, reject) => {
1769 | const total = maps.reduce(
1770 | (acc, value) => acc + value.lightSources.length * value.map[0].length * this.TILE_SIZE,
1771 | 0
1772 | );
1773 | let progress = 0;
1774 |
1775 | const lightingVersionTransaction = this.db.transaction('lightingVersion');
1776 | const lightingVersionObjectStore = lightingVersionTransaction.objectStore('lightingVersion');
1777 | const lightingVersionGetRequest = lightingVersionObjectStore.get(0);
1778 |
1779 | lightingVersionGetRequest.onsuccess = () => {
1780 | let mapsCompleted = 0;
1781 |
1782 | if (lightingVersionGetRequest?.result === this.lightingVersionNum) {
1783 | for (let i = 0; i < maps.length; i++) {
1784 | const transaction = this.db.transaction('lighting');
1785 | const objectStore = transaction.objectStore('lighting');
1786 | const getRequest = objectStore.get(i);
1787 |
1788 | getRequest.onsuccess = () => {
1789 | this.mapLightValues[i] = getRequest.result?.mapLightValues || null;
1790 | this.mapLightRefs[i] = getRequest.result?.mapLightRefs || null;
1791 | this.mapLightSides[i] = getRequest.result?.mapLightSides || null;
1792 | console.log(`Lighting for map ${i} has been retrieved`);
1793 | mapsCompleted++;
1794 | if (mapsCompleted === maps.length) resolve();
1795 | };
1796 | getRequest.onerror = () => {
1797 | console.log(`Unable to retrieve lighting for map ${i}`);
1798 | reject();
1799 | };
1800 | }
1801 | } else {
1802 | (function mapLoop(i = 0) {
1803 | if (i < maps.length) {
1804 | if (!maps[i].lightSources.length) {
1805 | this.mapLightValues[i] = null;
1806 | this.mapLightRefs[i] = null;
1807 | this.mapLightSides[i] = null;
1808 | console.log(`Lighting for map ${i} has been retrieved`);
1809 | mapsCompleted++;
1810 | if (mapsCompleted === maps.length) resolve();
1811 | } else {
1812 | document.querySelector('.loadingContainer').style.display = 'flex';
1813 | console.log(`Processing lighting for map ${i}`);
1814 | const curLevel = maps[i];
1815 | const curMap = curLevel.map;
1816 | const mapCols = curMap[0].length;
1817 | const mapRows = curMap.length;
1818 | const mapWidth = mapCols * this.TILE_SIZE;
1819 | const mapHeight = mapRows * this.TILE_SIZE;
1820 |
1821 | this.mapLightValues[i] = new Float32Array(mapWidth * mapHeight).fill(this.minBrightness);
1822 | this.mapLightRefs[i] = new Uint8Array(mapWidth * mapHeight);
1823 | this.mapLightSides[i] = new Int8Array(mapWidth * mapHeight).fill(-1);
1824 |
1825 | (function lightSourceLoop(j = 0) {
1826 | if (j < curLevel.lightSources.length) {
1827 | const light = curLevel.lightSources[j];
1828 | const textureWidth = this.textures[light.texture].width;
1829 | const sourceCol = light.col;
1830 | const sourceRow = light.row;
1831 | let lightX;
1832 | let lightY;
1833 |
1834 | switch (light.side) {
1835 | case 0:
1836 | lightX = sourceCol * this.TILE_SIZE + this.TILE_SIZE / 2;
1837 | lightY = sourceRow * this.TILE_SIZE - textureWidth / 2;
1838 | break;
1839 | case 1:
1840 | lightX = sourceCol * this.TILE_SIZE + this.TILE_SIZE + textureWidth / 2;
1841 | lightY = sourceRow * this.TILE_SIZE + this.TILE_SIZE / 2;
1842 | break;
1843 | case 2:
1844 | lightX = sourceCol * this.TILE_SIZE + this.TILE_SIZE / 2;
1845 | lightY = sourceRow * this.TILE_SIZE + this.TILE_SIZE + textureWidth / 2;
1846 | break;
1847 | case 3:
1848 | lightX = sourceCol * this.TILE_SIZE - textureWidth / 2;
1849 | lightY = sourceRow * this.TILE_SIZE + this.TILE_SIZE / 2;
1850 | break;
1851 | default:
1852 | lightX = sourceCol * this.TILE_SIZE + this.TILE_SIZE / 2;
1853 | lightY = sourceRow * this.TILE_SIZE + this.TILE_SIZE / 2;
1854 | }
1855 |
1856 | (function xLoop(x = 0) {
1857 | if (x < mapWidth) {
1858 | setTimeout(() => {
1859 | document.querySelector('.loadingFill').style.width = `${~~(
1860 | (progress / total) *
1861 | 100
1862 | )}%`;
1863 | document.querySelector('.loadingValue').innerText = `${~~(
1864 | (progress / total) *
1865 | 100
1866 | )}%`;
1867 |
1868 | if (x >= this.TILE_SIZE && x <= mapWidth - this.TILE_SIZE) {
1869 | loop1: for (let y = 0; y < mapHeight; y++) {
1870 | if (y < this.TILE_SIZE || y > mapHeight - this.TILE_SIZE) continue loop1;
1871 | for (let row = 0; row < mapRows; row++) {
1872 | loop2: for (let col = 0; col < mapCols; col++) {
1873 | const tile = curMap[row][col];
1874 | if (tile > 5) continue loop2;
1875 |
1876 | const x1 = col * this.TILE_SIZE;
1877 | const y1 = row * this.TILE_SIZE;
1878 |
1879 | const x2 = x1 + this.TILE_SIZE;
1880 | const y2 = y1;
1881 |
1882 | const x3 = x2;
1883 | const y3 = y1 + this.TILE_SIZE;
1884 |
1885 | const x4 = x1;
1886 | const y4 = y3;
1887 |
1888 | if (x > x1 && x < x2 && y > y1 && y < y3) continue loop1;
1889 | if (
1890 | Math.abs(x1 + this.TILE_SIZE / 2 - x) > Math.abs(lightX - x) &&
1891 | Math.abs(y1 + this.TILE_SIZE / 2 - y) > Math.abs(lightY - y)
1892 | ) {
1893 | continue loop2;
1894 | }
1895 |
1896 | let tX1 = 0;
1897 | let tY1 = 0;
1898 | let tX2 = 0;
1899 | let tY2 = 0;
1900 |
1901 | let sidesIntersected = 0;
1902 | let sharesCornerWithTile = false;
1903 | for (let i = 0; i < 4; i++) {
1904 | switch (i) {
1905 | case 0:
1906 | tX1 = x1;
1907 | tY1 = y1;
1908 | tX2 = x2;
1909 | tY2 = y2;
1910 | break;
1911 | case 1:
1912 | tX1 = x2;
1913 | tY1 = y2;
1914 | tX2 = x3;
1915 | tY2 = y3;
1916 | break;
1917 | case 2:
1918 | tX1 = x3;
1919 | tY1 = y3;
1920 | tX2 = x4;
1921 | tY2 = y4;
1922 | break;
1923 | case 3:
1924 | tX1 = x4;
1925 | tY1 = y4;
1926 | tX2 = x1;
1927 | tY2 = y1;
1928 | break;
1929 | }
1930 |
1931 | const denom = (tX1 - tX2) * (y - lightY) - (tY1 - tY2) * (x - lightX);
1932 |
1933 | if (denom !== 0) {
1934 | const t = ((tX1 - x) * (y - lightY) - (tY1 - y) * (x - lightX)) / denom;
1935 | const u = ((tX1 - x) * (tY1 - tY2) - (tY1 - y) * (tX1 - tX2)) / denom;
1936 |
1937 | if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
1938 | if (
1939 | !sharesCornerWithTile &&
1940 | ((x === tX1 && y === tY1) || (x === tX2 && y === tY2))
1941 | ) {
1942 | sharesCornerWithTile = true;
1943 | sidesIntersected++;
1944 | } else if (!((x === tX1 && y === tY1) || (x === tX2 && y === tY2)))
1945 | sidesIntersected++;
1946 | }
1947 | if (sidesIntersected > 1) continue loop1;
1948 | }
1949 | }
1950 | }
1951 | }
1952 |
1953 | loop3: for (let k = 0; k < curLevel?.thinWalls?.length; k++) {
1954 | if (curLevel.thinWalls[k].transparent) continue loop3;
1955 | let xStart = curLevel.thinWalls[k].colStart * this.TILE_SIZE;
1956 | let yStart = curLevel.thinWalls[k].rowStart * this.TILE_SIZE;
1957 | let xEnd = curLevel.thinWalls[k].colEnd * this.TILE_SIZE;
1958 | let yEnd = curLevel.thinWalls[k].rowEnd * this.TILE_SIZE;
1959 |
1960 | if (yStart < yEnd) {
1961 | xStart += this.TILE_SIZE / 2;
1962 | xEnd += this.TILE_SIZE / 2;
1963 | } else if (yEnd < yStart) {
1964 | xStart += this.TILE_SIZE / 2;
1965 | xEnd += this.TILE_SIZE / 2;
1966 | } else if (xStart < xEnd) {
1967 | yStart += this.TILE_SIZE / 2;
1968 | yEnd += this.TILE_SIZE / 2;
1969 | } else if (xEnd < xStart) {
1970 | yStart += this.TILE_SIZE / 2;
1971 | yEnd += this.TILE_SIZE / 2;
1972 | }
1973 | if (
1974 | (y === yStart &&
1975 | x >= Math.min(xStart, xEnd) &&
1976 | x <= Math.max(xStart, xEnd)) ||
1977 | (x === xStart && y >= Math.min(yStart, yEnd) && y <= Math.max(yStart, yEnd))
1978 | ) {
1979 | if (xStart === xEnd) {
1980 | if (x - lightX > 0) {
1981 | this.mapLightSides[i][y * mapWidth + x] = parseInt(
1982 | '' + this.mapLightSides[i][y * mapWidth + x] + 1
1983 | );
1984 | } else
1985 | this.mapLightSides[i][y * mapWidth + x] = parseInt(
1986 | '' + this.mapLightSides[i][y * mapWidth + x] + 3
1987 | );
1988 | } else {
1989 | if (y - lightY > 0) {
1990 | this.mapLightSides[i][y * mapWidth + x] = parseInt(
1991 | '' + this.mapLightSides[i][y * mapWidth + x] + 2
1992 | );
1993 | } else
1994 | this.mapLightSides[i][y * mapWidth + x] = parseInt(
1995 | '' + this.mapLightSides[i][y * mapWidth + x] + 0
1996 | );
1997 | }
1998 |
1999 | continue loop3;
2000 | }
2001 |
2002 | const denom = (xStart - xEnd) * (y - lightY) - (yStart - yEnd) * (x - lightX);
2003 |
2004 | if (denom !== 0) {
2005 | const t =
2006 | ((xStart - x) * (y - lightY) - (yStart - y) * (x - lightX)) / denom;
2007 | const u =
2008 | ((xStart - x) * (yStart - yEnd) - (yStart - y) * (xStart - xEnd)) / denom;
2009 |
2010 | if (t >= 0 && t <= 1 && u >= 0 && u <= 1) continue loop1;
2011 | }
2012 | }
2013 |
2014 | const d =
2015 | Math.sqrt((x - lightX) * (x - lightX) + (y - lightY) * (y - lightY)) || 0.001;
2016 | let s = d / light.strength / 1500;
2017 | if (s > 1) s = 1;
2018 | const maxIntensity = 1;
2019 | const falloffRate = 40;
2020 | this.mapLightValues[i][y * mapWidth + x] +=
2021 | maxIntensity * (((1 - s * s) * (1 - s * s)) / (1 + falloffRate * (s * s)));
2022 | this.mapLightRefs[i][y * mapWidth + x] = j;
2023 | }
2024 | }
2025 |
2026 | if (j === curLevel.lightSources.length - 1 && x === mapWidth - 1) {
2027 | const transaction = this.db.transaction('lighting', 'readwrite');
2028 | const objectStore = transaction.objectStore('lighting');
2029 | const request = objectStore.put(
2030 | {
2031 | mapLightValues: this.mapLightValues[i],
2032 | mapLightRefs: this.mapLightRefs[i],
2033 | mapLightSides: this.mapLightSides[i],
2034 | },
2035 | i
2036 | );
2037 |
2038 | request.onsuccess = () => {
2039 | console.log(`Lighting calculations complete for map ${i} - added to database`);
2040 | mapsCompleted++;
2041 |
2042 | if (mapsCompleted === maps.length) {
2043 | const lightingVersionTransaction = this.db.transaction(
2044 | 'lightingVersion',
2045 | 'readwrite'
2046 | );
2047 | const lightingVersionObjectStore =
2048 | lightingVersionTransaction.objectStore('lightingVersion');
2049 |
2050 | const lightingVersionRequest = lightingVersionObjectStore.put(
2051 | this.lightingVersionNum,
2052 | 0
2053 | );
2054 |
2055 | lightingVersionRequest.onsuccess = () => {
2056 | console.log(`Lighting updated to version ${this.lightingVersionNum}`);
2057 | };
2058 | lightingVersionRequest.onerror = () => {
2059 | console.log(
2060 | `Unable to update lighting to version ${this.lightingVersionNum}`
2061 | );
2062 | reject();
2063 | };
2064 | resolve();
2065 | }
2066 | };
2067 | request.onerror = () => {
2068 | console.log(`Unable to add lighting calculations to database for map ${i}`);
2069 | reject();
2070 | };
2071 | }
2072 | progress++;
2073 | xLoop.bind(this)(x + 1);
2074 | }, 1);
2075 | }
2076 | }).bind(this)();
2077 | lightSourceLoop.bind(this)(j + 1);
2078 | }
2079 | }).bind(this)();
2080 | }
2081 | mapLoop.bind(this)(i + 1);
2082 | }
2083 | }).bind(this)();
2084 | }
2085 | };
2086 | });
2087 | }
2088 |
2089 | async init() {
2090 | await this.preloadTextures();
2091 | await this.calculateLightValues();
2092 | this.setNewMapData();
2093 | this.setAngles();
2094 |
2095 | if (!this.DEBUG) {
2096 | this.canvas.classList.add('fullscreen');
2097 | } else {
2098 | const container = document.querySelector('.container');
2099 | const newCanvas = document.createElement('canvas');
2100 | newCanvas.id = 'debugCanvas';
2101 | this.debugCanvas = newCanvas;
2102 | this.debugCanvas.width = this.mapWidth;
2103 | this.debugCanvas.height = this.mapHeight;
2104 | this.debugCanvasWidth = this.debugCanvas.width;
2105 | this.debugCanvasHeight = this.debugCanvas.height;
2106 | this.debugCtx = this.debugCanvas.getContext('2d', { alpha: false });
2107 | container.appendChild(newCanvas);
2108 | }
2109 |
2110 | document.addEventListener(
2111 | 'pointerlockchange',
2112 | () => {
2113 | if (document.pointerLockElement === canvas) {
2114 | this.userIsInTab = true;
2115 | } else this.userIsInTab = false;
2116 | },
2117 | false
2118 | );
2119 |
2120 | document.addEventListener('mousedown', () => {
2121 | this.lockPointer();
2122 | });
2123 |
2124 | document.addEventListener('contextmenu', e => e.preventDefault());
2125 |
2126 | window.addEventListener('beforeunload', e => {
2127 | if (this.preventPageReloadDialog) return;
2128 | e.preventDefault();
2129 | return (e.returnValue = 'Exit Tab?');
2130 | });
2131 | }
2132 | }
2133 |
--------------------------------------------------------------------------------