User File #33370844597556344

Upload All User Files

#33370844597556344 - (NES) Mega Man 5 RNG canvas script (for BizHawk)

MM5_RNG.lua
987 downloads
Uploaded 9/2/2016 8:20 PM by FatRatKnight (see all 245)
Spits out a list of RNG values in the future. I don't know what items go to what, as I have not investigated that, but I did colorize based on which threshold is hit from what I interpret in the disassembly.
Two columns.
The one on the right is the list of RNG the game will generate in the future, assuming Timer_Lagged runs every frame. If there is a lag frame, the numbers will appear to increment while shifting up the column. I expose the numbers as this will be a way to understand whether lag can be used to shift in the right RNG value at the right time, by aiming to either remove some or add some. Whether you have that much control over lag is another issue, however.
The left column shows what happens if exactly one RNG call were to happen immediately. As can be demonstrated, it doesn't have any effect beyond 16 frames, so it's only good if you plan to destroy a pair of enemies close together, or you can somehow time other random actions at that moment. No real testing was done to ensure the quality of this column, however.
Someone wanted help with RNG stuff. So, here it is. Since I don't know what else I can do to help, this will be the only script I make for Mega Man 5, until someone suggests something reasonable.
--Set system bus. The full addresses are my preference.
--I desire information packed in the given address itself.
--Also, if pointers are used, they sometimes use the full bus address.
if not memory.usememorydomain("System Bus") then error("Someone renamed the domains again.") end

--Renaming. I like the descriptive names, but I want something shorter.
--I'm also selecting functions I think I'll need.
--Also has the advantage in case of renamed functions or different emulators.
local R1u= memory.read_u8


--#############################################################################
--Handle RNG

--Data structure expected:
--  RNG_Table
--    [1] 00E4  R0
--    [2] 00E5  R1
--    [3] 00E6  R2
--    [4] 00E7  R3
--    TA  0092  Timer_Always
--    TL  009D  Timer_Lagged

--These six values are what's required to know the output.
--If a rename is suggested, I'll probably listen.

--Because lag shifts the RNG, it's not precisely possible to know what the
--future RNG will hold, although knowing the underlying numbers may help to
--know what to expect as lag frames "shift" the RNG a little.

--In addition, whenever the RNG is called, R2 is modified using a different
--formula than the standard RNG shift, so if an enemy needs to "think", or an
--item is about to drop, the RNG can be "unpredictably" modified for the next
--16 frames. Since R2 is shifted out entirely after 16 frames, without any
--effect on the standard shifts, the change is only local to this call.
--It still happens more frequently than would be desired.



--*****************************************************************************
local function Roll(T)
--*****************************************************************************
--Gets the next RNG value. This is the "shift" calculation.
--In-place modification to the table. Make a copy first if you want the old.
--Will not modify timers, even though Timer_Always also updates with it.

    local v= bit.band(0x02,bit.bxor(T[1],T[2])) / 2
    for i= 1, 4 do
        T[i]= T[i]+256*v
        v= T[i]%2
        T[i]= math.floor(T[i]/2)
    end
end

--*****************************************************************************
local function LoadRNG(T)
--*****************************************************************************
--Reads the RNG values from memory.
--You can feed a table (it'll modify) or let it construct one itself.
--Returns the initialized table.

    T= T or {}

    T[1]= R1u(0x00E4)  --R0
    T[2]= R1u(0x00E5)  --R1
    T[3]= R1u(0x00E6)  --R2
    T[4]= R1u(0x00E7)  --R3
    T.TA= R1u(0x0092)  --Timer_Always
    T.TL= R1u(0x009D)  --Timer_Lagged

    return T
end

--*****************************************************************************
local function CalculateRNG(RNG)
--*****************************************************************************
--Gets the calculated RNG value. This is the "call" calculation.
--Returns three values: The RNG output, and the new R2.
--  Number: RNG output (value used for whatever RNG stuff is needed)
--  Number: What R2 is replaced with after the call is done
--This function will not do in-place modification. That's up to the caller.
--Take note that R2 and R3 both are modified. R3 takes the RNG output.

--[[
Carry is always set upon entry (add +1 to what you see)

0E:AE64 LDA $00E6    R2
0E:AE66 ADC $00E7    R3
0E:AE68 ADC $0000    #$59 (used for function call)
0E:AE6A SBC $009D    Frame timer (affected by lag)
0E:AE6C STA $00E6    Modify R2 right away
0E:AE6E ADC $0001    #$AE (used for function call)
0E:AE70 SBC $00E5    R1
0E:AE72 ADC $0092    Frame timer (always runs)
0E:AE74 STA $00E7    R3
]]--

    local RNG_Output= (( --Also New_R3
        8
        + RNG[3] --R2
        + RNG[4] --R3
        - RNG.TL --Timer_Lagged
        - RNG[2] --R1
      )%255 + 1
      + RNG.TA   --Timer_Always
    )%256

    local New_R2= ((
        0x59
        + RNG[3] --R2
        + RNG[4] --R3
      )%255 + 1
      + (255 - RNG.TL)
    )%256

    return RNG_Output, New_R2
end

local Item1= 0xFFFFFFFF
local Item2= 0xFF00FFFF
local Item3= 0xFF0080FF
local Item4= 0xFFFFFF00
local Item5= 0xFFFF8000
local NoItem=0xFF808080

local ItemThresholds= {
[0]=Item1,Item1,
    Item2,
    Item3,Item3,Item3,Item3,Item3,
    Item4,Item4,
    Item5,Item5,Item5,Item5
}

--*****************************************************************************
local function ItemColor(v)
--*****************************************************************************
    return ItemThresholds[v%50] or NoItem
end

--#############################################################################
--Canvas

--Sure feels slow, somehow. The extra space is still appreciated, however.
--Mostly just throwing something together and seeing what happens.
--The RNG appears to be predicted appropriately, if it's any consolation.

local RNG_Count= 40

local canvas = gui.createcanvas(200, RNG_Count*11+1);

local RNG= {{},{}}  --For sake of calling the table constructor just once
--*****************************************************************************
local function HandleCanvas()
--*****************************************************************************
--Yay, canvas!
--We also do RNG calculations here.

    canvas.Clear(0xFF000000);
    LoadRNG(RNG[1]) --If you never call the RNG, this is what you get
    LoadRNG(RNG[2]) --If called just once at this moment, what you get
    RNG[2][4],RNG[2][3]= CalculateRNG(RNG[2]) --To show effects of RNG call

    for i= 0, RNG_Count-1 do
        for r= 1, 2 do
            local v= CalculateRNG(RNG[r])
            local clr= ItemColor(v)

            canvas.DrawText(100 - 50*r,i*11,string.format("%3d",
                v
            ),clr)
            Roll(RNG[r])
            RNG[r].TL= (RNG[r].TL+1)%256
            RNG[r].TA= (RNG[r].TA+1)%256
        end
    end

    canvas.Refresh();
end


--#############################################################################
--Management

-- while true do emu.frameadvance() end ; The generic loop we're used to.
--Not much to do except call one other function, really.

--*****************************************************************************
while true do
--*****************************************************************************
    HandleCanvas()
    emu.frameadvance()
end