TPT Script Server

This page is used for previewing and submitting scripts for use with the Script Manager

Available Scripts

(2) TPTMulti by LBPHacker
(3) set wifi v2 by jacob1
(4) Script Paste by jacob1
(5) Random Element by jacob1
(6) Magical Merge Master 3000 by nucular
(7) More Fuel Mod-heavy by jward212
(9) Breakpoints (BRPT) by boxmein
(10) Cockroaches! by boxmein
(11) Random tree by ssccsscc
(12) Element with random properties by ssccsscc
(15) Minimalistic Element Dehider by nucular
(16) FPS Gauge by mniip
(17) TPT Radio by jward212
(18) Print Debugger by FeynmanLogomaker
(19) Powered BHOL by jacob1
(20) Light and lamps by electronic_steve
(21) Extremely Durable TTAN by QuentinADay
(22) New Buttons by QuentinADay
(23) Pure Energy by QuentinADay
(25) RCA's HUD XV Update I by RCAProduction
(27) 123456787654 vols by mjpowder
(28) Singularity Bomb by QuentinADay
(29) ES wifi set by electronic_steve
(30) Lua Elements Pack by FeynmanLogomaker
(31) stkm gun by jward212
(32) space building materials by kjack1111
(34) Rust bomb by Damian97
(35) Simple command block by ssccsscc
(36) Everlasting Fusion by QuentinADay
(37) Screenshot Organiser by mecha-man
(38) Napalm mod by cccp3
(39) Rocket fuel mod v0.15 by cccp3
(41) Useful web links by jward212
(42) TPT Logic Gates Mod by iamdumb
(43) ESTools by electronic_steve
(44) Head Crabs-HL2 by jward212
(45) Procedural Save Generator by boxmein
(46) smooth colours for nametag by jward212
(47) ZAKPACK by zak03
(49) Performance Monitor by FeynmanLogomaker
(51) Texter by byzod
(52) Texter default fonts by byzod
(53) Schicko's Font Pack for Texter by Schicko
(54) Realistic Element Names by Atomic10
(55) TPT's Mod V.3 Update 1 by Amy
(56) Temporaryaccount-Decorator by Temporaryaccount
(57) random save loader by jward212
(58) Tmp gradient display by ssccsscc
(59) particle re-orderer by mniip
(60) Electric Glow by jacob1
(61) More Fuel Mod-lite by jward212
(63) Rythidium by janekbe04
(64) Simple FPS GUI by Sfsjunior
(65) Enhanced Element Dehider by ChargedCreeper
(66) Graph of average temp by ssccsscc
(67) Template save loader by jacob1
(68) Lua Text Generator by jBot-42
(70) Pixel's Freezer by Pixelguru26
(71) Thingy | Fusion For Ever by TheChosenEvilOne
(72) op explosions by zolly_bro
(73) Scar by DorkyBobster
(74) ScreenShotMod by lill74
(77) Useful Things by TheEvilChosenOne
(78) Alchemy Mod by _MrN_
(79) Nuke v2 by Fnaf65
(80) Compressor mod by TheChosenEvilOne
(81) Custom Render Mode Loader by jacob1
(82) Spacewars by JosephMA
(83) MOAR - Alpha 0.1 by TheChosenEvilOne
(86) Element Creator by cxi
(88) Soapworm by LBPHacker
(90) Pressure Bomb by God_Kra
(91) SMFB by wntjq69
(92) Potato by cxi
(93) Subatomic Pack (BDS) by TPT_PL
(94) Acidic Pack (BDS) by TPT_PL
(95) Starbound Building Materials by Sanpypr
(96) Factory problems by TPT_PL
(97) Gamma Ray-diation by Kostia4381
(98) Magic by livingfossil
(99) Cross-window Copy/Cut/Paste by LBPHacker
(100) Langton's Ant with variations by LBPHacker
(101) Remote particle creator/deleter by TPT_PL
(102) Force fields by electronic_steve
(103) Reinforced Concrete by 12Me21
(105) TPT_PL's Lua Mod by TPT_PL
(106) Nuke v4 by Fnaf65
(107) CHEMMOD V1 by KevAK
(108) Chemicals by Ligan
(109) VonDaniel's Template by VonDaniel
(110) The Inaccurate Radioactivity Toy Mod by TuDoR2007
(112) textmonsterPack by textmonster404
(113) Meteor by TheScienceKid
(114) Tgpm by TuDoR2007
(115) Civilizations by TPT_PL
(116) RAD-MOD 1.2.1B by Kev_AK
(117) MicroLua by RamiLego4Game
(118) Extra customizable HUD by djy0212
(119) Ingame brush editor by ssccsscc
(120) Window Maker by Paul_31415
(121) CHEM-MOD V1.2B by Kev_AK
(122) Rainbow PHOT by Mrprocom
(123) stronger stickmanv by yuval
(124) 3D Pressure Visualizer by mniip
(125) Arkadian Liquid by JanKaszanka
(126) Fuel by nukers473
(127) Immersive Radioactivity v2.1 by Potbelly
(128) ElementLaunchingTool by juh9870
(129) CHEM-MOD_v1.2.2b by KevAK
(130) Slingshot by Mrprocom
(131) Perlin Noise Generator by DoubleF
(132) Element Replace by TomW1605
(133) Flooder V2 by TheAwesomeMutant
(134) Link Sign GUI by QuanTech
(135) Element dehider by 4e616d65
(136) Subphoton ROM Builder by mad-cow
(137) Hardened Dust by Liftski
(138) Bio-Vir by TheAwesomeMutant
(140) Orbit Simulator by Mrprocom
(141) johnnyou's Font for Texter by johnnyou (49796346)
(142) auto_wifi by phisically
(143) Layering helper by ssccsscc
(144) Layering Helper Extended by LuaMaster
(145) TPT Remade by TuDoR2007
(146) All-seeing sampler by djy0212
(147) Layering helper remastered by ssccsscc
(148) Eraser by thepowdertoy12
(149) EXPLOSIONS by olix3001
(150) Simple rocket fuel mod by ArseniyPlotnikov2006
(151) Pure Fission by Fnaf65
(154) Graph by ssccsscc
(155) Little's Pack! by LittleProgramming
(156) Lead by LoftisGaming
(157) WIFI Tuner by ssccsscc
(158) Previous Brush by TomW1605
(159) HUD Auto-Hider by Tim
(161) Stack tool by thepowdertoy12
(162) Oil and plastic by ArseniyPlotnikov2006
(163) Colored Ember by DUC
(164) Timer by ssccsscc
(165) Bacteria Mod by TuDoR2007
(166) Noise filter by LBPHacker
(167) Future-proof element dehider by LBPHacker
(168) RadioactiveNuke by DreamingWarlord
(169) Only Hot Element by DreamingWarlord
(170) Philosopher's Stone by Godhydra
(171) Conic section generator by LBPHacker
(172) Interface API by ssccsscc
(173) Metals&Materials by Ferrous26
(174) tpt.all by LBPHacker
(175) The Visual Elements Pack by Goblin01, vvv331
(176) Layering Helper Reforged by PowderNotSolid
(177) SNOWified SING by LBPHacker
(178) FPS Chart by Goblin01
(179) TPT font writer by Goblin01
(180) Simple Ruler by PowderNotSolid
(181) Heat Modifier by DreamingWarlord
(182) TPT Remade II by TuDoR2007
(183) Gravity simulator by ArseniyPlotnikov2k6
(184) Unobtainium by christheboss894
(185) TPTMIDI noteblock in tpt by djy0212
(186) DreamingWarlord's Lua Tool by DreamingWarlord
(187) Elements Tooltip by Goblin01
(189) Explodium script by 0d15ea5ebe57c0debadc0ffee0ddf00d
(190) more powered force elements by 6nop6nop
(191) Yzaak1Scifire Modpack by Yzaak1Scifire
(194) Fluor and more modpack! by galaktor
(195) Hot Powder by lieve_blendi
(196) Star by TUANMINHVIETNAM
(197) Heat Powders by lieve_blendi
(200) Tangeriinium (thx 2 cxi 4 code) by LostEditor
(201) Freezer by lieve_blendi
(202) Powder Power! by TPTSortaGuy
(203) PowderPlus v1.4 by PowderPlus Team
(204) fire by ME
(205) Stacked Goo Animations by Maticzpl
(206) Stickman Control for Android Version by PhauloRiquelme
(207) Spark Removal Button by Xyz
(208) More HEAC's! by Maxhd1234
(209) Immersive Radioactivity v3.0 by Potbelly
(210) Subframe Chipmaker Script by Maticzpl
(211) Realistic Propellants by ArseniyPlotnikov2k6
(212) Mass Equals Gravity by Maticzpl
(213) PhiMod v1 by ArolaunTech
(214) PC Controls for Android by Cracker1000
(217) Single-pixel pipe configurator by LBPHacker
(218) Omega Death Laser Gun by Dogeoum
(219) Notifications by Maticzpl
(221) Powderizer by ArolaunTech
(222) ElemDehider 1.2 by Inventor70
(223) Unobtainum V2 by DoomKittyAttack
(224) Organics Mod v0.2B by PowderPlus Team
(225) Gravity distortion by Avolte55
(226) tmp Wifi by PhauloRiquelme
(227) Alchemagica Mod v1.0 by RebMiami
(228) Fan Elements Mod by RebMiami
(229) Impossibilities by ArolaunTech
(230) Realistic Explosives by ArseniyPlotnikov2k6
(231) libactivation by anamorphic
(232) Alloy Brushes by Maticzpl
(233) Gravity bender by pres
(234) Slow Tick by Pixel
(235) Paste ID by Maticzpl
(236) many things by jadenflp2
(237) Territect by Rebmiami
(238) Better Descriptions v1.0.5 by ashyboi2022
(239) LIGHTNING SPRK by GOLmaster10101
(240) Small Bombs by juh9870
(241) Save Shop by aaccbb
(242) Moving solids v1.3.0 Beta by ArolaunTech
(244) Alchemistry by rdococ
(245) ETRD (Formerly PowderIM) by aaccbb
(246) RadonX by Justadirtblock
(248) Water-X by deuterium_oxide
(250) Indestructible INSL by CheekyRand0m
(252) Console's Mod by Console/Compec
(255) Slow motion by LBPHacker
(256) Powered Repeller by Hythonia
(257) Zeta's Electric Tools. by Zetalasis
(258) Azure serum (AZSR) by ALumpOfPowderToy
(259) COLORFULSAND by xert
(260) Lightning Circle by defaultuser0
(261) Powder Future Tech by JonaHungary
(262) TPTGlowingSolids by DestinyDyson
(263) Volcano Bomb by I_am_the_NugsWorld
(264) Neon Lights by Rebmiami
(265) Radioactive Materials by xyz
(266) Eater mod by VIPERGAMEZ
(267) the biology mod by someone
(268) Atomic Physics by qe
(269) Pure Radiation by ronansb
(270) Fake Elements by That_PowderToy_Guy
(271) Tachyons and MISC by RamenNoods
(272) Exotic Particles by rdococ
(273) FPS Slider by aaccbb
(274) Enphosian's Radioactive mod pack by Enphosian
(276) ROM Builder by QnpfvTPz
(277) acb's Idea Generator by aaccbb

