274 lines
9.4 KiB
Python
274 lines
9.4 KiB
Python
# -*- coding: utf-8 -*-
|
|
import math
|
|
import struct
|
|
from PIL import Image
|
|
|
|
SCREEN_W = 640
|
|
SCREEN_H = 480
|
|
HALF_W = 320
|
|
HALF_H = 240
|
|
from pathlib import Path
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
|
|
FP_1 = 65536
|
|
FP_HALF = 32768
|
|
MAP_WIDTH = 32
|
|
MAP_HEIGHT = 32
|
|
|
|
# Load map
|
|
with open(REPO_ROOT / 'casino3d_data.inc', 'r', encoding='latin1') as f:
|
|
text = f.read()
|
|
|
|
import re
|
|
lines = text.splitlines()
|
|
map_lines = []
|
|
capturing = False
|
|
for l in lines:
|
|
if 'casinoMap db' in l:
|
|
capturing = True
|
|
if capturing:
|
|
map_lines.append(l)
|
|
if len(map_lines) == 32:
|
|
break
|
|
|
|
casinoMap = []
|
|
for l in map_lines:
|
|
vals = [int(x.strip()) for x in re.findall(r'\d+', l)]
|
|
casinoMap.extend(vals)
|
|
|
|
# Load textures
|
|
tex_raw = open(REPO_ROOT / 'assets' / 'textures' / 'casino3d-hd.bin', 'rb').read()
|
|
slot_size = 128 * 128 * 4
|
|
|
|
def get_tex_pixel(slot, tx, ty):
|
|
idx = slot * slot_size + (ty * 128 + tx) * 4
|
|
b, g, r, a = tex_raw[idx:idx+4]
|
|
return (r, g, b, a)
|
|
|
|
# Angle table (512)
|
|
tableCos = [round(math.cos(i * 2 * math.pi / 512) * FP_1) for i in range(512)]
|
|
tableSin = [round(math.sin(i * 2 * math.pi / 512) * FP_1) for i in range(512)]
|
|
|
|
# Player State
|
|
playerX = 16 * FP_1 + FP_HALF
|
|
playerY = 16 * FP_1 + FP_HALF
|
|
playerAngle = 384 # Facing North
|
|
|
|
playerDirX = tableCos[playerAngle]
|
|
playerDirY = tableSin[playerAngle]
|
|
playerPlaneX = -66 * playerDirY // 100
|
|
playerPlaneY = 66 * playerDirX // 100
|
|
|
|
screen_pixels = [(20, 20, 25)] * (SCREEN_W * SCREEN_H)
|
|
zBuffer = [0x7FFFFFFF] * SCREEN_W
|
|
|
|
# 1. Ceiling and Floor
|
|
for y in range(HALF_H, SCREEN_H):
|
|
floorRow = y
|
|
pDist = (HALF_H * FP_1) // (y - HALF_H + 1)
|
|
shade = max(35, min(255, 255 - (pDist >> 13)))
|
|
cShade = max(20, shade - 25)
|
|
|
|
fStepX = (playerPlaneX * 2 * pDist // FP_1) // SCREEN_W
|
|
fStepY = (playerPlaneY * 2 * pDist // FP_1) // SCREEN_W
|
|
|
|
fX = playerX + ((playerDirX - playerPlaneX) * pDist // FP_1)
|
|
fY = playerY + ((playerDirY - playerPlaneY) * pDist // FP_1)
|
|
|
|
for x in range(SCREEN_W):
|
|
# Floor
|
|
tx = (fX >> 9) & 127
|
|
ty = (fY >> 9) & 127
|
|
r, g, b, _ = get_tex_pixel(0, tx, ty)
|
|
r = (r * shade) >> 8
|
|
g = (g * shade) >> 8
|
|
b = (b * shade) >> 8
|
|
screen_pixels[floorRow * SCREEN_W + x] = (r, g, b)
|
|
|
|
# Ceiling
|
|
cx = (fX >> 10) & 127
|
|
cy = (fY >> 10) & 127
|
|
cr, cg, cb, _ = get_tex_pixel(5, cx, cy)
|
|
cr = (cr * cShade) >> 8
|
|
cg = (cg * cShade) >> 8
|
|
cb = (cb * cShade) >> 8
|
|
screen_pixels[(SCREEN_H - 1 - floorRow) * SCREEN_W + x] = (cr, cg, cb)
|
|
|
|
fX += fStepX
|
|
fY += fStepY
|
|
|
|
# 2. Walls
|
|
for col in range(SCREEN_W):
|
|
camX = (2 * col - SCREEN_W) * FP_1 // SCREEN_W
|
|
rayDirX = playerDirX + (playerPlaneX * camX // FP_1)
|
|
rayDirY = playerDirY + (playerPlaneY * camX // FP_1)
|
|
|
|
mapX = playerX >> 16
|
|
mapY = playerY >> 16
|
|
|
|
deltaDistX = (1 << 32) // abs(rayDirX) if rayDirX != 0 else 0x7FFFFFFF
|
|
deltaDistY = (1 << 32) // abs(rayDirY) if rayDirY != 0 else 0x7FFFFFFF
|
|
|
|
if rayDirX < 0:
|
|
stepX = -1
|
|
sideDistX = ((playerX - (mapX << 16)) * deltaDistX) >> 16
|
|
else:
|
|
stepX = 1
|
|
sideDistX = (((mapX + 1 << 16) - playerX) * deltaDistX) >> 16
|
|
|
|
if rayDirY < 0:
|
|
stepY = -1
|
|
sideDistY = ((playerY - (mapY << 16)) * deltaDistY) >> 16
|
|
else:
|
|
stepY = 1
|
|
sideDistY = (((mapY + 1 << 16) - playerY) * deltaDistY) >> 16
|
|
|
|
side = 0
|
|
hit = 0
|
|
steps = 0
|
|
while hit == 0 and steps < 64:
|
|
steps += 1
|
|
if sideDistX < sideDistY:
|
|
sideDistX += deltaDistX
|
|
mapX += stepX
|
|
side = 0
|
|
else:
|
|
sideDistY += deltaDistY
|
|
mapY += stepY
|
|
side = 1
|
|
|
|
if 0 <= mapX < MAP_WIDTH and 0 <= mapY < MAP_HEIGHT:
|
|
tile = casinoMap[mapY * 32 + mapX]
|
|
if tile != 0:
|
|
hit = tile
|
|
else:
|
|
hit = 1
|
|
|
|
if side == 0:
|
|
perpWallDist = sideDistX - deltaDistX
|
|
else:
|
|
perpWallDist = sideDistY - deltaDistY
|
|
|
|
perpWallDist = max(2048, perpWallDist)
|
|
zBuffer[col] = perpWallDist
|
|
|
|
lineH = (SCREEN_H * FP_1) // perpWallDist
|
|
drawStart = max(0, HALF_H - lineH // 2)
|
|
drawEnd = min(SCREEN_H - 1, HALF_H + lineH // 2)
|
|
|
|
if side == 0:
|
|
wallHit = playerY + ((perpWallDist * rayDirY) >> 16)
|
|
else:
|
|
wallHit = playerX + ((perpWallDist * rayDirX) >> 16)
|
|
|
|
texX = ((wallHit & 0xFFFF) * 128) >> 16
|
|
if (side == 0 and rayDirX > 0) or (side == 1 and rayDirY < 0):
|
|
texX = 127 - texX
|
|
|
|
shade = max(40, min(255, 255 - (perpWallDist >> 12)))
|
|
if side == 1:
|
|
shade = (shade * 180) >> 8
|
|
|
|
texId = hit if hit != 0 else 1
|
|
|
|
for y in range(drawStart, drawEnd):
|
|
texY = (((y * 2 - SCREEN_H + lineH) * 128) // (lineH * 2)) & 127
|
|
r, g, b, _ = get_tex_pixel(texId, texX, texY)
|
|
r = (r * shade) >> 8
|
|
g = (g * shade) >> 8
|
|
b = (b * shade) >> 8
|
|
screen_pixels[y * SCREEN_W + col] = (r, g, b)
|
|
|
|
# 3. Sprites
|
|
sprites = [
|
|
{'name': 'VIP Dancer 1', 'x': 13 * FP_1 + FP_HALF, 'y': 4 * FP_1, 'tex': 10},
|
|
{'name': 'VIP Dancer 2', 'x': 16 * FP_1 + FP_HALF, 'y': 3 * FP_1 + FP_HALF, 'tex': 10},
|
|
{'name': 'VIP Dancer 3', 'x': 19 * FP_1 + FP_HALF, 'y': 4 * FP_1, 'tex': 10},
|
|
{'name': 'Blackjack', 'x': 12 * FP_1 + FP_HALF, 'y': 9 * FP_1, 'tex': 31},
|
|
{'name': 'Poker', 'x': 19 * FP_1 + FP_HALF, 'y': 9 * FP_1, 'tex': 35},
|
|
{'name': 'Casino Roulette', 'x': 16 * FP_1 + FP_HALF, 'y': 9 * FP_1, 'tex': 32},
|
|
{'name': 'Classic Roulette', 'x': 16 * FP_1 + FP_HALF, 'y': 12 * FP_1, 'tex': 32},
|
|
{'name': 'Slots 777', 'x': 5 * FP_1, 'y': 7 * FP_1, 'tex': 24},
|
|
{'name': 'Slots CloverPit', 'x': 5 * FP_1, 'y': 10 * FP_1, 'tex': 24},
|
|
{'name': 'Classic Slot', 'x': 5 * FP_1, 'y': 13 * FP_1, 'tex': 24},
|
|
{'name': 'Umamusume', 'x': 5 * FP_1, 'y': 16 * FP_1, 'tex': 36},
|
|
{'name': 'DBD Skillcheck', 'x': 5 * FP_1, 'y': 19 * FP_1, 'tex': 36},
|
|
{'name': 'Cashier / ATM', 'x': 27 * FP_1, 'y': 7 * FP_1, 'tex': 33},
|
|
{'name': 'VIP Boutique', 'x': 27 * FP_1, 'y': 10 * FP_1, 'tex': 30},
|
|
{'name': 'Online Casino', 'x': 27 * FP_1, 'y': 13 * FP_1, 'tex': 36},
|
|
{'name': 'Achievements', 'x': 27 * FP_1, 'y': 16 * FP_1, 'tex': 36},
|
|
{'name': 'Stats Board', 'x': 27 * FP_1, 'y': 19 * FP_1, 'tex': 36},
|
|
{'name': 'Durak', 'x': 12 * FP_1, 'y': 23 * FP_1, 'tex': 37},
|
|
{'name': 'Buckshot Roulette', 'x': 16 * FP_1, 'y': 23 * FP_1, 'tex': 34},
|
|
{'name': 'Russian Roulette', 'x': 20 * FP_1, 'y': 23 * FP_1, 'tex': 34},
|
|
{'name': 'Craps / Dice', 'x': 12 * FP_1, 'y': 26 * FP_1, 'tex': 38},
|
|
{'name': 'Skulls & Cups', 'x': 16 * FP_1, 'y': 26 * FP_1, 'tex': 38},
|
|
{'name': 'Coin Flip', 'x': 20 * FP_1, 'y': 26 * FP_1, 'tex': 38},
|
|
{'name': 'Training', 'x': 10 * FP_1, 'y': 14 * FP_1, 'tex': 37},
|
|
{'name': 'Observer AI', 'x': 22 * FP_1, 'y': 14 * FP_1, 'tex': 37},
|
|
{'name': 'Daily Tasks', 'x': 27 * FP_1, 'y': 22 * FP_1, 'tex': 36},
|
|
{'name': 'Settings', 'x': 27 * FP_1, 'y': 25 * FP_1, 'tex': 36},
|
|
{'name': 'Hell Tower Gate', 'x': 16 * FP_1, 'y': 29 * FP_1, 'tex': 39},
|
|
]
|
|
|
|
# Calculate distances
|
|
for s in sprites:
|
|
dx = (s['x'] - playerX) >> 8
|
|
dy = (s['y'] - playerY) >> 8
|
|
s['distSq'] = dx * dx + dy * dy
|
|
|
|
# Sort far to near
|
|
sprites.sort(key=lambda s: s['distSq'], reverse=True)
|
|
|
|
det = ((playerPlaneX * playerDirY) >> 16) - ((playerPlaneY * playerDirX) >> 16)
|
|
if det == 0:
|
|
det = 1
|
|
|
|
for s in sprites:
|
|
relX = s['x'] - playerX
|
|
relY = s['y'] - playerY
|
|
|
|
# Standard Lodev inverse camera transform:
|
|
# transX = (dirY * relX - dirX * relY) / det
|
|
# transY = (-planeY * relX + planeX * relY) / det
|
|
numX = ((playerDirY * relX) >> 16) - ((playerDirX * relY) >> 16)
|
|
transX = (numX << 16) // det
|
|
|
|
numY = ((-playerPlaneY * relX) >> 16) + ((playerPlaneX * relY) >> 16)
|
|
transY = (numY << 16) // det
|
|
|
|
# Discard sprites behind camera
|
|
if transY <= 2048:
|
|
continue
|
|
|
|
sprScreenX = HALF_W + (transX * HALF_W) // transY
|
|
sprH = (SCREEN_H * FP_1) // transY
|
|
sprW = sprH
|
|
|
|
drawStartY = max(0, HALF_H - sprH // 2)
|
|
drawEndY = min(SCREEN_H - 1, HALF_H + sprH // 2)
|
|
drawStartX = max(0, sprScreenX - sprW // 2)
|
|
drawEndX = min(SCREEN_W - 1, sprScreenX + sprW // 2)
|
|
|
|
shade = max(40, min(255, 255 - (transY >> 12)))
|
|
|
|
for stripe in range(drawStartX, drawEndX):
|
|
if 0 <= stripe < SCREEN_W and transY < zBuffer[stripe]:
|
|
texX = ((stripe - (sprScreenX - sprW // 2)) * 128 // sprW) & 127
|
|
for y in range(drawStartY, drawEndY):
|
|
texY = ((y - (HALF_H - sprH // 2)) * 128 // sprH) & 127
|
|
r, g, b, _ = get_tex_pixel(s['tex'], texX, texY)
|
|
# Chroma key check: Magenta (0xFF00FF) or pure black (0,0,0)
|
|
if (r > 240 and g < 20 and b > 240) or (r == 0 and g == 0 and b == 0):
|
|
continue
|
|
r = (r * shade) >> 8
|
|
g = (g * shade) >> 8
|
|
b = (b * shade) >> 8
|
|
screen_pixels[y * SCREEN_W + stripe] = (r, g, b)
|
|
|
|
# Save image
|
|
img = Image.new('RGB', (SCREEN_W, SCREEN_H))
|
|
img.putdata(screen_pixels)
|
|
out_path = REPO_ROOT / 'docs' / 'render_view.png'
|
|
img.save(out_path)
|
|
print(f'{out_path} saved successfully!')
|