Alternative to Recursive Table References (Lua)

Status
Not open for further replies.

Joat

Gum Phoenix
I understand that what I am trying to do is probably not possible how I'm trying to do it. However, I'm hoping that what I wish to do is possible in some form. Below are the functions relevant to what I am trying to do.

The task begins with converse(p,settingsMain) being called (where p is the player). This brings up the settings menu, where the player (currently) has the option to either change the stat display mode or exit.

The major issue arises when the player selects one of the stat display modes and, thus, converse(p,settingsMain) is called. Presumably because the ancestor table itself is settingsMain, this ends in an error: "WARNING: animalscript.wad|LUA_MAIN:160: attempt to index local 'd' (a nil value)", where line 160 is "if d[5] != nil then p.optionCount = 4".

I'm wondering if anyone here may be able to figure out an elegant way to return to the top menu of the settings. I COULD just have the settings menu exit every single time a setting is changed, but that's clunky and I'd rather avoid that.

Code:
//Initiates the dialogue. d, in this case, is the dialogue tree, while p is the player who is having the dialogue.
local function converse(p,d)
	p.oldPowers = {}
	for i=0,22
		p.oldPowers[i] = p.powers[i]
	end
	freezeAll(true)
	p.mo.momx = 0
	p.mo.momy = 0
	p.conversation = d //The current dialogue.
	p.cursor = 1 //The selected option.
	p.dialogueSector = p.mo.subsector.sector //The sector the conversation started in. Some conversations may modify the tag to make a different conversation happen later.
	//Set how many options there are. This will influence the movement wrapping of the cursor.
	if d[5] != nil then p.optionCount = 4
	elseif d[4] != nil then p.optionCount = 3
	elseif d[3] != nil then p.optionCount = 2
	else p.optionCount = 1 end
end

//Main settings menu dialogue.
local settingsMain = {"Select an option.\n\n\130Stat Mode:\128 Whether to show bars, numbers, or both for health and energy.",
	{"Stat Mode",
		{"\130Stat Mode\128\nWhether to show bars, numbers, or both for health and energy.\n\nWhile in the inventory, the display will default to numbers only, unless \"Always Bars\" is chosen.",
			{"Bars only (default)", function(p)
				opt_barmode = 1
				converse(p,settingsMain)
			end},
			{"Bars and numbers", function(p)
				opt_barmode = 2
				converse(p,settingsMain)
			end},
			{"Numbers only", function(p)
				opt_barmode = 3
				converse(p,settingsMain)
			end},
			{"Always bars", function(p)
				opt_barmode = 4
				converse(p,settingsMain)
			end}
		},
	},
	{"Exit", function(p)
		p.menuOpen = true //This part, to return to the inventory, has not been tested yet and probably does not yet work, but is not relevant to this question.
	end}
}

//Responds to input while dialogue is open.
local function dialogueHandler(p)
	//Move the cursor if up or down is held and play a beep.
	if p.cmd.forwardmove > 25 and p.oldForwardMove <= 25 and p.optionCount > 1 then
		p.cursor = (p.cursor - 1) % p.optionCount
		S_StartSound(nil,142,p)
	end
	if p.cmd.forwardmove < -25 and p.oldForwardMove >= -25 and p.optionCount > 1 then
		p.cursor = (p.cursor + 1) % p.optionCount
		S_StartSound(nil,142,p)
	end
	if p.cursor < 1 then p.cursor = p.cursor + p.optionCount end //If the cursor position is 0 or negative, correct that.
	//If the user presses jump or spin, activate the option selected and play a beep.
	if isPress(p,BT_USE) or isPress(p,BT_CUSTOM1) or isPress(p,BT_CUSTOM2) then
		S_StartSound(nil,142,p)
		//If it's a table, set the conversation to it.
		if type(p.conversation[p.cursor+1][2]) == "table" then
			converse(p,p.conversation[p.cursor+1][2])
		//If it's a function, close the conversation and activate it. Note that the function in question can open a new conversation.
		elseif type(p.conversation[p.cursor+1][2]) == "function" then
			local func = p.conversation[p.cursor+1][2]
			p.conversation = nil
			func(p)
			if p.menuOpen == false then freezeAll(false) end
		else
			p.conversation = nil //Close the conversation.
			freezeAll(false)
		end
	end
end
 
Is that the full script? As far as I can see "settingsMain" is not actually used in the script following it's creation, so I wonder if it's actually another call of converse() with something else as the second parameter that's causing the problems. Particularly since the error indicates that 'd' == nil, so you cannot use d[index] on it.
 
That was not the full script. Here is the full script:

Code:
//Freeslot object declarations. The rest of the declarations are in MAINCFG.
freeslot("MT_THERMOS","MT_CELL","MT_BATTERY","MT_REBREATHER","MT_CARD_EMPTY","MT_MEDIKIT","MT_CONVERTER","MT_BULLET","MT_SLUNGBULLET","MT_FIRESEED","MT_BULLETPILE","MT_CLIP","MT_BULLETBOX","MT_SPARKLASER")

//Options
local opt_barmode = 1 //If 1 or 4, show HP/EP bars only. If 2, show HP/EP bars and numbers both. If 3, show only numbers. Note that the latter is always done while in inventory or dialogue, unless mode 4 is chosen.

//Shop Inventories
local shopInv = {{},{},{},{},{}}
local buyBack = {}
local shopName = {"Hunted Deals","Global Grocery","Glenn's Boutique","Marshall's Arms","Wyre Wares"}
local shopSeeds = {}
local shopSeedsBuyback = {}

local interactText = {} //The text to show when using examining the area. If this is specified, item 1 will have the question mark icon.
local interactDialogue = {} //The dialogue tree to use when speaking with whoever is in the area. If this is specified, item 1 will have the talk icon.
local interactFunc = {} //The function to execute when interacting with the area. If this is specified, item 1 will have the exclamation mark icon.

//					1								 						2									3						4					5						6																7																				8							9								10													11									12											13										14							15										16											17										18										19														20									21									22											23														24					25										26								27							28						29											30								31
local price =		{0,														6,									8,						10,					10,						250,															400,																			410,						50,								500,												500,								550,										425,									450,						550,									425,										500,									500,									0,														1000,								1000,								6,											10,														8,					200,									50,								10,							50,						50,											50,								1000}
local itemName =	{"Misc. Action",										"Thermos",							"Water",				"Warm Drink",		"Cold Drink",			"Egg Rebreather",												"Egg Card",																		"Super Ring Egg Card",		"Medikit",						"Armageddon Shield Card",							"Force Shield Card",				"Robotnik Card",							"Elemental Shield Card",				"Shield Card",				"Invincibility Card",					"Speed Card",								"Whirlwind Shield Card",				"Attraction Shield Card",				"Sling",												"Inferno Shield Card",				"Liquid Shield Card",				"Fire Seeds",								"Instant Recharge",										"Sling Bullets",	"Flametongue",							"Duster",						"Bullet Clip",				"Bullet Box",			"Wyre Miner",								"Wyre Zapper",					"Ambrosial Apple"} //The names of each item, for the description bar. Unlike the display names at the top of the HUD, the item names shown in the description bar never change.
local itemDesc =	{"Examine and interact with the world in various ways",	"Fill with water to drink later.",	"Drink to cool off.",	"Drink to warm up.","Drink to cool off.",	"A prototype mini-rebreather device, used to replenish air.",	"A card that can retrieve and store the contents of a monitor.",				"A card storing 10 rings.",	"Treats bleeding and burns.",	"Use to receive an Armageddon Shield.",				"Use to receive a Force Shield.",	"Use to be healed by three bars of health.","Use to receive an elemental shield.",	"Use to receive a shield.",	"Use to gain brief invulnerability.",	"Use to gain increased speed for a while.",	"Use to receive a Whirlwind Shield.",	"Use to receive an Attraction Shield.",	"Hurls sling bullets at foes.",							"Use to receive an Inferno Shield.","Use to receive a Liquid Shield.",	"Eat to spew flames at your foes.",			"Refills one bar of energy instantly upon purchase.",	"30 sling bullets.","Spews fire at foes.",					"A standard, reliable pistol.",	"A clip of six bullets.",	"A box of 30 bullets.",	"Intended for easy mining.",				"Intended for self-defense.",	"Fully heals and permanently adds one bar to maximum health."} //The first line of the description.
local itemDesc2 =	{"",													"",									"",						"",					"",						"Revolutionary, but highly energy-inefficient.",				"Energy cost varies by monitor type. Must be right in front of the monitor.",	"Use to receive the rings.","",								"Use Misc. Action in midair to manually detonate.",	"",									"",											"",										"",							"",										"",											"Jump in midair for an extra boost.",	"",										"Somewhat slow, not good against heavily armored foes.","",									"",									"Damages the body, however.",				"",														"",					"Uses fire seeds instead of bullets.",	"",								"",							"",						"Extremely low range, but pierces armor.",	"Weak, but pierces armor.",		"Maximum health cannot exceed 12 bars."} //The second line of the description.
local itemShortDesc = {"Examine and interact with the world",				"Fill with water to drink later",	"Drink to cool off",	"Drink to warm up",	"Drink to cool off",	"Replenishes air at high energy cost",							"Extract contents of adjacent monitor.",										"Use to receive 10 rings.",	"Treats bleeding and burns.",	"Use to receive an Armageddon Shield.",				"Use to receive a Force Shield.",	"Use to be healed by three bars of health.","Use to receive an elemental shield.",	"Use to receive a shield.",	"Use to gain brief invulnerability.",	"Use to gain increased speed for a while.",	"Use to receive a Whirlwind Shield",	"Use to receive an Attraction Shield",	"Hurls sling bullets that only hurt weaker foes",		"Use to receive an Inferno Shield",	"Use to receive a Liquid Shield",	"Eat and spew fire at the cost of health.",	"Instantly refills one bar of energy",					"30 sling bullets",	"Uses fire seeds to spew fire",			"A standard pistol",			"A clip of six bullets.",	"A box of 30 bullets",	"Strong laser with extremely short range",	"Weak electrical projectile",	"Adds 1 bar to max health up to 12 and fully heals"} //The description to show at low resolutions.
local emptyItem =	{0,														0,									6,						6,					6,						0,																0,																				7,							0,								7,													7,									7,											7,										7,							7,										7,											7,										7,										0,														7,									7,									0,											0,														0,					0,										0,								0,							0,						0,											0,								0} //What the item becomes when emptied. For most, this is 0, which will forbid emptying the item.
local delayType =	{0,														0,									2,						2,					2,						0,																0,																				0,							0,								0,													0,									0,											0,										0,							0,										0,											0,										0,										1,														0,									0,									1,											0,														0,					1,										1,								0,							0,						1,											1,								0}
local gameFrozen = false //Is the game pseudo-paused?

local cachedIcons = {} //Loading these on the fly causes the game to slow to a crawl. A cache is necessary. To prevent the game from slowing down too much over time, this cache is cleared every time the inventory is opened or closed.
local cacheUsed = {} //Stores when the patch was last used. Any patch not used during that frame is removed from the cache.

local nearestMonitor = nil //Closest monitor within egg card range.

/*local function v.cachePatch(patchName)
	if cachedIcons[patchName] == nil then
		cachedIcons[patchName] = v.cachePatch(patchName)
		print("Added "..patchName.." to cache")
	end
	cacheUsed[patchName] = leveltime
	return cachedIcons[patchName]
end*/

local function freezeAll(frozen)
	gameFrozen = frozen
	if frozen then
		for obj in thinkers.iterate("mobj")
			if obj.flags & (MF_PUSHABLE|MF_BOSS|MF_MISSILE|MF_ENEMY) or obj.type == MT_PLAYER then
				A_SetObjectFlags(obj,MF_NOTHINK,2)
			end
		end
	else
		for obj in thinkers.iterate("mobj")
			if obj.flags & (MF_PUSHABLE|MF_BOSS|MF_MISSILE|MF_ENEMY) or obj.type == MT_PLAYER then
				A_SetObjectFlags(obj,MF_NOTHINK,1)
			end
		end
	end
end

local function STagChange(oldT,newT)
	for sec in sectors.iterate
		if sec.tag==oldT then sec.tag=newT end
	end
end

//Look for the tag in the sector the player is in or any of its FOFs.
local function checkSectors(p, st)
	local s = p.mo.subsector.sector
	if s.tag == st then return true end
	for fof in s.ffloors()
		if fof.sector.tag == st then return true end
	end
	return false