+ Submit new script!

Title: Author:
Script:
-- Prevent multiple instances of the script running
if MaticzplNotifications ~= nil then
    return
end

if iscrackmod and MANAGER.getsetting("CRK","notifval") == "0" then -- Disable when notification settings turned off in Cracker1000's Mod
    return
end

MaticzplNotifications = {
    lastTimeChecked = nil,
    fpCompare = nil,
    requests = {},
    saveCache = {},
    notifications = {},
    hoveringOnButton = false,
    windowOpen = false,
    scrolled = 0,
    specialMessage = "",
    version = 1
}

local json = {}
local notif = MaticzplNotifications
local MANAGER = rawget(_G, "MANAGER")    
local warning, colorR, colorG, colorB, colorA = 0, 148,148,148,200 --Default colours

local function getcrackertheme() -- Reserved for Cracker1000's Mod
	colorR = ar
	colorG = ag
	colorB = ab
	colorA = al
end --End

--Ik this code is awful but interface from tpt api is very limiting
local mouseX = 0
local mouseY = 0
local justClicked = false
local holdingScroll = false
local scrollLimit = 0
function MaticzplNotifications.DrawMenuContent()
    local function hover(x,y,dx,dy)       
        mouseX = x
        mouseY = y     
    end
    local function click(x,y,button)
        -- inside window
        if x > 418 and y > 250 and x < 418 + 193 and y < 250 + 155 and notif.windowOpen then
            justClicked = true
            
            if x > 418 and x < 418 + 12 and y > 261 and y < 250 + 155 then
                holdingScroll = true
            end
            return false
        end
    end
    local function unclick(x,y,button,reason)
        justClicked = false
        holdingScroll = false
    end
    --Notification Banner
	gfx.fillRect(418,238,193,11,colorR, colorG, colorB, colorA)
	gfx.drawText(480,240,"Notification panel",255,255,255,tonumber(colorA)+50)
	
    --Window
    gfx.fillRect(418,250,193,155,0,0,0,200)
    gfx.drawRect(418,250,193,155,colorR, colorG, colorB, colorA)

    --Exit button
    local exitIsHovering = mouseX > 418 and mouseX < 418 + 12 and mouseY > 250 and mouseY < 250 + 12 and notif.windowOpen
    if exitIsHovering then
        gfx.fillRect(418,250,12,12,128,128,128,colorA) 
		gfx.drawText(395,252,"Exit",colorR, colorG, colorB, colorA)	
    end
    gfx.drawRect(418,250,12,12,colorR, colorG, colorB, colorA)
    gfx.drawText(418+3,250+2,"X")

    --Read All button
    local readAllHovering = mouseX > 418 and mouseX < 418 + 12 and mouseY > 261 and mouseY < 261 + 12 and notif.windowOpen
    if readAllHovering then
        gfx.fillRect(418,261,12,12,128,128,128)   
		gfx.drawText(375,263,"Read all",colorR, colorG, colorB, colorA)			
    end
    gfx.drawRect(418,261,12,12,colorR, colorG, colorB, colorA) 
    gfx.drawText(418+4,261+2,"A")
    
    --Scroll Bar
    local scrollY = 275
    local scrollFieldHeight = 250 + 155 - scrollY
    local barRatio = math.min(1 - (scrollLimit * -5 / 155),1)
    local barHeight = math.max(scrollFieldHeight * barRatio,10)
    if holdingScroll and barHeight + scrollY ~= 404 and scrollLimit ~= 0 then
        -- Wolfram alpha saved me here xd
        notif.scrolled = (scrollLimit*(-(mouseY - barHeight/2) + scrollY - 1)) / (barHeight + scrollY - 404)
    end
    
    if notif.scrolled > 0 then
        notif.scrolled = 0
    end    
    if notif.scrolled < scrollLimit then
        notif.scrolled = scrollLimit
    end
    if scrollLimit ~= 0 then      
        local scrollFraction = notif.scrolled / scrollLimit
        local barPos = scrollY + ((250 + 154 - barHeight - scrollY) * scrollFraction) - 1
        gfx.fillRect(420,barPos,8,barHeight,colorR, colorG, colorB, colorA)
    else
        gfx.fillRect(420,scrollY - 1,8,155 - 26, colorR, colorG, colorB, colorA)  
    end    
    
    --Vertical line
    gfx.drawLine(418+11,250,418+11,250 + 154,colorR, colorG, colorB, colorA)
        
	if #notif.notifications == 0 or notif.specialMessage ~= "" then
        local msg = "No notifications to show";
        msg = msg.."\n"..notif.specialMessage

        gfx.drawText(438,257,msg,228,228,228,255)
    else  
        local y = 252 + notif.scrolled * 5
        local lastTitleY = y
        
        for i, n in ipairs(notif.notifications) do      
            local prev = notif.notifications[i-1]
            
            local saveID = n.save
            local title = n.title
            local msg = n.message
            
            --Group title
            if prev == nil or prev.title ~= title then
                lastTitleY = y
                if y >= 252 and y <= 250+155 - 10 then         
                    gfx.drawLine(418+12,y - 2,418 + 192,y - 2,colorR,colorG,colorB,colorA)     
                    gfx.drawText(418+15,y,title)
                end
                local sx,sy = gfx.textSize(title)
                y = y + sy
            end
            --Message
            if y >= 252 and y <= 250+155 - 10 then         
                gfx.drawText(418+22,y,msg,200,200,200)    
            end    
            local sx,sy = gfx.textSize(msg)
            y = y + sy
            
            local next = notif.notifications[i+1]
            if next == nil or next.title ~= title then
                if mouseX > 418 + 12 and mouseX < 418 + 193 and mouseY > lastTitleY and mouseY < y and mouseY > 250 and mouseY < 250 + 156 then
                    
                    local boxY = math.max(lastTitleY-1,251)
                    local height = math.min(y - boxY - 2,boxY + 155 - 253)
                    if height + boxY > 404 then --this is confusing
                        height = height - (height + boxY - 404)
                    end
                    gfx.drawRect(418 + 12,boxY,193 - 13,height)
                    
                    if justClicked then
                        local removing = i
                        while notif.notifications[removing].title == title do
                            table.remove(notif.notifications,removing)    
                            removing = removing - 1
                            if notif.notifications[removing] == nil then
                                break
                            end
                        end
                        notif.SaveNotifications()
                        
                        sim.loadSave(saveID)
                    end
                end
            end
            
            scrollLimit = -math.max((y - 250 - 154) / 5 - notif.scrolled, 0) 
        end
    end
  
    event.register(event.mousedown,click)
    event.register(event.mousemove,hover)
    event.register(event.mouseup,unclick)
    
    if exitIsHovering and justClicked then        
        notif.windowOpen = false
        notif.specialMessage = ""
        notif.SaveNotifications()
		warning = 0
        return false
    end    
    if readAllHovering and justClicked then      
        notif.notifications = {} 
        notif.SaveNotifications()
        return false
    end    
    justClicked = false
