Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A browser-based open-world first-person survival shooter built on the VoxelSpace terrain rendering engine — the same technique used in NovaLogic's Comanche (1992). No NPCs. Pure player-vs-player survival.
Built with vanilla JavaScript, HTML5 Canvas, and Nakama for real-time multiplayer.README 전문 보기 ↗A browser-based open-world first-person survival shooter built on the VoxelSpace terrain rendering engine — the same technique used in NovaLogic's Comanche (1992). No NPCs. Pure player-vs-player survival.
Built with vanilla JavaScript, HTML5 Canvas, and Nakama for real-time multiplayer.
| Action | Keyboard / Mouse | Gamepad |
|---|---|---|
| Move | WASD | Left Stick |
| Look | Mouse | Right Stick |
| Shoot | LMB | RT |
| ADS Toggle | RMB | LT |
| Jump (charge) | Hold Space, release | Hold LB, release |
| Sprint | Shift | L3 or A |
| Crouch | C | B |
| Reload | R | X |
| Swap Weapon | Q | Y |
| Pickup Weapon | E | X (context) |
| Talk to NPC | F | — |
| Settings | ESC | — |
Three clans are available on first login. Membership is permanently secret — no name tag, no colour, nothing reveals which clan another player belongs to.
| Clan | Theme |
|---|---|
| Iron Ravens | Steel blue — disciplined, long range |
| Ember Tide | Deep orange — aggressive, close quarters |
| Silent Root | Dark green — patient, snipers and survivors |
Friendly fire results in an immediate kick. This behaviour may change in future versions.
Quests are data-driven. Each quest is a .js file in the quests/ folder that calls QuestManager.register(). No build step or server is required.
| Field | Type | Description |
|---|---|---|
id | string | Unique quest identifier |
npcId | string | Matches the ID used in QuestManager.setNpcPosition() |
npcName | string | Display name shown in the dialog box |
canReject | boolean | Whether the player can decline this quest |
keyItems | array | Items that must be returned to complete the quest |
dialog | object | Keyed dialog phases: greeting, accept, reject, active, readyToComplete, complete |
not_started → active → ready_to_complete → complete
The player can also reject a quest (if canReject: true), which resets to not_started on next conversation.
Key items can be delivered by the player, another player, an NPC, or a game system:
QuestManager.giveKeyItem("TestQuest", "green_fragment");
When all key items are received, the quest advances to ready_to_complete automatically and a HUD notification is shown.
Walk within 80 units of an NPC and face it within 15° — a prompt appears. Press F to open the dialog.
quests/YourQuest.js calling QuestManager.register({ ... })index.html after greenCube.jsQuestManager.setNpcPosition('yourNpcId', x, y) when spawning the NPCA visual quest editor is planned that will:
quests/*.js files automatically| Opcode | Direction | Purpose |
|---|---|---|
| 1 | Client → Server → Others | Position update (x, y, height, angle, health) |
| 2 | Client → Server → Nearby | Chat emoji (vicinity or shout) |
| 3 | Client → Server | Hit report (target + damage) |
| 4 | Server → Joining client | Player list snapshot |
| 5 | Server → All | Player joined |
| 6 | Server → All | Player left |
| 7 | Server → Client | You have been kicked |
| 8 | Server → Client | You took damage |
| 9 | Server → Nearby | Radar reveal (shout used) |
| Method | Persistence |
|---|---|
| Anonymous (Guest) | Session only — all data wiped on disconnect |
| Username + Password | Full persistence (stats, loot, clan) |
| Steam | Planned |
No build step required. Serve the project with any static HTTP server:
# Python
python3 -m http.server 8080
# Node.js
npx serve .
Open http://localhost:8080 in your browser.
The game requires a running Nakama server. Configure the server address in
src/network/nakamaClient.js(SERVER_HOST/SERVER_PORT).
cd ~/nakama
docker compose up -d
# Copy Lua modules to the Nakama data directory
cp /path/to/Ring_Combat-Revolved/nakama-modules/*.lua ~/nakama/data/modules/
# Restart Nakama to load the new modules
docker compose restart nakama
http://YOUR_SERVER_IP:7351
The client (HTML + JS) can be hosted on GitHub Pages. However:
Important: GitHub Pages enforces HTTPS. Browsers block WebSocket (
ws://) connections from HTTPS pages — you need secure WebSocket (wss://), which requires TLS on your Nakama server.
Options for enabling public play:
| Option | Notes |
|---|---|
| Cloudflare Tunnel | Free, no port forwarding needed, provides SSL automatically |
| Port forward + DDNS | Forward ports 7349–7351 to your VM, use a free DDNS domain |
| VPS | Run Nakama on a public VPS with Let's Encrypt for SSL |
For LAN play (same network), HTTP/WS works fine without SSL — just open the page from http://SERVER_IP:PORT.
Nakama is exposed publicly via a Cloudflare Quick Tunnel (trycloudflare.com). The tunnel runs as a systemd user service on the Nakama VM (192.168.1.222).
Limitation: Quick Tunnels generate a new random hostname on every restart. The
SERVER_HOSTinsrc/network/nakamaClient.jsmust match the active URL or the game cannot connect.
A helper script /home/heromachine/update-tunnel-url.sh runs automatically on every tunnel start (via ExecStartPost in the systemd service). It:
~/cloudflared.logSERVER_HOST in src/network/nakamaClient.jsAfter every server reboot or tunnel restart, the repo receives an automatic commit updating the URL within ~15 seconds. No manual action needed.
# SSH into the Nakama VM
ssh [email protected]
# Get the current tunnel URL
grep -o 'https://[a-z0-9-]*\.trycloudflare\.com' ~/cloudflared.log | tail -1
# Run the update script manually
~/update-tunnel-url.sh
# Check tunnel status
systemctl --user status cloudflared-tunnel
# Restart tunnel (triggers auto-update of SERVER_HOST)
systemctl --user restart cloudflared-tunnel
# View tunnel log
tail -f ~/cloudflared.log
Quick Tunnels change URL on every restart. For a stable public URL, set up a named Cloudflare Tunnel (requires a Cloudflare account with a domain):
cloudflared tunnel create nakama
cloudflared tunnel route dns nakama nakama.yourdomain.com
Update the service ExecStart to use the named tunnel config and set SERVER_HOST to your permanent subdomain. The auto-update script would no longer be needed.
Ring_Combat-Revolved/
├── README.md
├── index.html # Main game entry point
├── nakama-modules/ # Server-side Lua (deploy to Nakama)
│ ├── match_handler.lua # Open world match logic
│ └── find_or_create_match.lua # RPC rcr_find_or_create_match — join/create the world match
├── data/ # JSON config files
│ ├── settings.json
│ ├── gunModel.json
│ ├── weapons.json
│ └── display.json
├── quests/ # Quest data files (one per quest)
│ └── TestQuest.js # Demo quest — GreenBox NPC
├── src/
│ ├── core/ # Globals, config loader, polyfills
│ ├── systems/
│ │ └── questManager.js # Quest state machine + NPC dialog
│ ├── network/
│ │ ├── nakamaClient.js # Auth + socket wrapper
│ │ └── multiplayer.js # Position sync, chat, hit reporting
│ ├── rendering/
│ │ ├── remotePlayerRenderer.js # Render other players as rectangles
│ │ └── ...
│ ├── entities/ # Camera physics, items
│ ├── weapons/ # Gun mechanics and renderer
│ ├── ui/
│ │ ├── loginScreen.js # Auth overlay
│ │ ├── clanScreen.js # Clan selection overlay
│ │ └── ...
│ ├── input/ # Keyboard, mouse, gamepad, touch
│ ├── map/ # Terrain map loader
│ └── main.js # Game loop + multiplayer startup
├── maps/ # Terrain colour + altitude PNG maps
├── images/ # Sprites and textures
└── modules/ # Display, weapon, scope config modules
1 world unit (WU) ≈ 1 foot ≈ 30 cm
The player eye height is 7 WU (≈ 7 feet, eye level of a ~6'3" person). All distances in the codebase use this scale — weapon ranges, proximity chat, NPC interaction radius, etc.
globals.js sets the fallback default. At startup, loadSettings() in settingsManager.js reads data/settings.json and calls _applyPlayerHeightFromJson(), which forces the canonical values onto all globals. This runs for all users — including admin — after any saved settings are applied, so localStorage/Nakama-cached values can never restore a stale player height.
Values loaded from data/settings.json → player:
| Key | Value | Meaning |
|---|---|---|
playerHeight | 7 | Eye height above terrain (WU) |
normalHeight | 56 | Camera height when standing |
crouchHeight | 24 | Camera height when crouching |
jumpMin | 2 | Tap jump strength |
jumpMax | 7 | Full charge jump strength |
All systems that depend on player size reference playerHeightOffset proportionally:
| System | File | Formula |
|---|---|---|
| Remote player hit sphere radius | camera.js | playerHeightOffset * (25/70) |
| Remote player hit sphere center | camera.js | rp.height - playerHeightOffset * (10/70) |
| Remote player body rect (height/width) | remotePlayerRenderer.js | playerHeightOffset * (78/70) / (20/70) |
| Player sprite scale | itemRenderer.js | playerHeightOffset * (2/70) / (6.5/70) |
History: A previous session scaled
playerHeightOffsetfrom 7 → 70 to fix a gun-shooting-from-the-knee issue. This caused the player to be effectively 70 feet tall. The fix restored 7 WU as the canonical value and made all dependent systems proportional. Admin users were initially missed because their saved settings in localStorage/Nakama stored the old 70-unit value — fixed by always running_applyPlayerHeightFromJson()afterapplySettings().
Gun world offsets (barrel position sliders in the admin settings panel) were calibrated for the 70-unit scale and will need to be re-tuned against the 7-unit player. The correct values will be saved to
data/settings.jsononce tested.
All multiplayer screens support back-button navigation:
Back buttons are positioned top-left and use a _bound guard to prevent duplicate event listeners on repeated show() calls.
| Flag | Default (non-admin) | Meaning |
|---|---|---|
isAdmin | false | Set to true for the heromachine account only |
showGMD | false | Gun mechanics debug overlay |
showHitRanges | false | Hit sphere circles on remote players (3D + minimap) |
showMinimaps | false | Admin debug minimap (overhead + side view) |
testTarget.enabled | false | Floating test target |
When showHitRanges is on:
Original VoxelSpace code: MIT License (see LICENSE) Terrain maps: Reverse-engineered from Comanche — excluded from license Game additions: All rights reserved — Heromachine