User File #33508220465369883

Upload All User Files

#33508220465369883 - (NES) Mega Man 5 RNG display v3 (FCEUX)

MM5_RNGv3_FCEUX.lua
1021 downloads
Uploaded 9/9/2016 12:49 AM by FatRatKnight (see all 245)
Added frame markers.
Largely the same as the previous version, otherwise.
--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")

    local frame= emu.framecount()
    for i= 1, 37 do
        local Y= 6*i+3

        local f= frame + i
        if (f%5) == 0 then
            local clr= "orange"
            if (f%10) == 0 then clr= "green" end
            if (f%100) == 0 then clr= "white" end
            gui.line(255,Y,255,Y+3,clr)
        end

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

            DrawNum(252 - 14*(r-1),Y,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