end
function MaticzplNotifications.ShowSpecialMesasge(msg)
    notif.specialMessage = msg;
	warning = 1
end

-- Request save data from the server
-- Called automatically every 5 minutes
function MaticzplNotifications.CheckForChanges()
    local name = tpt.get_name()
    if name ~= "" then          
        -- FP
        notif.fpCompare = http.get("https://powdertoy.co.uk/Browse.json?Start=0&Count=16");
        -- By date
        table.insert(notif.requests, http.get("https://powdertoy.co.uk/Browse.json?Start=0&Count=30&Search_Query=sort%3Adate%20user%3A"..name))
        table.insert(notif.requests, http.get("https://powdertoy.co.uk/Browse.json?Start=30&Count=30&Search_Query=sort%3Adate%20user%3A"..name))
        -- By votes
        table.insert(notif.requests, http.get("https://powdertoy.co.uk/Browse.json?Start=0&Count=30&Search_Query=user%3A"..name))
        table.insert(notif.requests, http.get("https://powdertoy.co.uk/Browse.json?Start=30&Count=30&Search_Query=user%3A"..name))
    end 
end

-- Called when recieved response from teh server after calling CheckForUpdates()
function MaticzplNotifications.OnResponse()
    local function split (input, sep)
        if sep == nil then
            sep = "%s"
        end
        local t={}
        for str in string.gmatch(input, "([^"..sep.."]+)") do
            table.insert(t, str)
        end
        return t
    end
    
    local saves = {}
    for id, req in ipairs(notif.requests) do
        local res = req:finish()
        
        local success, found = pcall(json.parse,res)
        if not success then
            notif.ShowSpecialMesasge("Error while fetching saves\nfrom the server.")
            return
        end
        for k, v in pairs(found.Saves) do
            saves[v.ID] = v            
        end
    end

    local fpRes = notif.fpCompare:finish()
    local success, fpsaves = pcall(json.parse,fpRes)
    if not success then
        notif.ShowSpecialMesasge("Error while fetching FP from server.")
        return
    end
    fpsaves = fpsaves.Saves

    if notif.saveCache ~= nil then
        for id, save in pairs(saves) do
            local isFP = 0
            for _, fpSave in pairs(fpsaves) do
                if fpSave.ID == save.ID then
                    isFP = 1
                end
            end
            saves[id].FP = isFP
            
            local cached = notif.saveCache[save.ID]
            if cached == nil then
                local saved = MANAGER.getsetting("MaticzplNotifications",""..save.ID)
                if saved == nil then
                    notif.saveCache[save.ID] = {}
                    notif.saveCache[save.ID].ScoreUp = save.ScoreUp
                    notif.saveCache[save.ID].ScoreDown = save.ScoreDown
                    notif.saveCache[save.ID].Comments = save.Comments    
                    notif.saveCache[save.ID].FP = isFP
                    notif.saveCache[save.ID].ID = save.ID
                    cached = notif.saveCache[save.ID]              
                else
                    local saved = split(saved,"|")
                    notif.saveCache[save.ID] = {}
                    notif.saveCache[save.ID].ID = save.ID
                    notif.saveCache[save.ID].ScoreUp = saved[2]
                    notif.saveCache[save.ID].ScoreDown = saved[3]
                    notif.saveCache[save.ID].Comments = saved[4]
                    notif.saveCache[save.ID].FP = saved[5]
                    cached = notif.saveCache[save.ID]
                end
            end
            
            if tonumber(isFP) ~= tonumber(cached.FP) then
                if tonumber(isFP) == 1 then
                    notif.AddNotification("This save is now on FP!!!",save.ShortName,save.ID)   
                end 
                if tonumber(cached.FP) == 1 then                
                    notif.AddNotification("This save went off FP.",   save.ShortName,save.ID)  
                end            
            end

            local icons = {
                "\x0F\1\255\1\238\129\139",
                "\br\238\129\138"
            }
            if tpt.version.jacob1s_mod ~= nil then
                icons = {
                    "\x0F\1\255\1\203",
                    "\br\202"
                }
            end

            local new = save.ScoreUp - cached.ScoreUp
            if new > 0 then
                notif.AddNotification(new.." new Upvotes!" .. icons[1],save.ShortName,save.ID)            
            end
            new = save.ScoreDown - cached.ScoreDown
            if new > 0 then
                notif.AddNotification(new.." new Downvotes" .. icons[2],save.ShortName,save.ID)                
            end
            new = save.Comments - cached.Comments
            if new > 0 then
                notif.AddNotification(new.." new Comments",save.ShortName,save.ID)               
            end
            MANAGER.savesetting("MaticzplNotifications",save.ID,notif.SaveToString(save))  
            notif.saveCache[save.ID] = save
        end
    else
        notif.saveCache = {}
    end
    
    notif.SaveNotifications()
