<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>HYPERSTITION: SHENZHEN</title>
<style>
* {
box-sizing: border-box;
touch-action: manipulation;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #020206;
color: #00ffcc;
font-family: 'Courier New', 'PingFang SC', 'Microsoft YaHei', monospace;
margin: 0;
padding: 6px;
display: flex;
justify-content: center;
align-items: flex-start;
user-select: none;
-webkit-user-select: none;
}
#game-wrapper {
position: relative;
width: 100%;
max-width: 380px;
display: flex;
flex-direction: column;
gap: 5px;
justify-content: flex-start;
}
/* CYBERPUNK CHINESE TITLE SCREEN */
#title-screen {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: radial-gradient(circle at center, #0e051c 0%, #020208 100%);
border: 2px solid #ff0055;
box-shadow: inset 0 0 30px rgba(255, 0, 85, 0.5), 0 0 20px rgba(0, 255, 204, 0.3);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 500;
padding: 15px;
text-align: center;
overflow: hidden;
}
/* CRT Scanline Overlay Effect */
#title-screen::before {
content: " ";
display: block;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.35) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.05), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.05));
z-index: 2;
background-size: 100% 3px, 6px 100%;
pointer-events: none;
}
/* Watermark Chinese Characters in Background */
.title-bg-kanji {
position: absolute;
font-size: 110px;
color: rgba(255, 0, 85, 0.05);
font-weight: 900;
z-index: 1;
pointer-events: none;
line-height: 0.9;
letter-spacing: -5px;
}
.title-main-box {
z-index: 3;
border-top: 1.5px solid #00ffcc;
border-bottom: 1.5px solid #00ffcc;
padding: 12px 10px;
width: 100%;
background: rgba(8, 3, 18, 0.75);
box-shadow: 0 0 15px rgba(0, 255, 204, 0.15);
}
.title-kanji-top {
font-size: 13px;
color: #ffcc00;
letter-spacing: 5px;
text-shadow: 0 0 8px #ffcc00;
margin-bottom: 4px;
font-weight: bold;
}
.title-glitch {
font-size: 21px;
font-weight: 900;
color: #ff0055;
text-shadow: 3px 0 #00ffcc, -2px 0 #ffcc00;
letter-spacing: 2px;
line-height: 1.1;
animation: glitch 1.6s infinite alternate;
}
.title-sub {
font-size: 11px;
color: #00ffcc;
margin-top: 6px;
letter-spacing: 2px;
text-shadow: 0 0 8px #00ffcc;
font-weight: bold;
}
.title-tags {
display: flex;
gap: 6px;
margin: 12px 0 6px 0;
z-index: 3;
}
.tag-badge {
font-size: 8px;
background: #160829;
border: 1px solid #ff0055;
color: #ff0055;
padding: 2px 6px;
border-radius: 2px;
letter-spacing: 1px;
}
.title-info {
z-index: 3;
font-size: 8px;
color: #8899a6;
margin: 10px 0 18px 0;
line-height: 1.5;
border-left: 2px solid #00ffcc;
padding-left: 8px;
text-align: left;
background: rgba(0, 0, 0, 0.4);
width: 100%;
}
.start-btn {
z-index: 3;
background: #ff0055;
border: 1.5px solid #00ffcc;
color: #020208;
font-family: inherit;
font-weight: 900;
font-size: 11px;
padding: 10px 22px;
cursor: pointer;
box-shadow: 0 0 15px rgba(255, 0, 85, 0.8);
letter-spacing: 1.5px;
animation: pulse 1.2s infinite alternate;
}
.start-btn:active {
background: #00ffcc;
color: #000;
box-shadow: 0 0 20px #00ffcc;
}
/* GAME UI */
#ui-stats {
width: 100%;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 2px;
font-weight: bold;
color: #ff0055;
font-size: 9px;
background: #08030f;
padding: 4px;
border: 1px solid #ff0055;
box-shadow: 0 0 6px rgba(255, 0, 85, 0.3);
}
#ui-stats div { text-align: center; white-space: nowrap; }
#game-container {
position: relative;
width: 100%;
}
#map {
background: #000;
border: 1.5px solid #ff0055;
box-shadow: 0 0 8px rgba(255, 0, 85, 0.25);
padding: 2px;
width: 100%;
height: 250px;
display: grid;
grid-template-columns: repeat(22, 1fr);
grid-template-rows: repeat(14, 1fr);
align-items: center;
justify-items: center;
}
.t {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: bold;
line-height: 1;
}
/* AIMING SKILL-CHECK MODAL */
#aim-modal {
display: none;
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
width: 88%;
background: rgba(6, 2, 14, 0.95);
border: 2px solid #00ffcc;
box-shadow: 0 0 20px rgba(0, 255, 204, 0.6);
padding: 10px;
flex-direction: column;
align-items: center;
gap: 8px;
z-index: 200;
cursor: pointer;
}
#aim-title {
color: #ff0055;
font-size: 10px;
font-weight: bold;
letter-spacing: 1px;
}
#aim-bar-container {
width: 100%;
height: 22px;
background: #000;
border: 1px solid #00ffcc;
position: relative;
overflow: hidden;
}
#aim-target-zone {
position: absolute;
top: 0; bottom: 0;
background: rgba(0, 255, 204, 0.35);
border-left: 2px solid #00ffcc;
border-right: 2px solid #00ffcc;
}
#aim-cursor {
position: absolute;
top: 0; bottom: 0;
width: 4px;
background: #ff0055;
box-shadow: 0 0 8px #ff0055, 0 0 12px #ff0055;
}
#aim-instruction {
font-size: 9px;
color: #ffcc00;
font-weight: bold;
letter-spacing: 1px;
}
#shop-modal {
display: none;
position: absolute;
top: 4px; left: 4px; right: 4px; bottom: 4px;
background: rgba(4, 2, 10, 0.98);
border: 1.5px solid #ffcc00;
box-shadow: 0 0 15px rgba(255, 204, 0, 0.4);
padding: 6px;
flex-direction: column;
z-index: 100;
}
#shop-title {
color: #ffcc00;
font-size: 10px;
font-weight: bold;
text-align: center;
border-bottom: 1px dashed #ffcc00;
padding-bottom: 2px;
margin-bottom: 4px;
}
.shop-items {
display: flex;
flex-direction: column;
gap: 4px;
overflow-y: auto;
flex: 1;
}
.shop-btn {
background: #0a0a1a;
border: 1px solid #00ffcc;
color: #00ffcc;
padding: 4px 6px;
font-size: 9px;
font-family: inherit;
cursor: pointer;
text-align: left;
display: flex;
justify-content: space-between;
}
.shop-btn:active { background: #00ffcc; color: #000; }
.close-shop {
margin-top: 4px;
background: #ff0055;
color: #fff;
border: none;
padding: 5px;
font-weight: bold;
cursor: pointer;
text-align: center;
font-size: 10px;
}
#action-panel, #inventory-panel {
width: 100%;
display: flex;
gap: 3px;
}
.action-btn, .inv-slot {
flex: 1;
background: #0f0a1c;
border: 1px solid #00ffcc;
color: #ffcc00;
font-size: 9px;
padding: 5px 1px;
text-align: center;
cursor: pointer;
border-radius: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-weight: bold;
}
.action-btn { background: #240a18; border-color: #ff0055; color: #ff0055; }
.action-btn:active, .inv-slot:active { background: #00ffcc; color: #000; }
#log {
width: 100%;
height: 42px;
background: #05020c;
border: 1px solid #222;
color: #ffcc00;
font-size: 9px;
padding: 3px 5px;
overflow-y: auto;
line-height: 1.2;
}
.controls-wrapper {
display: flex;
justify-content: center;
margin-top: 2px;
}
.controls {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
width: 170px;
}
.btn {
background: #0f0f24;
color: #00ffcc;
border: 1px solid #00ffcc;
padding: 7px 0;
font-size: 12px;
font-weight: bold;
text-align: center;
border-radius: 3px;
cursor: pointer;
}
.btn:active { background: #00ffcc; color: #000; }
.p-player { color: #00ffcc; }
.p-psychotic { color: #ff0055; animation: blink 0.4s infinite; }
.e-triad { color: #ff3333; }
.e-ministry { color: #ffcc00; }
.e-drone { color: #ff00ff; }
.i-vendor { color: #ffcc00; text-shadow: 0 0 5px #ffcc00; }
.i-item { color: #00ff66; }
.i-exit { color: #00ffcc; text-shadow: 0 0 6px #00ffcc; font-size: 12px; }
.tile-wall { color: #3d1c3d; }
.tile-floor { color: #160f24; }
.fog-hidden { color: #000000; }
.fog-explored { color: #12081c; }
.stunned { color: #777777 !important; }
@keyframes glitch {
0% { transform: skew(0deg); }
80% { transform: skew(0deg); }
85% { transform: skew(3deg); }
90% { transform: skew(-3deg); }
100% { transform: skew(0deg); }
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: 0 0 10px rgba(255, 0, 85, 0.6); }
100% { transform: scale(1.04); box-shadow: 0 0 20px rgba(255, 0, 85, 0.9); }
}
@keyframes blink { 0% { opacity: 1; } 50% { opacity: 0.2; } 100% { opacity: 1; } }
</style>
</head>
<body>
<div id="game-wrapper">
<!-- CHINESE CYBERPUNK TITLE SCREEN -->
<div id="title-screen">
<div class="title-bg-kanji">超迷信<br>深圳</div>
<div class="title-main-box">
<div class="title-kanji-top">【 超 迷 信 】</div>
<div class="title-glitch">HYPERSTITION</div>
<div class="title-sub">SHENZHEN // 深圳</div>
</div>
<div class="title-tags">
<span class="tag-badge">加速主义</span>
<span class="tag-badge">赛博朋克</span>
<span class="tag-badge">特区 S-01</span>
</div>
<div class="title-info">
> SYSTEM: NEURAL INTERFACE ONLINE<br>
> LOCATION: SHENZHEN SEZ [深圳特区]<br>
> WARNING: 人性流失 (HUMANITY DEPLETION)
</div>
<button class="start-btn" onclick="startGame()">[ 网络接入 // ACCESS ]</button>
</div>
<!-- GAME INTERFACE -->
<div id="ui-stats">
<div id="stat-level">ZONE: S-01</div>
<div id="stat-hp">INT: 35</div>
<div id="stat-ram">RAM: 4/4</div>
<div id="stat-psyche">HUM: 100%</div>
<div id="stat-food">CAL: 100%</div>
<div id="stat-ammo">AMMO: 8</div>
<div id="stat-atk">GUN: 4</div>
<div id="stat-cred">¥50</div>
</div>
<div id="game-container">
<div id="map"></div>
<!-- AIMING SKILL CHECK OVERLAY -->
<div id="aim-modal" onclick="resolveShot()">
<div id="aim-title">=== ZIELERFASSUNG // LOCK-ON ===</div>
<div id="aim-bar-container">
<div id="aim-target-zone"></div>
<div id="aim-cursor"></div>
</div>
<div id="aim-instruction">[F] / [SPACE] / TIPPE ZUM FEUERN</div>
</div>
<div id="shop-modal">
<div id="shop-title">=== SHENZHEN TAOBAO-LOOM [¥] ===</div>
<div class="shop-items">
<button class="shop-btn" onclick="buyShopItem('Sheng-Stim', 35, 0)"><span>[1] Sheng-Stim (+20 Int)</span><span>¥35</span></button>
<button class="shop-btn" onclick="buyShopItem('Synth-Baozi', 15, 0)"><span>[2] Synth-Baozi (+50% Cal)</span><span>¥15</span></button>
<button class="shop-btn" onclick="buyShopItem('Mag-Rounds', 25, 0)"><span>[3] Kinetic-Mag (+6 Ammo)</span><span>¥25</span></button>
<button class="shop-btn" onclick="buyShopItem('Xeno-Blade', 110, 25)"><span>[4] Xeno-Blade (+3 ATK)</span><span>¥110 (-25% H)</span></button>
<button class="shop-btn" onclick="buyShopItem('Subdermal-Weave', 110, 25)"><span>[5] Subdermal (+15 HP)</span><span>¥110 (-25% H)</span></button>
<button class="shop-btn" onclick="buyShopItem('Teleoplex-Chip', 110, 25)"><span>[6] Teleoplex (+2 RAM)</span><span>¥110 (-25% H)</span></button>
</div>
<div class="close-shop" onclick="closeShop()">TRENNEN [Ø]</div>
</div>
</div>
<div id="action-panel">
<div class="action-btn" onclick="shootNearest()">[F] KINETIK-SCHUSS</div>
<div class="action-btn" onclick="hackEnemy()">[H] OVERLOAD</div>
</div>
<div id="inventory-panel">
<div class="inv-slot" onclick="useItem(0)" id="inv-0">[1] -</div>
<div class="inv-slot" onclick="useItem(1)" id="inv-1">[2] -</div>
<div class="inv-slot" onclick="useItem(2)" id="inv-2">[3] -</div>
<div class="inv-slot" onclick="useItem(3)" id="inv-3">[4] -</div>
</div>
<div id="log"></div>
<div class="controls-wrapper">
<div class="controls">
<div></div>
<div class="btn" onclick="handleInput(0, -1)">▲</div>
<div></div>
<div class="btn" onclick="handleInput(-1, 0)">◄</div>
<div class="btn" onclick="handleInput(0, 0)">Wait</div>
<div class="btn" onclick="handleInput(1, 0)">►</div>
<div></div>
<div class="btn" onclick="handleInput(0, 1)">▼</div>
<div></div>
</div>
</div>
</div>
<script>
const WIDTH = 22;
const HEIGHT = 14;
const MAX_INV = 4;
const VISIBILITY_RADIUS = 6;
let audioCtx = null;
function initAudio() {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
}
function startGame() {
initAudio();
playSfx('exit');
document.getElementById('title-screen').style.display = 'none';
}
function playSfx(type) {
initAudio();
if (!audioCtx) return;
const now = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
if (type === 'move') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(110, now);
osc.frequency.exponentialRampToValueAtTime(40, now + 0.04);
gain.gain.setValueAtTime(0.04, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.04);
osc.start(now);
osc.stop(now + 0.04);
} else if (type === 'shoot') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(700, now);
osc.frequency.exponentialRampToValueAtTime(60, now + 0.12);
gain.gain.setValueAtTime(0.12, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.12);
osc.start(now);
osc.stop(now + 0.12);
} else if (type === 'hit') {
osc.type = 'square';
osc.frequency.setValueAtTime(140, now);
osc.frequency.exponentialRampToValueAtTime(30, now + 0.18);
gain.gain.setValueAtTime(0.15, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.18);
osc.start(now);
osc.stop(now + 0.18);
} else if (type === 'item') {
osc.type = 'square';
osc.frequency.setValueAtTime(440, now);
osc.frequency.setValueAtTime(880, now + 0.06);
gain.gain.setValueAtTime(0.08, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.18);
osc.start(now);
osc.stop(now + 0.18);
} else if (type === 'buy') {
osc.type = 'square';
osc.frequency.setValueAtTime(523, now);
osc.frequency.setValueAtTime(659, now + 0.06);
osc.frequency.setValueAtTime(783, now + 0.12);
gain.gain.setValueAtTime(0.08, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.22);
osc.start(now);
osc.stop(now + 0.22);
} else if (type === 'hack') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(220, now);
osc.frequency.linearRampToValueAtTime(880, now + 0.08);
osc.frequency.linearRampToValueAtTime(330, now + 0.16);
gain.gain.setValueAtTime(0.1, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.18);
osc.start(now);
osc.stop(now + 0.18);
} else if (type === 'exit') {
// 8-Bit Chiptune Warp / Level Clear Sound
osc.type = 'square';
const notes = [261.63, 329.63, 392.00, 523.25, 659.25, 783.99, 1046.50];
const step = 0.035;
notes.forEach((freq, i) => {
osc.frequency.setValueAtTime(freq, now + i * step);
});
gain.gain.setValueAtTime(0.1, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + notes.length * step);
osc.start(now);
osc.stop(now + notes.length * step);
} else if (type === 'gameover') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(300, now);
osc.frequency.exponentialRampToValueAtTime(30, now + 0.5);
gain.gain.setValueAtTime(0.2, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.5);
osc.start(now);
osc.stop(now + 0.5);
}
}
let player = {
x: 0, y: 0,
hp: 35, maxHp: 35,
ram: 4, maxRam: 4,
humanity: 100,
hunger: 100,
ammo: 8,
atk: 4,
creds: 50,
level: 1,
noise: 0,
inventory: ['Sheng-Stim', 'Synth-Baozi']
};
let map = [];
let visibleMap = [];
let exploredMap = [];
let enemies = [];
let items = [];
let vendors = [];
let exitGate = { x: 0, y: 0 };
let turnCounter = 0;
let inShop = false;
// AIMING SKILL CHECK VARIABLES
let isAiming = false;
let aimPos = 0;
let aimDir = 1;
let aimSpeed = 2.5;
let aimAnimFrame = null;
let currentAimTargetIdx = -1;
function log(msg) {
const logBox = document.getElementById('log');
if (logBox) {
logBox.innerHTML += `> ${msg}<br>`;
logBox.scrollTop = logBox.scrollHeight;
}
}
function initGame() {
log("SONDERWIRTSCHAFTSZONE LÄDT... NETZWERK GENERIERT.");
generateCitySector();
updateVision();
render();
document.addEventListener('keydown', e => {
initAudio();
if (isAiming) {
if (e.key === 'f' || e.key === 'F' || e.key === ' ' || e.key === 'Enter') {
resolveShot();
}
return;
}
if (inShop) {
if (e.key === 'Escape') closeShop();
return;
}
if (e.key === 'ArrowUp' || e.key === 'w') handleInput(0, -1);
if (e.key === 'ArrowDown' || e.key === 's') handleInput(0, 1);
if (e.key === 'ArrowLeft' || e.key === 'a') handleInput(-1, 0);
if (e.key === 'ArrowRight' || e.key === 'd') handleInput(1, 0);
if (e.key === ' ') handleInput(0, 0);
if (e.key === 'f' || e.key === 'F') shootNearest();
if (e.key === 'h' || e.key === 'H') hackEnemy();
if (e.key >= '1' && e.key <= '4') useItem(parseInt(e.key) - 1);
});
}
function generateCitySector() {
map = Array(HEIGHT).fill(null).map(() => Array(WIDTH).fill('▓'));
exploredMap = Array(HEIGHT).fill(null).map(() => Array(WIDTH).fill(false));
visibleMap = Array(HEIGHT).fill(null).map(() => Array(WIDTH).fill(false));
enemies = [];
items = [];
vendors = [];
let blocks = [];
let attempts = 0;
while (blocks.length < 7 && attempts < 120) {
attempts++;
let w = Math.floor(Math.random() * 3) + 3;
let h = Math.floor(Math.random() * 3) + 3;
let x = Math.floor(Math.random() * (WIDTH - w - 2)) + 1;
let y = Math.floor(Math.random() * (HEIGHT - h - 2)) + 1;
let overlap = blocks.some(b => !(x + w + 1 < b.x || x > b.x + b.w + 1 || y + h + 1 < b.y || y > b.y + b.h + 1));
if (!overlap) {
blocks.push({ x, y, w, h, center: { x: Math.floor(x + w/2), y: Math.floor(y + h/2) } });
for (let rx = x; rx < x + w; rx++) {
for (let ry = y; ry < y + h; ry++) map[ry][rx] = '·';
}
}
}
for (let i = 0; i < blocks.length; i++) {
let distances = blocks
.map((b, idx) => ({ idx, dist: Math.hypot(b.center.x - blocks[i].center.x, b.center.y - blocks[i].center.y) }))
.filter(d => d.idx !== i)
.sort((a, b) => a.dist - b.dist);
let neighbors = distances.slice(0, 2);
neighbors.forEach(n => {
let c1 = blocks[i].center;
let c2 = blocks[n.idx].center;
let currX = c1.x, currY = c1.y;
if (Math.random() < 0.5) {
while (currX !== c2.x) { map[currY][currX] = '·'; currX += currX < c2.x ? 1 : -1; }
while (currY !== c2.y) { map[currY][currX] = '·'; currY += currY < c2.y ? 1 : -1; }
} else {
while (currY !== c2.y) { map[currY][currX] = '·'; currY += currY < c2.y ? 1 : -1; }
while (currX !== c2.x) { map[currY][currX] = '·'; currX += currX < c2.x ? 1 : -1; }
}
});
}
for (let k = 0; k < 3; k++) {
let rx = Math.floor(Math.random() * (WIDTH - 6)) + 3;
let ry = Math.floor(Math.random() * (HEIGHT - 6)) + 3;
if (Math.random() < 0.5) {
for (let len = 0; len < 5; len++) { if (rx + len < WIDTH - 1) map[ry][rx + len] = '·'; }
} else {
for (let len = 0; len < 5; len++) { if (ry + len < HEIGHT - 1) map[ry + len][rx] = '·'; }
}
}
player.x = blocks[0].center.x;
player.y = blocks[0].center.y;
let sortedByDist = blocks.slice().sort((a, b) => {
let dA = Math.hypot(a.center.x - player.x, a.center.y - player.y);
let dB = Math.hypot(b.center.x - player.x, b.center.y - player.y);
return dB - dA;
});
let furthestBlock = sortedByDist[0];
exitGate = { x: furthestBlock.center.x, y: furthestBlock.center.y };
let vendorCandidates = blocks.filter(b => b !== blocks[0] && b !== furthestBlock);
if (vendorCandidates.length > 0) {
let vBlock = vendorCandidates[0];
vendors.push({ x: vBlock.center.x, y: vBlock.center.y, char: '¥' });
} else if (blocks.length > 1) {
vendors.push({ x: blocks[1].x + 1, y: blocks[1].y + 1, char: '¥' });
}
blocks.forEach((b) => {
let rand = Math.random();
let isReserved = (x, y) => (x === player.x && y === player.y) ||
(x === exitGate.x && y === exitGate.y) ||
vendors.some(v => v.x === x && v.y === y);
let enemyX = b.x + 1;
let enemyY = b.y + 1;
if (!isReserved(enemyX, enemyY) && rand > 0.3) {
let enemyType = rand < 0.6 ? 'triad' : (rand < 0.85 ? 'ministry' : 'drone');
enemies.push({
x: enemyX, y: enemyY,
type: enemyType,
char: enemyType === 'triad' ? '帮' : (enemyType === 'ministry' ? '官' : '∆'),
hp: enemyType === 'triad' ? 10 : (enemyType === 'ministry' ? 16 : 8),
atk: enemyType === 'triad' ? 4 : (enemyType === 'ministry' ? 7 : 8),
range: enemyType === 'triad' ? 1.5 : (enemyType === 'ministry' ? 4 : 6),
bounty: enemyType === 'triad' ? 25 : (enemyType === 'ministry' ? 45 : 60),
stunned: 0
});
}
let itemX = b.x + b.w - 2;
let itemY = b.y + b.h - 2;
if (!isReserved(itemX, itemY) && Math.random() > 0.35) {
let itemRand = Math.random();
let type = itemRand < 0.4 ? 'Sheng-Stim' : (itemRand < 0.7 ? 'Synth-Baozi' : 'Mag-Rounds');
let char = type === 'Sheng-Stim' ? '生' : (type === 'Synth-Baozi' ? '包' : '⌖');
items.push({ x: itemX, y: itemY, type, char });
}
});
}
function updateVision() {
for (let y = 0; y < HEIGHT; y++) {
for (let x = 0; x < WIDTH; x++) visibleMap[y][x] = false;
}
for (let angle = 0; angle < 360; angle += 5) {
let rad = angle * (Math.PI / 180);
let dx = Math.cos(rad);
let dy = Math.sin(rad);
let cx = player.x + 0.5;
let cy = player.y + 0.5;
for (let d = 0; d < VISIBILITY_RADIUS; d++) {
let tileX = Math.floor(cx);
let tileY = Math.floor(cy);
if (tileX >= 0 && tileX < WIDTH && tileY >= 0 && tileY < HEIGHT) {
visibleMap[tileY][tileX] = true;
exploredMap[tileY][tileX] = true;
if (map[tileY][tileX] === '▓') break;
}
cx += dx;
cy += dy;
}
}
}
function handleInput(dx, dy) {
initAudio();
if (player.hp <= 0 || inShop || isAiming) return;
let newX = player.x + dx;
let newY = player.y + dy;
if (map[newY][newX] !== '▓') {
let enemyIdx = enemies.findIndex(e => e.x === newX && e.y === newY);
let vendorIdx = vendors.findIndex(v => v.x === newX && v.y === newY);
if (enemyIdx !== -1) {
attackEnemyMelee(enemyIdx);
} else if (vendorIdx !== -1) {
openShop();
return;
} else {
player.x = newX;
player.y = newY;
playSfx('move');
checkItems();
checkExit();
}
endTurn();
}
}
function openShop() {
inShop = true;
playSfx('buy');
document.getElementById('shop-modal').style.display = 'flex';
log(`<span style="color:#ffcc00">Verbindung mit Automat [¥] hergestellt.</span>`);
}
function closeShop() {
inShop = false;
document.getElementById('shop-modal').style.display = 'none';
render();
}
function buyShopItem(itemType, cost, humanityCost) {
initAudio();
if (player.creds < cost) {
log(`<span style="color:#ff3333">FEHLER: Zu wenig Yuan! (¥${cost})</span>`);
return;
}
playSfx('buy');
if (humanityCost > 0) {
player.creds -= cost;
player.humanity = Math.max(0, player.humanity - humanityCost);
if (itemType === 'Xeno-Blade') player.atk += 3;
if (itemType === 'Subdermal-Weave') { player.maxHp += 15; player.hp += 15; }
if (itemType === 'Teleoplex-Chip') { player.maxRam += 2; player.ram += 2; }
log(`<span style="color:#ff00ff">CYBERWARE: ${itemType}. Menschlichkeit: ${player.humanity}%.</span>`);
if (player.humanity <= 30) {
log(`<span style="color:#ff0055; font-weight:bold;">HYPERSTITION: Cyber-Dämon [魔]!</span>`);
}
} else {
if (itemType === 'Mag-Rounds') {
player.creds -= cost;
player.ammo += 6;
log(`Magazin [⌖] erworben (+6 Ammo).`);
} else {
if (player.inventory.length < MAX_INV) {
player.creds -= cost;
player.inventory.push(itemType);
log(`${itemType} verstaut.`);
} else {
log(`<span style="color:#ff3333">KAUF ABGEBROCHEN: Inventar voll!</span>`);
}
}
}
render();
}
function shootNearest() {
initAudio();
if (player.hp <= 0 || inShop || isAiming) return;
if (player.ammo <= 0) {
log(`<span style="color:#ff3333">MAGAZIN LEER!</span>`);
return;
}
let targetIdx = -1;
let minDistance = 999;
enemies.forEach((e, idx) => {
if (visibleMap[e.y][e.x]) {
let dist = Math.hypot(e.x - player.x, e.y - player.y);
if (dist < minDistance) {
minDistance = dist;
targetIdx = idx;
}
}
});
if (targetIdx !== -1) {
currentAimTargetIdx = targetIdx;
startAimMiniGame();
} else {
log(`Kein Ziel sichtbar.`);
}
}
function startAimMiniGame() {
isAiming = true;
aimPos = 0;
aimDir = 1;
aimSpeed = 2.4 + Math.random() * 1.6;
let zoneLeft = 30 + Math.floor(Math.random() * 25);
let zoneWidth = 20;
let targetZoneElem = document.getElementById('aim-target-zone');
targetZoneElem.style.left = zoneLeft + '%';
targetZoneElem.style.width = zoneWidth + '%';
targetZoneElem.dataset.min = zoneLeft;
targetZoneElem.dataset.max = zoneLeft + zoneWidth;
document.getElementById('aim-modal').style.display = 'flex';
updateAimLoop();
}
function updateAimLoop() {
if (!isAiming) return;
aimPos += aimSpeed * aimDir;
if (aimPos >= 100) {
aimPos = 100;
aimDir = -1;
} else if (aimPos <= 0) {
aimPos = 0;
aimDir = 1;
}
document.getElementById('aim-cursor').style.left = aimPos + '%';
aimAnimFrame = requestAnimationFrame(updateAimLoop);
}
function resolveShot() {
if (!isAiming) return;
cancelAnimationFrame(aimAnimFrame);
isAiming = false;
document.getElementById('aim-modal').style.display = 'none';
player.ammo--;
player.noise += 3;
let targetZoneElem = document.getElementById('aim-target-zone');
let min = parseFloat(targetZoneElem.dataset.min);
let max = parseFloat(targetZoneElem.dataset.max);
let center = min + (max - min) / 2;
let e = enemies[currentAimTargetIdx];
if (!e) {
endTurn();
return;
}
if (aimPos >= min && aimPos <= max) {
let isCrit = Math.abs(aimPos - center) <= 3.5;
let bonusAtk = player.humanity <= 30 ? 6 : 0;
let dmg = player.atk + bonusAtk + Math.floor(Math.random() * 4);
if (isCrit) dmg = Math.floor(dmg * 1.6) + 4;
playSfx('shoot');
if (isCrit) {
log(`<span style="color:#ffcc00; font-weight:bold;">CRITICAL LOCK! [${e.char}] kritisch getroffen für ${dmg} DMG!</span>`);
} else {
log(`<span style="color:#00ffcc">TREFFER! Schuss auf [${e.char}] für ${dmg} DMG!</span>`);
}
e.hp -= dmg;
if (e.hp <= 0) {
player.creds += e.bounty;
log(`<span style="color:#00ffcc">[${e.char}] eliminiert! +¥${e.bounty}.</span>`);
enemies.splice(currentAimTargetIdx, 1);
}
} else {
playSfx('hit');
log(`<span style="color:#ff0055">FEHLSCHUSS! Zielverfehlung (${Math.round(aimPos)}%).</span>`);
}
endTurn();
}
function hackEnemy() {
initAudio();
if (player.ram < 2 || inShop || isAiming) {
log(`<span style="color:#ff3333">RAM ZU NIEDRIG! (2 K-RAM nötig)</span>`);
return;
}
let targetIdx = -1;
let minDistance = 999;
enemies.forEach((e, idx) => {
if (visibleMap[e.y][e.x] && e.stunned === 0) {
let dist = Math.hypot(e.x - player.x, e.y - player.y);
if (dist < minDistance) {
minDistance = dist;
targetIdx = idx;
}
}
});
if (targetIdx !== -1) {
playSfx('hack');
player.ram -= 2;
let e = enemies[targetIdx];
e.stunned = 3;
log(`<span style="color:#ff00ff">OVERCLOCK auf [${e.char}]! 3 Züge gelähmt.</span>`);
endTurn();
} else {
log(`Kein erfassbares Ziel.`);
}
}
function attackEnemyMelee(idx) {
playSfx('hit');
let e = enemies[idx];
let bonusAtk = player.humanity <= 30 ? 5 : 0;
let dmg = Math.floor(player.atk * 0.7) + bonusAtk;
e.hp -= dmg;
log(`Nahkampf auf [${e.char}] für ${dmg} DMG.`);
if (e.hp <= 0) {
player.creds += e.bounty;
log(`<span style="color:#00ffcc">[${e.char}] eliminiert! +¥${e.bounty}.</span>`);
enemies.splice(idx, 1);
}
}
function useItem(slot) {
initAudio();
if (player.hp <= 0 || slot >= player.inventory.length || inShop || isAiming) return;
playSfx('item');
let item = player.inventory[slot];
if (item === 'Sheng-Stim') {
player.hp = Math.min(player.maxHp, player.hp + 20);
log(`<span style="color:#00ffcc">Sheng-Stim [生] (+20 HP).</span>`);
} else if (item === 'Synth-Baozi') {
player.hunger = Math.min(100, player.hunger + 50);
player.hp = Math.min(player.maxHp, player.hp + 5);
log(`<span style="color:#00ffcc">Synth-Baozi [包] (+50% Cal).</span>`);
}
player.inventory.splice(slot, 1);
endTurn();
}
function checkItems() {
let idx = items.findIndex(i => i.x === player.x && i.y === player.y);
if (idx !== -1) {
let item = items[idx];
playSfx('item');
if (item.type === 'Mag-Rounds') {
player.ammo += 5;
log(`Magazin [⌖] aufgesammelt (+5).`);
items.splice(idx, 1);
} else {
if (player.inventory.length < MAX_INV) {
player.inventory.push(item.type);
log(`${item.type} [${item.char}] aufgesammelt.`);
items.splice(idx, 1);
} else {
log(`<span style="color:#ff0055">Inventar voll für ${item.type}.</span>`);
}
}
}
}
function checkExit() {
if (player.x === exitGate.x && player.y === exitGate.y) {
playSfx('exit');
player.level++;
log(`<span style="color:#00ffcc">ACCELERATING... Betrete Zone S-0${player.level} [门].</span>`);
generateCitySector();
}
}
function endTurn() {
turnCounter++;
updateVision();
if (turnCounter % 3 === 0 && player.ram < player.maxRam) player.ram++;
if (player.humanity <= 30 && Math.random() < 0.25) {
let psychosisEffect = Math.random();
if (psychosisEffect < 0.5) {
player.hp -= 2;
playSfx('hit');
log(`<span style="color:#ff0055; font-weight:bold;">HYPERSTITION! (-2 HP).</span>`);
} else {
player.noise += 5;
log(`<span style="color:#ff0055; font-weight:bold;">TIME-LOOP-PULSE!</span>`);
}
}
if (player.noise > 0) player.noise--;
if (turnCounter % 5 === 0 && player.hunger > 0) player.hunger--;
if (player.hunger <= 0) {
player.hp--;
playSfx('hit');
log(`<span style="color:#ff0055">METABOLISCHER KOLLAPS! -1 HP.</span>`);
}
enemies.forEach(e => {
if (e.stunned > 0) {
e.stunned--;
return;
}
let isVisible = visibleMap[e.y][e.x];
let dist = Math.hypot(e.x - player.x, e.y - player.y);
if (isVisible || dist < player.noise) {
if (dist <= e.range) {
let dmg = Math.max(1, e.atk - Math.floor(Math.random() * 2));
player.hp -= dmg;
playSfx('hit');
log(`<span style="color:#ff0055">[${e.char}] trifft dich für ${dmg} DMG!</span>`);
} else {
let dx = Math.sign(player.x - e.x);
let dy = Math.sign(player.y - e.y);
if (map[e.y + dy][e.x + dx] !== '▓' && !enemies.some(o => o.x === e.x + dx && o.y === e.y + dy)) {
e.x += dx;
e.y += dy;
}
}
}
});
if (player.hp <= 0) {
playSfx('gameover');
log(`<span style="color:#ff0000; font-weight:bold;">FLATLINE: Vom Kapital vollständig reabsorbiert. Game Over.</span>`);
}
render();
}
function render() {
document.getElementById('stat-level').innerText = `ZONE: S-0${player.level}`;
document.getElementById('stat-hp').innerText = `INT: ${player.hp}/${player.maxHp}`;
document.getElementById('stat-ram').innerText = `RAM: ${player.ram}/${player.maxRam}`;
document.getElementById('stat-psyche').innerText = `HUM: ${player.humanity}%`;
document.getElementById('stat-food').innerText = `CAL: ${player.hunger}%`;
document.getElementById('stat-ammo').innerText = `AMMO: ${player.ammo}`;
document.getElementById('stat-atk').innerText = `GUN: ${player.atk}`;
document.getElementById('stat-cred').innerText = `¥${player.creds}`;
for (let i = 0; i < MAX_INV; i++) {
let slot = document.getElementById(`inv-${i}`);
let name = player.inventory[i] || 'Leer';
let icon = name === 'Sheng-Stim' ? '[生]' : (name === 'Synth-Baozi' ? '[包]' : (name === 'Mag-Rounds' ? '[⌖]' : ''));
slot.innerText = `[${i+1}] ${name} ${icon}`;
}
let htmlMap = '';
for (let y = 0; y < HEIGHT; y++) {
for (let x = 0; x < WIDTH; x++) {
let isVisible = visibleMap[y][x];
let isExplored = exploredMap[y][x];
if (!isExplored) {
htmlMap += '<div class="t fog-hidden"></div>';
continue;
}
if (x === player.x && y === player.y) {
let pClass = player.humanity <= 30 ? 'p-psychotic' : 'p-player';
let pChar = player.humanity <= 30 ? '魔' : '@';
htmlMap += `<div class="t ${pClass}">${pChar}</div>`;
} else if (x === exitGate.x && y === exitGate.y) {
htmlMap += '<div class="t i-exit">门</div>';
} else {
let enemy = enemies.find(e => e.x === x && e.y === y);
let item = items.find(i => i.x === x && i.y === y);
let vendor = vendors.find(v => v.x === x && v.y === y);
if (isVisible && enemy) {
let cssClass = enemy.type === 'triad' ? 'e-triad' : (enemy.type === 'ministry' ? 'e-ministry' : 'e-drone');
if (enemy.stunned > 0) cssClass += ' stunned';
htmlMap += `<div class="t ${cssClass}">${enemy.char}</div>`;
} else if (isVisible && vendor) {
htmlMap += `<div class="t i-vendor">${vendor.char}</div>`;
} else if (isVisible && item) {
htmlMap += `<div class="t i-item">${item.char}</div>`;
} else {
let char = map[y][x];
let cssClass = isVisible ? (char === '▓' ? 'tile-wall' : 'tile-floor') : 'fog-explored';
htmlMap += `<div class="t ${cssClass}">${char}</div>`;
}
}
}
}
document.getElementById('map').innerHTML = htmlMap;
}
initGame();
</script>
</body>
</html>