end

local function isPress(p,bf) //This detects whether the given key has JUST been pressed down.
	if p.cmd.buttons & bf and not (p.oldButtons & bf) then return true end
	return false
end

//Thanks, StackOverflow! http://stackoverflow.com/questions/5249629/modifying-a-character-in-a-string-in-lua
local function replace_char(pos, str, r)
    return str:sub(1, pos-1) .. r .. str:sub(pos+1)
end

//A text wrapping function. This returns a string with newlines in proper locations to wrap the string s to the w constraint. This function assumes there are no double-spaces. This also does not properly wrap single words that exceed the width.
local function wrap(v,s,w,widthType)
	s = string.gsub(s,"-","- ") //This (and the reverse done later) is done to properly handle wrapping around dashes.
	//Relevent character positions.
	local lastNewline = 0
	local noth
	local lastSpace = string.find(s," ")
	if lastSpace == nil then return s end //If there are no spaces in the string, return the string.
	local currentSpace = string.find(s," ",lastSpace+1)
	local reachedEnd = false //If this is set to true, return the string at the end of the iteration.
	if widthType == nil then widthType = "normal" end //If widthType is undefined, presume normal.
	while true do //Not actually an infinite loop, since it contains a return statement.
		if currentSpace == nil then //If no new space was found, set the effective new space to just past the string length and set the function to return at the end of this iteration.
			currentSpace = string.len(s)+1
			reachedEnd = true
		end
		//This makes sure that manually-added newline characters do not confuse the algorithm.
		while string.find(string.sub(s,lastNewline+1,currentSpace),"\n") != nil do
			if v.stringWidth(string.gsub(string.sub(s,lastNewline+1,lastNewline+string.find(string.sub(s,lastNewline+1,currentSpace),"\n")),"- ","-"),0,widthType) > w then //The gsub prevents dashes from skewing the results. If the text from the last newline character exceeds the width allowed...
				s = replace_char(lastSpace, s, "\n")
			end
			lastNewline = lastNewline + string.find(string.sub(s,lastNewline+1,currentSpace),"\n")
		end
		if v.stringWidth(string.gsub(string.sub(s,lastNewline+1,currentSpace-1),"- ","-"),0,widthType) > w then //The gsub prevents dashes from skewing the results. If the text from the last newline character exceeds the width allowed...
			s = replace_char(lastSpace, s, "\n")
			lastNewline = lastSpace
		end
		//Get a new currentSpace and set lastSpace to old currentSpace value.
		lastSpace = currentSpace
		currentSpace = string.find(s," ",lastSpace+1)
		if reachedEnd == true then
			s = string.gsub(s,"- ","-")
			return s
		end
	end
end

//Initiates the dialogue. d, in this case, is the dialogue tree, while p is the player who is having the dialogue.
local function converse(p,d)
	p.oldPowers = {}
	for i=0,22
		p.oldPowers[i] = p.powers[i]
	end
	freezeAll(true)
	p.mo.momx = 0
	p.mo.momy = 0
	p.conversation = d //The current dialogue.
	p.cursor = 1 //The selected option.
	p.dialogueSector = p.mo.subsector.sector //The sector the conversation started in. Some conversations may modify the tag to make a different conversation happen later.
	//Set how many options there are. This will influence the movement wrapping of the cursor.
	if d[5] != nil then p.optionCount = 4
	elseif d[4] != nil then p.optionCount = 3
	elseif d[3] != nil then p.optionCount = 2
	else p.optionCount = 1 end
end

//Main settings menu dialogue.
local settingsMain = {"Select an option.\n\n\130Stat Mode:\128 Whether to show bars, numbers, or both for health and energy.",
	{"Stat Mode",
		{"\130Stat Mode\128\nWhether to show bars, numbers, or both for health and energy.\n\nWhile in the inventory, the display will default to numbers only, unless \"Always Bars\" is chosen.",
			{"Bars only (default)", function(p)
				opt_barmode = 1
				converse(p,settingsMain)
			end},
			{"Bars and numbers", function(p)
				opt_barmode = 2
				converse(p,settingsMain)
			end},
			{"Numbers only", function(p)
				opt_barmode = 3
				converse(p,settingsMain)
			end},
			{"Always bars", function(p)
				opt_barmode = 4
				converse(p,settingsMain)
			end}
		},
	},
	{"Exit", function(p)
		p.menuOpen = true
	end}
}

//Draws the dialogue boxes and text.
local function drawDialogue(v,p)
	if p.conversation == nil then return end //Draw nothing if not in a conversation.
	v.draw(7,10,v.cachePatch("DIALOGUE")) //Draw main box.
	v.draw(7,142,v.cachePatch("CHOICES")) //Draw choice box.
	v.drawString(12,15,wrap(v,p.conversation[1],296)) //Draw text for the text box.
	//For each option, draw it in the options box, in yellow if selected.
	for i=1,p.optionCount do
		if i==p.cursor then
			v.drawString(12,137+(i*10),string.char(0x82)..p.conversation[i+1][1]..string.char(0x80)) //For whatever reason, using V_YELLOWMAP forced the string into all caps.
		else v.drawString(12,137+(i*10),p.conversation[i+1][1]) end
	end
end

//Responds to input while dialogue is open.
local function dialogueHandler(p)
	//Move the cursor if up or down is held and play a beep.
	if p.cmd.forwardmove > 25 and p.oldForwardMove <= 25 and p.optionCount > 1 then
		p.cursor = (p.cursor - 1) % p.optionCount
		S_StartSound(nil,142,p)
	end
	if p.cmd.forwardmove < -25 and p.oldForwardMove >= -25 and p.optionCount > 1 then
		p.cursor = (p.cursor + 1) % p.optionCount
		S_StartSound(nil,142,p)
	end
	if p.cursor < 1 then p.cursor = p.cursor + p.optionCount end //If the cursor position is 0 or negative, correct that.
	//If the user presses jump or spin, activate the option selected and play a beep.
	if isPress(p,BT_USE) or isPress(p,BT_CUSTOM1) or isPress(p,BT_CUSTOM2) then
		S_StartSound(nil,142,p)
		//If it's a table, set the conversation to it.
		if type(p.conversation[p.cursor+1][2]) == "table" then
			converse(p,p.conversation[p.cursor+1][2])
		//If it's a function, close the conversation and activate it. Note that the function in question can open a new conversation.
		elseif type(p.conversation[p.cursor+1][2]) == "function" then
			local func = p.conversation[p.cursor+1][2]
			p.conversation = nil
			func(p)
			if p.menuOpen == false then freezeAll(false) end
		else
			p.conversation = nil //Close the conversation.
			freezeAll(false)
		end
	end
end

//Return the string representing the icon for the item in question. This could be handled by an array IN THEORY, but some items change icon depending on circumstances.
local function getIcon(p,inventory,num)
	local item = inventory[num]
	if item == 1 then
		if P_IsObjectOnGround(p.mo) == false and p.powers[pw_shield] == 4 then
			return "ICONARMA"
		elseif interactText[p.mo.subsector.sector.tag] != nil then
			return "ICONINTE"
		elseif interactDialogue[p.mo.subsector.sector.tag] != nil then
			return "ICONINTT"
		else
			return "ICONINTN"
		end
	elseif item == 2 then
		if checkSectors(p,701) or checkSectors(p,702) or checkSectors(p,703) then return "ICONTHOP"
		else return "ICONTHER" end
	elseif item == 3 then return "ICONTHWA"
	elseif item == 4 then return "ICONTHHT"
	elseif item == 5 then return "ICONTHCD"
	elseif item == 6 then return "ICONREBR"
	//Empty Card
	elseif item == 7 then
		if nearestMonitor == nil then return "ICONCDEM" end
		if nearestMonitor.type == MT_SUPERRINGBOX or nearestMonitor.type == MT_SNEAKERTV then
			if p.energy >= 500 then return "ICONCDYS" else return "ICONCDNO" end
		elseif nearestMonitor.type == MT_PITYTV then
			if p.energy >= 1000 then return "ICONCDYS" else return "ICONCDNO" end
		elseif nearestMonitor.type == MT_FIRETV or nearestMonitor.type == MT_WATERTV then
			if p.energy >= 1500 then return "ICONCDYS" else return "ICONCDNO" end
		elseif nearestMonitor.type == MT_BLUETV or nearestMonitor.type == MT_BLACKTV or nearestMonitor.type == MT_GREENTV or nearestMonitor.type == MT_WHITETV or nearestMonitor.type == MT_YELLOWTV then
			if p.energy >= 2000 then return "ICONCDYS" else return "ICONCDNO" end
		elseif nearestMonitor.type == MT_EGGMANBOX or nearestMonitor.type == MT_INV then
			if p.energy >= 3000 then return "ICONCDYS" else return "ICONCDNO" end
		end
		return "ICONCDEM"
	elseif item == 8 then return "ICONCDRG"
	elseif item == 9 then return "ICONMEDI"
	elseif item == 10 then return "ICONCDAR"
	elseif item == 11 then return "ICONCDFO"
	elseif item == 12 then return "ICONCDEG"
	elseif item == 13 then return "ICONCDEL"
	elseif item == 14 then return "ICONCDPI"
	elseif item == 15 then return "ICONCDIN"
	elseif item == 16 then return "ICONCDSP"
	elseif item == 17 then return "ICONCDWH"
	elseif item == 18 then return "ICONCDMA"
	elseif item == 19 then return "ICONSLNG"
	elseif item == 20 then return "ICONCDFI"
	elseif item == 21 then return "ICONCDWA"
	elseif item == 22 then return "ICONFISD"
	elseif item == 23 then return "ICONCEL"..(leveltime%6+1)
	elseif item == 24 then return "ICONBULS"
	elseif item == 25 then return "ICONFLTN"
	elseif item == 26 then return "ICONDUST"
	elseif item == 27 then return "ICONCLIP"
	elseif item == 28 then return "ICONBBOX"
	elseif item == 29 then return "ICONMINR"
	elseif item == 30 then return "ICONZAPR"
	elseif item == 31 then return "ICONAMAP"
	else return "" end
end

//Return the number to show under the item's icon.
local function getItemNum(p,num,overrideShopMode)
	if p.shopMode == 1 and num > 0 then
		local item = shopInv[p.currentShop][num]
		if item == 19 then return shopSlingBullets
		elseif item == 22 then return shopSeeds[p.currentShop]
		else return "" end
	elseif p.shopMode == 3 and num > 0 then
		local item = buyBack[num]
		if item == 19 then return shopSlingBulletsBuyback
		elseif item == 22 then return shopSeedsBuyback[p.currentShop]
		else return "" end
	else
		local item = p.slots[num]
		if item == 19 then return p.bullets
		elseif item == 26 then return p.ammo
		elseif item == 22 or item == 25 then return p.seeds
		else return "" end
	end
end

//Return the string representing the name for the item in question. This could be handled by an array IN THEORY, but some items change name depending on circumstances.
local function getName(p,num)
	local item = p.slots[num]
	if item == 1 then
		if P_IsObjectOnGround(p.mo) == false and p.powers[pw_shield] == 4 then
			return "Detonate"
		elseif interactText[p.mo.subsector.sector.tag] != nil then
			return "Examine"
		elseif interactDialogue[p.mo.subsector.sector.tag] != nil then
			return "Talk"
		else
			return ""
		end
	elseif item == 2 then return "Thermos"
	elseif item == 3 then return "Water"
	elseif item == 4 then return "Warm Drink"
	elseif item == 5 then return "Cold Drink"
	elseif item == 6 then return "Rebreather"
	elseif item == 7 then return "Egg Card"
	elseif item == 8 then return "10 Rings"
	elseif item == 9 then return "Medikit"
	elseif item == 10 then return "Armageddon"
	elseif item == 11 then return "Force"
	elseif item == 12 then return "Robotnik"
	elseif item == 13 then return "Elemental"
	elseif item == 14 then return "Shield"
	elseif item == 15 then return "Invincibility"
	elseif item == 16 then return "Speed"
	elseif item == 17 then return "Whirlwind"
	elseif item == 18 then return "Attraction"
	elseif item == 19 then return "Sling"
	elseif item == 20 then return "Inferno"
	elseif item == 21 then return "Liquid"
	elseif item == 22 then return "Fire Seeds"
	elseif item == 25 then return "Flametongue"
	elseif item == 26 then return "Duster"
	elseif item == 29 then return "Wyre Miner"
	elseif item == 30 then return "Wyre Zapper"
	else return "" end
end

//Use the specified item.
local function useItem(p,num)
	local item = p.slots[num]
	//Misc. Action
	if item == 1 then
		//Examine
		if P_IsObjectOnGround(p.mo) == false and p.powers[pw_shield] == 4 then
			P_BlackOw(p)
		elseif interactText[p.mo.subsector.sector.tag] != nil then
			print(interactText[p.mo.subsector.sector.tag])
		elseif interactDialogue[p.mo.subsector.sector.tag] != nil then
			converse(p,interactDialogue[p.mo.subsector.sector.tag])
		end
	//Thermos
	elseif item == 2 then
		if checkSectors(p,701) then
			p.slots[num] = 3
			S_StartSound(nil,53,p)
		elseif checkSectors(p,703) then
			p.slots[num] = 4
			S_StartSound(nil,53,p)
		elseif checkSectors(p,702) then
			p.slots[num] = 5
			S_StartSound(nil,53,p)
		end
	//Water
	elseif item == 3 then
		if p.temp >= 1000 then p.temp = p.temp - 1000
		elseif p.temp < 1000 and p.temp > 0 then p.temp = 0 end
		S_StartSound(nil,55,p)
		p.slots[num] = 2
	//Warm Drink
	elseif item == 4 then
		if p.temp <= -3000 then p.temp = p.temp + 3000
		elseif p.temp > -3000 and p.temp < 0 then p.temp = 0 end
		S_StartSound(nil,55,p)
		p.slots[num] = 2
	//Cold Drink
	elseif item == 5 then
		if p.temp >= 3000 then p.temp = p.temp - 3000
		elseif p.temp < 3000 and p.temp > 0 then p.temp = 0 end
		S_StartSound(nil,55,p)
		p.slots[num] = 2
	//Rebreather
	elseif item == 6 and p.mo.eflags & MFE_UNDERWATER and p.energy >= 1000 then
		p.powers[pw_underwater] = 1050
		P_RestoreMusic(p)
		p.energy = p.energy - 1000
		S_StartSound(nil,36,p)
	//Empty Card
	elseif item == 7 then
		if nearestMonitor == nil then return end
		if nearestMonitor.type == MT_SUPERRINGBOX then
			if p.energy >= 500 then
				p.slots[num] = 8
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 500
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_BLACKTV then
			if p.energy >= 2000 then
				p.slots[num] = 10
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 2000
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_BLUETV then
			if p.energy >= 2000 then
				p.slots[num] = 11
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 2000
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_EGGMANBOX then
			if p.energy >= 3000 then
				p.slots[num] = 12
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 3000
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_GREENTV then
			if p.energy >= 2000 then
				p.slots[num] = 13
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 2000
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_PITYTV then
			if p.energy >= 1000 then
				p.slots[num] = 14
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 1000
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_INV then
			if p.energy >= 3000 then
				p.slots[num] = 15
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 3000
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_SNEAKERTV then
			if p.energy >= 500 then
				p.slots[num] = 16
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 500
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_WHITETV then
			if p.energy >= 2000 then
				p.slots[num] = 17
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 2000
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_YELLOWTV then
			if p.energy >= 2000 then
				p.slots[num] = 18
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 2000
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_WATERTV then
			if p.energy >= 1500 then
				p.slots[num] = 21
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 1500
			else
				S_StartSound(nil,24,p)
			end
		elseif nearestMonitor.type == MT_FIRETV then
			if p.energy >= 1500 then
				p.slots[num] = 20
				S_StartSound(nil,298,p)
				nearestMonitor.type = MT_TV_EMPTY
				P_SetMobjStateNF(nearestMonitor, S_TV_EMPTY)
				p.energy = p.energy - 1500
			else
				S_StartSound(nil,24,p)
			end
		end
	//Super Ring Card
	elseif item == 8 then
		p.mo.health = p.mo.health + 10
		S_StartSound(nil,108,p)
		p.slots[num] = 7
	//Medikit
	elseif item == 9 and p.hp < p.temphp then
		p.hp = min(p.temphp,p.hp+30)
		S_StartSound(nil,163,p)
		p.slots[num] = 0
	//Armageddon Shield Card
	elseif item == 10 and p.powers[pw_shield] != 4 and p.powers[pw_shield] != 260 then
		p.powers[pw_shield] = SH_BOMB
		P_SpawnShieldOrb(p)
		S_StartSound(nil,120,p)
		p.slots[num] = 7
	//Force Shield Card
	elseif item == 11 and p.powers[pw_shield] < 512 then
		p.powers[pw_shield] = SH_FORCE|1
		P_SpawnShieldOrb(p)
		S_StartSound(nil,120,p)
		p.slots[num] = 7
	//Robotnik Card
	elseif item == 12 and p.hp < p.maxhp then
		p.hp = p.hp + 30
		p.temphp = p.temphp + 30
		S_StartSound(nil,163,p)
		p.slots[num] = 7
	//Elemental Shield Card
	elseif item == 13 and p.powers[pw_shield] != 3 and p.powers[pw_shield] != 259 then
		p.powers[pw_shield] = SH_ELEMENTAL
		P_SpawnShieldOrb(p)
		S_StartSound(nil,120,p)
		p.slots[num] = 7
	//Shield Card
	elseif item == 14 and p.powers[pw_shield] == 0 or p.powers[pw_shield] == 256 then
		p.powers[pw_shield] = SH_PITY
		P_SpawnShieldOrb(p)
		S_StartSound(nil,120,p)
		p.slots[num] = 7
	//Invincibility Card
	elseif item == 15 and p.powers[pw_invulnerability] == 0 then
		p.powers[pw_invulnerability] = 20*TICRATE
		P_RestoreMusic(p)
		p.slots[num] = 7
	//Speed Card
	elseif item == 16 and p.powers[pw_sneakers] == 0 then
		p.powers[pw_sneakers] = 20*TICRATE
		P_RestoreMusic(p)
		p.slots[num] = 7
	//Whirlwind Shield Card
	elseif item == 17 and p.powers[pw_shield] != 1 and p.powers[pw_shield] != 257 then
		p.powers[pw_shield] = SH_JUMP
		P_SpawnShieldOrb(p)
		S_StartSound(nil,120,p)
		p.slots[num] = 7
	//Attraction Shield Card
	elseif item == 18 and p.powers[pw_shield] != 2 and p.powers[pw_shield] != 258 then
		p.powers[pw_shield] = SH_ATTRACT
		P_SpawnShieldOrb(p)
		S_StartSound(nil,120,p)
		p.slots[num] = 7
	//Sling
	elseif item == 19 and p.bullets > 0 and p.attackTimer == 0 then
		S_StartSound(nil,103,p)
		p.bullets = p.bullets - 1
		P_SpawnPlayerMissile(p.mo,MT_SLUNGBULLET)
		p.attackTimer = 20
	//Inferno Shield Card
	elseif item == 20 and p.powers[pw_shield] != 9 and p.powers[pw_shield] != 265 then
		A_FlameShield(p.mo,-1)
		p.slots[num] = 7
	//Liquid Shield Card
	elseif item == 21 and p.powers[pw_shield] != 10 and p.powers[pw_shield] != 266 then
		A_AquaShield(p.mo,-1)
		p.slots[num] = 7
	//Fire Seeds
	elseif item == 22 and p.attackTimer == 0 then
		p.flameHarmful = true
		p.fireTimer = P_RandomRange(18,27)
		p.seeds = p.seeds - 1
		if p.seeds <= 0 then
			p.seeds = 0
			p.slots[num] = 0
		end
	//Flametongue
	elseif item == 25 and p.seeds > 0 and p.attackTimer == 0 then
		p.flameHarmful = false
		p.fireTimer = P_RandomRange(18,27)
		p.attackTimer = p.fireTimer //Doing this will prevent health from being lost, as well.
		p.seeds = p.seeds - 1
		if p.seeds <= 0 then
			p.seeds = 0
			p.slots[num] = 0
		end
	//Duster
	elseif item == 26 and p.ammo > 0 and p.attackTimer == 0 then
		S_StartSound(nil,sfx_s3k4d,p)
		p.ammo = p.ammo - 1
		P_SpawnPlayerMissile(p.mo,MT_JETTBULLET)
		p.attackTimer = 15
	//Wyre Miner
	elseif item == 29 and p.energy >= 100 and p.attackTimer == 0 then
		p.energy = p.energy - 100
		P_SpawnPlayerMissile(p.mo,MT_LASER)
		p.attackTimer = 10
	//Wyre Zapper
	elseif item == 30 and p.energy >= 100 and p.attackTimer == 0 then
		p.energy = p.energy - 100
		P_SpawnPlayerMissile(p.mo,MT_SPARKLASER)
		p.attackTimer = 50
	end
end

local function giveItem(p,item)
	if p.player.slots[-3]==0 then
		p.player.slots[-3]=item
		return false
	end
	if p.player.slots[-1]==0 then
		p.player.slots[-1]=item
		return false
	end
	if p.player.slots[-2]==0 then
		p.player.slots[-2]=item
		return false
	end
	for i=1, 32 do
		if p.player.slots[i]==0 then
			p.player.slots[i]=item
			return false
		end
	end
	return true //If there is no room, don't pick up the item.
end

local function grantSeeds(p,num)
	if p.seeds >= 99 or num < 0 then return true end //If seeds are already maxed or number given is negative, return false.
	if p.seeds == 0 then //If the player does not have any seeds, give the Fire Seeds item.
		if giveItem(p.mo,22) == true then return true end //If such cannot be done, return true (failure).
	end
	p.seeds = min(p.seeds+num,99)
end

local function segLength()
	local length = 3
	for i=0,6 do
		if emeralds & 2^i then
			length = length + 1
		end
	end
	return length
end