end

-- Message to display in notification
-- Title by which multiple notifications will be grouped
-- saveID optional to open save on click
function MaticzplNotifications.AddNotification(message,title,saveID)    
    local notification = {
        ["save"] = saveID,
        ["title"] = title,
        ["message"] = message
    }
    table.insert(notif.notifications, notification)   
end

function MaticzplNotifications.SaveNotifications()
    MANAGER.savesetting("MaticzplNotifications","Notifications",string.gsub(json.stringify(notif.notifications),"\"","~"))    
end

-- Draws the red circle notification button. Called every frame
local timerfornot = 255 -- Blinking not. dot
function MaticzplNotifications.DrawNotifications()

    local number = #notif.notifications
    
    if number > 99 then
        number = "99"
    end

    local posX = 572
    local posY = 415
    if tpt.version.jacob1s_mod ~= nil then
        posX = 584
    end
    if tpt.version.modid == 7 then --TPT Ultimata
        posX = 573
        posY = 435
    end
	if iscrackmod then --Cracker1000's Mod
          getcrackertheme()
    end
    local w,h = gfx.textSize(number)
    
    local nw,nh = gfx.textSize(tpt.get_name())
    
    if nw > 58 then
        gfx.fillRect(507,409,72,13,0,0,0,150)            
    end
    if number == 0 then
        gfx.fillCircle(posX,posY,5,5,50,50,50)
        gfx.fillCircle(posX,posY,4,4,60,60,60)
        gfx.drawText(posX + 1 -(w / 2),posY + 2 -(h / 2),number,128,128,128)
		    if warning == 1 then
