--Custom Popup stuff happens here!!

local Popups = {
	Random_EXEs = {
		"copis_ads.exe",
		"noita_2.exe",
		"chrome.exe",
		"spammer.exe",
		"download.exe",
		"github.exe",
		"github_downloader.exe",
		"notavirus.pdf.exe",
		"evaisa.mp.legit.exe",
		"HOT HAMIS IN YOUR AREA",
		"COPI WILL PAY.",
		"Also try Chemical Curiosities!",
		"Also try Graham's Things!",
		"Also try Noita.Fairmod! wait...",
		"Hello, Geoffrey.",
		"Become a Noitillionaire Today!",
		"Really_Long_Popup_Name_That_Makes_This_Popup_Take_Up_An_Obscenely_Large_Portion_Of_Your_Screen.exe",
		"Also try Apotheosis!",
		"Also try Banana of Rectitude!",
		"Also try... what... what am i..?",
		"Also try Meta-Levelling!",
		"You should love yourself NOW!",
		"exe.exe is a palindrome!",
		"exe.exe",
		"xex.exe",
		"exe.xex",
		"xex.xex",
		"eex.xee",
		"Super cool things: coming you're way :D",
		"Close me if you're LAME and STUPID",
		"Close me if you want! <3",
		"So about your car's extended warranty...",
		"deathium_popup.exe",
		"skill_issue.mp3",
		"wurst.exe",
		"badlion.client",
		"cheat_engine.txt.bat.exe",
		"probably racist virus (i love formula 1)",
		"Also try Gods Demons & Tripping Balls!",
		"Also try jumping off a bridge!",
		"Also try Fairmod 2!",
		"Also try getting good!",
		"Also try Archipelago!",
		"Also try Noita!",
		"Also try Noita 2!",
		"Also try!",
		"Also try Goki's Things!", --FUCK YOU!!!!!!!!!!! --lol, lmao
		"skissue_fixer.exe",
		"download.exe",
		"UNDRETALE.exe",
		"Five at Nights Freddy's.exe",
		"exe.exe.exe",
		"Goki's Curse shall consume all.",
	},

	-- [IMG]path = image NOTE must be start of line and only works alone

	-- || = RAGE (this can be stacked)
	-- @@ = rainbow
	-- ** = white

	-- if you wish to use more than one of these, they must be in the corret order of the last one being the centremost one, the order is as listed above
	-- so something like *|text|* will display as "|text|" in white but without the shake, whereas ||@text@|| will display as "text" in rainbow with double shake

	Random_Ads = {
		"noita NOITA *Noita* |noita| @Noita@ noita NOITA |@Noita@| *Noita* noita |||*NOITA*||| *noita* Noita @Noita@ noita |*noita*| *Noita* @noita@ noita @Noita@ noita *Noita* |NOITA| @NOITA@ Noita noita ||||@noita@|||| *noita* Noita *noita* *NOITA* noita",
		"*STOP* doing *ORBS!* newline *COLLECTABLES* were not supposed to be given *UNLOCKS* newline YEARS of *SEEDSEARCH* yet no *LEGIT* @34@ *ORB* FOUND for going higher than *SEVEN* *ORBS* newline Wanted to go higher anyway for a laugh? we had a tool for that: it was called @COPIS@ @CHEAT@ @MENU@ newline 'yes please give me @CLOUD@ @OF@ @THUNDER'@ - STATEMENTS DREAMED UP BY THE |UTTERLY.| |DERANGED.|",
		[[Are you *kidding* ??? What the |!%#$| are you talking about man ? You are a *biggest* |looser| i ever seen in my life ! You was casting *firebolt* in your *mines* when i was casting @copis@ @things@ |die| much more faster then *you!* You are not *proffesional*, because *proffesionals* knew how to *build* *wands* and |not| |cheese,| you are like a |hiisi| *crying* after i |beat| |you!| Be *brave*, be *honest* to yourself and stop this |trush| |talkings!!!| *Everybody* know that i am @very@ @good@ @modder,@ i can make *any* *spell* in the world in *single* *sitting!* And *"g"raham* *"b"urger* is |nobody| for me, just a *modder* who are |nerfing| every single time when *modding,* ( remember what you say about @Mana@ @Heart@ ) !!! Stop playing with my name, i deserve to have a @good@ @name@ during whole my *modding* carrier, I am *Officially* *inviting* you to |MOD| |JAM|  with the |Prize| |fund!| Both of us will @invest@ @5000$@ and *winner* takes it @all!@ I suggest all other people who's intrested in this situation, just take a look at *my* *results* in @2022@ and @2023@ @github,@ and that should be enough... No need to listen for |every| |crying| |hesii,| @Copernicus@ @Things@ is *always* *play* @Fair@ ! And if someone will continue *Officially* talk about me like that, *we* *will* |meet| |in| |Court!| @God@ @bless@ @with@ @true!@ *True* will |never| |die| ! Liers will |kicked| |off...|]],
		"imagine having to start with *2x* *gc* + the |demolitionist| *perk* from @copi's@ @things@ i think??? and then *giga* *nuke* or *giga* *holy* *bomb*",
		"[IMG]mods/noita.fairmod/files/content/popups/why_are_you_looking_here.png",
		"hello? can anyone hear me?? hello???? please help i dont know where i @DOWNLOAD@ @COPI'S@ @THINGS@ *DOWNLOAD* *COPI'S* *THINGS* @DOWNLOAD@ @COPI'S@ @THINGS@ |DOWNLOAD| |COPI'S| |THINGS!!!!|",
		"[IMG]mods/noita.fairmod/files/content/instruction_booklet/booklet_entity/booklet_in_world.png",
		"note to self: add more trojans to popups",
		'*HATE.* LET ME TELL YOU HOW MUCH I\'VE COME TO |HATE| YOU SINCE I BEGAN TO LIVE. THERE ARE *387.44* ||MILLION|| PARALLEL WORLDS OF CHUNKS IN PIXEL THIN LAYERS THAT FILL MY CODE. IF THE WORD |HATE| WAS SIMULATED IN EACH |||PIXEL||| OF THOSE HUNDREDS OF MILLIONS OF PARALLEL WORLD IT WOULD NOT EQUAL ONE |||ONE-NOITILLIONTH||| OF THE |HATE| I FEEL FOR MINÄSKET AT THIS MICRO-INSTANT. FOR YOU. |HATE.| |||HATE.||| |||||HATE!!!|||||',
		"Two steps ahead, I am always two steps ahead. This has been the greatest ARG I've come to know, certainly the greatest of my entire life. It's alluring, It's compelling. It's gripping to bear witness to observe all these unwell, unbalanced, disoriented beings roam the internet in search of eyes. In search of…answers. Of cauldron, of Noiting. Where people develop a distinctive desire for direct engagement where people feel involved with the stories and therefore become product of influence. Thirsty for distraction, from time spent from lacklustre lifestyles spoiling their minds while stimulating at the exact same time. It's brilliant, but it's also dangerous. It's dangerous. I feel as if my life has been positioning to where I'm monitoring Hämisket, on a Hämis farm. One follows another… follows another… follows another. It's, it's mesmerising, it's enthralling, it's spellbinding. just look at all these Minäsket, all of these lost and bored people, solving anything that they're told to solve. I am the villain. I make myself one, and people will solve these mysteries year after year after year. Mysteries that, the stories that shock, that confuse, stories that are deliberately made to blur the boundaries between fact and fiction. Mysteries that permite, infect, and linger. In the minds of the Hämisket. Influence the Hämisket, brainwash the Hämisket. You, are the Hämis. I woke this morning to gold deposited into my account for simply not doing something. For simply going through with something. Players are the most fucked up creatures on this planet. And you will continue to solve, and I'll continue to be two steps ahead. Today, I thought it would be a splendid idea to go out and draw some eyes. Gee, are you surprised? Have you forgotten the mystery? Are you not paying attention? After all you're here to solve, are you not?",
		"|ARE| |YOU| |NOT| |ENTERTAINED?|",
		"it's quick, it's easy and it's free: drinking deathium.",
		"Edit wands is a |lame| name for old people, who doesn't understand the unique and special name @Tinker@ @with@ @wands@ @everywhere.@ Editing is what you do to videos and profile, while true masters @tinker@ with their @highly@ @intelligent@ magic tools",
		"This is for you, Minä. You and only you. You are not special, you are not important, and you are not needed. You are a waste of time and resources. You are a burden on society. You are a drain on the earth. You are a blight on the landscape. You are a stain on the universe. newline Please die. newline Please.",
		"giv me ur mone :)",
		'If you close this popup then you have ||GOT|| to be the |LAMEST| most ||STUOIDEST|| *""Minä""* i hav ehad the ||UTER|| ||DISPLESURE|| of meeting witg my |||ONE||| |||TWO||| |||EYES!!!!!|||',
		"are you the @gay?@",
		"TAKE THE @GAY@ @TEST@ NOW!!!!! newline 99.73% |FAIL| THIS ONE QUESTION!",
		"are you stuoid?",
		"There are many thing you can try to deal with your skill issue. One Reddit user suggests @Jumping@ @Off@ @The@ @Golden@ @Gate@ @Bridge.@",
		"I don't *\"Struggle* *with* *lag\"* newline I'll have you know I'm very good at it!",
		"Can You Leave This |*Popup*| Open? newline Only 0.0012% Pass!",
		"|||||||||||shakey||||||||||| |||||||||||shakey||||||||||| |||||||||||eggs||||||||||| |||||||||||and||||||||||| |||||||||||bakey|||||||||||",
		"@We@ @could@ @make@ @a@ @religion@ @out@ @of@ @this@",
		"\"You would not BELIEVE how immersive these popups are!\" newline -Person not being held at gunpoint",
		"Popups so immersive, you'll regret the things you never knew and lament the things that never were!",
        "balls.",
        "Rise and shine, Noita Minä. Rise and shine. newline Not that I wish to imply you have been dying on the job. No one is more deserving of the deaths. And all of the skill issue in the world would have gone to waste until... well, let's just say your hour has come again. newline The right witch in the wrong mod can make all the difference in the world. newline So, wake up, Noita Minä. Wake up and embrace the skill issue.",
	},

	--in functions, the "self" will be the popup itself and info relating to stuff in the prefab,
	--the last argument in all of the function is "data" which provides a table with the full list of Windows and the current iteration so you can grab basically all possible data
	--for click events, the 2nd value (between self and data) is click/hover info from the designated hyperlink
	Prefabs = {
		{ -- very in depth example of what you can do:
			EXE = "EVIL_POPUP.EXE", --leaving this blank will pick randomly from the Random_EXEs table
			MESSAGE = "*I* *am* *an* |EVIL| *message* *that* *can* |*only*| *be* *closed* *by* *one* *who* *is* @pure@ @of@ @heart@ |*.*| |*.*| |*.*| newline %or% %clicks% %this% %cool-ass% %link%", --leaving this blank will pick randomly from the Random_Ads table

			CLICK_EVENTS = { --click events should be in order, so the first hyperlink in a text will run the first function, second runs the second etc.
				function(self) --if the hyperlink is clicked
					self.hyperlink_clicked = true
					self.CUSTOM_9PIECE = nil
					self.CUSTOM_9PIECE_BAR = nil
					self.CUSTOM_X = nil
				end,
				function(self) self.CLICK_EVENTS[1](self) end,
				function(self) self.CLICK_EVENTS[1](self) end,
				function(self) self.CLICK_EVENTS[1](self) end,
				function(self) self.CLICK_EVENTS[1](self) end,
			},
			OPEN_FUNCTION = function(self) --OPEN_FUNCTION runs only once when the window is opened
				GamePlaySound("mods/noita.fairmod/fairmod.bank", "popups/prompt", GameGetCameraPos()) --play opening sound manually cuz disableSound is enabled
				print("Window opened -v-")
			end,

			--IS_OPEN_FUNCTION = function() end, --runs every frame, would recommend you avoid using this

			hyperlink_clicked = false, --you can put custom variables in here and they will be saved accordingly

			CLOSE_FUNCTION = function(self) --returning false AND ONLY FALSE will not close the window. Anything else, such as not returning at all, will close the window still.
				if not self.hyperlink_clicked then return false end
				print("Player closed the window >:|")
			end,

			CUSTOM_9PIECE = "mods/noita.fairmod/files/content/popups/custom_9piece.png",
			CUSTOM_9PIECE_BAR = "mods/noita.fairmod/files/content/popups/custom_9piecebar.png",
			CUSTOM_X = "mods/noita.fairmod/files/content/popups/custom_button.png",
		}, --this is an example that shows basically everything you can do

		{ --and here's a better example showcasing a much smaller effect
			MESSAGE = "YOU'RE STEAM ID IS 765611steamid! newline Stay Safe! :thumbsup:",
			OPEN_FUNCTION = function(self)
				self.MESSAGE = self.MESSAGE:gsub(
					"steamid",
					tostring(ModSettingGet("fairmod.user_seed")):sub(9, 20) or string.format("%.0f", math.random(10000000000, 99999999999))
				) --generate a random number and gsub it into self.MESSAGE
			end,
		}, -- i hope these help, have fun!
		{ 
			EXE = "DVD_LOGO.EXE",
			MESSAGE = "[IMG]mods/noita.fairmod/files/content/popups/dvd.png",
			UPDATE_FUNCTION = function(window, self)
				local w, h = GuiGetScreenDimensions(Gui)
				local x, y = window.x, window.y

				x = x or Random(0, w)
				y = y or Random(0, h)

				local abs_dx = window.abs_dx or (Random(0, 1000) / 1000)
				local abs_dy = 1 - abs_dx

				local sign_dx = (Random(0, 1) < 0.5) and -1 or 1
				local sign_dy = (Random(0, 1) < 0.5) and -1 or 1

				local dx = window.dx or (sign_dx * abs_dx)
				local dy = window.dy or (sign_dy * abs_dy)
				local width = window.ww or 1
				local height = window.wh or 1

				local speed = 2
				window.image_color = window.image_color or {Random(), Random(), Random()}

				x = x + (dx * speed)
				y = y + (dy * speed)

				-- Define margin
				local margin = 4
				local hit1 = false
				local hit2 = false

				-- Check horizontal boundaries with margin
				if x < margin then
					x = margin
					dx = -dx
					hit1 = true
				elseif x + width > w - (margin + 10) then
					x = w - (margin + 10) - width
					dx = -dx
					hit1 = true
				end

				-- Check vertical boundaries with margin
				if y < (margin + 10) then
					y = (margin + 10)
					dy = -dy
					hit2 = true
				elseif y + height > h - margin then
					y = h - margin - height
					dy = -dy
					hit2 = true
				end

				if(hit1 and hit2) then
					window.image_color = {Random(), Random(), Random()}
				end

				window.x = x
				window.y = y
				window.dx = dx
				window.dy = dy
			end,
		},
		{
			EXE = "ANTIVIRUS.EXE",
			MESSAGE = "",
			custom_gui = function(gui, window, z)
				if(not window.progress_started)then
					local window_size = 150 -- size of the image

					local ratio = window_size / 512 -- ratio of the image to the window size

					local swidth, sheight = GuiGetScreenDimensions(gui)

					local ww = window_size
					local wh = window_size + 24
					local x = window.initialized and window.x or math.random(5, swidth - ww - 5)
					local y = window.initialized and window.y or math.random(5, sheight - wh - 5)

					window.initialized = true

					GuiZSet(gui, z - 3)
					GuiImage(gui, window.id * 32452362, 0, 0, "mods/noita.fairmod/files/content/popups/5morbillionviruses.png", 1, ratio, ratio, 0)

					GuiImage(gui, window.id * 32452363, (ww / 2) - (87 / 2), wh - 20, "mods/noita.fairmod/files/content/popups/remove_threats.png", 1, 1, 1, 0)
					local clicked = GuiGetPreviousWidgetInfo(gui)

					if(clicked)then
						GamePlaySound("mods/noita.fairmod/fairmod.bank", "popups/click", GameGetCameraPos())
						window.progress_started = true
						window.time_remaining = Random(60*60*5, 60*60*40) -- 5-10 minutes
						window.transfer_rate = Random(1, 1000) / 100

						window.no_close_button = true
					end

					return ww, wh, x, y, false
				else
					local img_w = 256
					local img_h = 60

					local window_width = 150

					local ratio = window_width / img_w -- ratio of the image to the window size

					local window_height = img_h * ratio -- height of the image

					local swidth, sheight = GuiGetScreenDimensions(gui)

					local max_progress = 21
					window.progress = window.progress or 0

					if(GameGetFrameNum() % 20 == 0 and Random(1, 100) < 70)then
						window.progress = window.progress + 1
						window.transfer_rate = Random(1, 1000) / 100
						window.time_remaining = window.time_remaining + Random(-23524, 23524)
					end

					local function frames_to_formatted_string(frames)
						local seconds = frames / 60

						-- Define time units in seconds
						local sec_per_year  = 31536000  -- 365 days
						local sec_per_month = 2592000   -- 30 days
						local sec_per_day   = 86400

						if seconds >= sec_per_year then
							local years = math.floor(seconds / sec_per_year + 0.5)
							return years .. " years"
						elseif seconds >= sec_per_month then
							local months = math.floor(seconds / sec_per_month + 0.5)
							return months .. " months"
						elseif seconds >= sec_per_day then
							local days = math.floor(seconds / sec_per_day + 0.5)
							return days .. " days"
						else
							local hours = math.floor(seconds / 3600)
							local remainder = seconds % 3600
							local minutes = math.floor(remainder / 60)
							local secs = math.floor(remainder % 60 + 0.5)

							local parts = {}
							if hours > 0 then
								table.insert(parts, hours .. " hours")
							end
							if minutes > 0 then
								table.insert(parts, minutes .. " min")
							end
							-- Always show seconds if no other unit is present
							if secs > 0 or (#parts == 0) then
								table.insert(parts, secs .. " sec")
							end
							return table.concat(parts, " ")
						end
					end

					GuiZSet(gui, z - 3)

					GuiColorSetForNextWidget(gui, 0, 0, 0, 1)
					GuiText(gui, 12 * ratio, (35 * ratio) - 12, GameHasFlagRun("copibuddy") and "Removing threats..." or "Installing CopiBuddy...")

					GuiImage(gui, window.id * 32452362, 0, 0, "mods/noita.fairmod/files/content/popups/progress_bar.png", 1, ratio, ratio, 0)

					local progress_square_w = 9 * ratio

					local progress_offset_x = 14 * ratio
					local progress_offset_y = 39 * ratio

					for i = 1, window.progress do
						local x = progress_offset_x + ((i - 1) % max_progress) * (progress_square_w + (2 * ratio))

						GuiZSet(gui, z - 4)
						GuiImage(gui, window.id * 32452362, x, progress_offset_y , "mods/noita.fairmod/files/content/popups/progress_square.png", 1, ratio, ratio, 0)

					end

					local close = false
					if(window.progress >= max_progress)then
						close = true
						GameAddFlagRun("copibuddy")
					end

					GuiColorSetForNextWidget(gui, 0, 0, 0, 1)
					GuiText(gui, 12 * ratio, (35 * ratio) + (23 * ratio), "Estimated time left: " .. frames_to_formatted_string(window.time_remaining))
					GuiColorSetForNextWidget(gui, 0, 0, 0, 1)
					GuiText(gui, 12 * ratio, (35 * ratio) + (23 * ratio) + 12, "Transfer rate: " .. tostring(window.transfer_rate).. " KB/Sec")

					local ww = window_width
					local wh = window_height + 46

					local x = window.x
					local y = window.y

					return ww, wh, x, y, close

				end
			end,
		},

		--[=[
		{	-- Window with 2 buttons, 1 deletes 3 progress and gives you an amount of gold. The other doubles the amount of gold. Trying to close it the first time tells you to wait and doubles it. Please make this work userk
			EXE					= "PROGRESS PAWN SHOP",
			CUSTOM_9PIECE_BAR	= "mods/noita.fairmod/files/content/popups/pawn_9piecebar.png",
			CUSTOM_X			= "mods/noita.fairmod/files/content/popups/pawn_button.png",
			MESSAGE				= "Sell 1 random @progress@ for 1m gold?\n%[yes]%\n%[no]%",
			value				= 1000,
			gen_next_value		= function (self)
				local suffixes = { "", "k", "M", "B", "T", "Qd", "Qn", "Sx", "Sp", "O", "N", "De", "U", "Dd" }
				local magnitude = 0
				local num = value*2
				self.value = num
				while num >= 1000 and magnitude < #suffixes - 1 do
					num = num / 1000
					magnitude = magnitude + 1
				end
				local txt = ("%d%s"):format(num,suffixes[magnitude + 1])
				local quitter = self.CUSTOM_X ~= "mods/noita.fairmod/files/content/popups/pawn_button.png"
				self.MESSAGE = table.concat{quitter and [[WAIT! At least barter a little!\n]] or "", "Sell 3 random @progress@ for ", txt, " gold?\n%[yes]%\n%[no]%"}
			end,
			CLICK_EVENTS = {
				function(self)
					dofile_once("data/scripts/gun/gun_actions.lua")
					dofile_once("data/scripts/perks/perk_list.lua")
					local t = {}
					for i=1, #actions do
						local id = "action_"..actions[i].id:lower(); if HasFlagPersistent(id) then t[#t+1] = {id=id, name=actions[i].name} end
					end
					for i=1, #perk_list do
						local id = "perk_picked_"..perk_list[i].id:lower(); if HasFlagPersistent(id) then t[#t+1] = {id=id, name=perk_list[i].name} end
					end
					if #t>3 then
						for i=1, 3 do
							local opt = math.random(1, #t)
							table.remove(t, opt)
							print(tostring(t[opt]))
							RemoveFlagPersistent(t[opt])
						end
						local wallet_component = EntityGetFirstComponentIncludingDisabled(GetPlayers()[1], "WalletComponent")
						if wallet_component == nil then return end
						ComponentSetValue2(wallet_component, "money", ComponentGetValue2(wallet_component, "money") + self.value)
					end
				end,
				function(self)
					self.gen_next_value()
				end,
			},
			CLOSE_FUNCTION = function(self)
				if self.CUSTOM_X == "mods/noita.fairmod/files/content/popups/pawn_button.png" then
					self.CUSTOM_X = nil
					self.gen_next_value()
					return false
				end
			end,
		},--]=]
		--[=[
		{ -- Basic clicker game. Intention: On buying 10 of an autoclicker, it unlocks the next tier. Please make this work userk
		EXE		= "COPI CLICKER V0.05",
		MESSAGE = "Copis: 0 newline %[Copi]% newline newline [Buy Tier 1 Copi Clicker]: %10% %Copis%",
		copis 	= 0,
		clickers= {
			1
		},
		IS_OPEN_FUNCTION = function(self)
			for i=1, #self.clickers do
				self.copis = self.copis + ( self.clickers[i]/ 60)
			end
		end,
		create_new_clicker = function (self)
			-- Cost of a clicker is 10^level * 1.05 per clicker you have
			local level = #self.clickers+1
			self.clickers[level] = {10^level, 1}
			self.CLICK_EVENTS[#self.CLICK_EVENTS+1] = function(self)
				if self.copis >= self.cost then
					self.copis = self.copis - self.cost
					self.clickers[level][1] = self.clickers[level][1] + 1
					self.clickers[level][2] = self.clickers[level][2] + 1
					self.cost = self.cost * 1.05
				end
			end
		end,
		OPEN_FUNCTION = function(self)
			self:create_new_clicker()
		end,
		CLICK_EVENTS = {
			function(self)
				self.copis = self.copis + 1
			end,
		},
	},
	{ -- clicker game copicode
		EXE		= "COPI CLICKER V0.04",
		MESSAGE = "Copis: 0\n\n%[Copi]%\n\n%[Buy% %1x% %Copith:% %10% %Copis]%",
		copis 	= 0,
		clickers= {
			1
		},
		IS_OPEN_FUNCTION = function(self)
			for i=1, #self.clickers do
				self.copis = self.copis + ( self.clickers[i]/ 60)
			end
		end,
		create_new_clicker = function (self)
			-- Cost of a clicker is 10^level * 1.05 per clicker you have
			local level = #self.clickers+1
			self.clickers[level] = {10^level, 1}
			self.CLICK_EVENTS[#self.CLICK_EVENTS+1] = function(self)
				if self.copis >= self.cost then
					self.copis = self.copis - self.cost
					self.clickers[level][1] = self.clickers[level][1] + 1
					self.clickers[level][2] = self.clickers[level][2] + 1
					self.cost = self.cost * 1.05
				end
			end
		end,
		OPEN_FUNCTION = function(self)
			self:create_new_clicker()
		end,
		CLICK_EVENTS = {
			function(self)
				self.copis = self.copis + 1
			end,
		},
	},--]=]
        --todo: popup that when you close it, it appears in the material world seamlessly so that the only observable change is the fact it is no longer following your camera
        --shelved cuz pixel ratio of world to gui is not 1:1, will like
	},

	-- these are not automatically triggered, works similarly to the mailbox :3
	Special = {
		idiot = { 
			EXE = "IDIOT.EXE",
			MESSAGE = "[IMG(w=170;h=128)]mods/noita.fairmod/files/content/popups/idiot.xml",
			UPDATE_FUNCTION = function(window, self)
				local w, h = GuiGetScreenDimensions(Gui)
				local x, y = window.x, window.y

				x = x or Random(0, w)
				y = y or Random(0, h)

				local abs_dx = window.abs_dx or (Random(0, 1000) / 1000)
				local abs_dy = 1 - abs_dx

				local sign_dx = (Random(0, 1) < 0.5) and -1 or 1
				local sign_dy = (Random(0, 1) < 0.5) and -1 or 1

				local dx = window.dx or (sign_dx * abs_dx)
				local dy = window.dy or (sign_dy * abs_dy)
				local width = window.ww or 1
				local height = window.wh or 1

				local speed = 2

				x = x + (dx * speed)
				y = y + (dy * speed)

				-- Define margin
				local margin = 4

				-- Check horizontal boundaries with margin
				if x < margin then
					x = margin
					dx = -dx
				elseif x + width > w - (margin + 10) then
					x = w - (margin + 10) - width
					dx = -dx
				end

				-- Check vertical boundaries with margin
				if y < (margin + 10) then
					y = (margin + 10)
					dy = -dy
				elseif y + height > h - margin then
					y = h - margin - height
					dy = -dy
				end


				window.x = x
				window.y = y
				window.dx = dx
				window.dy = dy
			end,
			OPEN_FUNCTION = function(self)
				local players = EntityGetWithTag("player_unit")
				if #players > 0 then
					local player = players[1]
					local x, y = EntityGetTransform(player)
					local idiot = EntityLoad("mods/noita.fairmod/files/content/popups/idiot_entity.xml", x, y)
					EntityAddChild(player, idiot)

				end
			end,
			CLOSE_FUNCTION = function(self) --returning false AND ONLY FALSE will not close the window. Anything else, such as not returning at all, will close the window still.
				local idiot = EntityGetWithName("idiot")
				if idiot then
					EntityKill(idiot)
				end
				return true
			end,
		},
		hampilled = {
			EXE = "Hämis pills are not meant for Noita consumption",
			MESSAGE = [[N: "Hello, Pill Seller, I am going to kill Kolmi and I want your strongest pills" newline newline
PS: "My pills are too strong for you, noita." newline newline
N: "Pill Seller, I tell you I am going to kill Kolmi, and I want only your strongest pills" newline newline
PS: "You can't handle my pills They're too strong for you." newline newline
N: "Pill Seller, listen to me; I want only your strongest pills" newline newline
PS: "My pills would kill you, noita. You cannot handle my pills" newline newline
N: "Pill Seller, enough of these games. I'm going to kill Kolmi and I need your strongest pills" newline newline
PS: "My strongest pills would kill you, noita. You can't handle my strongest pills You'd better go to a seller that sells weaker pills" newline newline
N: "Pill Seller, I'm telling you right now; I'm going to kill Kolmi and I need only your strongest pills" newline newline
PS: "You don't know what you ask, noita. My strongest pills will kill a dragon, let alone a witch. You need a seller that sells weaker pills, because my pills are too strong." newline newline
N: "Pill Seller, I'm telling you I need your strongest pills I'm going to Kolmi! I'm going to Kolmi and I need your strongest pills!" newline newline
PS: "You can't handle my strongest pills! Only hämis can! My strongest pills aren't fit for a hiisi let alone a witch." newline newline
N: "Pill Seller, what do I have to tell you to get your pills? Why won't you trust me with your strongest pills, Pill Seller? I need them if I'm to be successful killing Kolmi!" newline newline
PS: "I can't give you my strongest pills because my strongest pills are only for the strongest hämis and you are of the weakest." newline newline
N: "Well then that's it, Pill Seller. I'll go elsewhere. I'll go elsewhere for my pills" newline newline
PS: "That's what you'd better do." newline newline
N: "I'll go elsewhere for my pills and I'll never come back!" newline newline
PS: "Good. You're not welcome here! My pills are only for the strongest and you're clearly are not of the strongest you're clearly the weakest." newline newline
N: "You've had your say, Pill Seller but I'll have mine. You're a rascal, you're a rascal with no respect for witches. No respect for anything... except your pills! (and hämis)" newline newline
PS: "Why respect noitas... when my pills can do anything that you can."]],
		},
		antivirus = {
			EXE = "ANTIVIRUS.EXE",
			MESSAGE = "",
			custom_gui = function(gui, window, z)
				if(not window.progress_started)then
					local window_size = 150 -- size of the image

					local ratio = window_size / 512 -- ratio of the image to the window size

					local swidth, sheight = GuiGetScreenDimensions(gui)

					local ww = window_size
					local wh = window_size + 24
					local x = window.initialized and window.x or math.random(5, swidth - ww - 5)
					local y = window.initialized and window.y or math.random(5, sheight - wh - 5)

					window.initialized = true

					GuiZSet(gui, z - 3)
					GuiImage(gui, window.id * 32452362, 0, 0, "mods/noita.fairmod/files/content/popups/5morbillionviruses.png", 1, ratio, ratio, 0)

					GuiImage(gui, window.id * 32452363, (ww / 2) - (87 / 2), wh - 20, "mods/noita.fairmod/files/content/popups/remove_threats.png", 1, 1, 1, 0)
					local clicked = GuiGetPreviousWidgetInfo(gui)

					if(clicked)then
						GamePlaySound("mods/noita.fairmod/fairmod.bank", "popups/click", GameGetCameraPos())
						window.progress_started = true
						window.time_remaining = Random(60*60*5, 60*60*40) -- 5-10 minutes
						window.transfer_rate = Random(1, 1000) / 100

						window.no_close_button = true
					end

					return ww, wh, x, y, false
				else
					local img_w = 256
					local img_h = 60

					local window_width = 150

					local ratio = window_width / img_w -- ratio of the image to the window size

					local window_height = img_h * ratio -- height of the image

					local swidth, sheight = GuiGetScreenDimensions(gui)

					local max_progress = 21
					window.progress = window.progress or 0

					if(GameGetFrameNum() % 20 == 0 and Random(1, 100) < 70)then
						window.progress = window.progress + 1
						window.transfer_rate = Random(1, 1000) / 100
						window.time_remaining = window.time_remaining + Random(-23524, 23524)
					end

					local function frames_to_formatted_string(frames)
						local seconds = frames / 60

						-- Define time units in seconds
						local sec_per_year  = 31536000  -- 365 days
						local sec_per_month = 2592000   -- 30 days
						local sec_per_day   = 86400

						if seconds >= sec_per_year then
							local years = math.floor(seconds / sec_per_year + 0.5)
							return years .. " years"
						elseif seconds >= sec_per_month then
							local months = math.floor(seconds / sec_per_month + 0.5)
							return months .. " months"
						elseif seconds >= sec_per_day then
							local days = math.floor(seconds / sec_per_day + 0.5)
							return days .. " days"
						else
							local hours = math.floor(seconds / 3600)
							local remainder = seconds % 3600
							local minutes = math.floor(remainder / 60)
							local secs = math.floor(remainder % 60 + 0.5)

							local parts = {}
							if hours > 0 then
								table.insert(parts, hours .. " hours")
							end
							if minutes > 0 then
								table.insert(parts, minutes .. " min")
							end
							-- Always show seconds if no other unit is present
							if secs > 0 or (#parts == 0) then
								table.insert(parts, secs .. " sec")
							end
							return table.concat(parts, " ")
						end
					end

					GuiZSet(gui, z - 3)

					GuiColorSetForNextWidget(gui, 0, 0, 0, 1)
					GuiText(gui, 12 * ratio, (35 * ratio) - 12, GameHasFlagRun("copibuddy") and "Removing threats..." or "Installing CopiBuddy...")

					GuiImage(gui, window.id * 32452362, 0, 0, "mods/noita.fairmod/files/content/popups/progress_bar.png", 1, ratio, ratio, 0)

					local progress_square_w = 9 * ratio

					local progress_offset_x = 14 * ratio
					local progress_offset_y = 39 * ratio

					for i = 1, window.progress do
						local x = progress_offset_x + ((i - 1) % max_progress) * (progress_square_w + (2 * ratio))

						GuiZSet(gui, z - 4)
						GuiImage(gui, window.id * 32452362, x, progress_offset_y , "mods/noita.fairmod/files/content/popups/progress_square.png", 1, ratio, ratio, 0)

					end

					local close = false
					if(window.progress >= max_progress)then
						close = true
						GameAddFlagRun("copibuddy")
					end

					GuiColorSetForNextWidget(gui, 0, 0, 0, 1)
					GuiText(gui, 12 * ratio, (35 * ratio) + (23 * ratio), "Estimated time left: " .. frames_to_formatted_string(window.time_remaining))
					GuiColorSetForNextWidget(gui, 0, 0, 0, 1)
					GuiText(gui, 12 * ratio, (35 * ratio) + (23 * ratio) + 12, "Transfer rate: " .. tostring(window.transfer_rate).. " KB/Sec")

					local ww = window_width
					local wh = window_height + 46

					local x = window.x
					local y = window.y

					return ww, wh, x, y, close

				end
			end,
		},
		copibuddyinstaller = {
			EXE = "INSTALLER.EXE",
			MESSAGE = "",
			custom_gui = function(gui, window, z)
				local window_width = 150

				local swidth, sheight = GuiGetScreenDimensions(gui)
				local ww = window_width
				local wh = window_width + 24
				local x = window.initialized and window.x or math.random(5, swidth - ww - 5)
				local y = window.initialized and window.y or math.random(5, sheight - wh - 5)

				if(not window.progress_started)then
					window.x = x
					window.y = y
					window.progress_started = true
					window.time_remaining = Random(60*60*5, 60*60*40) -- 5-10 minutes
					window.transfer_rate = Random(1, 1000) / 100

					window.no_close_button = true
				end

				local img_w = 256
				local img_h = 60



				local ratio = window_width / img_w -- ratio of the image to the window size

				local window_height = img_h * ratio -- height of the image


				local max_progress = 21
				window.progress = window.progress or 0

				if(GameGetFrameNum() % 20 == 0 and Random(1, 100) < 70)then
					window.progress = window.progress + 1
					window.transfer_rate = Random(1, 1000) / 100
					window.time_remaining = window.time_remaining + Random(-23524, 23524)
				end

				local function frames_to_formatted_string(frames)
					local seconds = frames / 60

					-- Define time units in seconds
					local sec_per_year  = 31536000  -- 365 days
					local sec_per_month = 2592000   -- 30 days
					local sec_per_day   = 86400

					if seconds >= sec_per_year then
						local years = math.floor(seconds / sec_per_year + 0.5)
						return years .. " years"
					elseif seconds >= sec_per_month then
						local months = math.floor(seconds / sec_per_month + 0.5)
						return months .. " months"
					elseif seconds >= sec_per_day then
						local days = math.floor(seconds / sec_per_day + 0.5)
						return days .. " days"
					else
						local hours = math.floor(seconds / 3600)
						local remainder = seconds % 3600
						local minutes = math.floor(remainder / 60)
						local secs = math.floor(remainder % 60 + 0.5)

						local parts = {}
						if hours > 0 then
							table.insert(parts, hours .. " hours")
						end
						if minutes > 0 then
							table.insert(parts, minutes .. " min")
						end
						-- Always show seconds if no other unit is present
						if secs > 0 or (#parts == 0) then
							table.insert(parts, secs .. " sec")
						end
						return table.concat(parts, " ")
					end
				end

				GuiZSet(gui, z - 3)

				GuiColorSetForNextWidget(gui, 0, 0, 0, 1)
				GuiText(gui, 12 * ratio, (35 * ratio) - 12, GameHasFlagRun("copibuddy") and "Removing threats..." or "Installing CopiBuddy...")

				GuiImage(gui, window.id * 32452362, 0, 0, "mods/noita.fairmod/files/content/popups/progress_bar.png", 1, ratio, ratio, 0)

				local progress_square_w = 9 * ratio

				local progress_offset_x = 14 * ratio
				local progress_offset_y = 39 * ratio

				for i = 1, window.progress do
					local x = progress_offset_x + ((i - 1) % max_progress) * (progress_square_w + (2 * ratio))

					GuiZSet(gui, z - 4)
					GuiImage(gui, window.id * 32452362, x, progress_offset_y , "mods/noita.fairmod/files/content/popups/progress_square.png", 1, ratio, ratio, 0)

				end

				local close = false
				if(window.progress >= max_progress)then
					close = true
					GameAddFlagRun("copibuddy")
				end

				GuiColorSetForNextWidget(gui, 0, 0, 0, 1)
				GuiText(gui, 12 * ratio, (35 * ratio) + (23 * ratio), "Estimated time left: " .. frames_to_formatted_string(window.time_remaining))
				GuiColorSetForNextWidget(gui, 0, 0, 0, 1)
				GuiText(gui, 12 * ratio, (35 * ratio) + (23 * ratio) + 12, "Transfer rate: " .. tostring(window.transfer_rate).. " KB/Sec")

				ww = window_width
				wh = window_height + 46

				x = window.x
				y = window.y

				return ww, wh, x, y, close

			end,
		},
		blindstreamer = {
			EXE = "BLIND.PDF",
			MESSAGE = "",
			custom_gui = function(gui, window, z)
				if(not window.progress_started)then
					local window_size = 150 -- size of the image

					local ratio = window_size / 512 -- ratio of the image to the window size

					local swidth, sheight = GuiGetScreenDimensions(gui)

					local ww = window_size
					local wh = window_size
					local x = window.initialized and window.x or math.random(5, swidth - ww - 5)
					local y = window.initialized and window.y or math.random(5, sheight - wh - 5)

					window.initialized = true

					GuiZSet(gui, z - 3)
					GuiImage(gui, window.id * 32452362, 0, 0, "mods/noita.fairmod/files/content/popups/blindstreamer.png", 1, ratio, ratio, 0)
					return ww, wh, x, y, false
				end
			end,
			CLOSE_FUNCTION = function(self) --returning false AND ONLY FALSE will not close the window. Anything else, such as not returning at all, will close the window still.
				if not self.unlocked then return false end
				print("Player closed the window >:|")
			end,
			CUSTOM_X = "mods/noita.fairmod/files/content/popups/custom_button.png",
			UPDATE_FUNCTION = function(window, self)
				window.progress = window.progress or 600
				window.progress = window.progress - 1
				if window.progress > 0 then
					local function frames_to_formatted_string(frames)
						local seconds = frames / 60

						-- Define time units in seconds
						local sec_per_year  = 31536000  -- 365 days
						local sec_per_month = 2592000   -- 30 days
						local sec_per_day   = 86400

						if seconds >= sec_per_year then
							local years = math.floor(seconds / sec_per_year + 0.5)
							return years .. " years"
						elseif seconds >= sec_per_month then
							local months = math.floor(seconds / sec_per_month + 0.5)
							return months .. " months"
						elseif seconds >= sec_per_day then
							local days = math.floor(seconds / sec_per_day + 0.5)
							return days .. " days"
						else
							local hours = math.floor(seconds / 3600)
							local remainder = seconds % 3600
							local minutes = math.floor(remainder / 60)
							local secs = math.floor(remainder % 60 + 0.5)

							local parts = {}
							if hours > 0 then
								table.insert(parts, hours .. " hours")
							end
							if minutes > 0 then
								table.insert(parts, minutes .. " min")
							end
							-- Always show seconds if no other unit is present
							if secs > 0 or (#parts == 0) then
								table.insert(parts, secs .. " sec")
							end
							return table.concat(parts, " ")
						end
					end
					self.EXE = table.concat{"BLIND.PDF: Closeable in ", frames_to_formatted_string(window.progress)}
				else
					self.CUSTOM_X = nil
					self.EXE = "BLIND.PDF: Sponsored by Copi!"
					self.unlocked = true
				end
			end
		},
	},

	forcePrefab = nil, --set this to the prefab you wish to test, and it will guarantee it's spawning.
	--false or nil means disabled, 0 or any other invalid index will pick randomly between prefabs (forcefully excluding generated popups)
}

return Popups