//Status HUD draw function.
local function drawStatus(v,p)
	if p.playerstate != PST_LIVE then return nil end //This should not show anything if the player is dead or if the menu is open.
	local gdy = 12
	local dy = gdy
	if opt_barmode == 2 or opt_barmode == 3 or ((p.menuOpen == true or p.conversation != nil) and opt_barmode != 4) then
		//Draw Life
		/*v.drawString(310,dy,p.temphp.."\135/\128"..(p.maxhpseg*segLength()).." \135HP\128",V_SNAPTOTOP|V_SNAPTORIGHT,"right")
		v.drawString(310,dy+10,(p.energy/100).."\132/\128"..(p.maxEnergy*10).." \132EP\128",V_SNAPTOTOP|V_SNAPTORIGHT,"right")
		if p.temphp!=p.hp then v.drawString(310,dy+20,(p.temphp-p.hp).." \133Bleed\128",V_SNAPTOTOP|V_SNAPTORIGHT,"right") end*/
		v.drawString(310,dy-2,p.temphp.."/"..(p.maxhpseg*segLength()).." HP",V_SNAPTOTOP|V_SNAPTORIGHT,"right")
		if p.maxEnergy > 0 then v.drawString(310,dy+8,(p.energy/100).."/"..(p.maxEnergy*10).." EP",V_SNAPTOTOP|V_SNAPTORIGHT,"right") end
		if p.temphp!=p.hp then v.drawString(310,dy+18,(p.temphp-p.hp).." Bleed",V_SNAPTOTOP|V_SNAPTORIGHT,"right") end
	end
	if opt_barmode == 2 then
		gdy = 42
	end
	if !opt_barmode == 3 and ((p.menuOpen == false and p.conversation == nil) or opt_barmode == 4) then
		dy = gdy
		//Draw Life
		v.draw(304,10,v.cachePatch("LIFETOP"),V_SNAPTOTOP|V_SNAPTORIGHT)
		for seg=1,p.maxhpseg do
			if seg > 1 then
				v.draw(304,dy,v.cachePatch("LIFEMID"),V_SNAPTOTOP|V_SNAPTORIGHT)
				dy = dy + 1
			end
			local segL = segLength()
			for i=1,segL do
				if p.hp > seg*segL+i-segL-1 then v.draw(305,dy,v.cachePatch("LIFEFULL"),V_SNAPTOTOP|V_SNAPTORIGHT)
				elseif p.temphp > seg*segL+i-segL-1 then v.draw(305,dy,v.cachePatch("LIFETEMP"),V_SNAPTOTOP|V_SNAPTORIGHT)
				else v.draw(305,dy,v.cachePatch("ENERGYEM"),V_SNAPTOTOP|V_SNAPTORIGHT) end
				dy = dy + 1
			end
		end
		v.draw(304,dy,v.cachePatch("LIFEBOTT"),V_SNAPTOTOP|V_SNAPTORIGHT)
		//Draw Energy
		local dy = gdy+2
		if p.maxEnergy > 0 then
			v.draw(296,10,v.cachePatch("ENERGYTO"),V_SNAPTOTOP|V_SNAPTORIGHT)
			for seg=1,p.maxEnergy do
				if seg > 1 then
					v.draw(296,dy,v.cachePatch("ENERGYMI"),V_SNAPTOTOP|V_SNAPTORIGHT)
					dy = dy + 3
				end
				for i=1,10 do
					if p.energy > (seg*10+i-11)*100 then v.draw(297,dy,v.cachePatch("ENERGYFU"),V_SNAPTOTOP|V_SNAPTORIGHT)
					else v.draw(297,dy,v.cachePatch("ENERGYEM"),V_SNAPTOTOP|V_SNAPTORIGHT) end
					dy = dy + 1
				end
			end
			v.draw(296,dy,v.cachePatch("ENERGYBO"),V_SNAPTOTOP|V_SNAPTORIGHT)
		end
	end
	//Draw status effects.
	dy = 10
	if p.temp <= -1000 then
		v.drawString(10,dy+20,"Cold",V_SNAPTOTOP|V_SNAPTOLEFT|V_BLUEMAP)
		v.draw(10,dy+1,v.cachePatch("ICONCOLD"),V_SNAPTOTOP|V_SNAPTOLEFT)
		v.drawNum(50,dy+5,-p.temp/1000,V_SNAPTOTOP|V_SNAPTOLEFT)
		dy = dy + 30
	elseif p.temp >= 1000 then
		v.drawString(10,dy+20,"Hot",V_SNAPTOTOP|V_SNAPTOLEFT|V_REDMAP)
		v.draw(10,dy+1,v.cachePatch("ICONHEAT"),V_SNAPTOTOP|V_SNAPTOLEFT)
		v.drawNum(50,dy+5,p.temp/1000,V_SNAPTOTOP|V_SNAPTOLEFT)
		dy = dy + 30
	end
	if p.wet > 0 then
		v.drawString(10,dy+20,"Wet",V_SNAPTOTOP|V_SNAPTOLEFT|V_BLUEMAP)
		v.draw(10,dy+1,v.cachePatch("ICONWET"),V_SNAPTOTOP|V_SNAPTOLEFT)
		v.drawNum(50,dy+5,(p.wet-1)/500+1,V_SNAPTOTOP|V_SNAPTOLEFT)
		dy = dy + 30
	end
	if p.mo.eflags & MFE_UNDERWATER and 1050-p.powers[pw_underwater] > 350 and p.powers[pw_underwater]!=0 then
		v.drawString(10,dy+20,"Drowning",V_SNAPTOTOP|V_SNAPTOLEFT|V_BLUEMAP)
		v.draw(10,dy+1,v.cachePatch("ICONDRWN"),V_SNAPTOTOP|V_SNAPTOLEFT)
		v.drawNum(50,dy+5,(1050-p.powers[pw_underwater]-386)/70+1,V_SNAPTOTOP|V_SNAPTOLEFT)
		dy = dy + 30
	end
end

local function cannotAttack(p,slot)
	if p.attackTimer > 0 and delayType[p.slots[slot]] == 1 then return true end
	return false
end

local function drawInventory(v,p)
	if p.playerstate != PST_LIVE then return nil end //This should not show anything if the player is dead or if the menu is open.
	v.drawString(100-v.stringWidth("SPIN")/2,10,"SPIN",V_SNAPTOTOP)
	if cannotAttack(p,-3) then
		v.draw(89,22,v.cachePatch("NOATTACK"),V_SNAPTOTOP|V_50TRANS)
	end
	if p.slots[-3] > 0 then v.draw(91,24,v.cachePatch(getIcon(p,p.slots,-3)),V_SNAPTOTOP) end
	v.drawString(108,34,getItemNum(p,-3),V_SNAPTOTOP,"right")
	v.draw(88,21,v.cachePatch("MENUCURS"),V_SNAPTOTOP)
	v.drawString(160-v.stringWidth("C1")/2,10,"C1",V_SNAPTOTOP)
	if cannotAttack(p,-1) then
		v.draw(149,22,v.cachePatch("NOATTACK"),V_SNAPTOTOP|V_50TRANS)
	end
	if p.slots[-1] > 0 then v.draw(151,24,v.cachePatch(getIcon(p,p.slots,-1)),V_SNAPTOTOP) end
	v.drawString(168,34,getItemNum(p,-1),V_SNAPTOTOP,"right")
	v.draw(148,21,v.cachePatch("MENUCURS"),V_SNAPTOTOP)
	v.drawString(220-v.stringWidth("C2")/2,10,"C2",V_SNAPTOTOP)
	if cannotAttack(p,-2) then
		v.draw(209,22,v.cachePatch("NOATTACK"),V_SNAPTOTOP|V_50TRANS)
	end
	if p.slots[-2] > 0 then v.draw(211,24,v.cachePatch(getIcon(p,p.slots,-2)),V_SNAPTOTOP) end
	v.drawString(228,34,getItemNum(p,-2),V_SNAPTOTOP,"right")
	v.draw(208,21,v.cachePatch("MENUCURS"),V_SNAPTOTOP)
	//Draw the names if the screen is large enough and the menu is not open. Otherwise, don't bother. The text is either illegibly small or potentially covered up by the inventory.
	if not p.menuOpen and v.width() >= 640 and v.height() >= 400 then
		v.drawString(100-v.stringWidth(getName(p,-3),0,"small")/2,48,getName(p,-3),V_SNAPTOTOP,"small")
		v.drawString(160-v.stringWidth(getName(p,-1),0,"small")/2,48,getName(p,-1),V_SNAPTOTOP,"small")
		v.drawString(220-v.stringWidth(getName(p,-2),0,"small")/2,48,getName(p,-2),V_SNAPTOTOP,"small")
	end
	if not p.menuOpen then return nil end //This should not show anything else if the menu is closed.
	//The global offset for all things on the menu. The menu should be centered on the screen horizontally. The global vertical offset will likely be consistently 50, but this may change.
	local offsetX = 7
	local offsetY = 50 //This should be AT LEAST 50.
	//Heading
	if p.shopMode == 0 then
		v.drawString(160,offsetY,"Inventory",0,"center") //Draw inventory heading.
	else
		v.drawString(160,offsetY,shopName[p.currentShop],0,"center") //Draw inventory heading.
		//v.drawString(160-v.stringWidth(shopName, 0, "thin")/2,offsetY,shopName,0,"thin")
	end
	v.drawString(310,offsetY,"Rings: "..p.ringCount,0,"right") //Draw points.
	v.draw(offsetX,offsetY+10,v.cachePatch("INVENBOX")) //Draw main box.
	v.draw(offsetX+218,offsetY+10,v.cachePatch("SIDEBOX")) //Draw side box.
	v.draw(offsetX,offsetY+124,v.cachePatch("DESCBOX")) //Draw description box.
	//Draw current inventory.
	local invUsed = p.slots //The inventory table to draw.
	if p.shopMode == 1 then invUsed = shopInv[p.currentShop]
	elseif p.shopMode == 3 then invUsed = buyBack end
	for i=1, 32 //Draw inventory items.
		if invUsed[i] > 0 then v.draw(offsetX+9+((i-1)%8)*26,offsetY+19+((i-1)/8)*26,v.cachePatch(getIcon(p,invUsed,i))) end
		v.drawString(offsetX+26+((i-1)%8)*26,offsetY+29+((i-1)/8)*26,getItemNum(p,i),0,"right")
	end
	if p.shopMode > 0 then
		if p.shopMode == 1 then
			v.draw(offsetX+227,offsetY+19,v.cachePatch("ICONBUY"))
		else
			v.draw(offsetX+227,offsetY+19,v.cachePatch("ICONBUY"),V_TRANSLUCENT)
		end
		if p.shopMode == 2 then
			v.draw(offsetX+253,offsetY+19,v.cachePatch("ICONSELL"))
		else
			v.draw(offsetX+253,offsetY+19,v.cachePatch("ICONSELL"),V_TRANSLUCENT)
		end
		if p.shopMode == 3 then
			v.draw(offsetX+279,offsetY+19,v.cachePatch("ICONBUYB"))
		else
			v.draw(offsetX+279,offsetY+19,v.cachePatch("ICONBUYB"),V_TRANSLUCENT)
		end
		//Draw buy price.
		if p.shopMode == 1 and invUsed[8*p.cY+p.cX+1] > 0 then
			v.drawString(10,offsetY,"Price: "..(price[invUsed[8*p.cY+p.cX+1]])) //Draw price.
		//Draw sell or buy-back price.
		elseif invUsed[8*p.cY+p.cX+1] > 0 then
			if price[invUsed[8*p.cY+p.cX+1]] == 0 then
				v.drawString(10,offsetY,"NO SALE") //Draw points.
			else
				v.drawString(10,offsetY,"Price: "..(price[invUsed[8*p.cY+p.cX+1]]/2)) //Draw price.
			end
		end
		//Shop option description.
		if p.cX == 8 and p.cY == 0 then
			if v.width() < 640 or v.height() < 400 then
				v.drawString(offsetX+5,offsetY+129,"Buy items for rings",0,"thin")
			else
				v.drawString(offsetX+5,offsetY+129,"Buy items for rings.",0,"small")
			end
		elseif p.cX == 9 and p.cY == 0 then
			if v.width() < 640 or v.height() < 400 then
				v.drawString(offsetX+5,offsetY+129,"Sell items for rings",0,"thin")
			else
				v.drawString(offsetX+5,offsetY+129,"Sell items for rings.",0,"small")
			end
		elseif p.cX == 10 and p.cY == 0 then
			if v.width() < 640 or v.height() < 400 then
				v.drawString(offsetX+5,offsetY+129,"Buy items back at selling price",0,"thin")
			else
				v.drawString(offsetX+5,offsetY+129,"Buy items back at selling price.",0,"small")
				v.drawString(offsetX+5,offsetY+135,"Items not bought back are cleared at the end of each level.",0,"small")
			end
		end
	else
		//Draw options icon.
		v.draw(offsetX+253,offsetY+19,v.cachePatch("ICONSETT"))
		//Draw converter.
		if p.convertMode == 1 then v.draw(offsetX+227,offsetY+97,v.cachePatch("ICONCONH")) end
		if p.convertMode == 2 then v.draw(offsetX+227,offsetY+97,v.cachePatch("ICONCONE")) end
		//Draw batteries owned.
		if p.maxEnergy >= 1 then
			if leveltime%2 == 1 then
				v.draw(offsetX+248,offsetY+93,v.cachePatch("ICONBATT"))
			else
				v.draw(offsetX+248,offsetY+93,v.cachePatch("ICONBAT2"))
			end
			if p.maxEnergy < 10 then v.drawString(offsetX+263,offsetY+104,p.maxEnergy)
			else v.drawString(offsetX+262,offsetY+104,p.maxEnergy,0,"thin") end
		end
		//Draw any emeralds currently owned.
		if emeralds & EMERALD1 then v.draw(offsetX+284,offsetY+102,v.cachePatch("TEMER1")) end
		if emeralds & EMERALD2 then v.draw(offsetX+280,offsetY+96,v.cachePatch("TEMER2")) end
		if emeralds & EMERALD3 then v.draw(offsetX+288,offsetY+96,v.cachePatch("TEMER3")) end
		if emeralds & EMERALD4 then v.draw(offsetX+292,offsetY+102,v.cachePatch("TEMER4")) end
		if emeralds & EMERALD5 then v.draw(offsetX+288,offsetY+108,v.cachePatch("TEMER5")) end
		if emeralds & EMERALD6 then v.draw(offsetX+280,offsetY+108,v.cachePatch("TEMER6")) end
		if emeralds & EMERALD7 then v.draw(offsetX+276,offsetY+102,v.cachePatch("TEMER7")) end
		//Settings description.
		if p.cX == 9 and p.cY == 0 then
			if v.width() < 640 or v.height() < 400 then
				v.drawString(offsetX+5,offsetY+129,"Change various game settings",0,"thin")
			else
				v.drawString(offsetX+5,offsetY+129,"Change various game settings.",0,"small")
			end
		//Battery description.
		elseif p.cX == 9 and p.cY == 3 and p.maxEnergy > 0 then
			if v.width() < 640 or v.height() < 400 then
				v.drawString(offsetX+5,offsetY+129,"Your batteries. Used to power many devices.",0,"thin")
			else
				v.drawString(offsetX+5,offsetY+129,"Your collected Egg Batteries.",0,"small")
				v.drawString(offsetX+5,offsetY+135,"Used to power any of Eggman's devices you have taken.",0,"small")
			end
		//Emeralds description.
		elseif p.cX == 10 and p.cY == 3 and emeralds != 0 then
			if v.width() < 640 or v.height() < 400 then
				v.drawString(offsetX+5,offsetY+129,"Your chaos emeralds. Can often boost devices built by Eggman.",0,"thin")
			else
				v.drawString(offsetX+5,offsetY+129,"Your collected chaos emeralds.",0,"small")
				v.drawString(offsetX+5,offsetY+135,"Devices built by Eggman can often be boosted by these.",0,"small")
			end
		//Converter description.
		elseif p.cX == 8 and p.cY == 3 and p.convertMode != 0 then
			if v.width() < 640 or v.height() < 400 then
				v.drawString(offsetX+5,offsetY+129,"Converts rings to health or energy     Select to toggle preference",0,"thin")
			else
				v.drawString(offsetX+5,offsetY+129,"This converter converts collected rings to health or energy.",0,"small")
				v.drawString(offsetX+5,offsetY+135,"Select to toggle which one is prioritized.",0,"small")
			end
		//Debug room description.
		elseif p.cY == 1 and p.cX == 9 and gamemap != 172 then
			if v.width() < 640 or v.height() < 400 then
				v.drawString(offsetX+5,offsetY+129,"Nothing to see in this slot",0,"thin")
			else
				v.drawString(offsetX+5,offsetY+129,"Nothing to see in this slot.",0,"small")
				v.drawString(offsetX+5,offsetY+135,"Yup, completely blank.",0,"small")
			end
		end
	end
	//Draw item selection cursor.
	if (leveltime/10)%2 == 1 then
		if p.cX < 8 then v.draw(offsetX+6+(p.cX*26),offsetY+16+(p.cY*26),v.cachePatch("MENUCURS"))
		else v.draw(offsetX+16+(p.cX*26),offsetY+16+(p.cY*26),v.cachePatch("MENUCURS")) end
	end
	//Draw the selected item's name and description.
	if p.cX < 8 and invUsed[8*p.cY+p.cX+1] > 0 then
		//Draw a single thin line if at low res.
		if v.width() < 640 or v.height() < 400 then
			v.drawString(offsetX+5,offsetY+129,itemName[invUsed[8*p.cY+p.cX+1]]..":",V_YELLOWMAP,"thin")
			v.drawString(offsetX+5+v.stringWidth(itemName[invUsed[8*p.cY+p.cX+1]]..":  ",0,"thin"),offsetY+129,itemShortDesc[invUsed[8*p.cY+p.cX+1]],0,"thin")
		else
			v.drawString(offsetX+5,offsetY+129,itemName[invUsed[8*p.cY+p.cX+1]]..":",V_YELLOWMAP,"small")
			v.drawString(offsetX+5+v.stringWidth(itemName[invUsed[8*p.cY+p.cX+1]]..":  ",0,"small"),offsetY+129,itemDesc[invUsed[8*p.cY+p.cX+1]],0,"small")
			v.drawString(offsetX+5,offsetY+135,itemDesc2[invUsed[8*p.cY+p.cX+1]],0,"small")
		end
	end
end

local function initp(p)
	p.flameHarmful = false //Whether the fire being spewed damages the player.
	p.attackTimer = 0 //While this is positive, attacks (such as the sling) will not function.
	p.temp = 0 //Temperature. If this reaches positive or negative 10,000, the rat dies.
	p.wet = 0 //Wetness. Caps at 5,000.
	p.drown = 0 //Drowning timer. This will control the in-game timer. Default cap is 1050 (30 seconds). Egg Rebreather triples this time.
	p.menuOpen = false //This tracks whether the menu is open.
end

local function reinit(p)
	initp(p.player)
end

//Handles the inventory menu.
local function menuHandler(p)
	//Move the cursor, if needed.
	if p.cmd.forwardmove > 25 and p.oldForwardMove <= 25 then
		p.cY = (p.cY - 1) % 4
		S_StartSound(nil,142,p)
	end
	if p.cmd.forwardmove < -25 and p.oldForwardMove >= -25 then
		p.cY = (p.cY + 1) % 4
		S_StartSound(nil,142,p)
	end
	if p.cmd.sidemove > 25 and p.oldSideMove <= 25 then
		p.cX = (p.cX + 1) % 11
		S_StartSound(nil,142,p)
	end
	if p.cmd.sidemove < -25 and p.oldSideMove >= -25 then
		p.cX = (p.cX - 1) % 11
		S_StartSound(nil,142,p)
	end
	if p.cX < 0 then p.cX = p.cX + 11 end
	if p.cY < 0 then p.cY = p.cY + 4 end
	//Non-shop
	if p.shopMode == 0 then
		//Handle converter changing.
		if isPress(p,BT_USE) and (p.slots[-3] != 0 or p.slots[8*p.cY+p.cX+1]) != 0 and p.cX < 8 then
			local swap = p.slots[-3]
			p.slots[-3] = p.slots[8*p.cY+p.cX+1]
			p.slots[8*p.cY+p.cX+1] = swap
			S_StartSound(nil,244,p)
		elseif isPress(p,BT_CUSTOM1) and (p.slots[-1] != 0 or p.slots[8*p.cY+p.cX+1] != 0) and p.cX < 8 then
			local swap = p.slots[-1]
			p.slots[-1] = p.slots[8*p.cY+p.cX+1]
			p.slots[8*p.cY+p.cX+1] = swap
			S_StartSound(nil,244,p)
		elseif isPress(p,BT_CUSTOM2) and (p.slots[-2] != 0 or p.slots[8*p.cY+p.cX+1] != 0) and p.cX < 8 then
			local swap = p.slots[-2]
			p.slots[-2] = p.slots[8*p.cY+p.cX+1]
			p.slots[8*p.cY+p.cX+1] = swap
			S_StartSound(nil,244,p)
		elseif (isPress(p,BT_USE) or isPress(p,BT_CUSTOM1) or isPress(p,BT_CUSTOM2)) and p.cY == 3 and p.cX == 8 then
			if p.convertMode == 1 then p.convertMode = 2
			elseif p.convertMode == 2 then p.convertMode = 1 end
			if p.convertMode != 0 then S_StartSound(nil,319,p) end
		//Handle settings.
		elseif (isPress(p,BT_USE) or isPress(p,BT_CUSTOM1) or isPress(p,BT_CUSTOM2)) and p.cY == 0 and p.cX == 9 then
			p.menuOpen = false
			converse(p,settingsMain)
		//Handle debug room.
		elseif (isPress(p,BT_USE) or isPress(p,BT_CUSTOM1) or isPress(p,BT_CUSTOM2)) and gamemap != 172 and p.cY == 1 and p.cX == 9 then
			G_ExitLevel(172, true)
		end
	//Shop
	else
		//Draw current inventory.
		local invUsed = p.slots //The inventory table to draw.
		if p.shopMode == 1 then invUsed = shopInv[p.currentShop]
		elseif p.shopMode == 3 then invUsed = buyBack end
		//Shop right-side options.
		if (isPress(p,BT_USE) or isPress(p,BT_CUSTOM1) or isPress(p,BT_CUSTOM2)) and p.cY == 0 and p.cX > 7 and p.shopMode != p.cX - 7 then
			S_StartSound(nil,234,p)
			p.shopMode = p.cX - 7
		elseif (isPress(p,BT_USE) or isPress(p,BT_CUSTOM1) or isPress(p,BT_CUSTOM2)) and p.cX < 8 and p.shopMode > 0 and invUsed[8*p.cY+p.cX+1] > 0 then
			if p.shopMode == 2 then
				//Seeds
				if p.slots[8*p.cY+p.cX+1] == 22 then
					p.ringCount = p.ringCount + price[p.slots[8*p.cY+p.cX+1]]/2
					shopSeedsBuyback[p.currentShop] = shopSeedsBuyback[p.currentShop] + 1
					if shopSeeds[p.currentShop] > 99 then shopSeeds[p.currentShop] = 99 end
					p.seeds = p.seeds - 1
					if p.seeds == 0 then p.slots[8*p.cY+p.cX+1] = 0 end
					S_StartSound(nil,244,p)
					if shopSeedsBuyback[p.currentShop] == 1 then
						for i=1, 32 do
							if buyBack[i]==0 then
								buyBack[i]=22
								return false
							end
						end
					end
				//Misc. Sellables
				elseif price[p.slots[8*p.cY+p.cX+1]] > 0 then
					p.ringCount = p.ringCount + price[p.slots[8*p.cY+p.cX+1]]/2
					local itemSold = p.slots[8*p.cY+p.cX+1]
					p.slots[8*p.cY+p.cX+1] = 0
					S_StartSound(nil,244,p)
					for i=1, 32 do
						if buyBack[i]==0 then
							buyBack[i]=itemSold
							return false
						end
					end
				end
			else
				local priceFactor = 1
				if p.shopMode == 3 then priceFactor = 2 end
				//Bullet Box
				if invUsed[8*p.cY+p.cX+1] == 28 then
					if p.ringCount >= price[invUsed[8*p.cY+p.cX+1]]/priceFactor and p.ammo < 99 then
						p.ringCount = p.ringCount - price[invUsed[8*p.cY+p.cX+1]]/priceFactor
						p.ammo = p.ammo + 30
						if p.ammo > 99 then p.ammo = 99 end //If the player is over max energy, fix that.
						invUsed[8*p.cY+p.cX+1] = 0
						S_StartSound(nil,244,p)
					end
				//Bullet Clip
				elseif invUsed[8*p.cY+p.cX+1] == 27 then
					if p.ringCount >= price[invUsed[8*p.cY+p.cX+1]]/priceFactor and p.ammo < 99 then
						p.ringCount = p.ringCount - price[invUsed[8*p.cY+p.cX+1]]/priceFactor
						p.ammo = p.ammo + 6
						if p.ammo > 99 then p.ammo = 99 end //If the player is over max energy, fix that.
						invUsed[8*p.cY+p.cX+1] = 0
						S_StartSound(nil,244,p)
					end
				//Sling Bullets
				elseif invUsed[8*p.cY+p.cX+1] == 24 then
					if p.ringCount >= price[invUsed[8*p.cY+p.cX+1]]/priceFactor and p.bullets < 99 then
						p.ringCount = p.ringCount - price[invUsed[8*p.cY+p.cX+1]]/priceFactor
						p.bullets = p.bullets + 30
						if p.bullets > 99 then p.bullets = 99 end //If the player is over max energy, fix that.
						invUsed[8*p.cY+p.cX+1] = 0
						S_StartSound(nil,244,p)
					end
				//Instant Recharges
				elseif invUsed[8*p.cY+p.cX+1] == 23 then
					if p.ringCount >= price[invUsed[8*p.cY+p.cX+1]]/priceFactor and p.energy < p.maxEnergy * 1000 then
						p.ringCount = p.ringCount - price[invUsed[8*p.cY+p.cX+1]]/priceFactor
						p.energy = p.energy + 1000
						if p.energy > p.maxEnergy * 1000 then p.energy = p.maxEnergy * 1000 end //If the player is over max energy, fix that.
						invUsed[8*p.cY+p.cX+1] = 0
						S_StartSound(nil,244,p)
					end
				//Seeds
				elseif invUsed[8*p.cY+p.cX+1] == 22 then
					if p.ringCount >= price[invUsed[8*p.cY+p.cX+1]]/priceFactor and not grantSeeds(p,1) then
						p.ringCount = p.ringCount - price[invUsed[8*p.cY+p.cX+1]]/priceFactor
						if p.shopMode == 1 then shopSeeds[p.currentShop] = shopSeeds[p.currentShop] - 1 else shopSeedsBuyback[p.currentShop] = shopSeedsBuyback[p.currentShop] - 1 end
						if (shopSeeds[p.currentShop] < 1 and p.shopMode == 1) or (shopSeedsBuyback[p.currentShop] < 1 and p.shopMode == 3) then invUsed[8*p.cY+p.cX+1] = 0 end
						S_StartSound(nil,244,p)
					end
				//Misc. Buyables
				elseif price[invUsed[8*p.cY+p.cX+1]] > 0 and p.ringCount >= price[invUsed[8*p.cY+p.cX+1]]/priceFactor and not giveItem(p.mo,invUsed[8*p.cY+p.cX+1]) then
					p.ringCount = p.ringCount - price[invUsed[8*p.cY+p.cX+1]]/priceFactor
					invUsed[8*p.cY+p.cX+1] = 0
					S_StartSound(nil,244,p)
				end
			end
		end
	end
end


addHook("ThinkFrame", do
	for p in players.iterate()
		if p.wet == nil then
			p.energy = 0
			p.maxEnergy = 0 //Max energy is in energy segments, not points like energy is.
			p.maxhp = 6
			p.maxhpseg = 2 //How many health segments there are. I do not yet know how I plan for these to be increased. The max this can go to is 12.
			p.hp = 6
			p.temphp = 6
			p.convertMode = 0 //convertMode is 0 if converter is not present, 1 if refilling health first, 2 if refilling energy first.
			p.bullets = 0 //Number of sling bullets owned.
			p.seeds = 0 //Number of fire seeds owned.
			hud.disable("rings")
			hud.disable("time")
			hud.disable("score")
			hud.disable("lives")
			p.ringCount = 0
			p.oldButtons = 0
			p.oldSideMove = 0
			p.oldForwardMove = 0
			initp(p)
		end
		if gameFrozen == false then
			//Handle attack timer.
			if p.attackTimer > 0 then p.attackTimer = p.attackTimer - 1 end
			//Handle infinite lives.
			p.lives = 99
			//Handle player size. Yes, this does not match vanilla SRB2, but while the large animals are no big deal (no pun intended) in vanilla, the player is supposed to FEEL small, in this mod.
			if p.mo.skin == "mouse" then P_SetScale(p.mo,FRACUNIT/2) end
			//Handle bleeding and death.
			if leveltime%200 == 0 and p.temphp > p.hp then p.temphp = p.temphp - 1 end
			if p.temphp <= 0 and p.playerstate == PST_LIVE then P_DamageMobj(p.mo, nil, nil, 10000) end
			//Handle converting rings to ring count.
			local oldRings = p.ringCount
			p.ringCount = p.ringCount + p.mo.health - 1
			p.mo.health = 1
			p.health = 1
			if players[0].playerstate != PST_LIVE then return nil end //This should not do anything further if the player is dead.
			//Sector handlers for heat and cold.
			if checkSectors(p,801) then
				if p.temp > 0 then p.temp = p.temp - 9 end
				p.temp = p.temp - 1
				if p.wet > 0 then p.temp = p.temp - ((p.wet-1)/1000+1) end //Get cold faster when wet.
				p.wet = p.wet - 1 //Dry slower in cold environments.
			elseif checkSectors(p,802) then
				if p.temp > -995 then p.temp = -995 end
				p.temp = p.temp - 5
				if p.wet > 0 then p.temp = p.temp - ((p.wet-1)/200+1) end //...I would not recommend going into bitter cold weather while wet.
				p.wet = p.wet - 1 //Dry slower in cold environments.
			elseif checkSectors(p,803) then
				if p.temp < 0 then p.temp = p.temp + 9 end
				p.temp = p.temp + 1
				p.wet = p.wet - 4 //Dry quicker in hot environments.
			elseif checkSectors(p,804) then
				if p.temp < 995 then p.temp = 995 end
				p.temp = p.temp + 5
				p.wet = p.wet - 20 //Dry quicker in hot environments.
			else
				if abs(p.temp) < 5 then p.temp = 0
				elseif p.temp > 0 then p.temp = p.temp - 5
				elseif p.temp < 0 then p.temp = p.temp + 5 end
				p.wet = p.wet - 2
			end
			if string.sub(p.mo.subsector.sector.ceilingpic,0,6) == "F_SKY1" and (curWeather == PRECIP_STORM or curWeather == PRECIP_RAIN or curWeather == PRECIP_STORM_NOSTRIKES) then p.wet = p.wet + 7 end
			if P_RandomRange(0,4999) < p.wet then p.temp = p.temp - 1 end //Being wet may help cool off and hinder warming up.
			if p.wet < 0 then p.wet = 0 end //If negative wetness, fix it.
			if p.wet > 5000 or p.mo.eflags & MFE_UNDERWATER then p.wet = 5000 end //If p.wetness over cap, fix it. If the player is underwater, soak them.
			if p.wet < 500 and p.mo.eflags & MFE_TOUCHWATER then p.wet = 500 end //If the player is touching water, they should get at least a LITTLE wet.
			if emeralds == 127 then
				p.energy = p.energy + 10
				if p.energy > p.maxEnergy * 1000 then p.energy = p.maxEnergy * 1000 end
			end //Regenerate energy if all emeralds have been collected.
			//Handle shield environmental immunities.
			if p.powers[pw_shield] == 9 or p.powers[pw_shield] == 265 or p.powers[pw_shield] == SH_ELEMENTAL or p.powers[pw_shield] == SH_ELEMENTAL|256 then //Inferno Shield keeps the user dry (though it still disappears if plunged into water) and at just the right temperature.
				p.wet = 0
				p.temp = 0
			elseif p.powers[pw_shield] == 10 or p.powers[pw_shield] == 266 then //Liquid Shield also keeps the user dry (somewhat ironically, depending on how you look at it).
				p.wet = 0
			end
			//Handle being too cold or too hot.
			if abs(p.temp) >= 10000 and leveltime % 35 == 0 then
				p.hp = p.hp - 1
				p.temphp = p.temphp - 1
			end
			//Handle fire seeds and Flametongue
			if p.fireTimer != nil and p.fireTimer>0 then
				p.fireTimer = p.fireTimer - 1
				if p.fireTimer % 3 == 0 then
					local flame = P_SpawnPlayerMissile(p.mo,MT_FLAMEJETFLAME)
					flame.momx = flame.momx*5
					flame.momy = flame.momy*5
					flame.momz = flame.momz*5
					S_StartSound(p.mo,sfx_fire,nil)
					if p.fireTimer % 15 == 0 and p.flameHarmful == true then
						p.hp = p.hp - 1
						p.temphp = p.temphp - 1
					end
				end
			end
		end
		//Set nearest monitor for egg cards.
		nearestMonitor = nil
		local monitorDist = 50*FRACUNIT //The distance away the nearest monitor is. The starting distance sets the max distance a monitor can be away to be affected by this item.
		for spot in mapthings.iterate do
			if spot.mobj != nil then
				local mon = spot.mobj
				if mon.type == MT_SUPERRINGBOX or mon.type == MT_BLACKTV or mon.type == MT_BLUETV or mon.type == MT_EGGMANBOX or mon.type == MT_GREENTV or mon.type == MT_PITYTV or mon.type == MT_INV or mon.type == MT_SNEAKERTV or mon.type == MT_WHITETV or mon.type == MT_YELLOWTV or mon.type == MT_FIRETV or mon.type == MT_WATERTV then //If it's ANY valid monitor type.
					if P_CheckSight(p.mo,mon) then
						if R_PointToDist2(p.mo.x,p.mo.y,mon.x,mon.y) < monitorDist then
							monitorDist = R_PointToDist2(p.mo.x,p.mo.y,mon.x,mon.y)
							nearestMonitor = mon
						end
					end
				end
			end
		end
		//Handle if the menu is currently open.
		if p.conversation != nil then
			//Stop the character from moving.
			p.normalspeed = 0
			p.jumpfactor = 0
			p.charability = 0
			p.charability2 = 0
			for i=0,22
				p.powers[i] = p.oldPowers[i]
			end
			dialogueHandler(p)
		elseif p.menuOpen == true then
			//Stop the character from moving.
			p.normalspeed = 0
			p.jumpfactor = 0
			p.charability = 0
			p.charability2 = 0
			for i=0,22
				p.powers[i] = p.oldPowers[i]
			end
			if isPress(p,BT_CUSTOM3) then
				freezeAll(false)
				p.menuOpen = false
				p.shopMode = 0
			else
				menuHandler(p)
			end
		//Handle if dialogue is currently open.
		//Handle special input if the menu is not open.
		else
			//Otherwise, set speed, jump height, and abilities back to normal.
			p.normalspeed = skins[p.mo.skin].normalspeed
			p.jumpfactor = skins[p.mo.skin].jumpfactor
			p.charability = skins[p.mo.skin].ability
			p.charability2 = skins[p.mo.skin].ability2
			if isPress(p,BT_CUSTOM3) then
				p.oldPowers = {}
				for i=0,22
					p.oldPowers[i] = p.powers[i]
				end
				freezeAll(true)
				p.menuOpen = true
				//Reset the cursor coordinates.
				p.cX = 0
				p.cY = 0
			elseif isPress(p,BT_USE) then
				useItem(p,-3)
			elseif isPress(p,BT_CUSTOM1) then
				useItem(p,-1)
			elseif isPress(p,BT_CUSTOM2) then
				useItem(p,-2)
			end
		end
		p.oldButtons = p.cmd.buttons //Keep the oldbuttons variable up to date.
		p.oldSideMove = p.cmd.sidemove
		p.oldForwardMove = p.cmd.forwardmove
		//Remove any cached images not used during this or the previous frame.
		/*for key,val in pairs(cacheUsed) do
			if cacheUsed[key] + 1 < leveltime then
				cachedIcons[key] = nil
				cacheUsed[key] = nil
				print("Removed "..key.." from cache")
			end
		end*/
	end
end)

addHook("MobjDeath", reinit, MT_PLAYER)

//Ring
addHook("TouchSpecial", function(o,p)
	p = p.player
	if p.convertMode == 1 then
		if p.hp < p.maxhp then
			p.hp = p.hp+1
			p.temphp = p.temphp+1
		elseif p.energy < p.maxEnergy * 1000 then
			p.energy = p.energy + 100
		end
	elseif p.convertMode == 2 then
		if p.energy < p.maxEnergy * 1000 then
			p.energy = p.energy + 100
		elseif p.hp < p.maxhp then
			p.hp = p.hp+1
			p.temphp = p.temphp+1
		end
	end
	if p.temphp > p.maxhp then p.temphp = p.maxhp end
end, MT_RING)

//Thermos
addHook("TouchSpecial", function(o,p)
	//print("Got a thermos. This should be useful for a long journey.")
	return giveItem(p,2)
end, MT_THERMOS) //I'm not sure why MT_THERMOS did not work, but 417 did, so that works.

//Recharge Cell
addHook("TouchSpecial", function(o,p)
	if p.player.energy >= p.player.maxEnergy * 1000 then return true end //If the player is already at max energy, don't pick this up.
	p.player.energy = p.player.energy + 1000
	if p.player.energy > p.player.maxEnergy * 1000 then p.player.energy = p.player.maxEnergy * 1000 end //If the player is over max energy, fix that.
	//print("Got an Egg Battery Recharge Cell.")
	return false
end, MT_CELL)

//Battery
addHook("TouchSpecial", function(o,p)
	if p.player.maxEnergy >= 10 then return true end //If the player is already at capped max energy, don't pick this up.
	p.player.maxEnergy = p.player.maxEnergy + 1
	p.player.energy = p.player.energy + 1000
	//if p.player.maxEnergy == 1 then print("Got a Rechargable Egg Battery! Now you can power various devices!")
	//else print("Got a Rechargable Egg Battery! Energy storage increased!") end
	return false
end, MT_BATTERY)

//Rebreather
addHook("TouchSpecial", function(o,p)
	return giveItem(p,6)
end, MT_REBREATHER)

//Empty Card
addHook("TouchSpecial", function(o,p)
	return giveItem(p,7)
end, MT_CARD_EMPTY)

//Medikit
addHook("TouchSpecial", function(o,p)
	return giveItem(p,9)
end, MT_MEDIKIT)

//Converter
addHook("TouchSpecial", function(o,p)
	p.player.convertMode = 1
end, MT_CONVERTER)

//Sling Bullet
addHook("TouchSpecial", function(o,p)
	if p.player.bullets >= 99 then return true end //Enforce the 99 bullet cap.
	p.player.bullets = p.player.bullets + 1 //Gain a bullet.
end, MT_BULLET)