gfx.drawText(570,412,"X",255,0,0,255)
end
        return
    end

    local brig = 0
    if notif.hoveringOnButton then
        brig = 80
    end
    if timerfornot > 0 then
        timerfornot = timerfornot - 2
    elseif timerfornot <= 0 then
        timerfornot = 255
    end
    
    gfx.fillCircle(posX,posY,6,6,120,brig,brig,timerfornot)
    gfx.fillCircle(posX,posY,5,5,255,brig,brig,timerfornot)
    gfx.drawText(posX + 1 -(w / 2),posY + 2 -(h / 2),number,255,255,255)
end


-- Used for saving current state of saves
function MaticzplNotifications.SaveToString(save)
    local separator = "|"

    return save.ID..separator..save.ScoreUp..separator..save.ScoreDown..separator..save.Comments..separator..save.FP
end


function MaticzplNotifications.Mouse(x,y,dx,dy)
    local posX = 572
    local posY = 415
    if tpt.version.jacob1s_mod ~= nil then
        posX = 585
    end
    
    notif.hoveringOnButton = math.abs(posX - x) < 5 and math.abs(posY - y) < 5
end

function MaticzplNotifications.OnClick(x,y,button)
    if notif.hoveringOnButton then
        notif.scrolled = 0
        notif.windowOpen = true
        
        notif.DrawMenuContent()
        return false
    end
