User File #33395645917665711

Upload All User Files

#33395645917665711 - (NES) Mega Man 5 RNG display v2 (FCEUX)

MM5_RNGv2_FCEUX.lua
1006 downloads
Uploaded 9/3/2016 11:09 PM by FatRatKnight (see all 245)
An off-by-one error fixed. I hope.
Also, I experimented with an item seeker code. It will indicate how long you'll have to wait for some item, with the frame count shown as the number, and the color that indicates the item. There is one more number, with an inverted white, that indicates how "out of sync" the two timers are.
I've also recolored the items. Red(s) and orange(L) are the energy drops. Blue(s) and cyan(L) are the weapon drops. White is the extra life.
It is difficult to work with an RNG predictor where there are three factors in its output: Frames passed, lag frames, and whether an RNG call was made recently. The more fancy I try to make it, the more I realize there are factors out of control I can't readily predict.
I can probably add controls to the item seeker so that you can specify a specific "out of syncness" of the timers, but that generally assumes you can get things to lag appropriately. I can try a lag tracker, a good pair up with TAS Editor, but now we assume things will lag exactly how they do when you change the timing of your shots. Then, of course, there are other factors that may use the RNG, and a call will adjust the RNG output for the next 16 frames.
I'm questioning the validity of that "second column" that I threw in there in v1, since it seems a different RNG call is made that adjusts it differently when an enemy needs to think or something. It's still here in v2, though.
--Renaming. I like the descriptive names, but I want something shorter.
--I'm also selecting functions I think I'll need.
--Turns out I needed to swap emulators. Yay, I just have one spot to change!
local R1u= memory.readbyte

--Don't like gui.text, so here's my number drawings.

--*****************************************************************************
Draw= {}   --Draw[button]( x , y , c )
--*****************************************************************************
--Coordinates is the top-left pixel of the 3x5 digit.
--Used for drawing compact, colored numbers.

local Px,Li= gui.pixel, gui.line

Draw[0]= function(x,y,c) -- ###
    Li(x  ,y  ,x  ,y+4,c)-- # #
    Li(x+2,y  ,x+2,y+4,c)-- # #
    Px(x+1,y  ,c)        -- # #
    Px(x+1,y+4,c)        -- ###
end

Draw[1]= function(x,y,c) --  #
    Li(x  ,y+4,x+2,y+4,c)-- ##
    Li(x+1,y  ,x+1,y+3,c)--  #
    Px(x  ,y+1,c)        --  #
end                      -- ###

Draw[2]= function(x,y,c) -- ###
    Li(x  ,y  ,x+2,y  ,c)--   #
    Li(x  ,y+3,x+2,y+1,c)-- ###
    Li(x  ,y+4,x+2,y+4,c)-- #
    Px(x  ,y+2,c)        -- ###
    Px(x+2,y+2,c)
end

Draw[3]= function(x,y,c) -- ###
    Li(x  ,y  ,x+1,y  ,c)--   #
    Li(x  ,y+2,x+1,y+2,c)-- ###
    Li(x  ,y+4,x+1,y+4,c)--   #
    Li(x+2,y  ,x+2,y+4,c)-- ###
end

Draw[4]= function(x,y,c) -- # #
    Li(x  ,y  ,x  ,y+2,c)-- # #
    Li(x+2,y  ,x+2,y+4,c)-- ###
    Px(x+1,y+2,c)        --   #
end                      --   #

Draw[5]= function(x,y,c) -- ###
    Li(x  ,y  ,x+2,y  ,c)-- #
    Li(x  ,y+1,x+2,y+3,c)-- ###
    Li(x  ,y+4,x+2,y+4,c)--   #
    Px(x  ,y+2,c)        -- ###
    Px(x+2,y+2,c)
end

Draw[6]= function(x,y,c) -- ###
    Li(x  ,y  ,x+2,y  ,c)-- #
    Li(x  ,y+1,x  ,y+4,c)-- ###
    Li(x+2,y+2,x+2,y+4,c)-- # #
    Px(x+1,y+2,c)        -- ###
    Px(x+1,y+4,c)
