MANAGO - Bible

Somewhat of a documentation on how the game's code works.

The [GAME] consists of the following files:

game.html
- main HTML file (use browser's view-source function to see it)

loadfirst.js
-loads one audio file at the beginning which is used in the intro

three-kbrecordzz.js
-3D renderer (a simplified and minimized three.js)

ascend.js
-creates the 3D world = creates, and shows and hides terrain chunks and objects (trees, houses, etc) 

functions.js
-game functions, and lists of all area-based events, all story-based events, and all dialogue

basic.js
-images, sounds, etc (files)

input.js
-user input, and a built-in level editor

main.js
-the main game loop

files/*
-folder with all asset files used in the game
.jpg images
.png images
.mp3 sounds
.ttf fonts


The code is mostly the same as in This Is (NOT!) A Car Club, but some things are new or changed:

New technology:
- Custom shader for houses
(and new object codes for houses)

- function roof(), for roofs over larger buildings

- (Custom shader for sprites?)

Large changes:
- (three.js is heavily stripped down to be smaller and faster?)

Small optimizations:
- function xz_move()

- function pxz()

- ...?


Here's a more detailed description of the code, see it as a snapshot from during the middle of development (read this for some more background on the documentation). Not all details are correct anymore, but the big picture is. It's also automatically translated from Swedish, so some variable names may have been translated into false names in here as well. Generally, the darker text color the heavier the code is, which most often means that it runs many times and takes up lots of runtime and performance cost, even if it's just one or a few lines taking up very small space in the code. The background color behind the text is just for style.

"LESS IMPORTANT" FEATURES (for the game's CONTENT, not its SYSTEM): (ascend.js:) basic.js: tex - create texture material snd - create sound spr - create sprite pln - create plane box - create 3D box cln - create 3D cylinder roof functions.js: music_play -- -- WRITE MORE HERE -- -- (input.js:) (main.js:) -- -- WRITE MORE HERE -- --


AT THE START: loadfirst.js three-kbrecordzz.js What happens? "Polyfills" are created, variables are created, all classes and objects are created (not sure if this happens at the start or not). More... ascend.js Constants and variables are created 3D objects such as the camera, scene, and texture loader are created Variables for 3D Variables for path creation Variables for materials Skybox, clouds, water The start chunk is set via URL, cookie, or default Variables for chunk creation grelf profiles arrays for heightmap, objects, vertices, etc. UV -coordinates (and normals) are created for buildings (and other objects) and for paths-2,401 iterations in a for-loop, calculating 12 UVs and setting 12 normals to default values per iteration. roads: 700 iterations in the for-loop, 12 UVs per iteration BufferAttributes with Float32Arrays are created for UVs and normals custom shader materials are created for sprites custom shader materials are created for houses functions.js Constants are created for names and cuts. Then just functions basic.js Variables and arrays are created, sprites and other objects are created and their values are set, sound effects are created input.js Variables and DEBUG variables are created main.js The start cut is retrieved from a cookie or URL. The HTML element save_text is set. ascend_intro is run: Runs once at the beginning A lot of things are done... The ocean, rain, skybox, and clouds are created. seed, master, master2, change4, change5 (seed variables) are set. i1, j1, i, j (chunk-loop variables) are created. Each chunk 21-45, 21-45 (25x25 = 625 chunks) is processed. Primarily, arrays/variables are created for each "row" (25 in total; since each "row" gets an array with 25 values, that makes 25x25 values... sort of), and some values in these arrays are also set for each chunk (625 in total). For chunks 23–44, 23–44 (22x22 = 484 chunks), a pseudorandom value is set as the "chunkmap" (variation in the terrain per chunk). Large arrays-hm and om (fi and fj arrays for these two)-are created for all 484 chunks. Races/roads are created for the entire world . First, elevation and objects are created for the chunk where O'Malley's office is located (the start screen). Then, the 7x7 (49) chunks surrounding the starting chunk are processed. A few elements are set per chunk. For each block (50x50 = 2,500), elevation and objects are generated. Some interpolation is done with elevation so that each elevation value doesn't need to be calculated. For each chunk, calculate_height_after and generate_objects_after are run ---calculate_height_after - runs per chunk. An if-else statement checks which chunk it is, and runs for-loops through i and j to set values in the heightmap array. After the if statements, there are thus only a few nested for-loops per chunk. 5x5 chunks (25 in total) are processed. Each block within these (50x50 = 2,500) checks a few things and creates vertices for houses, trees, and terrain. For each chunk, terrain meshes, tree meshes, and house meshes are created with the correct textures (three.js stuff) Renderers are created Time is set using frame_counter The URL is cleared The player's position is set cut is set to SPLASHSCREEN Checks if it's a mobile device or not etc.


INPUT (can interrupt the main loop at any time): onDocumentKeyDown onDocumentKeyUp checks which key was pressed, sets variables touch_start touch_end mouseDown mouseUp


MAIN-LOOP: The following runs every frame, unless otherwise specified: values are set DEBUG Default values for variables, which can be overridden in cut_set, are set the "mouseclick" sprite's UI position is set, and visible = false the player is rescued from any water The player is moved back from any out-of-bounds areas. Throws an Error if the player is outside the out-of-bounds area for Zanz Dungeon layout_set() - takes about one millisecond Positions for desktop/mobile adaptation are updated every frame during pause, etc., every frame in cutscenes, and every tenth frame in freeroam/race - An "if" statement checks window dimensions and the "mobile" variable, then sets the style for 25 HTML elements Every frame, the "cut" value and other factors are checked, and HTML elements are set to visible or hidden. Plus a few other things chunk_set() - takes a few milliseconds runs every frame values are set, actions are performed music plays per chunk rain & snow are placed in certain chunks checks which chunk it is only the actions specific to the current chunk are performed-gameplay code, placing objects (some functions that in turn call other functions here, but nothing too complicated), etc. The current day is checked, and certain things are only displayed depending on the day (AND the chunk) Roofs and floors are turned on and off in different chunks mesh_skybox, mesh_cloudbox cut_check_splashscreen_race() checks which cut it is -if less than 0, does some splash screen stuff -if race, does race stuff crew_visible = false; -- WRITE MORE HERE -- cut_roomlist() New separate if-else block, checks cuts for the room list (and CUTSCENE_BLACKINTRO) -code runs for each room attach_sprites_to_cars() new separate if statement, checks for cutscene; if so, turn shelf on/off character sprites are placed next to their cars for every other character, special effects are triggered (arrow over character) cut_dialoglist() switch-case for all dialogue-equivalent to >1,000 if-else statements. Many if statements are checked, but only code from one of them is executed, so it's only the if testing that takes time, if anything. -Mostly, the cc function is called, which fetches an image and sets HTML elements for name/image, and then calls the rolling_dialog function, which updates the HTML elements for the dialog with new text and increments a variable to create scrolling text. //cut_set() - takes barely a millisecond //runs every frame //the player is moved to the correct position if it's a TALK character speed camera -- WRITE MORE HERE -- if Tuktuk, set character's goal place the player's frontlight in FREEROAM only; throw an error if the character's goal x/z is less than 0 move player -- WRITE MORE HERE -- camera_set() - takes close to 0 milliseconds Every frame during pause, etc., every frame in a cutscene, and every tenth frame in freeroam/race, the canvas/camera is set to the screen size 3 if statements for cut values run every frame; during pause, etc., the if statement checks for additional cut values inside, and the camera, CSS, and player's position, etc., are set in freeroam/race, mathematical calculations are performed for the camera's position in cutscenes, some mathematical calculations are also performed fire animation ascend_main() - takes about 1 millisecond every frame in a for loop: - show_and_hide_terrain_chunks is called 40 times --- the function iterates through the chunks in an order independent of the function call; it's counted globally. So here, 40 chunks per frame are checked to see if they are CHUNK_SHOW or CHUNK_HIDE, and meshes & point sprites are turned off/on based on that. Simple code, but it runs quite a few times. - dynload_hm runs 3,000 times (actually 200; see the description of dynload_objects?) ---Some operations are performed once. Row 1 and column 1 of a chunk are processed in full ONLY if the game is about to transition to the next chunk (since chunks must "fit together" at their boundaries-in this case, 100 iterations in a loop inside a loop that runs 3,000 times per frame = 300,000 iterations) ---Height and objects are generated for the 4 blocks surrounding the current block (i, j) using the functions set_weights, calculate_height, and generate_objects_array. Height is generated only for every fourth block; the rest are interpolated. However, objects are generated for all four at the same time. So 3,000 × 4 = 12,000 blocks receive height and object code per frame, if needed; otherwise, I assume it's skipped? -----set_weights - mathematical calculations are performed, for every fourth block -----calculate_height - Grelf functions are called; the chunkmap is calculated; terrain_make_plains is executed; then there are if-else statements for different chunks. There are a few separate if-else statements for different terrain phenomena. Simple code for each chunk; the Grelf and chunkmap code at the beginning is probably the "heaviest." -----generate_objects_array - calculations and if statements, only for trees. Pretty simple code, runs for each block - download_objects runs 3,000 times ---actually 200 times? if (main_loop_counter < 200 && nextpart_start === 1). So it only runs if nextpart_start === 1; otherwise, it waits. And it only runs as long as main_loop_counter is less than 200 ---Some things are done once. Only at the beginning of the chunk-so not 200 times per frame-the following runs: ---- -create_sprites_3d_vertices (experimental) -----create_3d_meshes-for terrain, buildings, roads, roadlights, and trees, the respective buffer geometry and WebGL attributes for position, normal, and UV are created. For the position attribute, a new THREE is created. Float32BufferAttribute is created based on ver_terrain (vertices) and certain others do the same, while some others have theirs created beforehand (they are essentially JavaScript Float32Arrays). An if-else statement iterates through all chunks and sets the terrain texture. Then meshes (or points for roadlights) are created and added to the three.js scene. They are set to invisible (to be turned on in another function, I assume) -----Here is the code for creating field grass for the old Notacarclub gameplay ---For each block (i,j), the following is executed: roadlights that are too close to each other are removed (strange code). ---- -create_objects_3d_vertices - object code is checked using an if-else statement; either road (a few checks are performed and 18 vertices are created), roadlight (3 vertices are created), tree (a few checks are performed; 18 vertices create a plane, 18 UVs, 12 sheets), bush (not used), grass (not used), fire (a simple sprite is created), or house. For houses, a separate function is called, where height and other values are calculated based on the digits in a long number (the object code); 90 vertices are created for the mesh, vertex colors are calculated and set in a 30-iteration for-loop, UVs are set in a 4-iteration for-loop, etc. -----create_terrain_3d_vertices - 18 vertices are created Positions are set for the sun, water, and clouds The create_terrain_chunks_before_showing_them function runs-it iterates through 7x7=49 chunks around the current one and turns them off, then 5x5=25 chunks and turns them on (sets the chunk_next [][] value), then ONLY when you've just entered a new chunk, mark 5x5 chunks around the current one in the chunk2load array to indicate they should be created. No weird code, just variables being set, and it runs once per frame The hide_bushes_grass_etc_outside_current_chunk function runs-it iterates through 5x5 chunks around the current one and turns off houses, roads, roadlights, and trees. Then it iterates through 3x3 chunks and turns on roads, streetlights, and trees (not trees in low-res mode). Then it displays houses and trees in the current chunk. Houses are then displayed in neighboring chunks, but only if you're 40 meters away from the chunk-that is, not all 3x3 chunks around you //light_set() fog_set() - takes close to 0 milliseconds Every frame (BOTH CUTSCENES AND OTHER SCENES?!) sets the fog. A few if statements; otherwise, it sets the far, near, and color values. -fog_area() may run every frame in certain locations; it checks positions and performs some calculations based on those positions using x2Chunk-like helper functions In freeroam/race, only if you're within valid chunks, save to cookies. Different cookies are written at different frames, since it takes a long time. The entire save function runs twice per frame, but it is split across different frames within that time. Throw an Error if last_cut_before_pause === 0 The save URL is stored in the save_text element indoors = 0 -- WRITE MORE HERE -- cars_control() - takes close to 0 milliseconds (without other characters) Calculations are performed every frame except during CUTSCENES Then, every other character that is "active" is processed. The same calculations are performed. "Goal" is calculated, requiring slightly more calculations than for the player. This is done regardless of how far away the characters are cars_physics() - takes close to 0 milliseconds (without other characters) Runs every frame except during CUTSCENES The current object is checked, and simple physics reactions are applied to it Gravity is checked-and the current object is checked to see if it lands on top of a house. Helper functions are sometimes called to perform simple physics calculations for speed on uphill and downhill slopes and to prevent drowning underwater Collisions with other characters are checked Then every other character that is "active" is processed. Friction, gravity, and slope calculations are run when the character is in the nearest chunks (otherwise, only the friction calculation is run and nothing else) The interact_timer is set; if it's freeroam and the last cut was a cutscene, this prevents the player from accidentally jumping into a new cutscene immediately afterward. gameplay_fps() Checks if the rainbow gun has been found; if so, changes the light's color. Checks timers Iterates through the player's 4 "bullets." Checks timers and sets some positions. The light follows the bullet; checks if the swan and other objects have been hit. Sets visibility based on whether the rainbow gun is present or not. Sets the bullet's position. Plays a sound and deactivates the bullet if it hits the ground. Sets the rainbow gun's position for each individual color. Checks for collision with buildings. Checks for bullet collision with other characters? Checks for "bullet against bullet"? Other characters are shooting: -- WRITE MORE HERE -- Sets the sprite for current_character-that is, which character you "are" In each frame of FREEROAM, the light's intensity and color (sunset-type) are set -- WRITE MORE HERE -- Light and "indoor" variables (which affect which ground sound is played) are set for different areas in the game world cars_sound() Every fifth frame, volumes are calculated based on speed; sounds are triggered based on the surface (above or below water level). If in FREEROAM or RACE mode, the car sound is played. Upon landing, the volume is calculated and the corresponding sound is played. Only in "lowres" mode; all other characters are iterated through, and nearly the same code as before is executed for each active character The sprite for current_character is set-that is, which character the player "is" Every 5th frame, the money value is set in the HTML elements interact_timer, collision_timer, turbo_timer lastpos is set DEBUG lastcut renderer.render() - takes a few milliseconds Unless embedded games are running, every frame is rendered. The WebGL function viewport is executed The three.js function updateMatrixWorld is executed for the scene, which performs calculations for matrices, etc. The three.js function updateMatrixWorld is executed for the camera, which performs calculations for matrices, etc. The render list is fetched - The projectObject function is executed. It iterates through all objects-a lot happens there... Objects in the render list are sorted Lights are set up - The renderObjects function runs. It iterates through all objects. Matrices are multiplied. The renderBufferDirect function runs for each object-WebGL settings are configured there, and gl.drawArrays is finally called In freeroam/race, the frame_counter is incremented by 1 (time passes in the game). If it exceeds a certain value, it wraps back to 0, and the day_counter is incremented by 1 ("weekdays" pass in the game) mouseclick = false The value for frame_end is set. It is compared to frame_start, which was set at the very beginning of the frame, to determine how long the frame took, in order to switch to low-res mode if necessary, among other things requestAnimationFrame is called by the main function If the time since the last frame is less than 14 ms (over 60 FPS), highfps_count is incremented. Every 10th frame highfps_count is reset to zero. If highfps_count exceeds 100-that is, if at least 100 out of 600 frames are above 60 FPS-it is assumed that the display has a high refresh rate (where the game, for example, runs twice as fast). In that case, a while loop runs until 15 ms has elapsed (resulting in a more reasonable FPS), so that the rendering isn't executed too frequently If the time between frame_end and frame_start is greater than 16.67 ms (i.e., below 60 FPS), lowres_count is increased; if it is 60 FPS, lowres_count is decreased by 0.1. Every 20th frame, lowres_count is reset to zero. If lowres_count is greater than 10, lowres is set to 0.75. It remains at this value until lowres_count becomes less than -10, at which point lowres is set to 1. There is some code in the game that runs or does not run based on this value DEBUG