end

function MaticzplNotifications.Scroll(x,y,d)
    d = d / math.abs(d) --clamp to 1 / -1
    
    --In window
    if x > 418 and y > 250 and x < 418 + 193 and y < 250 + 155 and notif.windowOpen then
        notif.scrolled = notif.scrolled + d
        return false
    end
end

function MaticzplNotifications.Tick()
    local time = os.time(os.date("!*t"))
    
    if time - notif.lastTimeChecked > (5 * 60) then
        notif.lastTimeChecked = time
        
        notif.CheckForChanges()
    end
    
    local allDone = true;
    for _, req in ipairs(notif.requests) do
        if req:status() ~= "done" then
            allDone = false
            break
        end
    end    

    if allDone and notif.fpCompare ~= nil and notif.fpCompare:status() == "done" then   
        notif.OnResponse()
        notif.requests = {}
        notif.fpCompare = nil
        MANAGER.savesetting("MaticzplNotifications","lastTime",notif.lastTimeChecked)                    
    end
    
    
    notif.DrawNotifications()
    
    if notif.windowOpen then
        notif.DrawMenuContent()
    end
end

---------------------------------------------------------------------------------
-- JSON parsing from https://gist.github.com/tylerneylon/59f4bcf316be525b30ab  --
-- Credit to tylerneylon                                                       --
-- Stated to be public domain by the author (check comments in the link)       --
---------------------------------------------------------------------------------
--#region

