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 159819
(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 159819
(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-IM 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
(275) TPTMulti v2.2.5 Beta by LBPHacker
(276) ROM Builder by QnpfvTPz
(277) acb's Idea Generator by aaccbb
(278) Bode's Grapher by Bodester
(279) Gradient Tool by Bodester
(280) Better FPS Slider by ngmoco
(281) MyWIFI by Bodester
(282) acb's Slow-Mo Script by aaccbb
(283) Fun n' Chemicals by 0xHenryMC
(284) Resistant coating by git4rker
(285) Nationwide script by Kit237
(286) weird alphabet.lua by alice_loona_ot12
(287) MT's font for texter by Kit237
(288) Compiler for the SCE computer by NoVIcE by NoVIcE
(289) Perlin Noise Map Generator by Kit236
(290) hell fire element by Hecker
(291) Power stuff by William
(292) Missile by BaCOn
(293) Wargame's things by Kit236
(294) Inert Gas by EwguhMicBewguhGuy
(295) Average Temperature Display by tptQuantification
(296) Radicons by stillthere
(297) ROCKET by stillthere
(298) NUUKE! by stillthere
(299) SubpixelScope by Rebmiami
(300) Unobtanium Element Pack by AlgoPowdo
(301) Magnets by TheSuperNova
(302) library007 3000 by 008cff
(303) Element tuner by TheSuperNova
(304) Somethings by TheSuperNova
(305) Realistic Water by yuht
(306) Futurism by TheSuperNova
(307) MMod by MeltedLawnFlamingo
(308) Wierd energy by mellowmonster12
(309) Time Reversing by crocodilea
(310) Weather System by Bigmann
(311) infinite framerate by kayra
(312) Diamondifier mod by Believer0914
(313) Radtech by ReallyJustDont
(314) TPT 'port' of IRC Crackbot game by creator_of_fail
(315) Fireshoas Stuff by Fireshoa
(316) TPTASM: Universal assembler for TPT computers by LBPHacker
(317) Ronansb's Elements by ronansb
(318) AeroStuff v1.2 by AerospaceFan
(319) TPT Font Writer Expanded by budc123
(320) Element Table Pack by RetroBoy2040
(321) One Time Conductor by Believer0914
(322) Extra Debug Info by creator_of_fail
(323) burst v1 by ml_lom
(324) Nuclide Expansion by Alchnoious
(325) Nuclear Elems by xhrqnx2
(326) UM by Sunshine1304
(327) Android stamp rescanner by LBPHacker
(328) Temp Resetting Button by DoobsterBro1234
(329) INFEKTOR_infection_V1 by PowderTechnician1475
(330) Nanotech V2 by TheSuperNova
(331) super explosives by egglegendbro
(332) The Return of the Powder Dinn by savask

+ Submit new script!

Title: Author:
Script:
local ptMax = 0 -- Max particle type number in use
local visibleNum = 0 -- Number of visible elements
-- For vanilla ptMax = 193 (including NONE = 0), visibleNum = 172 (20 hidden elements)

-- Compute maximum element number used
for t = 1, sim.PT_NUM do
	if elements.exists(t) and elem.property(t, "Identifier"):sub(1, 11) == "DEFAULT_PT_" and t > ptMax then
		ptMax = t
	end
end

local cipherFactor = 113 -- a number used to scramble elements, should be coprime to ptMax+1
local correctInfo = {}
local decipher = {}
local unlockedElements = {}
local elemExists = {}
local elemVisible = {}
local guesses = {}
local amountHidden = 0 -- scrambled elements
local amountMenuHidden = 0 -- scrambled elements that are in menus
local userUnlockedVisible = false
local unlockedSomething = false -- did user unlock at least one element?
local savedNotes = {}

for t = 1, ptMax do
    elemExists[t] = elements.exists(t)

    if elemExists[t] then
        if elem.property(t, "Identifier"):sub(1, 11) == "DEFAULT_PT_" then
            elemVisible[t] = elements.property(t, "MenuVisible") ~= 0
            if elemVisible[t] then
                visibleNum = visibleNum + 1
            end
        else
            elemExists[t] = false 
        end
    end
end

-- By default none of the elements are unlocked
for t = 1, ptMax do
	unlockedElements[t] = false

	-- Save correct info
	if elemExists[t] then
		correctInfo[elements.property(t, "Name")] = t
	end
end
-- ... besides life
unlockedElements[elements.DEFAULT_PT_LIFE] = true

local function cipher(t)
	return tostring((t*cipherFactor) % (ptMax+1))
end

-- Scramble elements
local scramble = function ()
	for t = 1, ptMax do
		if elemExists[t] and not unlockedElements[t] then
			local sections = {elements.SC_ELEC, elements.SC_POWERED, elements.SC_SENSOR, elements.SC_FORCE, elements.SC_EXPLOSIVE, elements.SC_GAS,
			elements.SC_LIQUID, elements.SC_POWDERS, elements.SC_SOLIDS, elements.SC_NUCLEAR, elements.SC_SPECIAL }

			-- Use existing guess, otherwise use cipher
			local ciphertext = guesses[t] and guesses[t] or cipher(t)
			decipher[ciphertext] = t

			-- Table used for hiding identifying information
			local col = 20 + (cipher(t) % 230)
			hiddenInfo = {
				Name = ciphertext,
				MenuSection = sections[1 + (cipher(t) % 11)],
				MenuSort = cipher(t),
				Description = savedNotes[t] and savedNotes[t] or "???",
				Color = gfx.getHexColor(col, col, col),
			}

			-- Clear graphics function too
			if t ~= elements.DEFAULT_PT_SOAP then
				hiddenInfo["Graphics"] = function() return 1 end
			end

			elem.element(t, hiddenInfo)
			amountHidden = amountHidden + 1
			if elemVisible[t] then
				amountMenuHidden = amountMenuHidden + 1
			end
		end
	end
end

-- Check which elements were guessed correctly
local checkElements = function ()
	local correct = {}
	local correctNum = 0

	for t, v in pairs(guesses) do
		if correctInfo[elements.property(t, "Name")] == t then
			table.insert(correct, t)
			correctNum = correctNum + 1
		end
	end

	-- If guessed correctly, restore all info
	local correctNames = ""
	local firstTime = false

	if correctNum >= 3 or (correctNum >= 1 and amountMenuHidden < 3) -- When less than 3 visible elements left, solve one at a time
		or (correctNum >= 1 and not unlockedSomething) -- Accept the first correct guess
	then
		for _, t in ipairs(correct) do
			unlockedElements[t] = true
			guesses[t] = nil
			elem.loadDefault(t)
			elements.property(t, "Name", elements.property(t, "Name")) -- A workaround for the menu bug
			correctNames = correctNames .. elements.property(t, "Name") .. ", "
			amountHidden = amountHidden - 1

			if elemVisible[t] then
				amountMenuHidden = amountMenuHidden - 1
			end

			-- Show rules when the user guesses something correctly for the first time
			if not unlockedSomething then
				unlockedSomething = true
				firstTime = true
			end
		end
	end

	return { names = correctNames, popup = firstTime }
end

-- File used to store progress
local config = "powderDinn.txt"

local saveProgress = function ()
	local file = io.open(config, "w")

	file:write("FACTOR " .. cipherFactor .. " 0\n")

	if userUnlockedVisible then
		file:write("ALMOST 0 0\n") -- Use useless 0s for the ease of parsing
	end

	for t = 1, ptMax do
		if elemExists[t] then
			local unlocked = unlockedElements[t] and "1" or "0"
			file:write("UNLOCK " .. t .. " " .. unlocked .. "\n")
		end
	end
	for t, v in pairs(guesses) do
		file:write("GUESS " .. t .. " " .. v .. "\n")

		if savedNotes[t] then
			file:write("NOTE " .. t .. " " .. savedNotes[t] .. "\n")
		end
	end

	for t, v in pairs(savedNotes) do
		if savedNotes[t] and not guesses[t] and not unlockedElements[t] then
			file:write("NOTE " .. t .. " " .. savedNotes[t] .. "\n")
		end
	end
	file:close()
end

-- Read the config file or create it if there isn't one
if fs.exists(config) then
	local file = io.open(config, "r")

	-- Read the file line by line
	for line in file:lines() do
		local words = line:gmatch("%S+")
		local configType = words()
		local t = tonumber(words())
		local arg3 = words()

		if configType == "FACTOR" then
			cipherFactor = t
		elseif configType == "ALMOST" then
			userUnlockedVisible = true
		elseif configType == "GUESS" then
			guesses[t] = arg3
		elseif configType == "UNLOCK" then
			if elemExists[t] and arg3 == "1" then
				unlockedElements[t] = true
				-- Prevent LIFE from being counted as "unlocked something"
				if t ~= elements.DEFAULT_PT_LIFE then
					unlockedSomething = true
				end
			end
		elseif configType == "NOTE" then
			local descr = arg3
			for wrd in words do
				descr = descr .. " " .. wrd
			end
			savedNotes[t] = descr
		end
	end

	-- Bad guesses in the config somehow
	for t, v in pairs(guesses) do
		if unlockedElements[t] then
			guesses[t] = nil
		end
	end

	file:close()
else
	interface.beginMessageBox("RETURN OF THE POWDER DINN",
	"You return to this wretched god-forgotten game, abandoned even by its creators.\
	Element names have been withered away by time, but you seem to remember them as if it was yesterday...\n\
	Is it still possible to salvage what's left? Can you restore everything to its original form?\n\
	The ledger awaits. Let the dead elements speak once more.")

	-- Setup cipherFactor
	local coprimeNumbers = {}
	local coprimeAmount = 0

	-- Greatest common divisor of two numbers
	local gcd = function (a, b)
		while b ~= 0 do
			a, b = b, a % b
		end
		return a
	end

	-- Compute numbers coprime to ptMax+1
	for t = 2, ptMax do
		if gcd(t, ptMax+1) == 1 then
			coprimeAmount = coprimeAmount + 1
			coprimeNumbers[coprimeAmount] = t
		end
	end

	-- Select a random cipherFactor
	if coprimeAmount > 0 then
		cipherFactor = coprimeNumbers[math.random(coprimeAmount)]
	end
end

-- Save progress on startup no matter what
saveProgress()

-- Scramble the not yet unlocked elements
scramble()

-- Save progress when exiting
event.register(event.close, saveProgress)

local function pos(component)
	local x, y = component:position()
	return {
		x = x,
		y = y
	}
end

local function size(component)
	local w, h = component:size()
	return {
		x = w,
		y = h
	}
end

local function positionFrom(component, offsetX, offsetY)
	local posX, posY = component:position()
	local sizeX, sizeY = component:size()
	return {
		x = posX + sizeX + offsetX,
		y = posY + sizeY + offsetY
	}
end

local function getLabel(x, y, text)
	local w, h = gfx.textSize(text)
	local label = Label:new(x - 1, y, w + 2, h - (h % 12) + 16, text)

	return label
end

local function drawElem(t, x, y, selected)
	local name = elem.property(t, "Name")
	local col = elem.property(t, "Color")
	local colr, colg, colb = gfx.getColors(col)
	gfx.fillRect(x + 2, y + 2, 26, 14, colr, colg, colb)
	local tcol = colb + colg * 3 + colr * 2 < 544 and 255 or 0
	gfx.drawText(x + 3 + (26 - gfx.textSize(name)) / 2, y + 5, name, tcol, tcol, tcol)

	-- Force cutoff long text
	gfx.fillRect(x, y, 2, 18, 0, 0, 0)
	gfx.fillRect(x + 28, y, 2, 18, 0, 0, 0)

	if selected then
		gfx.drawRect(x, y, 30, 18, 255, 0, 0)
	end
end

local function showProgressWindow()
	local progWindow = Window:new(-1, -1, 350, 250)
	progWindow:onTryExit(function() interface.closeWindow(progWindow) end)

	local titleLabel = getLabel(4, 2, "\x0F\x8C\x8C\xFFProgress report")
	progWindow:addComponent(titleLabel)

	progWindow:onDraw(function()
		local titleLabelY = pos(progWindow).y + pos(titleLabel).y + size(titleLabel).y
		gfx.drawLine(pos(progWindow).x, titleLabelY, pos(progWindow).x + size(progWindow).x - 1, titleLabelY)
	end)

	local paginatedComponents = {}
	local pageNum = 1

	local function addProgress()
		local stillHidden 

		if amountMenuHidden == 0 then
			stillHidden = getLabel(4, 20, "There are still \bo" .. amountHidden .. "\bw elements lurking in the margins.")
		else
			stillHidden = getLabel(4, 20, "There are still \bo" .. amountMenuHidden .. "\bw elements unsettled in the ledger.")
		end

		progWindow:addComponent(stillHidden)

		local infoLabel = getLabel(4, 50, "Reinstate the original names of the elements.\nThe truth will be revealed only in groups of 3 correct guesses.\n\nPending guesses are recorded herein:")
		progWindow:addComponent(infoLabel)

		table.insert(paginatedComponents, stillHidden)
		table.insert(paginatedComponents, infoLabel)
	end


	local function addHeader(yPos)
		local elemLabel = getLabel(4, yPos, "Name")
		progWindow:addComponent(elemLabel)

		local descLabel = getLabel(40, yPos, "Notes")
		progWindow:addComponent(descLabel)

		table.insert(paginatedComponents, elemLabel)
		table.insert(paginatedComponents, descLabel)
	end

	local function addGuess(yPos, name, description)
		local elemLabel = getLabel(4, yPos, name)
		progWindow:addComponent(elemLabel)

		local descLabel = getLabel(40, yPos, description)
		-- Description too big to fit. Cut it off to window width
		-- Trying to multiline it is almost entirely infeasible with the current api
		if size(descLabel).x > size(progWindow).x - 40 then
			descLabel = Label:new(39, yPos, size(progWindow).x - 40, 16, description)
		end
		progWindow:addComponent(descLabel)

		table.insert(paginatedComponents, elemLabel)
		table.insert(paginatedComponents, descLabel)
	end

	local function addAllGuesses(yPos)
		addHeader(yPos)
		yPos = yPos + 20
		for t, v in pairs(guesses) do
			addGuess(yPos, "\bt" .. v, elem.property(t, "Description"))
			yPos = yPos + 20
		end
		for t, v in pairs(savedNotes) do
			if not unlockedElements[t] and not guesses[t] then
				addGuess(yPos, elem.property(t, "Name"), elem.property(t, "Description"))
				yPos = yPos + 20
			end
		end
	end

	local function showPage(newPageNum)
		local perPage = size(progWindow).y - 75
		local yPos = 20 - (pageNum  - 1) * perPage
		local offset = (pageNum - newPageNum) * perPage

		local prevEnabled, nextEnabled = false, false
		for i, v in ipairs(paginatedComponents) do
			local currPos = pos(v)
			v:position(currPos.x, currPos.y + offset)
			if currPos.y + offset < 20 then
				v:visible(false)
				prevEnabled = true
			elseif currPos.y + offset > 20 + perPage then
				v:visible(false)
				nextEnabled = true
			else
				v:visible(true)
			end
		end

		pageNum = newPageNum
		return prevEnabled, nextEnabled
	end

	local function addPagination()
		local prevButton = Button:new(5, size(progWindow).y - 36, 60, 16, "Prev Page")
		progWindow:addComponent(prevButton)

		local nextButton = Button:new(size(progWindow).x - 65, size(progWindow).y - 36, 60, 16, "Next Page")
		progWindow:addComponent(nextButton)

		local function changePage(offset)
			prevEnabled, nextEnabled = showPage(pageNum + offset)
			prevButton:enabled(prevEnabled)
			nextButton:enabled(nextEnabled)
		end
		prevButton:action(function(sender) changePage(-1) end)
		nextButton:action(function(sender) changePage(1) end)

		-- Set initial pagination
		changePage(0)
	end

	addProgress()
	addAllGuesses(positionFrom(paginatedComponents[#paginatedComponents], 0, 5).y)
	addPagination()

	local okButton = Button:new(0, size(progWindow).y - 16, size(progWindow).x, 16, "OK")
	okButton:action(function(sender)
		interface.closeWindow(progWindow)
	end)
	progWindow:addComponent(okButton)

	interface.showWindow(progWindow)
end

-- Define it early to use in showGuessWindow
local removeGuessButton

local function showGuessWindow()
	-- Guess window
	local guessWindow =  Window:new(-1, -1, 230, 140)
	guessWindow:onTryExit(function() interface.closeWindow(guessWindow) end)

	local titleLabel = getLabel(4, 2, "\x0F\x8C\x8C\xFFUpdate ledger")
	guessWindow:addComponent(titleLabel)

	local function addTextboxes(yPos, oldName, newName, newDesc)
		local renameLabel = getLabel(size(guessWindow).x / 2 - 50, yPos, "Modify")
		guessWindow:addComponent(renameLabel)

		local oldNameTextbox = Textbox:new(positionFrom(renameLabel, 5, 0).x + 5, yPos, 40, 16, oldName)
		guessWindow:addComponent(oldNameTextbox)

		local nameLabel = getLabel(7, positionFrom(oldNameTextbox, 0, 5).y, "New name")
		guessWindow:addComponent(nameLabel)

		local descLabel = getLabel(size(guessWindow).x / 3, positionFrom(oldNameTextbox, 0, 5).y, "Optional notes")
		guessWindow:addComponent(descLabel)

		local newNameTextbox = Textbox:new(pos(nameLabel).x, positionFrom(nameLabel, 0, 1).y, 40, 16, newName, "????")
		guessWindow:addComponent(newNameTextbox)

		local newDescTextbox = Textbox:new(pos(descLabel).x, positionFrom(descLabel, 0, 1).y, size(guessWindow).x * 2 / 3 - 10, 16, newDesc, "????")
		guessWindow:addComponent(newDescTextbox)

		if newNameTextbox.focus then
            newNameTextbox:focus(true)
        end

		oldNameTextbox:onTextChanged(function(sender)
			local t = decipher[sender:text()]
			if t then
				local desc = elem.property(t, "Description")
				if desc == "???" then return end
				newDescTextbox:text(desc)
			else
				newDescTextbox:text("")
			end
		end)

		return oldNameTextbox, newNameTextbox, newDescTextbox, positionFrom(newNameTextbox, 0, 5).y
	end

	local oldNameTextbox, newNameTextbox, newDescTextbox, nextY
	do
		local selectedElem = elem[tpt.selectedl]
		local oldName = selectedElem and not unlockedElements[selectedElem] and elem.property(selectedElem, "Name") or ""
		local newDesc = selectedElem and not unlockedElements[selectedElem] and elem.property(selectedElem, "Description") or ""
		if newDesc == "???" then newDesc = "" end
		oldNameTextbox, newNameTextbox, newDescTextbox, nextY = addTextboxes(positionFrom(titleLabel, 0, 5).y, oldName, "", newDesc)
	end

	local ledgerButton = Button:new(size(guessWindow).x / 2 - 40, size(guessWindow).y - 46, 80, 16, "Progress report")
	ledgerButton:action(function(sender)
		showProgressWindow()
	end)
	guessWindow:addComponent(ledgerButton)


	local cancelButton = Button:new(0, size(guessWindow).y - 16, size(guessWindow).x * 2 / 3, 16, "Cancel")
	cancelButton:action(function(sender)
		interface.closeWindow(guessWindow)
	end)
	guessWindow:addComponent(cancelButton)

	local function removeGuess(t, tName)
        local ciphertext = cipher(t)
        guesses[t] = nil
        elements.property(t, "Name", ciphertext)
        decipher[ciphertext] = t
        decipher[tName] = nil
	end

	-- Validate textbox input, and show error messages to the user
	local function validate(oldName, newName, newDesc, t, validateCallback)
		if #oldName  == 0 then
			interface.beginThrowError("Select the element you want to update in the ledger first")
			return
		end
		if elem.getByName(oldName) and unlockedElements[elem.getByName(oldName)] then
			interface.beginThrowError("You can't rename elements that you already unlocked")
			return
		end
		if elem.getByName(newName) and unlockedElements[elem.getByName(newName)] then
			interface.beginThrowError("You've already unlocked that element")
			return
		end

		if t and #newName == 0 then
			local oldDesc = elem.property(t, "Description")
			if oldDesc ~= newDesc then
				elem.property(t, "Description", newDesc)
				savedNotes[t] = newDesc
				interface.closeWindow(guessWindow)
				saveProgress() -- autosave
				return
			end
		end
		if #newName == 0 then
			if t and guesses[t] and #newName == 0 then
				interface.beginConfirm("Remove guess?", "Remove guess for " .. oldName .. "?", function(confirmed)
					if confirmed then
						removeGuess(t, oldName)
						interface.closeWindow(guessWindow)
					end
				end)
			else
				interface.beginThrowError("Guess the original name to proceed, or update the description if unsure")
			end
			return
		end
		if not correctInfo[newName] then
			interface.beginThrowError("An extensive search turned up no records for an element named " .. string.upper(newName))
			return
		end
		if decipher[newName] then
			interface.beginConfirm("Replace guess", "You have already guessed " .. newName .. " for another element. Reset and replace your guess?", function(confirmed)
				if confirmed then
					local replacedType = decipher[newName]
					removeGuess(replacedType, newName)

					validateCallback()
				end
			end)
			return
		end

		-- Allow changing description and renaming elements at the same time
		if t then
			local oldDesc = elem.property(t, "Description")
			if oldDesc ~= newDesc then
				elem.property(t, "Description", newDesc)
				savedNotes[t] = newDesc
			end
		end

		validateCallback()
	end

	local function onOK()
		local oldName = string.upper(oldNameTextbox:text())
		local newName = string.upper(newNameTextbox:text())
		local newDesc = newDescTextbox:text()
		if #newDesc == 0 then newDesc = "???" end
		local t = decipher[oldName]

		local function afterValidate()
			if t == nil or decipher[newName] ~= nil or correctInfo[newName] == nil then
				return
			end

			guesses[t] = newName
			elements.property(t, "Name", newName)
			decipher[oldName] = nil
			decipher[newName] = t

			local answer = checkElements()

			saveProgress() -- autosave

			interface.closeWindow(guessWindow)

			-- Show elements which were guessed correctly or a winning screen
			if answer.popup then
				interface.beginMessageBox("First victory", "I was correct, it is indeed " .. answer.names:sub(1, -3) ..
				".\n\nIt appears as if the rusty cogs of the system will no longer spin as easily as they did in the glory days of the game. " ..
				"From now on I will need 3 correct guesses, and only in the groups of 3 the ledger will reveal its secrets. Back to work.")
			elseif answer.names ~= "" then
				-- Show one of the messages
				if amountHidden == 0 then
					interface.beginMessageBox("The Ledger is Complete",
					"The fog has lifted. Every grain, every powder, every spark is accounted for. My work here is done.")
					removeGuessButton()
				else
					if amountMenuHidden == 0 and not userUnlockedVisible then
						interface.beginMessageBox("Is it the End?",
						"The main accounts are settled, yet the ledger feels light. " ..
						"Something still lingers in the margins... something I have yet to name.")
						userUnlockedVisible = true
					else
						interface.beginMessageBox("Elements restored", answer.names:sub(1, -3) .. " salvaged.")
					end
				end
			end
		end

		validate(oldName, newName, newDesc, t, afterValidate)
	end

	local okButton = Button:new(size(guessWindow).x * 2 / 3 - 1, size(guessWindow).y - 16, size(guessWindow).x / 3 + 1, 16, "\x0F\x8C\x8C\xFFOK")
	okButton:action(onOK)
	guessWindow:addComponent(okButton)

	guessWindow:onDraw(function()
		local titleLabelY = pos(guessWindow).y + pos(titleLabel).y + size(titleLabel).y
		gfx.drawLine(pos(guessWindow).x, titleLabelY, pos(guessWindow).x + size(guessWindow).x - 1, titleLabelY)
	end)

	guessWindow:onKeyPress(function(key, scan, rep, shift, ctrl, alt)
		if scan == ui.SDL_SCANCODE_RETURN or scan == ui.SDL_SCANCODE_KP_ENTER then
			onOK()
		end

		-- Not being able to focus components bothered me enough that I added this api
        if newNameTextbox.focus then
            if scan == ui.SDL_SCANCODE_TAB and not ctrl and not shift and not alt then
                if oldNameTextbox:focus() then
                    newNameTextbox:focus(true)
                elseif newNameTextbox:focus() then
                    newDescTextbox:focus(true)
                else
                    oldNameTextbox:focus(true)
                end
            end
        end
	end)

	interface.showWindow(guessWindow)
end

local guessButtonEvents = {}
local function addGuessButton()
	local buttonClicked = false
	local buttonX, buttonY = sim.XRES / 4, 10
	local buttonW, buttonH = 50, 17

	local function withinButton(x, y)
		return x >= buttonX and x < buttonX + buttonW and y >= buttonY and y < buttonY + buttonH
	end

	local function tick()
		local insideButton = withinButton(interface.mousePosition())
		if not insideButton then
			buttonClicked = false
		end

		local buttCol = insideButton and 255 or 225
		gfx.drawRect(buttonX, buttonY, buttonW, buttonH, buttCol, buttCol, buttCol, 255)
		do
			local fillCol, fillAlpha = 0, 255
			if buttonClicked then
				fillCol = 255
			elseif insideButton then
				fillCol = 255
				fillAlpha = 25
			end
			gfx.fillRect(buttonX + 1, buttonY + 1, buttonW - 2, buttonH - 2, fillCol, fillCol, fillCol, fillAlpha)
		end
		local textW, textH = gfx.textSize("Guess")
		local textCol = buttonClicked and 0 or 255
		gfx.drawText(buttonX + (buttonW - textW) / 2, buttonY + (buttonH - textH + 2) / 2, "Guess", textCol, textCol, textCol, 255)
	end

	local function mousedown(x, y, button)
		if withinButton(x, y) then
			buttonClicked = true
			return false
		end
	end

	local function mouseup(x, y, button)
		if buttonClicked and withinButton(x, y) then
			showGuessWindow()
			buttonClicked = false
		end
	end

	local function registerEvent(func, eventType)
		guessButtonEvents[eventType] = evt.register(eventType, func)
	end
	registerEvent(tick, evt.TICK)
	registerEvent(mousedown, evt.MOUSEDOWN)
	registerEvent(mouseup, evt.MOUSEUP)
end

removeGuessButton = function()
	for k, v in pairs(guessButtonEvents) do
		evt.unregister(k, v)
	end
	guessButtonEvents = {}
end

-- Don't show the button if everything is solved
if amountHidden > 0 then
	addGuessButton()
end

-- Intro pic
-- Legend:
-- 0 1 2 3
--     # #
--   #   #
local introPic = {{0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{3,2,3,3,2,2,2,3,1,0,0,0,0,0,0,0,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,3,1,0,3,1,0,0,0,0,0,0,0,0,0,0},{0,0,3,3,1,1,1,3,2,0,0,0,0,0,0,0,1,3,3,1,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,3,3,1,0,0,1,3,3,1,3,3,0,0,0,0,0,0,0,0,0,0},{0,0,3,3,2,2,3,1,0,0,0,1,3,2,3,1,0,3,3,0,3,3,0,3,3,0,3,3,3,3,1,3,3,3,3,1,0,0,0,1,3,3,3,1,0,3,3,0,0,0,0,3,3,0,3,3,3,3,1,0,1,3,2,3,1,0},{1,0,3,3,0,0,3,3,0,0,0,3,3,1,3,2,0,3,3,0,3,3,0,3,3,0,3,3,0,0,0,3,3,0,3,3,0,0,0,3,3,0,3,3,0,3,3,0,0,0,0,3,3,0,3,3,0,3,3,0,3,3,1,3,2,0},{2,3,3,2,0,0,2,3,3,2,1,2,3,1,1,1,1,3,3,1,2,3,3,2,3,1,3,2,0,0,1,3,3,0,2,3,0,0,0,2,3,3,3,2,1,3,3,0,0,0,1,3,3,1,3,3,0,3,3,1,2,3,1,1,1,0},{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{3,2,3,3,2,2,2,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,2,3,3,2,2,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},{0,0,3,3,1,1,1,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,3,0,0,0,3,3,0,2,2,0,1,0,0,0,0,0,1,0,0,0,0},{0,0,3,3,2,2,2,1,3,3,3,1,2,3,1,0,3,1,0,3,3,2,1,3,3,3,3,0,1,3,2,3,1,0,3,3,3,3,1,0,0,0,0,0,3,3,0,0,0,3,3,0,3,3,0,3,3,3,3,1,0,3,3,3,3,1},{1,0,3,3,0,0,0,3,3,0,3,3,0,3,3,1,3,3,1,3,3,0,3,3,0,3,3,0,3,3,1,3,2,0,3,3,0,0,0,0,0,0,1,0,3,3,0,0,0,3,3,0,3,3,0,3,3,0,3,3,0,3,3,0,3,3},{2,3,3,2,0,0,1,2,3,3,3,2,0,0,2,3,3,2,3,3,2,1,3,2,3,3,3,1,2,3,1,1,1,1,3,2,0,0,0,0,0,0,2,3,3,2,3,3,3,2,0,1,3,3,1,3,3,0,2,3,1,3,3,0,2,3}}

local colorFade = 500

-- Start fading intro pic
local beginFade = function ()
	colorFade = math.min(colorFade, 255)
end

-- Draw intro pic
local drawPic
drawPic = function ()
	local height = #introPic
	local width = #(introPic[1])
	local x = 70
	local y = 60
	local size = 7

	if colorFade > 0 then
		colorFade = colorFade - 2

		for i = 1, height do
			for j = 1, width do
				local c = introPic[i][j]
				local color = math.min(255, colorFade)

				if c == 1 then
					graphics.fillRect(x + size*j, y + 2*size*i + size, size, size, 255, 255, 255, color)
				elseif c == 2 then
					graphics.fillRect(x + size*j, y + 2*size*i, size, size, 255, 255, 255, color)
				elseif c == 3 then
					graphics.fillRect(x + size*j, y + 2*size*i, size, 2*size, 255, 255, 255, color)
				end
			end
		end
	end

	-- Self-destruct intro pic
	if colorFade <= 0 then
		event.unregister(event.aftersimdraw, drawPic)
		event.unregister(event.mousedown, beginFade)
	end
end

-- Show intro picture if the game is incomplete
if not (userUnlockedVisible and amountHidden == 0) then
	-- Hide vanilla intro text
	tpt.hud(0)
	tpt.hud(1)

	event.register(event.aftersimdraw, drawPic)
	event.register(event.mousedown, beginFade)
end

-- Hidden but enabled elements
--[[
DYST, 64
MORT, 77 -- bad
LIFE, 78
LOVE, 94 -- bad
FRZW, 101
BIZG, 104
BIZS, 105
PSTS, 112
EQVE, 116 -- bad
SPWN2, 117
SPWN, 118
SHD2, 120
SHD3, 121
SHD4, 122
LOLZ, 123 -- bad
BRAY, 127
EMBR, 147
VRSS, 175
VRSG, 176
RFGL, 184
--]]

Description:

Changelog: