qweawe
This commit is contained in:
@@ -1,382 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Simple 3D Dungeon Crawler</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
font-family:
|
|
||||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
|
|
||||||
"Helvetica Neue", sans-serif;
|
|
||||||
background-color: #111;
|
|
||||||
color: #fff;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
#info-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
left: 10px;
|
|
||||||
background-color: rgba(0, 0, 0, 0.7);
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 10px;
|
|
||||||
max-width: 350px;
|
|
||||||
border: 1px solid #444;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
p,
|
|
||||||
li {
|
|
||||||
font-size: 0.9em;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
#crosshair {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 4px;
|
|
||||||
height: 4px;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
pointer-events: none; /* So it doesn't interfere with mouse lock */
|
|
||||||
}
|
|
||||||
#game-canvas {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="info-container">
|
|
||||||
<h1>First-Person Dungeon Crawler</h1>
|
|
||||||
<p>Create a <strong>map.txt</strong> file to load your own dungeon:</p>
|
|
||||||
<ul>
|
|
||||||
<li><code>#</code> = Wall</li>
|
|
||||||
<li><code> </code> (space) = Floor</li>
|
|
||||||
<li><code>P</code> = Player Start</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Controls:</strong></p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Click Screen:</strong> Lock mouse for camera control</li>
|
|
||||||
<li><strong>W / S:</strong> Move Forward / Backward</li>
|
|
||||||
<li><strong>A / D:</strong> Strafe Left / Right</li>
|
|
||||||
<li><strong>Mouse:</strong> Look Around</li>
|
|
||||||
<li><strong>ESC:</strong> Unlock mouse</li>
|
|
||||||
</ul>
|
|
||||||
<input type="file" id="map-upload" accept=".txt" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="crosshair"></div>
|
|
||||||
<canvas id="game-canvas"></canvas>
|
|
||||||
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/**
|
|
||||||
* This type represents configuration settings of `AsciiEffect`.
|
|
||||||
*
|
|
||||||
* @typedef {Object} AsciiEffect~Options
|
|
||||||
* @property {number} [resolution=0.15] - A higher value leads to more details.
|
|
||||||
* @property {number} [scale=1] - The scale of the effect.
|
|
||||||
* @property {boolean} [color=false] - Whether colors should be enabled or not. Better quality but slows down rendering.
|
|
||||||
* @property {boolean} [alpha=false] - Whether transparency should be enabled or not.
|
|
||||||
* @property {boolean} [block=false] - Whether blocked characters should be enabled or not.
|
|
||||||
* @property {boolean} [invert=false] - Whether colors should be inverted or not.
|
|
||||||
* @property {('low'|'medium'|'high')} [strResolution='low'] - The string resolution.
|
|
||||||
**/
|
|
||||||
|
|
||||||
// --- Basic Setup ---
|
|
||||||
let scene, camera, renderer;
|
|
||||||
let mapData = [],
|
|
||||||
mapWidth,
|
|
||||||
mapHeight;
|
|
||||||
const TILE_SIZE = 5; // Size of each grid square in the world
|
|
||||||
const WALL_HEIGHT = 5;
|
|
||||||
|
|
||||||
// --- Player State ---
|
|
||||||
const player = {
|
|
||||||
height: WALL_HEIGHT / 2,
|
|
||||||
speed: 0.15,
|
|
||||||
turnSpeed: 0.05,
|
|
||||||
velocity: new THREE.Vector3(),
|
|
||||||
controls: {
|
|
||||||
moveForward: false,
|
|
||||||
moveBackward: false,
|
|
||||||
moveLeft: false,
|
|
||||||
moveRight: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- Initialization ---
|
|
||||||
function init() {
|
|
||||||
// Scene
|
|
||||||
scene = new THREE.Scene();
|
|
||||||
scene.background = new THREE.Color(0x1a1a1a);
|
|
||||||
scene.fog = new THREE.Fog(0x1a1a1a, 10, 50);
|
|
||||||
|
|
||||||
// Camera (First-person view)
|
|
||||||
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
||||||
camera.position.y = player.height;
|
|
||||||
|
|
||||||
// Renderer
|
|
||||||
renderer = new THREE.WebGLRenderer({ canvas: document.getElementById("game-canvas"), antialias: true });
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
renderer.setPixelRatio(window.devicePixelRatio);
|
|
||||||
document.body.appendChild(renderer.domElement);
|
|
||||||
|
|
||||||
// Lighting
|
|
||||||
const ambientLight = new THREE.AmbientLight(0x404040, 2);
|
|
||||||
scene.add(ambientLight);
|
|
||||||
|
|
||||||
const pointLight = new THREE.PointLight(0xffffff, 1.5, 100);
|
|
||||||
pointLight.position.set(0, WALL_HEIGHT * 1.5, 0); // Light is attached to player
|
|
||||||
camera.add(pointLight); // Attach light to camera
|
|
||||||
scene.add(camera); // Add camera to scene to ensure light is added
|
|
||||||
|
|
||||||
// Event Listeners
|
|
||||||
document.getElementById("map-upload").addEventListener("change", handleMapUpload);
|
|
||||||
window.addEventListener("resize", onWindowResize);
|
|
||||||
document.addEventListener("keydown", onKeyDown);
|
|
||||||
document.addEventListener("keyup", onKeyUp);
|
|
||||||
|
|
||||||
// Mouse Look Controls
|
|
||||||
setupPointerLock();
|
|
||||||
|
|
||||||
// Load a default map
|
|
||||||
loadMap(getDefaultMap());
|
|
||||||
|
|
||||||
// Start the game loop
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Map Handling ---
|
|
||||||
function getDefaultMap() {
|
|
||||||
return [
|
|
||||||
"##########",
|
|
||||||
"#P # #",
|
|
||||||
"# # ### #",
|
|
||||||
"#### # # #",
|
|
||||||
"# # #",
|
|
||||||
"# ###### #",
|
|
||||||
"# # #",
|
|
||||||
"# # ######",
|
|
||||||
"# # #",
|
|
||||||
"##########",
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleMapUpload(event) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
loadMap(e.target.result);
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadMap(data) {
|
|
||||||
// Clear existing map objects from scene
|
|
||||||
const objectsToRemove = [];
|
|
||||||
scene.children.forEach((child) => {
|
|
||||||
if (child.userData.isMapTile) {
|
|
||||||
objectsToRemove.push(child);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
objectsToRemove.forEach((obj) => scene.remove(obj));
|
|
||||||
|
|
||||||
// Parse new map data
|
|
||||||
mapData = data.split("\n").map((row) => row.split(""));
|
|
||||||
mapHeight = mapData.length;
|
|
||||||
mapWidth = mapData[0].length;
|
|
||||||
|
|
||||||
// Create geometry and materials once
|
|
||||||
const wallGeometry = new THREE.BoxGeometry(TILE_SIZE, WALL_HEIGHT, TILE_SIZE);
|
|
||||||
const wallMaterial = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.8 });
|
|
||||||
|
|
||||||
const floorGeometry = new THREE.PlaneGeometry(TILE_SIZE, TILE_SIZE);
|
|
||||||
const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x444444, side: THREE.DoubleSide });
|
|
||||||
|
|
||||||
// Build the scene from the map data
|
|
||||||
for (let y = 0; y < mapHeight; y++) {
|
|
||||||
for (let x = 0; x < mapWidth; x++) {
|
|
||||||
const char = mapData[y][x];
|
|
||||||
const worldX = (x - mapWidth / 2) * TILE_SIZE;
|
|
||||||
const worldZ = (y - mapHeight / 2) * TILE_SIZE;
|
|
||||||
|
|
||||||
if (char === "#") {
|
|
||||||
const wall = new THREE.Mesh(wallGeometry, wallMaterial);
|
|
||||||
wall.position.set(worldX, WALL_HEIGHT / 2, worldZ);
|
|
||||||
wall.userData.isMapTile = true;
|
|
||||||
scene.add(wall);
|
|
||||||
} else {
|
|
||||||
// Add floor for every non-wall tile
|
|
||||||
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
|
|
||||||
floor.rotation.x = -Math.PI / 2;
|
|
||||||
floor.position.set(worldX, 0, worldZ);
|
|
||||||
floor.userData.isMapTile = true;
|
|
||||||
scene.add(floor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (char === "@") {
|
|
||||||
camera.position.x = worldX;
|
|
||||||
camera.position.z = worldZ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a ceiling
|
|
||||||
const ceilingGeometry = new THREE.PlaneGeometry(mapWidth * TILE_SIZE, mapHeight * TILE_SIZE);
|
|
||||||
const ceilingMaterial = new THREE.MeshStandardMaterial({ color: 0x555555, side: THREE.DoubleSide });
|
|
||||||
const ceiling = new THREE.Mesh(ceilingGeometry, ceilingMaterial);
|
|
||||||
ceiling.rotation.x = Math.PI / 2;
|
|
||||||
ceiling.position.y = WALL_HEIGHT;
|
|
||||||
ceiling.userData.isMapTile = true;
|
|
||||||
scene.add(ceiling);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isWall(x, z) {
|
|
||||||
const mapX = Math.floor(x / TILE_SIZE + mapWidth / 2);
|
|
||||||
const mapY = Math.floor(z / TILE_SIZE + mapHeight / 2);
|
|
||||||
|
|
||||||
if (mapX < 0 || mapX >= mapWidth || mapY < 0 || mapY >= mapHeight) {
|
|
||||||
return true; // Treat out of bounds as a wall
|
|
||||||
}
|
|
||||||
return mapData[mapY][mapX] === "#";
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Controls & Movement ---
|
|
||||||
function setupPointerLock() {
|
|
||||||
const canvas = renderer.domElement;
|
|
||||||
canvas.addEventListener("click", () => {
|
|
||||||
canvas.requestPointerLock();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("pointerlockchange", () => {
|
|
||||||
if (document.pointerLockElement === canvas) {
|
|
||||||
document.addEventListener("mousemove", onMouseMove);
|
|
||||||
} else {
|
|
||||||
document.removeEventListener("mousemove", onMouseMove);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseMove(event) {
|
|
||||||
if (document.pointerLockElement !== renderer.domElement) return;
|
|
||||||
const movementX = event.movementX || 0;
|
|
||||||
camera.rotation.y -= movementX * 0.002;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeyDown(event) {
|
|
||||||
switch (event.code) {
|
|
||||||
case "KeyW":
|
|
||||||
case "ArrowUp":
|
|
||||||
player.controls.moveForward = true;
|
|
||||||
break;
|
|
||||||
case "KeyS":
|
|
||||||
case "ArrowDown":
|
|
||||||
player.controls.moveBackward = true;
|
|
||||||
break;
|
|
||||||
case "KeyA":
|
|
||||||
case "ArrowLeft":
|
|
||||||
player.controls.moveLeft = true;
|
|
||||||
break;
|
|
||||||
case "KeyD":
|
|
||||||
case "ArrowRight":
|
|
||||||
player.controls.moveRight = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onKeyUp(event) {
|
|
||||||
switch (event.code) {
|
|
||||||
case "KeyW":
|
|
||||||
case "ArrowUp":
|
|
||||||
player.controls.moveForward = false;
|
|
||||||
break;
|
|
||||||
case "KeyS":
|
|
||||||
case "ArrowDown":
|
|
||||||
player.controls.moveBackward = false;
|
|
||||||
break;
|
|
||||||
case "KeyA":
|
|
||||||
case "ArrowLeft":
|
|
||||||
player.controls.moveLeft = false;
|
|
||||||
break;
|
|
||||||
case "KeyD":
|
|
||||||
case "ArrowRight":
|
|
||||||
player.controls.moveRight = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePlayerPosition() {
|
|
||||||
const direction = new THREE.Vector3();
|
|
||||||
camera.getWorldDirection(direction);
|
|
||||||
|
|
||||||
const right = new THREE.Vector3();
|
|
||||||
right.crossVectors(camera.up, direction).normalize();
|
|
||||||
|
|
||||||
player.velocity.set(0, 0, 0);
|
|
||||||
|
|
||||||
if (player.controls.moveForward) {
|
|
||||||
player.velocity.add(direction);
|
|
||||||
}
|
|
||||||
if (player.controls.moveBackward) {
|
|
||||||
player.velocity.sub(direction);
|
|
||||||
}
|
|
||||||
if (player.controls.moveLeft) {
|
|
||||||
player.velocity.add(right);
|
|
||||||
}
|
|
||||||
if (player.controls.moveRight) {
|
|
||||||
player.velocity.sub(right);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (player.velocity.length() > 0) {
|
|
||||||
player.velocity.normalize().multiplyScalar(player.speed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collision detection
|
|
||||||
const collisionMargin = TILE_SIZE / 4;
|
|
||||||
let moveX = true,
|
|
||||||
moveZ = true;
|
|
||||||
|
|
||||||
if (isWall(camera.position.x + player.velocity.x * collisionMargin, camera.position.z)) {
|
|
||||||
moveX = false;
|
|
||||||
}
|
|
||||||
if (isWall(camera.position.x, camera.position.z + player.velocity.z * collisionMargin)) {
|
|
||||||
moveZ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moveX) camera.position.x += player.velocity.x;
|
|
||||||
if (moveZ) camera.position.z += player.velocity.z;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Main Loop ---
|
|
||||||
function animate() {
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
updatePlayerPosition();
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Utility ---
|
|
||||||
function onWindowResize() {
|
|
||||||
camera.aspect = window.innerWidth / window.innerHeight;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Start everything ---
|
|
||||||
init();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
background-color: #000;
|
background-color: #000;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
font-family: "Courier New", monospace;
|
font-family: "Courier New", monospace;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#gameContainer {
|
#gameContainer {
|
||||||
@@ -20,7 +19,7 @@
|
|||||||
|
|
||||||
#viewport {
|
#viewport {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
line-height: 8px;
|
line-height: 7px;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
border: 2px solid #0f0;
|
border: 2px solid #0f0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -30,12 +29,11 @@
|
|||||||
}
|
}
|
||||||
#minimap {
|
#minimap {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 12px;
|
line-height: 11.5px;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border: 5px solid #666;
|
border: 5px solid #666;
|
||||||
/* color: #666; */
|
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +44,8 @@
|
|||||||
|
|
||||||
#mapInput {
|
#mapInput {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
overflow-x: scroll;
|
||||||
|
overflow-wrap: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
@@ -80,15 +80,15 @@
|
|||||||
<div id="mapInput">
|
<div id="mapInput">
|
||||||
←→↑↓
|
←→↑↓
|
||||||
<br />
|
<br />
|
||||||
<textarea id="mapText" rows="10" cols="50">
|
<textarea id="mapText" rows="10" cols="120" wrap="off">
|
||||||
############################################################
|
############################################################
|
||||||
############################################################
|
############################################################
|
||||||
############################################################
|
############################################################
|
||||||
## ################# ########################
|
## # ################# ########################
|
||||||
## # ################# # ## ########################
|
#### # ################### # ## ########################
|
||||||
## #P Z###############Z # ## ################ ::: P(north) Z(channel_1) Z(channel_1) // Comments
|
## P Z###############Z # ## ################ ::: P(east) Z(a, b, west) Z(b, a, east) // Comments
|
||||||
## # # ################# # ## #### ####
|
## ######################## # ## #### ####
|
||||||
### #E # # ## # #### # # #### ::: E(gnoll)
|
### E # # ## # #### # # #### ::: E(gnoll)
|
||||||
#### ################## ## #### # ####
|
#### ################## ## #### # ####
|
||||||
##### ################### # ## # # #### ####
|
##### ################### # ## # # #### ####
|
||||||
######E#################### # #### ::: E(Goblins, gnoll) // These are comments
|
######E#################### # #### ::: E(Goblins, gnoll) // These are comments
|
||||||
@@ -101,21 +101,21 @@
|
|||||||
###### #################### # ############################
|
###### #################### # ############################
|
||||||
######E#################### # ############################ ::: E(gnoll)
|
######E#################### # ############################ ::: E(gnoll)
|
||||||
###### ## ##### ## ############################ :::
|
###### ## ##### ## ############################ :::
|
||||||
###### ## Z#### ## # # ############################ ::: Z(2) // Channel 2
|
###### ## Z#### ## # # ############################ ::: Z(c, d, west)
|
||||||
###### ## ####Z ## ######## ############ ::: Z(2) // Channel 2
|
###### ## ####Z ## ######## ############ ::: Z(d, c, east)
|
||||||
###### ## ## # ########### ## ######## ############
|
###### ## ## # ########### ## ######## ############
|
||||||
######E## # #E ############ ::: E(Dwarves, gnoll) ; E(Gelatinous_Cube, gnoll)
|
######E## # #E ############ ::: E(Dwarves, gnoll) ; E(Gelatinous_Cube, gnoll)
|
||||||
###### # # # ############
|
###### # # # ############
|
||||||
######### # ## ########### # ######### # ############
|
######### # ## ########### # ######### # ############
|
||||||
######### # # ########### # ######### # # ############
|
######### # # ########### # ######### # # ############
|
||||||
######### ########### # ######### ############
|
######### ########### # ######### ############
|
||||||
###########O############### # ######### #### ############### ::: O(1)
|
###########Z############### # ######### #### ############### ::: Z(e, f, null)
|
||||||
########################### # ######### #### ###############
|
########################### # ######### #### ###############
|
||||||
########################### # ######### #### ###############
|
########################### # ######### #### ###############
|
||||||
########################### # ######### #### ###############
|
########################### # ######### #### ###############
|
||||||
########################### # #### ###############
|
########################### # #### ###############
|
||||||
######################### # #### # # # ######## ###
|
######################### # #### # # # ######## ###
|
||||||
########################o # # ######## # ### ::: o(2)
|
########################Z # # ######## # ### ::: Z(f, null, east) // you can teleport here, but you cannot teleport back
|
||||||
######################### # ##### # # # # ######## ###
|
######################### # ##### # # # # ######## ###
|
||||||
######################### # # ###
|
######################### # # ###
|
||||||
######################### ####################### # ###
|
######################### ####################### # ###
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ export const DefaultRendererOptions = {
|
|||||||
ceilingColor: 0x555555,
|
ceilingColor: 0x555555,
|
||||||
|
|
||||||
commitToDOM: true,
|
commitToDOM: true,
|
||||||
|
|
||||||
|
fillChar: "#",
|
||||||
};
|
};
|
||||||
|
|
||||||
export class FirstPersonRenderer {
|
export class FirstPersonRenderer {
|
||||||
@@ -40,6 +42,7 @@ export class FirstPersonRenderer {
|
|||||||
this.floorColor = options.floorColor ?? DefaultRendererOptions.floorColor;
|
this.floorColor = options.floorColor ?? DefaultRendererOptions.floorColor;
|
||||||
this.ceilingColor = options.ceilingColor ?? DefaultRendererOptions.ceilingColor;
|
this.ceilingColor = options.ceilingColor ?? DefaultRendererOptions.ceilingColor;
|
||||||
this.commitToDOM = options.commitToDOM ?? DefaultRendererOptions.commitToDOM;
|
this.commitToDOM = options.commitToDOM ?? DefaultRendererOptions.commitToDOM;
|
||||||
|
this.fillChar = options.fillChar ?? DefaultRendererOptions.fillChar;
|
||||||
|
|
||||||
//
|
//
|
||||||
// THREE variables
|
// THREE variables
|
||||||
@@ -216,9 +219,6 @@ export class FirstPersonRenderer {
|
|||||||
}),
|
}),
|
||||||
wallPlanes.length,
|
wallPlanes.length,
|
||||||
);
|
);
|
||||||
instancedMesh.userData.pastelMaterial = new THREE.MeshBasicMaterial({
|
|
||||||
color: 0xffffff,
|
|
||||||
});
|
|
||||||
|
|
||||||
instancedMesh.userData.parimaryMaterial = instancedMesh.material;
|
instancedMesh.userData.parimaryMaterial = instancedMesh.material;
|
||||||
this.scene.add(instancedMesh);
|
this.scene.add(instancedMesh);
|
||||||
@@ -366,7 +366,7 @@ export class FirstPersonRenderer {
|
|||||||
g.toString(16).padStart(2, "0") +
|
g.toString(16).padStart(2, "0") +
|
||||||
b.toString(16).padStart(2, "0");
|
b.toString(16).padStart(2, "0");
|
||||||
|
|
||||||
this.window.put(x, y, "#", cssColor);
|
this.window.put(x, y, this.fillChar, cssColor);
|
||||||
|
|
||||||
idx += 4;
|
idx += 4;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { AsciiWindow } from "./ascii_window.js";
|
|||||||
|
|
||||||
export class MiniMap {
|
export class MiniMap {
|
||||||
/**
|
/**
|
||||||
* @param {AsciiWindow} aWindow
|
* @param {AsciiWindowInterface} aWindow
|
||||||
* @param {TileMap} map
|
* @param {TileMap} map
|
||||||
*/
|
*/
|
||||||
constructor(aWindow, map) {
|
constructor(aWindow, map) {
|
||||||
@@ -174,10 +174,10 @@ export class MiniMap {
|
|||||||
|
|
||||||
//
|
//
|
||||||
y = startY;
|
y = startY;
|
||||||
for (let mapY = minY; mapY < maxY; mapY++) {
|
for (let mapY = minY; mapY <= maxY; mapY++) {
|
||||||
//
|
//
|
||||||
x = startX;
|
x = startX;
|
||||||
for (let mapX = minX; mapX < maxX; mapX++) {
|
for (let mapX = minX; mapX <= maxX; mapX++) {
|
||||||
//
|
//
|
||||||
const [putX, putY] = switchXY ? [y, x] : [x, y];
|
const [putX, putY] = switchXY ? [y, x] : [x, y];
|
||||||
|
|
||||||
@@ -186,13 +186,12 @@ export class MiniMap {
|
|||||||
this.window.put(putX, putY, tile.minimapChar, tile.minimapColor);
|
this.window.put(putX, putY, tile.minimapChar, tile.minimapColor);
|
||||||
} else {
|
} else {
|
||||||
// this.window.put(putX, putY, "░", "#666");
|
// this.window.put(putX, putY, "░", "#666");
|
||||||
this.window.put(putX, putY, " ", "#666");
|
this.window.put(putX, putY, "█", "#222");
|
||||||
}
|
}
|
||||||
x += dX;
|
x += dX;
|
||||||
}
|
}
|
||||||
y += dY;
|
y += dY;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.window.put(this.distance, this.distance, "@", "#4f4fff");
|
this.window.put(this.distance, this.distance, "@", "#4f4fff");
|
||||||
this.window.commitToDOM();
|
this.window.commitToDOM();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,23 @@ import { ParsedCall } from "../utils/callParser";
|
|||||||
import { Orientation, Vector2i } from "./ascii_types";
|
import { Orientation, Vector2i } from "./ascii_types";
|
||||||
|
|
||||||
export class Tile {
|
export class Tile {
|
||||||
/** @type {string} How should this tile be rendered on the minimap.*/
|
/** @type {string|number} What is the id of this tile - only interactive tiles have IDs */
|
||||||
|
id;
|
||||||
|
|
||||||
|
/** @type {string} Icon char of tile */
|
||||||
minimapChar;
|
minimapChar;
|
||||||
/** @type {string} How should this tile be rendered on the minimap.*/
|
/** @type {string} Icon char of tile after tile's secrets have been revealed */
|
||||||
|
revealedMinimapChar;
|
||||||
|
/** @type {string} Icon of tile */
|
||||||
minimapColor;
|
minimapColor;
|
||||||
|
/** @type {string} Icon char of tile after tile's secrets have been revealed */
|
||||||
|
revealedMinimapColor;
|
||||||
|
|
||||||
|
/** @type {boolean} Is this a portal exit and/or entry */
|
||||||
|
isPortal;
|
||||||
|
/** @type {string|number} Where is the player transported if they enter the portal */
|
||||||
|
portalTargetId;
|
||||||
|
|
||||||
/** @type {boolean} Should this be rendered as a wall? */
|
/** @type {boolean} Should this be rendered as a wall? */
|
||||||
looksLikeWall;
|
looksLikeWall;
|
||||||
/** @type {boolean} Can the player walk here? */
|
/** @type {boolean} Can the player walk here? */
|
||||||
@@ -14,25 +27,22 @@ export class Tile {
|
|||||||
isEncounter;
|
isEncounter;
|
||||||
/** @type {boolean} Is this where they player starts? */
|
/** @type {boolean} Is this where they player starts? */
|
||||||
isStartLocation;
|
isStartLocation;
|
||||||
/** @type {boolean} Is this a two-way portal entry/exit */
|
/** @type {boolean} Has the secret properties of this tile been revealed? */
|
||||||
isTwoWayPortal;
|
isRevealed;
|
||||||
/** @type {boolean} Is this a one-way portal entry */
|
/** @type {string|number} */
|
||||||
isOneWayPortalEntry;
|
hasBumpEvent;
|
||||||
/** @type {boolean} Is this a one-way portal exit */
|
|
||||||
isOneWayPortalExit;
|
|
||||||
/** @type {boolean} Has the secret properties of this tile been uncovered? */
|
|
||||||
isUncovered;
|
|
||||||
/** @type {string|number} The portals "channel" - each tile in a portal pair must have the same channel */
|
/** @type {string|number} The portals "channel" - each tile in a portal pair must have the same channel */
|
||||||
channel;
|
channel;
|
||||||
/** @type {number|string} id of texture to use */
|
/** @type {number|string} id of texture to use */
|
||||||
textureId;
|
textureId;
|
||||||
/** @type {number|string} id the encounter located on this tile */
|
/** @type {number|string} id of texture to use after the secrets of this tile has been revealed */
|
||||||
encounterId;
|
revealedTextureId;
|
||||||
|
/** @type {number|string} type of encounter located on this tile. May or may not be unique*/
|
||||||
|
encounterType;
|
||||||
/** @type {boolean} Can/does this tile wander around on empty tiles? */
|
/** @type {boolean} Can/does this tile wander around on empty tiles? */
|
||||||
isRoaming;
|
isRoaming;
|
||||||
/** @type {Orientation} */
|
/** @type {Orientation} */
|
||||||
orientation;
|
orientation;
|
||||||
|
|
||||||
/** @type {number} If this is a roaming tile, what is its current x-position on the map */
|
/** @type {number} If this is a roaming tile, what is its current x-position on the map */
|
||||||
currentPosX;
|
currentPosX;
|
||||||
/** @type {number} If this is a roaming tile, what is its current y-position on the map*/
|
/** @type {number} If this is a roaming tile, what is its current y-position on the map*/
|
||||||
@@ -64,21 +74,32 @@ export class Tile {
|
|||||||
if (char === " ") return new FloorTile();
|
if (char === " ") return new FloorTile();
|
||||||
if (char === "#") return new WallTile();
|
if (char === "#") return new WallTile();
|
||||||
if (char === "P") return new PlayerStartTile(opt.getValue("orientation", 0));
|
if (char === "P") return new PlayerStartTile(opt.getValue("orientation", 0));
|
||||||
if (char === "E") return new EncounterTile(x, y, opt.getValue("encounterId", 0), opt.getValue("textureId", 1));
|
if (char === "E")
|
||||||
if (char === "O") return new SecretOneWayPortalEntryTile(opt.getValue("channel", 0));
|
return new EncounterTile(x, y, opt.getValue("encounterType", 0), opt.getValue("textureId", 1));
|
||||||
if (char === "o") return new SecretOneWayPortalExitTile(opt.getValue("channel", 0));
|
if (char === "Z")
|
||||||
if (char === "Z") return new SecretTwoWayPortalTile(opt.getValue("channel", 0));
|
return new SecretPortalTile(
|
||||||
|
opt.getValue("id", 0),
|
||||||
|
opt.getValue("destinationid", 1),
|
||||||
|
opt.getValue("orientation", 3),
|
||||||
|
);
|
||||||
|
|
||||||
console.warn("Unknown character", { char, options: opt });
|
console.warn("Unknown character", { char, options: opt });
|
||||||
return new FloorTile();
|
return new FloorTile();
|
||||||
}
|
}
|
||||||
|
|
||||||
hasTexture() {
|
hasTexture() {
|
||||||
if (this.textureId === "") {
|
if (typeof this.textureId === "number") {
|
||||||
return false;
|
return true;
|
||||||
|
}
|
||||||
|
if (typeof this.textureId === "string" && this.textureId !== "") {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return typeof this.textureId === "number" || typeof this.textureId === "string";
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBumpEvent() {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
clone() {
|
clone() {
|
||||||
@@ -118,67 +139,42 @@ export class EncounterTile extends Tile {
|
|||||||
isEncounter = true;
|
isEncounter = true;
|
||||||
isRoaming = true;
|
isRoaming = true;
|
||||||
minimapChar = "†";
|
minimapChar = "†";
|
||||||
minimapColor = "#faa";
|
minimapColor = "#f44";
|
||||||
|
hasBumpEvent = true;
|
||||||
|
|
||||||
constructor(x, y, encounterId, textureId) {
|
/**
|
||||||
|
* @param {number} x x-component of the encounter's initial position
|
||||||
|
* @param {number} y y-component of the encounter's initial position
|
||||||
|
* @param {string|number} encounterType name/id of the encounter that will be triggered when player bumps into this tile
|
||||||
|
* @param {string|number} textureId id of the texture to use.
|
||||||
|
*/
|
||||||
|
constructor(x, y, encounterType, textureId) {
|
||||||
super();
|
super();
|
||||||
this.textureId = textureId ?? encounterId;
|
this.textureId = textureId ?? encounterType;
|
||||||
this.encounterId = encounterId;
|
this.encounterType = encounterType;
|
||||||
this.currentPosX = x;
|
this.currentPosX = x;
|
||||||
this.currentPosY = y;
|
this.currentPosY = y;
|
||||||
|
this.id = `E_${encounterType}_${x}_${y}`;
|
||||||
console.info("creating encounter", { encounter: this });
|
console.info("creating encounter", { encounter: this });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
export class SpriteTile extends Tile {
|
getBumpEvent() {
|
||||||
isTraversable = true;
|
return ["attack", { encounterType: this.encounterType }];
|
||||||
constructor(textureId, orientation) {
|
|
||||||
console.debug({ textureId, orientation });
|
|
||||||
super({ textureId, orientation: orientation ?? Orientation.NORTH });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class SecretPortalTile extends WallTile {
|
||||||
* One-way portal entries look exactly like walls. You need to
|
revealedTextureId = "secretTwoWayPortal";
|
||||||
* probe for them, or otherwise unlock their location.
|
isPortal = true;
|
||||||
* You can walk into them, and then the magic happens
|
internalMapChar = "Z";
|
||||||
*/
|
isRevealed = false;
|
||||||
export class SecretOneWayPortalEntryTile extends WallTile {
|
revealedMinimapChar = "Ω";
|
||||||
textureId = 0;
|
revealedMinimapColor = "#4f4";
|
||||||
looksLikeWall = true;
|
|
||||||
isTraversable = true; // we can walk in to it?
|
|
||||||
isOneWayPortalEntry = true;
|
|
||||||
internalMapChar = "O";
|
|
||||||
isUncovered = false;
|
|
||||||
|
|
||||||
// Change minimap char once the tile's secret has been uncovered.
|
// Change minimap char once the tile's secret has been uncovered.
|
||||||
|
|
||||||
constructor(channel) {
|
constructor(id, portalTargetId, orientation) {
|
||||||
super({ channel });
|
super({ id, portalTargetId, orientation });
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SecretOneWayPortalExitTile extends FloorTile {
|
|
||||||
isOneWayPortalExit = true;
|
|
||||||
internalMapChar = "o";
|
|
||||||
isUncovered = false;
|
|
||||||
//
|
|
||||||
// Change minimap char once the tile's secret has been uncovered.
|
|
||||||
|
|
||||||
constructor(channel) {
|
|
||||||
super({ channel });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SecretTwoWayPortalTile extends WallTile {
|
|
||||||
isTraversable = true;
|
|
||||||
isTwoWayPortalEntry = true;
|
|
||||||
internalMapChar = "0";
|
|
||||||
isUncovered = false;
|
|
||||||
|
|
||||||
// Change minimap char once the tile's secret has been uncovered.
|
|
||||||
|
|
||||||
constructor(channel) {
|
|
||||||
super({ channel });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,14 @@ export class AsciiWindow {
|
|||||||
this.initializeCanvaas();
|
this.initializeCanvaas();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fill(char = " ", color = "#000") {
|
||||||
|
for (let y = 0; y < this.height; y++) {
|
||||||
|
for (let x = 0; x < this.width; x++) {
|
||||||
|
this.put(x, y, char, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the html elements that make up the canvas,
|
* Create the html elements that make up the canvas,
|
||||||
* as well as a buffer that holds a copy of the data
|
* as well as a buffer that holds a copy of the data
|
||||||
|
|||||||
Reference in New Issue
Block a user