local function kind_of(obj)
    if type(obj) ~= 'table' then return type(obj) end
    local i = 1
    for _ in pairs(obj) do
        if obj[i] ~= nil then i = i + 1 else return 'table' end
    end
    if i == 1 then return 'table' else return 'array' end
end
local function escape_str(s)
    local in_char  = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'}
    local out_char = {'\\', '"', '/',  'b',  'f',  'n',  'r',  't'}
    for i, c in ipairs(in_char) do
        s = s:gsub(c, '\\' .. out_char[i])
    end
    return s
end
local function skip_delim(str, pos, delim, err_if_missing)
    pos = pos + #str:match('^%s*', pos)
    if str:sub(pos, pos) ~= delim then
        if err_if_missing then
            error('Expected ' .. delim .. ' near position ' .. pos)
        end
        return pos, false
    end
    return pos + 1, true
end
local function parse_str_val(str, pos, val)
    val = val or ''
    local early_end_error = 'End of input found while parsing string.'
    if pos > #str then error(early_end_error) end
    local c = str:sub(pos, pos)
    if c == '"'  then return val, pos + 1 end
    if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end
    -- We must have a \ character.
    local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'}
    local nextc = str:sub(pos + 1, pos + 1)
    if not nextc then error(early_end_error) end
    return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc))