end
                         -- ###
Draw[7]= function(x,y,c) --   #
    Li(x  ,y  ,x+1,y  ,c)--  ##
    Li(x+2,y  ,x+1,y+4,c)--  #
end                      --  #

Draw[8]= function(x,y,c) -- ###
    Li(x  ,y  ,x  ,y+4,c)-- # #
    Li(x+2,y  ,x+2,y+4,c)-- ###
    Px(x+1,y  ,c)        -- # #
    Px(x+1,y+2,c)        -- ###
    Px(x+1,y+4,c)
end

Draw[9]= function(x,y,c) -- ###
    Li(x  ,y  ,x  ,y+2,c)-- # #
    Li(x+2,y  ,x+2,y+3,c)-- ###
    Li(x  ,y+4,x+2,y+4,c)--   #
    Px(x+1,y  ,c)        -- ###
    Px(x+1,y+2,c)
end

Draw[10]=function(x,y,c) --  #
    Li(x  ,y+1,x  ,y+4,c)-- # #
    Li(x+2,y+1,x+2,y+4,c)-- # #
    Px(x+1,y  ,c)        -- ###
    Px(x+1,y+3,c)        -- # #
end

Draw[11]=function(x,y,c) -- ##
    Li(x  ,y  ,x  ,y+4,c)-- # #
    Li(x+1,y  ,x+2,y+1,c)-- ##
    Li(x+1,y+4,x+2,y+3,c)-- # #
    Px(x+1,y+2,c)        -- ##
end

Draw[12]=function(x,y,c) --  #
    Li(x  ,y+1,x  ,y+3,c)-- # #
    Li(x+1,y  ,x+2,y+1,c)-- #
    Li(x+1,y+4,x+2,y+3,c)-- # #
end                      --  #

Draw[13]=function(x,y,c) -- ##
    Li(x  ,y  ,x  ,y+4,c)-- # #
    Li(x+2,y+1,x+2,y+3,c)-- # #
    Px(x+1,y  ,c)        -- # #
    Px(x+1,y+4,c)        -- ##
end

Draw[14]=function(x,y,c) -- ###
    Li(x  ,y  ,x  ,y+4,c)-- #
    Li(x+1,y  ,x+2,y  ,c)-- ##
    Li(x+1,y+4,x+2,y+4,c)-- #
    Px(x+1,y+2,c)        -- ###
end

Draw[15]=function(x,y,c) -- ###
    Li(x  ,y  ,x  ,y+4,c)-- #
    Li(x+1,y  ,x+2,y  ,c)-- ##
    Px(x+1,y+2,c)        -- #
end                      -- #

--*****************************************************************************
local function __DN_AnyBase(right, y, Number, c, bkgnd, div)
--*****************************************************************************
-- Works with any base from 2 to 16. Paints the input number.
-- Returns the x position where it would paint another digit.
-- It only works with integers. Rounds fractions toward zero.

    if div < 2 then return end  -- Prevents the function from never returning.

    local Digit= {}
    local Negative= false
    if Number < 0 then
        Number= -Number
        Negative= true
    end

    Number= math.floor(Number)
    c= c or "white"
    bkgnd= bkgnd or "clear"

    local i= 0
    if Number < 1 then
        Digit[1]= 0
        i= 1
    end

    while (Number >= 1) do
        i= i+1
        Digit[i]= Number % div
        Number= math.floor(Number/div)
    end

    if Negative then  i= i+1  end
    local x= right - i*4
    gui.box(x+1, y-1, right+1, y+5,bkgnd,bkgnd)

    i= 1
    while Draw[Digit[i]] do
        Draw[Digit[i]](right-2,y,c)
        right= right-4
        i=i+1
    end

    if Negative then
        gui.line(right, y+2,right-2,y+2,c)
        right= right-4
    end
    return right
end


--*****************************************************************************
local function DrawNum(right, y, Number, c, bkgnd)
--*****************************************************************************
-- Paints the input number as right-aligned. Decimal version.

    return __DN_AnyBase(right, y, Number, c, bkgnd, 10)
end

--*****************************************************************************
local function DrawNumx(right, y, Number, c, bkgnd)
--*****************************************************************************
-- Paints the input number as right-aligned. Hexadecimal version.

    return __DN_AnyBase(right, y, Number, c, bkgnd, 16)
end


--#############################################################################
--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 RollAndTimer(T)
--*****************************************************************************
--Rolls the RNG. Will also update timers, too.

    Roll(T)
    T.TA= (T.TA+1)%256
    T.TL= (T.TL+1)%256
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
        9
        + 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 BigNum= 0xFFFFFFFF + 1  --The 80red glitch handler

local BigW= 0x00FFFFFF           --Big weapon recharge
local Life= 0xFFFFFFFF - BigNum  --Extra life
local SmlE= 0xFF0000FF - BigNum  --Small energy recharge
local BigE= 0xFF8000FF - BigNum  --Big energy recharge
local SmlW= 0x0080FFFF           --Small weapon recharge
local NoItem=0x808080FF - BigNum --nil

local ItemThresholds= {
[0]=BigW,BigW,
    Life,
    SmlE,SmlE,SmlE,SmlE,SmlE,
    BigE,BigE,
    SmlW,SmlW,SmlW,smlW
}

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


--#############################################################################
--GUI

local ST= {} --Scratch Table. Optimizing those table constructors.
local DropsTable= {}
local X_ShowDrops= 220
--*****************************************************************************
local function ShowDrops()
--*****************************************************************************
--The display half of the drop seeker.
--Dividing the two halves have their 

    local Y= 3
    for i= 1, #DropsTable do
        local Y= 6*i + 3
        local entry= DropsTable[i]
        local Bclr= "black"
--        if entry.frames <= 16 then Bclr= "grey" end
        DrawNum(X_ShowDrops,Y,entry.frames,entry.clr,Bclr)
    end

    LoadRNG(ST)
    DrawNum(X_ShowDrops,#DropsTable*6 + 14,(ST.TA - ST.TL)%256,"black","white")
end

local RNG= {{},{}}  --For sake of calling the table constructor just once
--*****************************************************************************
local function RNG_HUD()
--*****************************************************************************
--To display some basic RNG information.

    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

--    DrawNum(15,9,(RNG[1].TA-RNG[1].TL)%256,"black","white")

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

            DrawNum(252 - 14*(r-1),6*i+3,v,clr,"black")

            RollAndTimer(RNG[r])
        end
    end
end

--*****************************************************************************
local function BasicHUD()
--*****************************************************************************
    RNG_HUD()
    ShowDrops()
end
gui.register(BasicHUD)


--#############################################################################
--Base

local SeekRNG= {}
--*****************************************************************************
local function SeekDrops(count)
--*****************************************************************************
--The processing half of the drop seeker.
--It assumes zero lag and you don't trigger RNG calls.
--In practice, I'm not sure how helpful this would be. I'm getting the
--impression that a whole lot of things need the RNG, and even with the 16
--frame window, you're bound to crash into a few of these unexpected changes.

--Frames, lag, and RNG calls are the three factors in what a new number is.
--Very much less "clean" and more unpredictable than a single factor, which is
--what I'm more used to.

    LoadRNG(SeekRNG)
    local Wait= 0
    for i= 1, count do
        local Item= DropsTable[i] or {}

        local v
        repeat
            v= CalculateRNG(SeekRNG)
            v= ItemThresholds[v%50]

            RollAndTimer(SeekRNG)   --Advance RNG
            Wait= Wait+1
        until v
        Item.frames= Wait
        Item.clr= v

        DropsTable[i]= Item
    end
end

--*****************************************************************************
local function FrmAdv()
--*****************************************************************************
    SeekDrops(20)
end
emu.registerafter(FrmAdv)

--*****************************************************************************
--while true do
--*****************************************************************************
--    SeekDrops(20)
--    emu.frameadvance()
--end