665 lines
20 KiB
JavaScript
665 lines
20 KiB
JavaScript
const http = require("http");
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
|
||
const PORT = process.env.PORT || 3000;
|
||
const DATA_FILE = path.join(__dirname, "gameState.json");
|
||
|
||
const DEFAULT_STATE = {
|
||
part: 1,
|
||
chapter: "intro",
|
||
inGameTime: "02:00",
|
||
playerPosition: { x: 5, y: 5 },
|
||
playerRoom: "bedroom",
|
||
monsterPosition: { x: 15, y: 3 },
|
||
monsterRoom: "outside",
|
||
monsterVisibility: 5,
|
||
monsterAggression: 0,
|
||
doorClosed: false,
|
||
doorLocked: true,
|
||
lightsOn: false,
|
||
flashlightOn: false,
|
||
phoneLightOn: false,
|
||
wifiOn: true,
|
||
powerOn: true,
|
||
panicLevel: 0,
|
||
messages: [
|
||
{ from: "СИСТЕМА", text: "Начало игры. Вы дома одни.", time: "02:00", read: true }
|
||
],
|
||
unreadMessages: 0,
|
||
monsterBehavior: {
|
||
checkDoor: 0,
|
||
checkBed: 0,
|
||
checkLight: 0,
|
||
checkCamera: 0,
|
||
checkCloset: 0,
|
||
checkBathroom: 0,
|
||
adaptivity: 0.5,
|
||
memoryIndex: {}
|
||
},
|
||
cameraViews: {},
|
||
inventory: [],
|
||
fusesCollected: 0,
|
||
fusesNeeded: 3,
|
||
recorderNotes: [],
|
||
discoveredSecrets: [],
|
||
houseDistortion: 0,
|
||
loopCount: 0,
|
||
ending: null,
|
||
endingChoice: null,
|
||
phonesFound: 0,
|
||
totalPhones: 12,
|
||
radioFrequency: 0,
|
||
correctFrequency: 73.5,
|
||
alexeyRevealed: false,
|
||
mirrorMode: false,
|
||
rooftopReached: false,
|
||
transmitterDestroyed: false,
|
||
neighborsExplored: { 145: false, 146: false, 147: false },
|
||
fuseLocations: [
|
||
{ room: "bathroom", collected: false },
|
||
{ room: "neighbor_146", collected: false },
|
||
{ room: "stairwell_5", collected: false }
|
||
],
|
||
hidingSpots: [],
|
||
monsterLastCheck: {},
|
||
staircaseDirection: "down",
|
||
currentFloor: 7,
|
||
radioHint: "73 point 5",
|
||
endingA: false,
|
||
endingB: false,
|
||
endingC: false
|
||
};
|
||
|
||
function loadState() {
|
||
try {
|
||
return JSON.parse(fs.readFileSync(DATA_FILE, "utf8"));
|
||
} catch (_) {
|
||
return JSON.parse(JSON.stringify(DEFAULT_STATE));
|
||
}
|
||
}
|
||
|
||
function saveState(state) {
|
||
fs.writeFileSync(DATA_FILE, JSON.stringify(state, null, 2));
|
||
}
|
||
|
||
function resetState() {
|
||
const state = JSON.parse(JSON.stringify(DEFAULT_STATE));
|
||
saveState(state);
|
||
return state;
|
||
}
|
||
|
||
function body(req) {
|
||
return new Promise((resolve) => {
|
||
let s = "";
|
||
req.on("data", (c) => (s += c));
|
||
req.on("end", () => {
|
||
try {
|
||
resolve(JSON.parse(s || "{}"));
|
||
} catch (_) {
|
||
resolve({});
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function updateMonster(state, playerAction, playerRoom, playerPosition) {
|
||
const behavior = state.monsterBehavior;
|
||
|
||
if (playerAction) {
|
||
if (playerAction === "check_door") behavior.checkDoor++;
|
||
if (playerAction === "hide_bed" || playerAction === "hide_under") {
|
||
behavior.checkBed++;
|
||
behavior.memoryIndex.lastHide = "bed";
|
||
}
|
||
if (playerAction === "hide_closet") {
|
||
behavior.checkCloset++;
|
||
behavior.memoryIndex.lastHide = "closet";
|
||
}
|
||
if (playerAction === "hide_bathroom") {
|
||
behavior.checkBathroom++;
|
||
behavior.memoryIndex.lastHide = "bathroom";
|
||
}
|
||
if (playerAction === "toggle_light") behavior.checkLight++;
|
||
if (playerAction === "use_camera") behavior.checkCamera++;
|
||
if (playerAction === "use_flashlight") behavior.checkFlashlight++;
|
||
if (playerAction === "panic") behavior.panicLevel++;
|
||
}
|
||
|
||
behavior.adaptivity = Math.min(1, behavior.adaptivity + 0.02);
|
||
|
||
if (behavior.checkDoor > 3) behavior.monsterAggression += 0.15;
|
||
if (behavior.checkBed > 2) behavior.monsterAggression += 0.2;
|
||
if (behavior.checkCloset > 2) behavior.monsterAggression += 0.2;
|
||
if (behavior.checkLight > 4) behavior.monsterAggression += 0.1;
|
||
if (behavior.checkCamera > 3) behavior.monsterAggression += 0.25;
|
||
|
||
state.monsterVisibility = Math.min(100, state.monsterVisibility + (behavior.monsterAggression * 3));
|
||
state.panicLevel = Math.min(100, state.panicLevel + (behavior.monsterAggression * 2));
|
||
|
||
if (state.lightsOn && behavior.checkLight > 2) {
|
||
state.monsterRoom = "shadows";
|
||
}
|
||
|
||
if (!state.lightsOn && !state.flashlightOn && !state.phoneLightOn) {
|
||
if (Math.random() < 0.25 * behavior.monsterAggression) {
|
||
state.monsterRoom = playerRoom === "corridor" ? "corridor_end" : "corridor";
|
||
}
|
||
}
|
||
|
||
if (behavior.checkBed > 3 && Math.random() < 0.5) {
|
||
state.monsterRoom = "bedroom_corner";
|
||
}
|
||
if (behavior.checkCloset > 3 && Math.random() < 0.5) {
|
||
state.monsterRoom = "closet_near";
|
||
}
|
||
|
||
if (state.part >= 4 && Math.random() < 0.3) {
|
||
const lastHide = behavior.memoryIndex.lastHide;
|
||
if (lastHide) {
|
||
state.monsterRoom = lastHide === "bed" ? "bedroom_corner" :
|
||
lastHide === "closet" ? "closet_near" : "bathroom_door";
|
||
}
|
||
}
|
||
|
||
return state;
|
||
}
|
||
|
||
function advancePlot(state, trigger, data = {}) {
|
||
const chapter = state.chapter;
|
||
const part = state.part;
|
||
|
||
if (part === 1) {
|
||
if (chapter === "intro" && trigger === "start_routine") {
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Вы дома одни. Родители уехали до утра. 02:00 ночи.",
|
||
time: "02:00",
|
||
read: false
|
||
});
|
||
state.chapter = "routine";
|
||
}
|
||
|
||
if (chapter === "routine" && trigger === "check_phone") {
|
||
state.messages.push({
|
||
from: "Лёха",
|
||
text: "Ты тоже это слышишь?",
|
||
time: "02:14",
|
||
read: false
|
||
});
|
||
state.unreadMessages++;
|
||
state.chapter = "message_received";
|
||
state.inGameTime = "02:14";
|
||
}
|
||
|
||
if (chapter === "message_received" && trigger === "look_window") {
|
||
state.messages.push({
|
||
from: "Лёха",
|
||
text: "...",
|
||
time: "02:15",
|
||
read: false
|
||
});
|
||
state.unreadMessages++;
|
||
state.chapter = "light_across";
|
||
}
|
||
|
||
if (chapter === "light_across" && trigger === "see_silhouette") {
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "В квартире напротив, которая пуста 3 года, горит свет...",
|
||
time: "02:18",
|
||
read: false
|
||
});
|
||
state.chapter = "power_outage";
|
||
state.inGameTime = "02:18";
|
||
}
|
||
|
||
if (chapter === "power_outage" && trigger === "lights_out") {
|
||
state.lightsOn = false;
|
||
state.powerOn = false;
|
||
state.wifiOn = false;
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Свет погас. Wi-Fi пропал. Только телефон светит.",
|
||
time: "02:20",
|
||
read: false
|
||
});
|
||
state.chapter = "self_message";
|
||
}
|
||
|
||
if (chapter === "self_message" && trigger === "door_knock") {
|
||
state.messages.push({
|
||
from: "ВАШ НОМЕР",
|
||
text: "НЕ ОТКРЫВАЙ ДВЕРЬ.",
|
||
time: "02:25",
|
||
read: false,
|
||
special: true
|
||
});
|
||
state.unreadMessages++;
|
||
state.chapter = "monster_inside";
|
||
}
|
||
|
||
if (chapter === "monster_inside" && trigger === "second_message") {
|
||
state.messages.push({
|
||
from: "ВАШ НОМЕР",
|
||
text: "Я УЖЕ ВНУТРИ.",
|
||
time: "02:25",
|
||
read: false,
|
||
special: true
|
||
});
|
||
state.unreadMessages++;
|
||
state.monsterRoom = "inside_house";
|
||
state.chapter = "part1_climax";
|
||
}
|
||
|
||
if (chapter === "part1_climax" && trigger === "door_unlock") {
|
||
state.doorLocked = false;
|
||
state.part = 2;
|
||
state.chapter = "part2_start";
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "ЧАСТЬ 2: Аномальный тамбур. Нужно выбраться из квартиры.",
|
||
time: "02:30",
|
||
read: false
|
||
});
|
||
}
|
||
}
|
||
|
||
if (part === 2) {
|
||
if (chapter === "part2_start" && trigger === "explore_tambour") {
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Тамбур затоплен. Виден свет из квартиры 146 (баба Люба).",
|
||
time: "02:32",
|
||
read: false
|
||
});
|
||
state.chapter = "neighbor_146";
|
||
}
|
||
|
||
if (chapter === "part2_start" && trigger === "enter_146") {
|
||
state.neighborsExplored[146] = true;
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Квартира 146. На стенах вырезки: 20 лет исчезновений подростков.",
|
||
time: "02:35",
|
||
read: false
|
||
});
|
||
state.chapter = "tv_static";
|
||
}
|
||
|
||
if (chapter === "neighbor_146" && trigger === "find_fuse") {
|
||
state.fusesCollected++;
|
||
state.inventory.push("Предохранитель #1");
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: `Найден предохранитель (${state.fusesCollected}/${state.fusesNeeded}). Нужно ещё ${state.fusesNeeded - state.fusesCollected}.`,
|
||
time: "02:37",
|
||
read: false
|
||
});
|
||
if (state.fusesCollected >= state.fusesNeeded) {
|
||
state.chapter = "fusebox";
|
||
}
|
||
}
|
||
|
||
if (chapter === "fusebox" && trigger === "activate_fusebox") {
|
||
state.powerOn = true;
|
||
state.lightsOn = true;
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Свет включился! В конце коридора... что-то стоит.",
|
||
time: "02:45",
|
||
read: false
|
||
});
|
||
state.monsterRoom = "corridor_end";
|
||
state.monsterVisibility = 40;
|
||
state.chapter = "part2_climax";
|
||
}
|
||
|
||
if (chapter === "part2_climax" && trigger === "monster_approach") {
|
||
state.part = 3;
|
||
state.chapter = "part3_start";
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "ЧАСТЬ 3: Зацикленный пролёт. Лестница ведёт обратно...",
|
||
time: "02:50",
|
||
read: false
|
||
});
|
||
}
|
||
}
|
||
|
||
if (part === 3) {
|
||
if (chapter === "part3_start" && trigger === "run_down") {
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Вы бежите вниз. 7 этаж... 6... 5... 1... 7?! Вы снова на своём этаже.",
|
||
time: "02:52",
|
||
read: false
|
||
});
|
||
state.loopCount++;
|
||
state.currentFloor = 7;
|
||
state.chapter = "loop_realization";
|
||
}
|
||
|
||
if (chapter === "loop_realization" && trigger === "check_phone_call") {
|
||
const fakeCalls = [
|
||
{ from: "Мама", text: "Данил, выйди на балкон и прыгай! Я поймаю!" },
|
||
{ from: "МЧС", text: "Запрись в мусоропроводе. Помощь едет." },
|
||
{ from: "Лёха", text: "Встретимся на крыше. Я жду." }
|
||
];
|
||
const call = fakeCalls[Math.floor(Math.random() * fakeCalls.length)];
|
||
state.messages.push({
|
||
from: call.from + " (ЗВОНОК)",
|
||
text: call.text,
|
||
time: "02:55",
|
||
read: false,
|
||
special: true
|
||
});
|
||
state.unreadMessages++;
|
||
state.chapter = "spatial_anomaly";
|
||
}
|
||
|
||
if (chapter === "spatial_anomaly" && trigger === "check_map") {
|
||
state.houseDistortion = 0.6;
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "КАРТА ИЗМЕНИЛАСЬ! Лифтовая шахта исчезла. На её месте — КВАРТИРА 148.",
|
||
time: "03:00",
|
||
read: false,
|
||
special: true
|
||
});
|
||
state.chapter = "find_148";
|
||
}
|
||
|
||
if (chapter === "find_148" && trigger === "enter_148") {
|
||
state.part = 4;
|
||
state.chapter = "part4_start";
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "ЧАСТЬ 4: Изнутри. Квартира 148 — это сам дом.",
|
||
time: "03:05",
|
||
read: false
|
||
});
|
||
}
|
||
}
|
||
|
||
if (part === 4) {
|
||
if (chapter === "part4_start" && trigger === "explore_rooms") {
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Комнаты ведут в другие места: школа, больница, детский сад... Это сон.",
|
||
time: "03:08",
|
||
read: false
|
||
});
|
||
state.chapter = "phone_room";
|
||
}
|
||
|
||
if (chapter === "phone_room" && trigger === "find_phones") {
|
||
state.phonesFound++;
|
||
if (state.phonesFound >= 3) {
|
||
state.chapter = "alexey_truth";
|
||
}
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: `Найден телефон (${state.phonesFound}/${state.totalPhones}). На них все те же сообщения...`,
|
||
time: "03:15",
|
||
read: false
|
||
});
|
||
}
|
||
|
||
if (chapter === "phone_room" && trigger === "find_alexey_chat") {
|
||
state.alexeyRevealed = true;
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "ПЕРЕПИСКА 2018: Лёха пропал 8 лет назад. Сущность использовала его профиль.",
|
||
time: "03:20",
|
||
read: false,
|
||
special: true
|
||
});
|
||
state.chapter = "radio_puzzle";
|
||
}
|
||
|
||
if (chapter === "radio_puzzle" && trigger === "set_frequency") {
|
||
const freq = data.frequency || 0;
|
||
if (Math.abs(freq - state.correctFrequency) < 1) {
|
||
state.radioFrequency = state.correctFrequency;
|
||
state.chapter = "secret_door";
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Частота настроена! Герметичная дверь открывается...",
|
||
time: "03:30",
|
||
read: false
|
||
});
|
||
} else {
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Шум. Нужно точнее. Подсказка: " + state.radioHint,
|
||
time: "03:28",
|
||
read: false
|
||
});
|
||
}
|
||
}
|
||
|
||
if (chapter === "secret_door" && trigger === "enter_center") {
|
||
state.part = 5;
|
||
state.chapter = "part5_start";
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "ЧАСТЬ 5: Обратная сторона глазка. Финал.",
|
||
time: "03:35",
|
||
read: false
|
||
});
|
||
}
|
||
}
|
||
|
||
if (part === 5) {
|
||
if (chapter === "part5_start" && trigger === "mirror_reality") {
|
||
state.mirrorMode = true;
|
||
state.messages.push({
|
||
from: "СИСТЕМА",
|
||
text: "Вы в своей комнате... но всё ЗЕРКАЛЬНО. 02:14. Круг замкнулся.",
|
||
time: "02:14",
|
||
read: false
|
||
});
|
||
state.chapter = "final_call";
|
||
}
|
||
|
||
if (chapter === "final_call" && trigger === "video_call") {
|
||
state.messages.push({
|
||
from: "ВИДЕОЗВОНОК",
|
||
text: "На экране ВЫ СТОИТЕ посреди комнаты и смотрите на себя.",
|
||
time: "??",
|
||
read: false,
|
||
special: true
|
||
});
|
||
state.unreadMessages++;
|
||
state.chapter = "ending_choice";
|
||
}
|
||
|
||
if (chapter === "ending_choice" && trigger === "choice_a") {
|
||
state.ending = "A";
|
||
state.endingA = true;
|
||
state.messages.push({
|
||
from: "КОНЦОВКА А: СИГНАЛ",
|
||
text: "Вы на крыше. Шаг в туман. Новое SMS: 'Ты тоже это слышишь?' отправлено следующему.",
|
||
time: "∞",
|
||
read: false,
|
||
special: true,
|
||
ending: true
|
||
});
|
||
}
|
||
|
||
if (chapter === "ending_choice" && trigger === "choice_b") {
|
||
state.ending = "B";
|
||
state.endingB = true;
|
||
state.transmitterDestroyed = true;
|
||
state.messages.push({
|
||
from: "КОНЦОВКА Б: СБРОС",
|
||
text: "Передатчик уничтожен. 08:00. Родители вернулись. Но в окне напротив КТО-ТО СМОТРИТ.",
|
||
time: "08:00",
|
||
read: false,
|
||
special: true,
|
||
ending: true
|
||
});
|
||
}
|
||
|
||
if (chapter === "ending_choice" && trigger === "choice_c") {
|
||
state.ending = "C";
|
||
state.endingC = true;
|
||
state.messages.push({
|
||
from: "КОНЦОВКА В: НОВЫЙ ЖИЛЕЦ",
|
||
text: "Вы садитесь за стол в кв.148. Берёте телефон. Печатаете: 'Ты тоже это слышишь?'",
|
||
time: "∞",
|
||
read: false,
|
||
special: true,
|
||
ending: true
|
||
});
|
||
}
|
||
}
|
||
|
||
return state;
|
||
}
|
||
|
||
http.createServer(async (req, res) => {
|
||
const url = req.url.split("?")[0];
|
||
|
||
if (req.method === "GET" && url === "/api/state") {
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify(loadState()));
|
||
}
|
||
|
||
if (req.method === "POST" && url === "/api/reset") {
|
||
const state = resetState();
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify(state));
|
||
}
|
||
|
||
if (req.method === "POST" && url === "/api/action") {
|
||
const data = await body(req);
|
||
let state = loadState();
|
||
|
||
state = updateMonster(state, data.action, state.playerRoom, state.playerPosition);
|
||
state = advancePlot(state, data.trigger, data);
|
||
|
||
if (data.move) {
|
||
state.playerPosition.x += data.move.x || 0;
|
||
state.playerPosition.y += data.move.y || 0;
|
||
}
|
||
|
||
if (data.action === "toggle_door") {
|
||
state.doorClosed = !state.doorClosed;
|
||
if (!state.doorClosed) state.doorLocked = false;
|
||
}
|
||
if (data.action === "toggle_light") {
|
||
state.lightsOn = !state.lightsOn;
|
||
}
|
||
if (data.action === "toggle_flashlight") {
|
||
state.flashlightOn = !state.flashlightOn;
|
||
}
|
||
if (data.action === "toggle_phone_light") {
|
||
state.phoneLightOn = !state.phoneLightOn;
|
||
}
|
||
|
||
if (data.action === "collect_fuse") {
|
||
state.fusesCollected++;
|
||
state.inventory.push(`Предохранитель #${state.fusesCollected}`);
|
||
}
|
||
|
||
if (data.action === "find_phone") {
|
||
state.phonesFound++;
|
||
}
|
||
|
||
if (data.action === "record_sound") {
|
||
state.recorderNotes.push({
|
||
time: state.inGameTime,
|
||
sound: data.sound || "аномальный шум",
|
||
analyzed: false
|
||
});
|
||
}
|
||
|
||
saveState(state);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify(state));
|
||
}
|
||
|
||
if (req.method === "POST" && url === "/api/camera") {
|
||
const data = await body(req);
|
||
let state = loadState();
|
||
|
||
const cameraId = data.cameraId;
|
||
const visibility = state.monsterVisibility;
|
||
const aggression = state.monsterAggression;
|
||
|
||
let monsterVisible = false;
|
||
let monsterData = null;
|
||
|
||
if (cameraId === "phone" && state.monsterRoom === state.playerRoom) {
|
||
if (Math.random() * 100 < visibility * (1 + aggression)) {
|
||
monsterVisible = true;
|
||
monsterData = { type: "silhouette", position: "background" };
|
||
}
|
||
}
|
||
|
||
if (cameraId === "entrance" && (state.monsterRoom === "corridor" || state.monsterRoom === "entrance")) {
|
||
if (Math.random() * 100 < visibility * 0.7 * (1 + aggression)) {
|
||
monsterVisible = true;
|
||
monsterData = { type: "shadow", position: "stairs" };
|
||
}
|
||
}
|
||
|
||
if (cameraId === "across" && state.monsterRoom === "apartment_across") {
|
||
if (Math.random() * 100 < visibility * 0.5 * (1 + aggression)) {
|
||
monsterVisible = true;
|
||
monsterData = { type: "figure", position: "window" };
|
||
}
|
||
}
|
||
|
||
if (cameraId === "stairwell" && state.part === 3) {
|
||
if (Math.random() * 100 < visibility * 0.8) {
|
||
monsterVisible = true;
|
||
monsterData = { type: "multiple", position: "all_floors" };
|
||
}
|
||
}
|
||
|
||
state.cameraViews[cameraId] = {
|
||
timestamp: Date.now(),
|
||
monsterVisible,
|
||
monsterData,
|
||
noise: Math.random() * 0.3 + state.houseDistortion * 0.3 + aggression * 0.2,
|
||
distortion: state.houseDistortion
|
||
};
|
||
|
||
saveState(state);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify(state.cameraViews[cameraId]));
|
||
}
|
||
|
||
if (req.method === "POST" && url === "/api/recorder") {
|
||
const data = await body(req);
|
||
let state = loadState();
|
||
|
||
state.recorderNotes.push({
|
||
time: state.inGameTime,
|
||
sound: data.sound || "неизвестный шум",
|
||
frequency: data.frequency || 0,
|
||
analyzed: data.analyzed || false
|
||
});
|
||
|
||
saveState(state);
|
||
res.writeHead(200, { "Content-Type": "application/json" });
|
||
return res.end(JSON.stringify(state.recorderNotes));
|
||
}
|
||
|
||
let file = url === "/" ? "/index.html" : url;
|
||
const full = path.join(__dirname, file);
|
||
|
||
if (fs.existsSync(full) && fs.statSync(full).isFile()) {
|
||
const ext = path.extname(full);
|
||
const type = ext === ".css" ? "text/css" : ext === ".js" ? "application/javascript" : "text/html";
|
||
res.writeHead(200, { "Content-Type": type + "; charset=utf-8" });
|
||
return res.end(fs.readFileSync(full));
|
||
}
|
||
|
||
res.writeHead(404);
|
||
res.end("Not found");
|
||
}).listen(PORT, () => console.log("🎮 НОЧНОЙ ДОМ запущен на порту " + PORT));
|