end
local function parse_num_val(str, pos)
    local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos)
    local val = tonumber(num_str)
    if not val then error('Error parsing number at position ' .. pos .. '.') end
    return val, pos + #num_str
end
function json.stringify(obj, as_key)
    local s = {}  
    local kind = kind_of(obj)
    if kind == 'array' then
        if as_key then error('Can\'t encode array as key.') end
        s[#s + 1] = '['
        for i, val in ipairs(obj) do
            if i > 1 then s[#s + 1] = ', ' end
            s[#s + 1] = json.stringify(val)
        end
        s[#s + 1] = ']'
    elseif kind == 'table' then
        if as_key then error('Can\'t encode table as key.') end
        s[#s + 1] = '{'
        for k, v in pairs(obj) do
            if #s > 1 then s[#s + 1] = ', ' end
            s[#s + 1] = json.stringify(k, true)
            s[#s + 1] = ':'
            s[#s + 1] = json.stringify(v)
        end
        s[#s + 1] = '}'
    elseif kind == 'string' then
        return '"' .. escape_str(obj) .. '"'
    elseif kind == 'number' then
        if as_key then return '"' .. tostring(obj) .. '"' end
        return tostring(obj)
    elseif kind == 'boolean' then
        return tostring(obj)
    elseif kind == 'nil' then
        return 'null'
    else
        error('Unjsonifiable type: ' .. kind .. '.')
    end
    return table.concat(s)
end
json.null = {}
function json.parse(str, pos, end_delim)
    pos = pos or 1
    if pos > #str then error('Reached unexpected end of input.') end
    local pos = pos + #str:match('^%s*', pos)
    local first = str:sub(pos, pos)
    if first == '{' then
        local obj, key, delim_found = {}, true, true
        pos = pos + 1
        while true do
            key, pos = json.parse(str, pos, '}')
            if key == nil then return obj, pos end
            if not delim_found then error('Comma missing between object items.') end
            pos = skip_delim(str, pos, ':', true)
            obj[key], pos = json.parse(str, pos)
            pos, delim_found = skip_delim(str, pos, ',')
        end
    elseif first == '[' then 
        local arr, val, delim_found = {}, true, true
        pos = pos + 1
        while true do
            val, pos = json.parse(str, pos, ']')
            if val == nil then return arr, pos end
            if not delim_found then error('Comma missing between array items.') end
            arr[#arr + 1] = val
            pos, delim_found = skip_delim(str, pos, ',')
        end
    elseif first == '"' then 
        return parse_str_val(str, pos + 1)
    elseif first == '-' or first:match('%d') then
        return parse_num_val(str, pos)
    elseif first == end_delim then 
        return nil, pos + 1
    else
        local literals = {['true'] = true, ['false'] = false, ['null'] = json.null}
        for lit_str, lit_val in pairs(literals) do
            local lit_end = pos + #lit_str - 1
            if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end
        end
        local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10)
        error('Invalid json syntax starting at ' .. pos_info_str)
    end
end
--#endregion
-- On launch
notif.lastTimeChecked = MANAGER.getsetting("MaticzplNotifications","lastTime") or 0
local notifJson = MANAGER.getsetting("MaticzplNotifications","Notifications")
if notifJson then
    local jsonStr = string.gsub(notifJson,"~","\"")
    notif.notifications = json.parse(jsonStr)  
end
event.register(event.tick,notif.Tick)
event.register(event.mousemove,notif.Mouse)
event.register(event.mousedown,notif.OnClick)
event.register(event.mousewheel,notif.Scroll)

local name = tpt.get_name()
if name == "" then          
    notif.ShowSpecialMesasge("You need to be logged in\nto use the notifications script.")
end

Description:

Changelog: