Skip to content

Instantly share code, notes, and snippets.

@taotao54321
Last active February 11, 2023 20:05
Show Gist options
  • Save taotao54321/129ee7cfc70805af9a27a2c582f52b16 to your computer and use it in GitHub Desktop.
Save taotao54321/129ee7cfc70805af9a27a2c582f52b16 to your computer and use it in GitHub Desktop.
NES Vice Project: Doom HUD script for BizHawk
--[[
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