//Fire Seed
addHook("TouchSpecial", function(o,p)
	if grantSeeds(p.player,1) then return true end
end, MT_FIRESEED)

//Sling Bullet Pile
addHook("TouchSpecial", function(o,p)
	if p.player.bullets >= 99 then return true end //Enforce the 99 bullet cap.
	p.player.bullets = p.player.bullets + 30 //Gain 30 bullets.
	if p.player.bullets > 99 then p.player.bullets = 99 end //Enforce the 99 bullet cap again.
end, MT_BULLETPILE)

//Ammo Clip
addHook("TouchSpecial", function(o,p)
	if p.player.ammo >= 99 then return true end //Enforce the 99 gun bullet cap.
	p.player.ammo = p.player.ammo + 6 //Gain 6 gun bullets.
	if p.player.ammo > 99 then p.player.ammo = 99 end //Enforce the 99 gun bullet cap again.
end, MT_CLIP)

//Ammo Box
addHook("TouchSpecial", function(o,p)
	if p.player.ammo >= 99 then return true end //Enforce the 99 gun bullet cap.
	p.player.ammo = p.player.ammo + 30 //Gain 6 gun bullets.
	if p.player.ammo > 99 then p.player.ammo = 99 end //Enforce the 99 gun bullet cap again.
end, MT_BULLETBOX)

//Recalculate max HP when touching any emerald.
addHook("TouchSpecial", function(o,p)
	p = p.player
	p.maxhp = p.maxhp + p.maxhpseg
	p.hp = p.hp + p.maxhpseg
	p.temphp = p.temphp + p.maxhpseg
end, MT_EMERALD1)
addHook("TouchSpecial", function(o,p)
	p = p.player
	p.maxhp = p.maxhp + p.maxhpseg
	p.hp = p.hp + p.maxhpseg
	p.temphp = p.temphp + p.maxhpseg
end, MT_EMERALD2)
addHook("TouchSpecial", function(o,p)
	p = p.player
	p.maxhp = p.maxhp + p.maxhpseg
	p.hp = p.hp + p.maxhpseg
	p.temphp = p.temphp + p.maxhpseg
end, MT_EMERALD3)
addHook("TouchSpecial", function(o,p)
	p = p.player
	p.maxhp = p.maxhp + p.maxhpseg
	p.hp = p.hp + p.maxhpseg
	p.temphp = p.temphp + p.maxhpseg
end, MT_EMERALD4)
addHook("TouchSpecial", function(o,p)
	p = p.player
	p.maxhp = p.maxhp + p.maxhpseg
	p.hp = p.hp + p.maxhpseg
	p.temphp = p.temphp + p.maxhpseg
end, MT_EMERALD5)
addHook("TouchSpecial", function(o,p)
	p = p.player
	p.maxhp = p.maxhp + p.maxhpseg
	p.hp = p.hp + p.maxhpseg
	p.temphp = p.temphp + p.maxhpseg
end, MT_EMERALD6)
addHook("TouchSpecial", function(o,p)
	p = p.player
	p.maxhp = p.maxhp + p.maxhpseg
	p.hp = p.hp + p.maxhpseg
	p.temphp = p.temphp + p.maxhpseg
end, MT_EMERALD7)

//Hook and function to handle damage from various sources.

local function damagePlayer(p,damage,temp)
	if p.powers[pw_shield] != 0 and p.powers[pw_shield] != 256 then //I have no intention to give the mouse a fire flower, but for the sake of reusability, I'll account for the possibility anyway.
		//Sound 121
		if p.powers[pw_shield] % 256 == 4 then
			P_BlackOw(p)
		elseif p.powers[pw_shield] > 512 and p.powers != 768 then //If the player has a force shield and it has more than 1 health.
			p.powers[pw_shield] = p.powers[pw_shield] - 1
			P_SpawnShieldOrb(p)
		else //Destroy the shield.
			P_RemoveShield(p)
		end
		return //Do not deal any damage if the player is protected by a shield.
	end
	local ndamage = 0
	local ntemp = 0
	while damage > 0 do
		ndamage = ndamage + P_RandomRange(1,8)
		damage = damage - 1
	end
	while temp > 0 do
		ntemp = ntemp + P_RandomRange(1,12)
		temp = temp - 1
	end
	p.hp = p.hp - ndamage - ntemp
	p.temphp = p.temphp - ndamage
	if p.hp < 0 then
		p.temphp = p.temphp + p.hp
		p.hp = 0
	end
end

//Damage player?
addHook("ShouldDamage", function(p, damager, source, damage)
	if damager == nil or damager.type == MT_NULL then
		p.player.hp = 0 //p.player.maxhp
		p.player.temphp = 0 //p.player.maxhp
		return true //This should only happen in the event of something that should instantly kill.
	elseif p.player.powers[pw_flashing] > 0 or p.player.powers[pw_invulnerability] > 0 then return false //Only death from loss of HP should bypass invulnerability.
	elseif damager.type == MT_BLUECRAWLA then
		damagePlayer(p.player,2,1)
	elseif damager.type == MT_DEMONFIRE then
		if p.player.powers[pw_shield] == SH_ELEMENTAL or p.player.powers[pw_shield] == SH_ELEMENTAL|SH_FIREFLOWER or p.player.powers[pw_shield] == 9 or p.player.powers[pw_shield] == 9|SH_FIREFLOWER then return false end //Elemental and Inferno shields protect against this.
		damagePlayer(p.player,3,0)
	elseif damager.type == MT_FLAME then
		if p.player.powers[pw_shield] == SH_ELEMENTAL or p.player.powers[pw_shield] == SH_ELEMENTAL|SH_FIREFLOWER or p.player.powers[pw_shield] == 9 or p.player.powers[pw_shield] == 9|SH_FIREFLOWER then return false end //Elemental and Inferno shields protect against this.
		damagePlayer(p.player,4,0)
	elseif damager.type == MT_SPECIALSPIKEBALL then
		damagePlayer(p.player,0,3)
	else
		print("Damager type not recognized by the damage script: "..damager.type)
		damagePlayer(p.player,3,2)
	end
	//print(p.player.hp,p.player.temphp,damager.type)
	P_DoPlayerPain(p.player, damager, source)
	return false
end, MT_PLAYER)

//Damage blue crawla?
//Note that, for all damage functions, an extra 1 damage will be applied to the enemy unless they are outright immune.
addHook("ShouldDamage", function(e, damager, source, damage)
	if damager == nil or damager.type == MT_NULL then
		e.health = 0
		return true //This should only happen in the event of something that should instantly kill.
	elseif damager.type == MT_FLAMEJETFLAME then
		e.health = e.health - P_RandomRange(3,5)
	elseif damager.type == MT_SLUNGBULLET then //Average bullets per Blue Crawla: 2
		e.health = e.health - P_RandomRange(0,1)
		//S_StartSound(e,99)
	elseif damager.type == MT_PLAYER then //Either an invincible player or armageddon shield blast. Either way, this should insta-kill most normal enemies.
		e.health = 0
	else
		print("Damager type not recognized by the damage script: "..damager.type)
		e.health = 0
		return true
	end
	return true
end, MT_BLUECRAWLA)

//Damage FaceStabber?
addHook("ShouldDamage", function(e, damager, source, damage)
	if damager == nil or damager.type == MT_NULL then
		e.health = 0
		return true //This should only happen in the event of something that should instantly kill.
	elseif damager.type == MT_FLAMEJETFLAME then
		e.health = e.health - P_RandomRange(3,5)
	elseif damager.type == MT_SLUNGBULLET then
		S_StartSound(e,286)
		return false
	elseif damager.type == MT_PLAYER then //Either an invincible player or armageddon shield blast. Either way, this should insta-kill most normal enemies.
		e.health = 0
	else
		print("Damager type not recognized by the damage script: "..damager.type)
		e.health = 0
		return true
	end
	return true
end, MT_FACESTABBER)



//Here begins the shop initialization scripts.

local function singleShopInit(shopNum,shopItemPool,shopItemNum,itemPoolSize)
	shopSeeds[shopNum] = 0
	shopSeedsBuyback[shopNum] = 0
	//Clear shop inventory and insert new inventory.
	local i = 1
	while i <= 32 do
		if i <= shopItemNum then
			local itemNum = shopItemPool[P_RandomRange(1,itemPoolSize)]
			if itemNum == 22 and shopSeeds[shopNum] > 0 then
				shopItemNum = shopItemNum - 1
				i = i - 1
			else shopInv[shopNum][i] = itemNum end
			if itemNum == 22 then shopSeeds[shopNum] = shopSeeds[shopNum] + 1 end
		else shopInv[shopNum][i] = 0 end
		buyBack[i] = 0
		i = i + 1
	end
end

local function shopInit(p)
	for i=1,32 do
		buyBack[i] = 0
	end
	//Gilbert Plume
	//                                           10                            20
	singleShopInit(1,{ 4, 5, 6, 7,22,24,25,26,28,31},P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8),10)
	//Martin Milton
	//                                           10                            20
	singleShopInit(2,{ 3, 3, 3, 4, 4, 5, 5,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,31},P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8),28)
	//Glenn
	//                                           10                            20
	singleShopInit(3,{ 3, 4, 5,23,24},P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8),5)
	//Marshall Armond
	//                                           10                            20                            30                            40
	singleShopInit(4,{22,23,24,24,24,24,24,25,26,26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,30},P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8),44)
	//Copper Wyre
	//Though she has a low chance of selling other tools, she primarily deals in technology. Given how rare and valuable her stock is, she tends not to have much inventory at a time.
	//                                           10                            20                            30                            40                            50                            60
	singleShopInit(5,{ 2, 6, 7, 7, 7, 7, 7,10,11,12,13,14,15,16,17,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,29,29,29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30,30,30},P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8)+P_RandomRange(1,8),60)
end



//Here begins the scripting for maps. I would have put these in separate lumps, but that appears to be impractical.

//A shortcut for spawning objects.
local function intSpawn(x,y,z,obj)
	P_SpawnMobj(x*FRACUNIT, y*FRACUNIT, z*FRACUNIT, obj)
end

//Initialize maps.
local function mapInit(map)
	shopInit()
	//Reset inventory, ring count, and max energy to starting point if the map load is a result of death or retry. Also, deplete energy supply.
	for p in players.iterate() do
		p.wet = 0
		p.temp = 0
		p.attackTimer = 0
		p.fireTimer = 0
		p.maxhp = p.maxhpseg*segLength()
		p.hp = p.maxhp
		p.temphp = p.maxhp
		p.shopMode = 0 //0 means not in a shop, 1 means buying, 2 means selling, 3 means buying back.
		if p.lastLev != nil then
			if p.lastLev == gamemap then
				for i=-3,32 do
					p.slots[i] = p.lastInv[i]
					p.energy = 0
					p.maxEnergy = p.lastMaxEnergy
					p.ringCount = p.oldRings
					p.convertMode = p.oldConvertMode
					p.bullets = p.oldBullets
					p.seeds = p.oldSeeds
				end
			end
		else
			//Energy is used for various items.
			p.energy = 0
			p.maxEnergy = 0 //Max energy is in energy segments, not points like energy is.
			p.convertMode = 0
			p.ammo = 0
			p.bullets = 0
			p.seeds = 0
			//Slots is the array of inventory items. -3 is the item in the Spin slot, -2 is Custom 2, and -1 is Custom 1. Positive numbers are the reserve inventory slots.
			p.slots = {}
			p.slots[-3] = 1 //Player starts with Interact in their Spin slot.
			p.slots[-2] = 19 //DUMMY - For Sling Testing
			p.slots[-1] = 0
			for i=1, 32 do
				p.slots[i] = 0
			end
			p.lastInv = {}
			p.ringCount = 0
		end
		for i=-3,32 do
			p.lastInv[i] = p.slots[i]
		end
		p.lastLev = gamemap
		p.lastMaxEnergy = p.maxEnergy
		p.oldRings = p.ringCount
		p.oldConvertMode = p.convertMode
		p.oldBullets = p.bullets
		p.oldSeeds = p.seeds
		//Refill health upon ANY map change.
		//p.maxhp = 10 + (p.ringCount / 100 * 10)
		p.hp = p.maxhp
		p.temphp = p.maxhp
	end
	//Reset the arrays.
	interactText = {}
	interactDialogue = {}
	interactFunc = {}
	local r //Variable for random spawns.
	local r2 //Variable for random spawns.
	local r3 //Variable for random spawns.
	//MAPC0
	if map == 172 then
		//Text
		interactText[801] = "A cold test area, for snowy areas and the like. Getting to cold level 10 results in losing a life. Getting wet here is not advised."
		interactText[802] = "A freezing test area, for truly unbearably cold areas. Getting wet in this area will result in a fairly quick death."
		interactText[803] = "A hot test area, for deserts and the like. Getting to heat level 10 results in losing a life."
		interactText[804] = "A scorching test area, for truly unbearably hot areas. Water on characters evaporates quickly here."
		//Dialogue
		interactDialogue[4] = {"Summon a different enemy?",
			{"Cancel", nil}
		}
		interactDialogue[5] = {"This is a test of the dialogue and shop for Copper the Owl. Who, by the way, is female. Bit of trivia, there.",
			{"Shop", function(p)
				p.shopMode = 1
				p.currentShop = 5
				p.menuOpen = true
			end},
			{"1000 Rings", function(p)
				p.ringCount = p.ringCount + 1000
			end},
			{"10000 Rings", function(p)
				p.ringCount = p.ringCount + 10000
			end},
			{"End Conversation", nil}
		}
		interactDialogue[6] = {"This is a test of the dialogue and shop for Marshall Armond the arms dealer. Also, fun fact, his gun is called The Spectre.",
			{"Shop", function(p)
				p.shopMode = 1
				p.currentShop = 4
				p.menuOpen = true
			end},
			{"1000 Rings", function(p)
				p.ringCount = p.ringCount + 1000
			end},
			{"10000 Rings", function(p)
				p.ringCount = p.ringCount + 10000
			end},
			{"End Conversation", nil}
		}
		interactDialogue[7] = {"This is a test of the dialogue and shop for Martin Milton the farmer. He has the most states by far, among the shopkeepers.",
			{"Shop", function(p)
				p.shopMode = 1
				p.currentShop = 2
				p.menuOpen = true
			end},
			{"1000 Rings", function(p)
				p.ringCount = p.ringCount + 1000
			end},
			{"10000 Rings", function(p)
				p.ringCount = p.ringCount + 10000
			end},
			{"End Conversation", nil}
		}
		interactDialogue[8] = {"This is a test of the dialogue and shop for Plume the eagle.",
			{"Shop", function(p)
				p.shopMode = 1
				p.currentShop = 1
				p.menuOpen = true
			end},
			{"1000 Rings", function(p)
				p.ringCount = p.ringCount + 1000
			end},
			{"10000 Rings", function(p)
				p.ringCount = p.ringCount + 10000
			end},
			{"End Conversation", nil}
		}
		interactDialogue[9] = {"This is a test of the dialogue and shop for Glenn the poodle. Who is male, though it may not be immediately apparent.",
			{"Shop", function(p)
				p.shopMode = 1
				p.currentShop = 3
				p.menuOpen = true
			end},
			{"1000 Rings", function(p)
				p.ringCount = p.ringCount + 1000
			end},
			{"10000 Rings", function(p)
				p.ringCount = p.ringCount + 10000
			end},
			{"End Conversation", nil}
		}
	//MAPC1
	elseif map == 173 then
		//Text
		interactText[1] = "The meter on the top right of the screen is your health."
		//Dialogue
		interactDialogue[3] = {"It's rough out there. Be careful, alright?",
			{"End conversation"}
		}
		interactDialogue[5] = {"Get out there and show that big bully who's boss!",
			{"End conversation"}
		}
		interactDialogue[6] = {"I don't think this is a good idea at all, but you seem set on it. All I can say is, good luck.",
			{"End conversation"}
		}
		interactDialogue[7] = {"I feel sorry for the little guy. Living in the looming shadow of Robotnik's schemes is terrible for us all, but for your own mother to be taken away...",
			{"End conversation"}
		}
		interactDialogue[8] = {"I miss mommy... *sniffle*",
			{"End conversation"}
		}
		interactDialogue[9] = {"I'm sorry, but I don't want to talk right now.",
			{"End conversation"}
		}
		interactDialogue[10] = {"I know, it's pretty dreary weather, but you never know how long it'll be before the badniks get you. I want to get as much time in the open air as I can before that time comes.",
			{"End conversation"}
		}
		interactDialogue[11] = {"I don't think you'll be able to protect yourself with rings like Sonic and his friends can. But if you collect enough of them, maybe something good'll happen.\n\nThey look shiny, and something that shiny just has to be lucky, right?",
			{"End conversation"}
		}
		interactDialogue[17] = {"I beg you to reconsider. Most of Robotnik's badniks can't squeeze in here, and this place appears to be a low-priority target. It'll probably be a while yet before we're in any real danger. We probably have at least a few weeks more left to live free. Please don't throw it all away like this.",
			{"End conversation"}
		}
		//Random dialogue for forest NPCs.
		r = P_RandomRange(1,5)
		repeat r2 = P_RandomRange(1,5) until(r != r2)
		repeat r3 = P_RandomRange(1,5) until(r != r3 and r2 != r3)
		print(r,r2)
		if r == 1 or r2 == 1 or r3 == 1 then
			interactDialogue[3] = {"I really admire you for having the courage to even step out of our hiding place, let alone try to go after Robotnik. I could never do anything like that...\n\nI know it's not much, but here, take this first aid kit. You're bound to get hurt while you're out there and this will help keep you going.",
				{"Thanks.", function(p)
					giveItem(p.mo,9)
					STagChange(3,4)
					S_StartSound(nil,244,p)
				end}
			}
			interactDialogue[4] = {"It's rough out there. Be careful, alright?",
				{"End conversation"}
			}
		end
		if r == 2 or r2 == 2 or r3 == 2 then
			interactDialogue[5] = {"Wow... So you're really gonna go out there, huh? I wish I could help... Oh! I know! Here, take my thermos! You can fill it up near the lake!",
				{"Thanks.", function(p)
					giveItem(p.mo,2)
					STagChange(5,13)
					S_StartSound(nil,244,p)
				end}
			}
			interactDialogue[13] = {"Get out there and show that big bully who's boss!",
				{"End conversation"}
			}
		end
		if r == 3 or r2 == 3 or r3 == 3 then
			interactDialogue[11] = {"Hi! I've been thinking about this big journey you're going on, and I remembered those rings I gathered up on the way here. I think you could use them more than I can. Here you go!",
				{"Thanks.", function(p)
					STagChange(11,14)
					p.mo.health = p.mo.health + 12
					S_StartSound(nil,108,p)
				end}
			}
			interactDialogue[14] = {"I don't think you'll be able to protect yourself with rings like Sonic and his friends can. But if you collect enough of them, maybe something good'll happen.\n\nThey look shiny, and something that shiny just has to be lucky, right?",
				{"End conversation"}
			}
		end
		if r == 4 or r2 == 4 or r3 == 4 then
			interactDialogue[10] = {"I found these on the way here. They're very special seeds, called Fire Seeds. You can eat them and you'll breathe fire! But, well... having fire burst from your stomach isn't exactly the healthiest thing in the world, so only use them if you REALLY need to!",
				{"Thanks.", function(p)
					//print("Fire seeds have not yet been implemented. Here, have some rings, instead!")
					grantSeeds(p,6)
					p.mo.health = p.mo.health + 12
					//S_StartSound(nil,108,p)
					STagChange(10,15)
				end}
			}
			interactDialogue[15] = {"I know, it's pretty dreary weather, but you never know how long it'll be before the badniks get you. I want to get as much time in the open air as I can before that time comes.",
				{"End conversation"}
			}
		end
		if r == 5 or r2 == 5 or r3 == 5 then
			interactDialogue[9] = {"I brought some sling bullets here with me. They won't work against the more heavily-armored badniks, but they'll definitely work on Robotnik if you manage to corner him.\n\nTake them with you. And please, bring back my beloved.",
				{"Thanks.", function(p)
					p.bullets = p.bullets + 20
					S_StartSound(nil,311,p)
					STagChange(9,16)
				end}
			}
			interactDialogue[16] = {"I'm sorry, but I don't want to talk right now.",
				{"End conversation"}
			}
		end
		//Random spawn for the lake secret's main chamber.
		r = P_RandomRange(1,2)
		if r == 1 then
			intSpawn(-960, 1040, -40, MT_RING)
			intSpawn(-960, 1072, -40, MT_RING)
			intSpawn(-960, 1104, -40, MT_RING)
			intSpawn(-832, 1040, -40, MT_RING)
			intSpawn(-832, 1072, -40, MT_RING)
			intSpawn(-832, 1104, -40, MT_RING)
			intSpawn(-960, 1040, -40, MT_RING)
			intSpawn(-960, 1072, -40, MT_RING)
			intSpawn(-928, 1136, -40, MT_RING)
			intSpawn(-896, 1136, -40, MT_RING)
			intSpawn(-864, 1136, -40, MT_RING)
			intSpawn(-928, 1008, -40, MT_RING)
			intSpawn(-896, 1008, -40, MT_RING)
			intSpawn(-864, 1008, -40, MT_RING)
		elseif r == 2 then
			for i = 1,25 do
				local x = P_RandomRange(-1088,-624)
				local y = P_RandomRange(864,1312)
				if R_PointInSubsector(x*FRACUNIT, y*FRACUNIT).sector.floorheight == -80*FRACUNIT then intSpawn(x, y, -80, MT_BULLET) end // else
				//print(x..","..y.." ("..i..")".." ["..R_PointInSubsector(x, y).sector.floorheight/FRACUNIT.."]") end
			end
		end
		//Random spawn for the lake secret's side chamber.
		r = P_RandomRange(1,2)
		if r == 1 then
			intSpawn(-736, 1280, -88, MT_RING)
			intSpawn(-704, 1280, -88, MT_RING)
			intSpawn(-736, 1248, -88, MT_RING)
			intSpawn(-704, 1248, -88, MT_RING)
			intSpawn(-672, 1248, -88, MT_RING)
			intSpawn(-704, 1216, -88, MT_RING)
			intSpawn(-672, 1216, -88, MT_RING)
			
			intSpawn(-736, 1280, -64, MT_RING)
			intSpawn(-704, 1280, -64, MT_RING)
			intSpawn(-736, 1248, -64, MT_RING)
			intSpawn(-704, 1248, -64, MT_RING)
			intSpawn(-672, 1248, -64, MT_RING)
			intSpawn(-704, 1216, -64, MT_RING)
			intSpawn(-672, 1216, -64, MT_RING)
			
			intSpawn(-736, 1280, -40, MT_RING)
			intSpawn(-704, 1280, -40, MT_RING)
			intSpawn(-736, 1248, -40, MT_RING)
			intSpawn(-704, 1248, -40, MT_RING)
			intSpawn(-672, 1248, -40, MT_RING)
			intSpawn(-704, 1216, -40, MT_RING)
			intSpawn(-672, 1216, -40, MT_RING)
		elseif r == 2
			intSpawn(-704, 1248, -88, 417)
		end
	else
		print(map)
	end
end

addHook("MapLoad", mapInit)

hud.add(drawStatus,"game")
hud.add(drawInventory,"game")
hud.add(drawDialogue,"game")

I'm honestly surprised I didn't run afoul of the character limit, with that.

Anyway, the script works fine up until I select "Bars only (default)", "Bars and numbers", "Numbers only", or "Always bars".

EDIT: And yes, I know I need to clean things up in that code. For example, the cache variables are part of a failed experiment and are no longer used.
 
When you declare the variable and set its initial table in the same statement, the variable itself doesn't seem to be accessible to any functions in that initial table declaration. (It doesn't make sense since a function declared and defined in one line can call itself recursively, but them's the breaks. :S) A couple isolated test cases:

Code:
-- Works properly
local test
test = {"f", function() print(test[1]) end}
test[2]()

-- Nil access error
local test = {"f", function() print(test[1]) end}
test[2]()
So it looks like the solution to your problem is simply to separate the variable's declaration from its contents. (For bonus bad style points, keep both statements on one line so that someone else will see "local test test = {...}" and wonder wtf you're doing)

(For the record, this exact same issue happens in other Lua-based engines outside of SRB2, so it's ~not our fault~. Congrats on stumbling across what is probably an obscure bug in the actual Lua interpreter!)
 
Last edited:
Status
Not open for further replies.

Who is viewing this thread (Total: 1, Members: 0, Guests: 1)

Back
Top