Last active
February 11, 2023 20:05
-
-
Save taotao54321/129ee7cfc70805af9a27a2c582f52b16 to your computer and use it in GitHub Desktop.
NES Vice Project: Doom HUD script for BizHawk
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ | |
NES Vice Project Doom - HUD script for BizHawk | |
全スキャンライン表示かつウィンドウサイズ 2 倍設定を仮定している。 | |
--]] | |
-- 現在のセグメントの開始フレームを返す。 | |
-- | |
-- TAStudio 使用時は、直前のマーカーがあればそのフレームを、なければ 0 を返す。 | |
-- TAStudio を使っていない場合は単に 0 を返す。 | |
local function segment_start_frame() | |
if tastudio.engaged() then | |
for f = emu.framecount(), 0, -1 do | |
if tastudio.getmarker(f) ~= "" then | |
return f | |
end | |
end | |
end | |
return 0 | |
end | |
-- 符号ビット+絶対値のバイトを整数に変換する。 | |
local function signmag8(b) | |
local value = bit.band(b, 0x7F) | |
if bit.band(b, 0x80) ~= 0 then | |
value = -value | |
end | |
return value | |
end | |
-- ROM 内の hitbox テーブルを得る。 | |
local function get_hitbox_table() | |
local ADDR_BASE = 7 * 0x4000 + 0x4C0 | |
memory.usememorydomain("PRG ROM") | |
local table = {} | |
for i = 0, 38-1 do | |
local addr = ADDR_BASE + 4 * i | |
local dx = memory.read_s8(addr + 0) | |
local dy = memory.read_s8(addr + 1) | |
local w = memory.read_u8(addr + 2) | |
local h = memory.read_u8(addr + 3) | |
table[i] = { | |
dx = dx, | |
dy = dy, | |
w = w, | |
h = h, | |
} | |
end | |
return table | |
end | |
local HITBOX_TABLE = get_hitbox_table() | |
-- NMI カウンタを得る。 | |
local function get_nmi_counter() | |
return mainmemory.read_u8(0x0F) | |
end | |
-- ゲーム内フレームカウンタを得る。 | |
local function get_game_frame_counter() | |
return mainmemory.read_u8(0x11) | |
end | |
-- 乱数インデックスを得る。 | |
local function get_rand_idx() | |
return mainmemory.read_u8(0x94) | |
end | |
-- 画面遷移に関する値を得る。 | |
-- bit0-6:画面遷移タイマー, bit7:未解析 | |
local function get_transition() | |
return mainmemory.read_u8(0x036B) | |
end | |
-- カメラの状態を得る。 | |
local function get_camera() | |
local x = mainmemory.read_u16_le(0x0B) | |
local y = mainmemory.read_u8(0x0D) | |
return { | |
x = x, | |
y = y, | |
} | |
end | |
-- 現在のステージが車ステージかどうかを返す。 | |
local function is_car_stage() | |
return mainmemory.read_u8(0x90) == 10 | |
end | |
-- インデックス i のアクターの状態を得る。 | |
local function get_actor(i) | |
local status = mainmemory.read_u8(0x0160 + i) | |
local ay = signmag8(mainmemory.read_u8(0x0190 + i)) | |
local vy = signmag8(mainmemory.read_u8(0x01A0 + i)) | |
local sub_y = mainmemory.read_u8(0x01B0 + i) | |
local ax = signmag8(mainmemory.read_u8(0x01C0 + i)) | |
local vx = signmag8(mainmemory.read_u8(0x01D0 + i)) | |
local sub_x = mainmemory.read_u8(0x01E0 + i) | |
local x = 256 * mainmemory.read_u8(0x0200 + i) + mainmemory.read_u8(0x01F0 + i) | |
local y = 256 * mainmemory.read_u8(0x0220 + i) + mainmemory.read_u8(0x0210 + i) | |
local health = mainmemory.read_u8(0x0280 + i) | |
local hitbox_id = mainmemory.read_u8(0x02A0 + i) | |
local value_02B0 = mainmemory.read_u8(0x02B0 + i) | |
local is_alive = bit.band(status, 0x80) ~= 0 | |
local invin_timer = bit.band(value_02B0, 0x7F) | |
return { | |
status = status, | |
is_alive = is_alive, | |
x = x, | |
sub_x = sub_x, | |
y = y, | |
sub_y = sub_y, | |
vx = vx, | |
vy = vy, | |
ax = ax, | |
ay = ay, | |
health = health, | |
invin_timer = invin_timer, | |
hitbox_id = hitbox_id, | |
} | |
end | |
-- 全てのアクターの状態を得る。 | |
local function get_actors() | |
local actors = {} | |
for i = 0, 16-1 do | |
actors[i] = get_actor(i) | |
end | |
return actors | |
end | |
-- 自機の状態を得る。 | |
local function get_hero() | |
local hero = get_actor(0) | |
hero.is_jumping = bit.band(hero.status, 0x01) ~= 0 | |
hero.is_grabbing_ladder = bit.band(hero.status, 0x02) ~= 0 | |
hero.is_ducking = bit.band(hero.status, 0x04) ~= 0 | |
hero.is_dying = bit.band(hero.status, 0x08) ~= 0 | |
hero.is_knockback = bit.band(hero.status, 0x20) ~= 0 | |
return hero | |
end | |
-- (ピクセル, サブピクセル) を実数ピクセルに変換する。 | |
local function px_real(px, subpx) | |
return px + subpx / 16.0 | |
end | |
-- テキストを描画する。 | |
local function draw_text(x, y, text) | |
local COLOR_FG = 0xFF00C000 | |
gui.text(x, y, text, COLOR_FG) | |
end | |
-- アクターインデックスに対応する hitbox の線の色を返す。 | |
local function hitbox_color(idx) | |
if idx == 0 then | |
return 0xFF00FFFF | |
elseif 1 <= idx and idx <= 3 then | |
return 0xFFFFFF00 | |
else | |
return 0xFFFFFFFF | |
end | |
end | |
-- hitbox を描画する。アクターインデックスにより色を変える。 | |
local function draw_hitbox(idx, x, y, w, h) | |
gui.drawRectangle(x, y, w, h, hitbox_color(idx)) | |
end | |
-- 描画を行う。各フレーム終了時に呼ばれる。 | |
local function draw() | |
local nmi_counter = get_nmi_counter() | |
local game_frame_counter = get_game_frame_counter() | |
local transition = get_transition() | |
local rand_idx = get_rand_idx() | |
local camera = get_camera() | |
local hero = get_hero() | |
local actors = get_actors() | |
-- ドロー系の呼び出しが一切行われないケースで画面にゴミが残るのを防ぐための措置。 | |
-- 今のところ他のスクリプトと併用することは考えていないので問題ないだろう。 | |
gui.clearGraphics() | |
-- 現在のセグメント内フレームを描画。 | |
-- XXX: これ BizHawk だと重いので却下。 | |
--[[ | |
do | |
local segment_frame = emu.framecount() - segment_start_frame() | |
draw_text(544, 0, string.format("%4d", segment_frame)) | |
end | |
--]] | |
-- カメラ座標を描画。 | |
draw_text(0, 466, string.format("CAM:%d %d", camera.x, camera.y)) | |
-- NMI カウンタ、ゲーム内フレームカウンタ、乱数インデックス、画面遷移に関する値を描画。 | |
draw_text(264, 466, string.format("NMI:%d", nmi_counter)) | |
draw_text(344, 466, string.format("FRM:%d", game_frame_counter)) | |
draw_text(424, 466, string.format("RNG:%d", rand_idx)) | |
draw_text(504, 466, string.format("TRN:0x%02X", transition)) | |
-- 自機の各種フラグを描画。 | |
do | |
local strs = {} | |
if hero.is_jumping then | |
table.insert(strs, "AIR") | |
end | |
if hero.is_grabbing_ladder then | |
table.insert(strs, "LAD") | |
end | |
if hero.is_ducking then | |
table.insert(strs, "DUK") | |
end | |
if hero.is_knockback then | |
table.insert(strs, "KNK") | |
end | |
if hero.is_dying then | |
table.insert(strs, "DIE") | |
end | |
draw_text(0, 494, table.concat(strs, " ")) | |
end | |
-- 自機の位置、速度、加速度を描画。 | |
draw_text(0, 522, string.format( | |
"P:%7.2f %7.2f V:%5.2f %5.2f A:%5.2f %5.2f", | |
px_real(hero.x, hero.sub_x), | |
px_real(hero.y, hero.sub_y), | |
px_real(0, hero.vx), | |
px_real(0, hero.vy), | |
px_real(0, hero.ax), | |
px_real(0, hero.ay) | |
)) | |
-- 自機以外の位置、速度、加速度、HP, 無敵時間を描画。 | |
for i = 1, 16-1 do | |
local actor = actors[i] | |
if actor.is_alive then | |
local y = 550 + 14 * (i - 1) | |
draw_text(0, y, string.format( | |
"[%2d] P:%7.2f %7.2f V:%5.2f %5.2f A:%5.2f %5.2f H:%d%s", | |
i, | |
px_real(actor.x, actor.sub_x), | |
px_real(actor.y, actor.sub_y), | |
px_real(0, actor.vx), | |
px_real(0, actor.vy), | |
px_real(0, actor.ax), | |
px_real(0, actor.ay), | |
actor.health, | |
actor.invin_timer == 0 and "" or string.format("(%d)", actor.invin_timer) | |
)) | |
end | |
end | |
-- hitbox を描画。 | |
for i = 0, 16-1 do | |
local actor = actors[i] | |
-- HITBOX_TABLE に対する配列外参照が起こらないかチェックする。 | |
-- ゲーム起動時など、hitbox_id の値が正しくないケースがありうるので。 | |
if actor.is_alive and actor.hitbox_id < #HITBOX_TABLE then | |
local hitbox = HITBOX_TABLE[actor.hitbox_id] | |
-- 車ステージ以外ではカメラ座標による補正を行う。 | |
local x = actor.x + hitbox.dx | |
local y = actor.y + hitbox.dy | |
if not is_car_stage() then | |
x = x - camera.x | |
y = y - camera.y | |
end | |
local w = hitbox.w | |
local h = hitbox.h | |
draw_hitbox(i, x, y, w, h) | |
end | |
end | |
end | |
-- フォームを作る。 | |
-- | |
-- 現状、セグメント内フレーム計算専用 (毎回描画すると重くなるのでこれで妥協)。 | |
local function form_new() | |
local form = forms.newform(320, 320, "ViceProjectDoom HUD") | |
local label = forms.label(form, "", 0, 0, 200, 32) | |
forms.button(form, "seg frame", function () | |
local global_frame = emu.framecount() | |
local segment_frame = global_frame - segment_start_frame() | |
forms.settext(label, string.format("frame %d = segment frame %d", global_frame, segment_frame)) | |
end, 0, 32) | |
end | |
local function main() | |
client.SetClientExtraPadding(0, 0, 0, 320) | |
event.onframeend(draw) | |
form_new() | |
end | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment