' Bliss (track editor for Stunts)
' Copyright (C) 2016-2020 Lucas Pedrosa

' This program is free software: you can redistribute it and/or modify
' it under the terms of the GNU General Public License as published by
' the Free Software Foundation, version 3 of the License.

' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
' GNU General Public License for more details.

' You should have received a copy of the GNU General Public License
' along with this program.  If not, see <http://www.gnu.org/licenses/>.

' COMPILATION:
' Bliss has been compiled with FreeBasic 1.05.0 for GNU/Linux,
' DOS and Windows. It should compile well with any newer version
' and likely, with some older versions. You can get FreeBasic at:
' - http://www.freebasic.net
' For FreeDOS (or any other DOS), compile with:
' - fbc bliss.bas
' For GNU/Linux, compile with:
' - fbc bliss.bas bliss.xpm
' For Windows, compile with:
' - fbc -s gui bliss.bas bliss.rc


#ifndef __FB_DOS__
	#include "http.bi"
	#undef GetParent
#endif

#ifdef __FB_LINUX__
	#include once "fbgfx.bi"
	#include once "X11/Xlib.bi"
	#include once "X11/Xutil.bi"
	#include once "X11/keysymdef.bi"
	
	Declare Function Linkey As String
	Dim Shared LKdisp As Display Ptr, LKwindow As Window
	Dim Shared LKevent As XEvent, LKsym As KeySym
	Dim Shared LKstring As ZString * 26, LKinitialised As Byte = 0
	
	#define DIR_DIVISOR "/"
#else
	#define DIR_DIVISOR "\"
#endif

#include "vbcompat.bi"
#include "targalib.bi"
#include "dir.bi"

#define UNDOLEVEL 30
#define MAXPATHS 1000
#define THISVERSION "2.5.8"
#define THISVERSION_NOPERIOD 20508
#define LENGTHOF4AM 1895
#define MAXCARS 20

#define FORMAT_RAW 1
#define FORMAT_COMBINED 2
#define FORMAT_BINARY_SPLIT 3
#define FORMAT_TEXT_SPLIT 4

'If uncommented, font rendering in the TrackInfo menu will be done
'with Draw String and CP437 will be used. Otherwise, PutString will
'be used with Latin-1-Plus.
'#define RENDER_TO_CP437

#ifdef __FB_DOS__
	#undef ScreenLock
	#define ScreenLock DoNothing
	'#define ScreenLock FakeScreenLock
	#undef ScreenUnlock
	#define ScreenUnlock DoNothing
	'#define ScreenUnlock FakeScreenUnlock
	#undef WindowTitle
	#define WindowTitle DOSScreenTitle
	Declare Sub DoNothing
	Declare Sub DOSScreenTitle(title As String)
	Declare Sub FakeScreenLock
	Declare Sub FakeScreenUnlock
#endif


Type EncX	'Encoding translation
	utf32 As Long
	o As UByte		'Output
End Type

Type SGrid
	track As UByte
	land As UByte
	bgc As ULong	'Background colour
	border As ULong	'Border colour
End Type

Type TransType Field = 1	'To locate an icon for a track code
	x As UByte
	y As UByte
	w As UByte
	h As UByte
	id As ZString * 20
	hflip As UByte
	vflip As UByte
	cr As UByte		'Clockwise rotation
	ccr As UByte	'Counter-clockwise rotation
	xsmall As UByte	'Coordinates to small icon
	ysmall As UByte
	ctype(0 To 3) As UByte		'Connector types
	cto(0 To 3) As UByte		'Connector "to" (possible destinations)
	cisalt(0 To 3) As Byte		'Connector is alternative
	length As UByte				'Weighed length
	material As Byte
	entity As UByte				'What the tile does
	reserved(0 To 18) As UByte
End Type

Type ButtonType
	x1 As Short
	y1 As Short
	x2 As Short
	y2 As Short
	value As Short
	title As String
End Type

Type SelectorType
	opt(1 To 20) As String
	options As Byte
	current As Byte
	redraw As Byte
	wasinlasttime As Byte
	x1 As Short
	y1 As Short
	x2 As Short
	y2 As Short
End Type

Type FilerType
	x1 As Short
	y1 As Short
	x2 As Short
	y2 As Short
	reread As Byte
	mask As String
End Type

Type DirLink
	text As String
	directory As String
End Type

Type StringerType
	maxlength As Short
	x As Short
	y As Short
	s32 As String	'String in UTF-32 encoding
	sr As String	'String in CP437 (for rendering)
	last As String	'To keep the last edited string
	cursor_pos As Short
	t As Double
	fileonly As Byte
	cursor As Byte
	redraw As Byte
	init As Byte
	background As ULong
End Type

Type MetaData
	title As String
	author As String
	cyear As Short
	cmonth As Byte
	cday As Byte
	tool As String
	toolversion As ULong		'Example: 21305 for 2.13.5
	comment As String
	championship As String
	editing_time As Long		'In seconds
End Type

Type TrackVector
	Union
		coors As UShort	'Packed coordinates
		Type
			x As Byte
			y As Byte
		End Type
	End Union
	bearing As Byte		'Where it's going
	origin As Byte		'Where it came from
	e As Byte			'Error
End Type

Type TrackSection
	Union					'Starting node coordinates
		initial As UShort
		Type
			xo As Byte
			yo As Byte
		End Type
	End Union
	Union					'Ending node coordinates
		final As UShort
		Type
			xf As Byte
			yf As Byte
		End Type
	End Union
	solving As Byte			'Flag for this section being solved
	origin As Byte			'Direction from which the section was entered
	bearing As Byte			'Starting bearing
	parent(2) As Short		'Parent sections
	child(2) As Short		'Child sections
	finishes As Byte		'Leads to the finish line
	cycle As Byte			'Leads to a cycle
	wrongway As Byte		'Leads to wrong way
	errors As Byte			'Contains local errors
	e As Byte				'Error code
End Type

Type TrackError
	e As Byte				'Error type/code
	Union					'Error coordinates if applicable
		coors As UShort
		Type
			x As Byte
			y As Byte
		End Type
	End Union
	section As Short
End Type

Type TrackPath
	p As String				'The path itself
	e As Byte				'The error status
	finishes As Byte
End Type

Type Scoreboard
	racer As String
	handicap As Byte
	realtime As String
	hctime As String		'Handicapped time or effective points
	car As String
	style As String
	verified As Byte
End Type

Type Car
	id As String * 4
	cname As String
	handicap As Single
End Type

Declare Function Enc_UTF32_to_CP437 (s As String) As String
Declare Function Enc_UTF32_to_Latin1 (s As String) As String
Declare Function Enc_UTF32_to_UTF8 (s As String) As String
Declare Function Enc_UTF8_to_UTF32 (s As String) As String
Declare Function ConvertColour(c As String) As String

Declare Sub DrawPanel
Declare Sub DrawSpot(x As UByte, y As UByte, inked As Byte = 0)
Declare Sub DrawTrack
Declare Sub DrawBox(x1 As Short, y1 As Short, x2 As Short, y2 As Short)
Declare Sub MenuBox(boxwidth As Short, boxheight As Short, title As String)
Declare Sub SelectBackground

Declare Sub LoadFont (fontfile As String)
Declare Sub PutString (x As Short, y As Short, s As String, col As ULong, col2 As ULong = RGB(&HFF, 0, &HFF))
Declare Sub TCentre(y As Short = -1, text As String = "", colour As ULong = RGB(255, 255, 255))
Declare Sub TCont(text As String, eol As Byte = 0)
Declare Sub AddButton(x As Short = -1, y As Short = -1, title As String, value As Short)
Declare Sub StackButton(title As String, value As Short = -1, direction As Byte = 0, separation As UShort = 16)
Declare Sub EndOfButtonStack
Declare Function ManageButtons As Short
Declare Sub ManageIcons
Declare Function ManageString(ByRef s As String) As String
Declare Function ManageSelector(sel As SelectorType) As Byte
Declare Sub Error_Message(text As String, title As String = "Error!")
Declare Sub Menu_License
Declare Sub Menu_StartNewTrack
Declare Sub Menu_LoadTrack
Declare Sub Menu_SaveTrack
Declare Sub Menu_TrackInfo
Declare Sub Menu_Help
Declare Sub Menu_Analysis
Declare Sub Menu_Scenery
Declare Sub Menu_Settings
Declare Sub Menu_Tournaments
Declare Sub Menu_TrackShot
Declare Sub Menu_Colouring

Declare Sub LoadConfiguration
Declare Sub LoadGraphics
Declare Sub LoadTrack(trk As String)
Declare Sub SaveTrack(trk As String)
Declare Sub SaveTrackImage(trk As String)
Declare Sub LoadMetaData(filenumber As Short = 0, content As String = "")
Declare Sub SaveMetaData(filenumber As Short)
Declare Function GetMetaDataFromRegistry As Byte
Declare Sub InitFiles(mask As String, x1 As Short, y1 As Short, x2 As Short, y2 As Short)
Declare Sub DetectDrives
Declare Sub SortFiles
Declare Function ManageFiles(akey As String = "") As String
Declare Sub ChangeTrackDirectory(d As String)

Declare Sub LoadTransformations
Declare Sub SaveTransformations

Declare Function FindStart As TrackVector
Declare Sub CheckTrack
Declare Function GetNext(slot As TrackVector, detour As Byte = 0) As TrackVector
Declare Sub GenerateSections
Declare Sub SolveSection(sn As Short)
Declare Sub SolvePath(pn As Short)
Declare Function PathToError(te As Short) As String
Declare Function PathToFinishLine(which As Byte = 0) As String
Declare Sub FollowPath(s As String, e As Byte = 0)
Declare Sub TrackErrorMessage(e As UByte)
Declare Sub DetectTerrainErrors(ByRef e As UByte, ByRef x As Byte, ByRef y As Byte)
Declare Function PathLength(n As Short, weighed As Byte = 0) As Long
Declare Function Timey(t As Long) As String
Declare Function AntiTimey(t As String) As Long
Declare Sub SelectByTyping
Declare Sub SmartSelect(etype As String, direction As Byte = 1)
Declare Sub CreatePath

Declare Sub PutIcon(u As UByte, v As UByte, x As UShort, y As UShort)
Declare Sub PutSmallIcon(u As UByte, v As UByte, x As UShort, y As UShort)

Declare Sub StartUp
Declare Sub QuitProgram
Declare Sub Editor
Declare Sub UpdateTitleBar

Declare Sub ClearTrack(x As UByte, y As UByte)
Declare Sub PickTrack(x As UByte, y As UByte)
Declare Sub SetTrack(x As UByte, y As UByte, code As UByte)
Declare Function GetParent(x As UByte, y As UByte) As UByte
Declare Sub BuildClosedCircuit
Declare Sub LinkTiles

Declare Sub RaiseTerrain(x As UByte, y As UByte)
Declare Sub LowerTerrain(x As UByte, y As UByte)
Declare Sub Flood(x As UByte, y As UByte)
Declare Sub Dry(x As UByte, y As UByte)

Declare Sub CopyOrCut(cut As Byte = 0)
Declare Sub Paste
Declare Sub CheckClipboardImport
Declare Function GetTrack(x As UByte, y As UByte, x2 As UByte, y2 As UByte) As String
Declare Sub PutTrack(x As UByte, y As UByte, t As String, forcefull As Byte = 0)
Declare Function HFlipTrack(t As String) As String
Declare Function VFlipTrack(t As String) As String
Declare Function CRotate(t As String) As String
Declare Function CCRotate(t As String) As String
Declare Sub NotASquare

Declare Sub PushUndo
Declare Sub Undo
Declare Sub Redo

Declare Function Hash32 Overload (content As String) As ULong
Declare Function Hash32 Overload As ULong
Declare Function PackedClip(m() As SGrid, wi As Byte = -1, he As Byte = -1) As String
Declare Sub DetectNotStunts(ByRef x As Byte, ByRef y As Byte, ByRef what As Byte)
Declare Sub UnRLETerrain(s As String)

Declare Function TMT_GetCurrentTrack(taddress As String) As Byte
Declare Function TMT_GetMain(taddress As String, ByRef curtrack As String, ByRef deadline As String) As Byte
Declare Function TMT_GetScoreboard(taddress As String, item() As Scoreboard, ByRef items As Byte) As Byte

Const ptitle = "Bliss " + THISVERSION '+ " beta"

'============================== Track structure variables
Dim Shared grid(1 To 31, 1 To 31) As SGrid
'Grid is 30x30, but an extra row and column are added to prevent
'segmentation fault in calculations involving path following and
'track element shortcut keys when a large element has been placed
'across the "fence".
Dim Shared clipboard As String, pasting As Byte = 0
Dim Shared last_cb_file_length As Long = 0
Dim Shared As Byte xselect, yselect, x2select, y2select, selecting = 0
Dim Shared As Byte xcursor = 1, ycursor = 1, drawkeyboardcursor	'Keyboard cursor
Dim Shared vlast As TrackVector
Dim Shared tr(0 To 255) As TransType, itr(0 To 5, 0 To 5, 0 To 11) As UByte
Dim Shared ttr(0 To 18) As TransType
Dim Shared section(1 To 255) As TrackSection, sections As Short
Dim Shared terror(1 To 100) As TrackError, terrors As Short
Dim Shared path(1 To MAXPATHS) As TrackPath, paths As Short
Dim Shared As UByte landscape = 4, format_byte = 152
Dim Shared meta As MetaData, started_editing As Double
Dim Shared default_format As UByte = FORMAT_COMBINED, thisfileformat As UByte = FORMAT_COMBINED
Dim Shared default_author As String, racer_weigh As Double = 7.2955

'Other objects
Dim Shared cars As Short, car(1 To MAXCARS) As Car, activecar As Byte

'Panel and graphics
Dim Shared graphic_size As UByte = 16, bigwidth As UByte = 22
Dim Shared As Short xoffs = 352, yoffs = 22	'Where to draw the track
Dim Shared As Short xpanel = 0, ypanel = 0	'Where to draw the panel
Dim Shared As Short xpalette = 198, ypalette = 484
Dim Shared As Short xpicons = 80, ypicons = 74
Dim Shared As Short xswitches = 70, yswitches = 380
Dim Shared current_page As UByte = 0, current_brush As UByte = 1
Dim Shared current_bgc As ULong = 0
Dim Shared current_border As ULong = RGB(220, 200, 0)
Dim Shared current_terrain_brush As UByte = 0
Dim Shared bigicons As Any Pointer, track_image_buffer As Any Pointer = 0
Dim Shared imageformat As String

'Undo-Redo
Dim Shared undobuffer(0 To UNDOLEVEL - 1) As String
Dim Shared As Short undohead = 0, undotail = UNDOLEVEL -1, undopointer = 0

'Switches
Dim Shared As Byte show_errors = -1, allow_errors = 0
Dim Shared As Byte data_codes = 0, smart_editing = 0
Dim Shared As Byte show_grid = -1, colouring_mode = 0
Dim Shared As Byte affect_terrain = 0, affect_track = -1

'Toolkit-related variables
Dim Shared button(1 To 8) As ButtonType, buttons As Byte = 0
Dim Shared cenx As Short, ceny As Short, lefx As Short
Dim Shared conx As Short, cony As Short, concolour As ULong = RGB(200, 200, 200)

'File-related variables
Dim Shared As String program_path, track_path, track_file
Dim Shared big_graphics_file As String
Dim Shared modified As Byte = 0
Dim Shared filey(1 To 512) As String, fileys As Short = 0
Dim Shared filer As FilerType, stringer As StringerType
Dim Shared dirlinks As Byte = 0, dirlink(1 To 10) As DirLink

'Latin-1 font pointers
Dim Shared As Any Ptr font, mask
Dim Shared changing_title As Byte	'Flag for window title updates

'Encoding translation arrays
Dim Shared toCP437(100) As EncX = _
	{(&HC1, 65), (&HC9, 144), (&HCD, 73), (&HD3, 79), (&HDA, 85), _
	 (&HE1, 160), (&HE9, 130), (&HED, 161), (&HF3, 162), (&HFA, 163), _
	 (&HD1, 165), (&HF1, 164), _
	 (&HC4, 142), (&HCB, 69), (&HCF, 73), (&HD6, 153), (&HDC, 154), _
	 (&HE4, 132), (&HEB, 137), (&HEF, 139), (&HF6, 148), (&HFC, 129), _
	 (&HC7, 128), (&HE7, 135), _
	 (&HC0, 65), (&HC8, 69), (&HCC, 73), (&HD2, 79), (&HD9, 85), _
	 (&HE0, 133), (&HE8, 138), (&HEC, 141), (&HF2, 149), (&HF9, 151), _
	 (&HC3, 65), (&HE3, 97), (&HD5, 79), (&HF5, 111), _
	 (&HC2, 65), (&HCA, 69), (&HCE, 73), (&HD4, 79), (&HDB, 85), _
	 (&HE2, 131), (&HEA, 136), (&HEE, 140), (&HF4, 147), (&HFB, 150), _
	 (&HA1, 173), (&HBF, 168), (&HAB, 174), (&HBB, 175), _
	 (&H150, 153), (&H151, 148), (&H170, 154), (&H171, 129)}

'CP437 to Unicode code points
Dim Shared fromCP437(128) As Long = _
	{&HC7, &HFC, &HE9, &HE2, &HE4, &HE0, &HE5, &HE7, &HEA, &HEB, &HE8, &HEF, &HEE, &HEC, &HC4, &HC5, _
	&HC9, &HE6, &HC6, &HF4, &HF6, &HF2, &HFB, &HF9, &HFF, &HD6, &HDC, &HA2, &HA3, &HA5, &H20A7, &H192, _
	&HE1, &HED, &HF3, &HFA, &HF1, &HD1, &HAA, &HBA, &HBF, &H2310, &HAC, &HBD, &HBC, &HA1, &HAB, &HBB, _
	&H2591, &H2592, &H2593, &H2502, &H2524, &H2561, &H2562, &H2556, &H2555, &H2563, &H2551, &H2557, &H255D, &H255C, &H255B, &H2510, _
	&H2514, &H2534, &H252C, &H251C, &H2500, &H253C, &H255E, &H255F, &H255A, &H2554, &H2569, &H2566, &H2560, &H2550, &H256C, &H2567, _
	&H2568, &H2564, &H2565, &H2559, &H2558, &H2552, &H2553, &H256B, &H256A, &H2518, &H250C, &H2588, &H2584, &H258C, &H2590, &H2580, _
	&H321, &HDF, &H393, &H3C0, &H3A3, &H3C3, &HB4, &H3C4, &H3A6, &H398, &H3A9, &H3B4, &H221E, &H3C6, &H3B5, &H2229, _
	&H2261, &HB1, &H2265, &H2264, &H2320, &H2321, &HF7, &H2248, &HB0, &H2219, &HB7, &H221A, &H207F, &HB2, &H25A0, &HA0}

'Colouring palette
Dim Shared cpal(0 To 23) As ULong = _
	{0, &HFFE0E000, &HFF5050FF, &HFFE02000, &HFF20E000, &HFFF08000, _
	&HFFA020FF, &HFFE0E0E0, &HFF808080, &HFF404040, &HFF000000, &HFFA0E000, _
	&HFF804020, &HFFE010F0, &HFF20E0A0, &HFFE04040, &HFF4040E0, &HFF00E0E0, _
	&HFFF020B0, &HFFFED000, &HFFE080FF, &HFF806010, &HFFA7E189, &HFF495827 }

'DOSBox flag
Dim Shared dosbox As Byte = 0

StartUp

#ifdef __FB_DOS__
	Dim Shared fake_screenlock_level As Short = 0
	dosbox = ScreenRes(1024, 768, 32, 2)
	If dosbox Then ScreenRes 1024, 768, 16, 2
	Width 128, 48
	Line (0, 0)-(1023, 767), RGB(30, 30, 50), BF
	yoffs += 64
	ypanel += 64
#else
	ScreenRes 1024, 704, 32
	Width 128, 44
	Line (0, 0)-(1023, 703), RGB(30, 30, 50), BF
	HTTP_Start
#endif

big_graphics_file = "biggfx"

LoadConfiguration
LoadGraphics
LoadTransformations
#ifndef RENDER_TO_CP437
	LoadFont "latin1p"
#endif


Dim today As Double
today = Now
meta.cyear = DatePart("yyyy", today)
meta.cmonth = DatePart("m", today)
meta.cday = DatePart("d", today)
meta.tool = "Bliss"
meta.toolversion = THISVERSION_NOPERIOD
meta.author = default_author
started_editing = Timer
thisfileformat = default_format

If bigwidth = 16 Then xoffs += 180

If Len(Command) Then
	Dim afile As String
	
	afile = Trim(Command)
	If FileExists(afile) Or FileExists(afile + ".trk") Then
		LoadTrack afile
		
		'This makes the file menus default to the directory
		'that the track was loaded from
		Dim n As Short
		#ifdef __FB_LINUX__
		n = InStrRev(afile, "/")
		#else
		n = InStrRev(afile, "\")
		#endif
		
		If n Then
			track_path = Left(afile, n)
		Else
			track_path = ""
		End If
	End If
End If

UpdateTitleBar
DrawTrack
DrawPanel

Menu_License
Editor

ImageDestroy bigicons
#ifndef RENDER_TO_CP437
	ImageDestroy font
	ImageDestroy mask
#endif

#ifndef __FB_DOS__
	HTTP_End
#endif
End


Function PackedClip(m() As SGrid, wi As Byte = -1, he As Byte = -1) As String
	Dim As Byte w, h, c, i, j
	Dim t As String, r As String
	
	If wi = -1 Then w = UBound(m, 1) Else w = wi
	If he = -1 Then h = UBound(m, 2) Else h = he
	t = Chr(w, h)
	
	For j = 1 To h
		For i = 1 to w
			c = 0 : r = ""
			If m(i, j).track Then
				c Or= 1
				r &= Chr(m(i, j).track)
			End If
			If m(i, j).land Then
				c Or= 2
				r &= Chr(m(i, j).land)
			End If
			If m(i, j).border Then
				c Or= 4
				r &= MkL(m(i, j).border)
			End If
			If m(i, j).bgc Then
				c Or= 8
				r &= MkL(m(i, j).bgc)
			End If
			
			t &= Chr(c) & r
		Next i
	Next j
	
	Return t
End Function


Sub DrawPanel
	Dim As Short sourcex, sourcey, i
	Dim s As String
	Dim indicator(0 To 8) As Short
	
	indicator(0) = Len(clipboard) <> 0
	indicator(1) = show_errors
	indicator(2) = allow_errors
	indicator(3) = data_codes
	indicator(4) = show_grid
	indicator(5) = affect_track
	indicator(6) = affect_terrain
	indicator(7) = colouring_mode
	indicator(8) = 0
	
	ScreenLock
	Line (xpanel, ypanel)-(xoffs - 1, ypanel + 704), RGB(30, 30, 50), BF

	'Draw border around menu icons
	Line (xpanel + xpicons - 32, ypanel + ypicons - 32)- STEP (239, 315), RGB(55, 65, 110), B
	Line (xpanel + xpicons - 31, ypanel + ypicons - 33)- STEP (239, 315), RGB(20, 20, 20), B
	If dosbox Then
		Put (xpanel + xpicons, ypanel + ypicons), bigicons, (13 * bigwidth, 12 * 22)- STEP (8 * bigwidth - 1, 219), Trans
	Else
		Put (xpanel + xpicons, ypanel + ypicons), bigicons, (13 * bigwidth, 12 * 22)- STEP (8 * bigwidth - 1, 219), Alpha
	End If
	
	'Draw switch indicator icons
	For i = 0 To 8
		PutIcon 21 + i, 12 - 2 * indicator(i), xpanel + xswitches + bigwidth * i, ypanel + yswitches
	Next i
	
	Select Case landscape
		Case 0 : s = "Desert"
		Case 1 : s = "Tropical"
		Case 2 : s = "Alpine"
		Case 3 : s = "City"
		Case 4 : s = "Country"
		Case Else : s = "Background #" + Trim(Str(landscape))
	End Select
	Draw String (100 - Len(s) * 4, 640 + ypanel), s, RGB(70, 200, 240)
	If landscape < 5 Then
		For i = 0 To 9
			PutSmallIcon i + 31, landscape + 21, xpanel + i * graphic_size + 20, ypanel + 664
		Next i
	Else
		Draw String (72, 664), "Chaotic", RGB(240, 160, 50)
	End If
	
	'Display border for coordinates and pointed element
	'and colouring options if activated
	If colouring_mode Then
		Line (xpanel + 16, ypanel + 420)- STEP (165, 47), RGB(55, 65, 110), B
		Line (xpanel + 17, ypanel + 419)- STEP (165, 47), RGB(20, 20, 20), B
		Line (xpanel + xpalette, ypanel + 420)- STEP (132, 47), RGB(55, 65, 110), B
		Line (xpanel + xpalette + 1, ypanel + 419)- STEP (132, 47), RGB(20, 20, 20), B
		If current_bgc Then
			Line (xpanel + xpalette + 16, ypanel + 434)- STEP (21, 21), current_bgc, BF
		Else
			Line (xpanel + xpalette + 16, ypanel + 434)- STEP (21, 21), RGB(100, 100, 100), B
		End If
		If current_border Then
			Line (xpanel + xpalette + 48, ypanel + 434)- STEP (21, 21), current_border, B
			Line (xpanel + xpalette + 49, ypanel + 435)- STEP (19, 19), current_border, B
		Else
			Line (xpanel + xpalette + 48, ypanel + 434)- STEP (21, 21), RGB(100, 100, 100), B, &H5555
		End If
	Else
		Line (xpanel + 16, ypanel + 420)- STEP (314, 47), RGB(55, 65, 110), B
		Line (xpanel + 17, ypanel + 419)- STEP (314, 47), RGB(20, 20, 20), B
	End If
	
	'Display current brush
	Line (xpanel + 16, ypanel + ypalette + 1)-(xpanel + 181, ypanel + ypalette + 131), RGB(55, 65, 110), B
	Line (xpanel + 17, ypanel + ypalette)-(xpanel + 182, ypanel + ypalette + 130), RGB(20, 20, 20), B
	If current_page >= 10 Then
		If current_terrain_brush <= 18 Then _
			PutIcon ttr(current_terrain_brush).x + 2, ttr(current_terrain_brush).y + 1, xpanel + 88, ypanel + ypalette + 44
		
		Select Case current_terrain_brush
			Case 0 : s = "Grass"
			Case 1 To 5 : s = "Water"
			Case 6 To 18 : s = "Mountain"
			Case Else : s = "Invalid"
		End Select
		Draw String (xpanel + 99 - Len(s) * 4, ypanel + ypalette + 82), s, RGB(255, 255, 255)
	Else
		If tr(current_brush).h = 1 And tr(current_brush).w = 1 Then
			PutIcon tr(current_brush).x, tr(current_brush).y, xpanel + 88, ypanel + ypalette + 44
		ElseIf tr(current_brush).h = 2 And tr(current_brush).w = 2 Then
			PutIcon tr(current_brush).x, tr(current_brush).y, xpanel + 77, ypanel + ypalette + 33
			PutIcon tr(current_brush).x + 1, tr(current_brush).y, xpanel + 99, ypanel + ypalette + 33
			PutIcon tr(current_brush).x, tr(current_brush).y + 1, xpanel + 77, ypanel + ypalette + 55
			PutIcon tr(current_brush).x + 1, tr(current_brush).y + 1, xpanel + 99, ypanel + ypalette + 55
		ElseIf tr(current_brush).h = 2 And tr(current_brush).w = 1 Then
			PutIcon tr(current_brush).x, tr(current_brush).y, xpanel + 88, ypanel + ypalette + 33
			PutIcon tr(current_brush).x, tr(current_brush).y + 1, xpanel + 88, ypanel + ypalette + 55
		ElseIf tr(current_brush).h = 1 And tr(current_brush).w = 2 Then
			PutIcon tr(current_brush).x, tr(current_brush).y, xpanel + 77, ypanel + ypalette + 44
			PutIcon tr(current_brush).x + 1, tr(current_brush).y, xpanel + 99, ypanel + ypalette + 44
		End If
		Draw String (xpanel + 99 - Len(Trim(tr(current_brush).id)) * 4, ypanel + ypalette + 82), Trim(tr(current_brush).id), RGB(255, 255, 255)
	End If
	
	'Draw palette
	If current_page = 10 Then	
		Put (xpanel + xpalette, ypanel + ypalette), bigicons, (0, 12 * 22)- STEP (6 * bigwidth - 1, 6 * 22 - 1), PSet
		
		'Draw palette grid
		For i = 0 To 6
			Line (xpanel + xpalette, ypanel + ypalette + 22 * i)- STEP (6 * bigwidth - 1, 0), 0
			Line (xpanel + xpalette + bigwidth * i, ypanel + ypalette)- STEP (0, 6 * 22 - 1), 0
		Next i
	ElseIf current_page >= 11 Then
		Line (xpanel + xpalette, ypanel + ypalette)- STEP(6 * bigwidth - 1, 6 * 22 - 1), RGB(&H0B, &H64, &H1B), BF
		Line (xpanel + xpalette, ypanel + ypalette)- STEP(6 * bigwidth, 6 * 22), 0, B
		Put (xpanel + xpalette + bigwidth, ypanel + ypalette + 2 * 22), bigicons, (2 * bigwidth, 14 * 22)- STEP (2 * bigwidth - 1, 2 * 22 - 1), PSet
		Put (xpanel + xpalette + 3 * bigwidth, ypanel + ypalette + 2 * 22), bigicons, (2 * bigwidth, 16 * 22)- STEP (2 * bigwidth - 1, 2 * 22 - 1), PSet
	Else
		sourcex = 6 * bigwidth * (current_page Mod 5)
		sourcey = 6 * 22 * (current_page \ 5)
		
		'Draw palette grid
		Line (xpanel + xpalette, ypanel + ypalette)- STEP(6 * bigwidth - 1, 6 * 22 - 1), RGB(&H0B, &H64, &H1B), BF
		For i = 0 To 6
			Line (xpanel + xpalette, ypanel + ypalette + 22 * i)- STEP (6 * bigwidth - 1, 0), 0
			Line (xpanel + xpalette + bigwidth * i, ypanel + ypalette)- STEP (0, 6 * 22 - 1), 0
		Next i
		
		'Draw palette icons
		If dosbox Then
			Put (xpanel + xpalette, ypanel + ypalette), bigicons, (sourcex, sourcey)- STEP (6 * bigwidth - 1, 6 * 22 - 1), Trans
		Else
			Put (xpanel + xpalette, ypanel + ypalette), bigicons, (sourcex, sourcey)- STEP (6 * bigwidth - 1, 6 * 22 - 1), Alpha
		End If
	End If
	
	Line (xpanel + xpalette, ypanel + ypalette + 22 * 7)- STEP (6 * bigwidth - 1, 2 * 22 - 1), RGB(30, 30, 50), BF

	If current_page < 10 Then
		Line (xpanel + xpalette + (current_page Mod 5) * bigwidth, ypanel + ypalette + (current_page \ 5 + 7) * 22)- STEP (21, 21), RGB(40, 160, 160), BF
	Else
		Line (xpanel + xpalette + 5 * bigwidth, ypanel + ypalette + (current_page - 3) * 22)- STEP (21, 21), RGB(40, 160, 160), BF
	End If
	
	'Draw palette page list buttons
	If dosbox Then
		Put (xpanel + xpalette, ypanel + ypalette + 22 * 7), bigicons, (7 * bigwidth, 12 * 22)- STEP (6 * bigwidth - 1, 2 * 22 - 1), Trans
	Else
		Put (xpanel + xpalette, ypanel + ypalette + 22 * 7), bigicons, (7 * bigwidth, 12 * 22)- STEP (6 * bigwidth - 1, 2 * 22 - 1), Alpha
	End If
	ScreenUnlock
End Sub


Sub DrawSpot(x As UByte, y As UByte, inked As Byte = 0)
	'Draw the full contents of a square in the map (both track and terrain)
	Dim As UByte c, t
	Dim As ULong bgc, border
	Dim As Short xg, yg
	Dim conflict As Byte
	
	c = grid(x, y).land
	t = grid(x, y).track
	bgc = grid(x, y).bgc
	border = grid(x, y).border
	xg = (x - 1) * bigwidth
	yg = (y - 1) * 22
	If track_image_buffer = 0 Then
		xg += xoffs
		yg += yoffs
	End If
	
	'First, draw the terrain
	If c > 18 Then
		If show_errors Then
			PutIcon 6, 12, xg, yg
			If data_codes <> 0 AndAlso t = 0 Then
				Draw String (xg + 4, yg + 4), Hex(c, 2), RGB(0, 0, 0)
			End If
		Else
			PutIcon 0, 12, xg, yg
		End If
	Else
		If show_errors Then
			Select Case c
				Case 0, 6
					PutIcon ttr(c).x + 2, ttr(c).y + 1, xg, yg
				Case 7
					If t = 0 Or t = 4 Or t = 14 Or t = 24 Or t = 39 Or t = 59 Or t = 98 Then
						PutIcon ttr(c).x + 2, ttr(c).y + 1, xg, yg
					Else
						PutIcon 6, 13, xg, yg
					End If
				Case 8
					If t = 0 Or t = 5 Or t = 15 Or t = 25 Or t = 36 Or t = 56 Or t = 95 Then
						PutIcon ttr(c).x + 2, ttr(c).y + 1, xg, yg
					Else
						PutIcon 6, 13, xg, yg
					End If
				Case 9
					If t = 0 Or t = 4 Or t = 14 Or t = 24 Or t = 38 Or t = 58 Or t = 97 Then
						PutIcon ttr(c).x + 2, ttr(c).y + 1, xg, yg
					Else
						PutIcon 6, 13, xg, yg
					End If
				Case 10
					If t = 0 Or t = 5 Or t = 15 Or t = 25 Or t = 37 Or t = 57 Or t = 96 Then
						PutIcon ttr(c).x + 2, ttr(c).y + 1, xg, yg
					Else
						PutIcon 6, 13, xg, yg
					End If
				Case 11 To 18
					If t = 0 Then
						PutIcon ttr(c).x + 2, ttr(c).y + 1, xg, yg
					Else
						PutIcon 6, 13, xg, yg
					End If
				Case Else	'The ones that have water
					If (tr(t).w > 1 Or tr(t).h > 1) And (t < 105 Or t > 108) Then
						PutIcon 6, 16, xg, yg
					Else
						PutIcon ttr(c).x + 2, ttr(c).y + 1, xg, yg
					End If
			End Select
		Else
			PutIcon ttr(c).x + 2, ttr(c).y + 1, xg, yg
		End If
	End If
	
	'Paint background colour if any
	If bgc Then
		Line track_image_buffer, (xg, yg)- Step (bigwidth - 1, 21), bgc, BF
	End If
	
	'If grid view is active, show grid between terrain and track
	'If show_grid Then Line (xg, yg)- Step (bigwidth, 22), 0, B
	If show_grid Then
		If track_image_buffer = 0 Then
			Line (xg + bigwidth - 1, yg)-(xg, yg), 0
			Line -(xg, yg + 21), 0
		Else
			Line track_image_buffer, (xg + bigwidth - 1, yg)-(xg, yg), RGB(0, 0, 0)
			Line track_image_buffer, -(xg, yg + 21), RGB(0, 0, 0)
		End If
	End If
	
	'Second, if it's a slope, draw the guides
	If (c = 7 Or c = 9) And (t = 4 Or t = 14 Or t = 24) Then		'North and South
		PutIcon 6, 15, xg, yg
	ElseIf (c = 8 Or c = 10) And (t = 5 Or t = 15 Or t = 25) Then	'East and West
		PutIcon 7, 15, xg, yg
	End If
		
	'Then draw the track
	If t >= 182 And t <= 252 Then
		If show_errors Then
			If data_codes Then
				Draw String (xg + 4, yg + 4), Hex(t, 2), RGB(200, 180, 0)
			Else
				PutIcon 6, 14, xg, yg
			End If
		End If
	ElseIf t < 182 Then
		If t = &H42 Then		'Vertical tunnel
			If (y = 1 OrElse grid(x, y - 1).track <> &H42) And (y = 30 OrElse grid(x, y + 1).track <> &H42) then
				PutIcon tr(t).x, tr(t).y, xg, yg
			ElseIf y > 1 And y < 30 AndAlso grid(x, y - 1).track = &H42 AndAlso grid(x, y + 1).track = &H42 Then
				PutIcon 8, 17, xg, yg
			ElseIf y > 1 AndAlso grid(x, y - 1).track = &H42 Then
				PutIcon 9, 17, xg, yg
			Else
				PutIcon 7, 17, xg, yg
			End If
		ElseIf t = &H43 Then	'Horizontal tunnel
			If (x = 1 OrElse grid(x - 1, y).track <> &H43) And (x = 30 OrElse grid(x + 1, y).track <> &H43) then
				PutIcon tr(t).x, tr(t).y, xg, yg
			ElseIf x > 1 And x < 30 AndAlso grid(x - 1, y).track = &H43 AndAlso grid(x + 1, y).track = &H43 Then
				PutIcon 8, 16, xg, yg
			ElseIf x > 1 AndAlso grid(x - 1, y).track = &H43 Then
				PutIcon 9, 16, xg, yg
			Else
				PutIcon 7, 16, xg, yg
			End If
		ElseIf data_codes <> 0 AndAlso (tr(t).w > 1 Or tr(t).h > 1) Then
			PutIcon tr(t).xsmall, tr(t).ysmall, xg, yg
		Else
			PutIcon tr(t).x, tr(t).y, xg, yg
		End If
		conflict = 0
		If x > 1 AndAlso tr(grid(x - 1, y).track).w = 2 Then conflict = -1
		If y > 1 AndAlso tr(grid(x, y - 1).track).h = 2 Then conflict = -1
		If (x > 1 And y > 1) AndAlso (tr(grid(x - 1, y - 1).track).h = 2 And tr(grid(x - 1, y - 1).track).w = 2) Then conflict = -1
		If conflict <> 0 And show_errors <> 0 Then Line (xg, yg)-(xg + bigwidth - 1, yg + 21), RGB(&HF0, &HD0, 0), B
	ElseIf t = 253 Then
		If data_codes Then
			PutIcon 7, 14, xg, yg
		Else
			If x = 1 Or y = 1 OrElse tr(grid(x - 1, y - 1).track).w < 2 OrElse tr(grid(x - 1, y - 1).track).h < 2 Then
				If show_errors Then PutIcon 6, 14, xg, yg
			Else
				PutIcon tr(grid(x - 1, y - 1).track).x + 1, tr(grid(x - 1, y - 1).track).y + 1, xg, yg
			End If
		End If
	ElseIf t = 254 Then
		If data_codes Then
			PutIcon 8, 14, xg, yg
		Else
			If y = 1 OrElse tr(grid(x, y - 1).track).h < 2 Then
				If show_errors Then PutIcon 6, 14, xg, yg
			Else
				PutIcon tr(grid(x, y - 1).track).x, tr(grid(x, y - 1).track).y + 1, xg, yg
			End If
		End If
	ElseIf t = 255 Then
		If data_codes Then
			PutIcon 9, 14, xg, yg
		Else
			If x = 1 OrElse tr(grid(x - 1, y).track).w < 2 Then
				If show_errors Then PutIcon 6, 14, xg, yg
			Else
				PutIcon tr(grid(x - 1, y).track).x + 1, tr(grid(x - 1, y).track).y, xg, yg
			End If
		End If
	End If
	
	'Draw border if any
	If border Then
		If y < 1 OrElse grid(x, y - 1).border <> border Then _
			Line track_image_buffer, (xg, yg)- Step (bigwidth - 1, 1), border, B
		If x < 1 OrElse grid(x - 1, y).border <> border Then _
			Line track_image_buffer, (xg, yg)- Step (1, 21), border, B
		If y > 29 OrElse grid(x, y + 1).border <> border Then _
			Line track_image_buffer, (xg, yg + 21)- Step (bigwidth - 1, -1), border, B
		If x > 29 OrElse grid(x + 1, y).border <> border Then _
			Line track_image_buffer, (xg + bigwidth - 1, yg)- Step (-1, 21), border, B
	End If
	
	'Mark as selected if it is
	If xselect Then
		Dim As Short xs1, xs2, ys1, ys2
		
		If xselect <= x2select Then xs1 = xselect : xs2 = x2select Else xs1 = x2select : xs2 = xselect
		If yselect <= y2select Then ys1 = yselect : ys2 = y2select Else ys1 = y2select : ys2 = yselect
		
		If x >= xs1 And x <= xs2 And _
			y >= ys1 And y <= ys2 Then
			If dosbox Then
				PutIcon 9, 15, xg, yg
			Else
				PutIcon 8, 15, xg, yg
			End If
		End If
	End If
	
	'Also mark it if just requested
	If inked Then PutIcon 8, 15, xg, yg
End Sub


Sub DrawTrack
	Dim As Short x, y
	
	ScreenLock
	If track_image_buffer = 0 Then
		If show_grid Then
			Line (xoffs, yoffs)-(xoffs + 30 * bigwidth, yoffs + 30 * 22), 0, B	
		Else
			Line (xoffs, yoffs)-(xoffs + 30 * bigwidth, yoffs + 30 * 22), RGB(30, 30, 50), B
		End If
	Else
		If show_grid Then
			Line track_image_buffer, (0, 0)-(30 * bigwidth, 30 * 22), RGB(0, 0, 0), B
		Else
			Line track_image_buffer, (0, 0)-(30 * bigwidth, 30 * 22), RGB(30, 30, 50), B
		End If
	End If
	For y = 1 To 30
		For x = 1 To 30
			DrawSpot x, y
		Next x
	Next y
	ScreenUnlock
End Sub


'Detect whether something exists on the grid that could not have
'been created by Stunts built-in editor
Sub DetectNotStunts(ByRef x As Byte, ByRef y As Byte, ByRef what As Byte)
	Dim As Byte i, j
	
	what = 0
	
	'Non-standard terrain elements
	For j = 1 To 30
		For i = 1 To 30
			If grid(i, j).land > 18 Then
				what = 1
				x = i : y = j
				Exit Sub
			End If
		Next i
	Next j
	
	'Non-standard track elements
	For j = 1 To 30
		For i = 1 To 30
			If grid(i, j).track >= &HB6 And grid(i, j).track <= &HFC Then
				what = 2
				x = i : y = j
				Exit Sub
			ElseIf grid(i, j).track = 2 Or grid(i, j).track = 3 Then
				what = 3	'Player's or Opponent's car
				x = i : y = j
				Exit Sub
			End If
		Next i
	Next j
	
	'Big track element without its corresponding fillers
	For j = 1 To 30
		For i = 1 To 30
			If tr(grid(i, j).track).w = 2 Then
				If i = 30 OrElse grid(i + 1, j).track <> 255 Then _
					what = 4
			End If
			If tr(grid(i, j).track).h = 2 Then
				If j = 30 OrElse grid(i, j + 1).track <> 254 Then
					what = 4
				ElseIf tr(grid(i, j).track).w = 2 Then
					If i = 30 OrElse grid(i + 1, j + 1).track <> 253 Then _
						what = 4
				End If
			End If
			If what Then
				x = i : y = j
				Exit Sub
			End If
		Next i
	Next j
	
	'Filler without a parent track element
	For j = 1 To 30
		For i = 1 To 30
			Select Case grid(i, j).track
				Case 253
					If i = 1 Or j = 1 OrElse _
						(tr(grid(i - 1, j - 1).track).h < 2 Or _
						tr(grid(i - 1, j - 1).track).w < 2) Then what = 5
				Case 254
					If j = 1 OrElse tr(grid(i, j - 1).track).h < 2 Then what = 5
				Case 255
					If i = 1 OrElse tr(grid(i - 1, j).track).w < 2 Then what = 5
			End Select
			If what Then
				x = i : y = j
				Exit Sub
			End If
		Next i
	Next j
	
	'Mountain corner with something on top
	For j = 1 To 30
		For i = 1 To 30
			If grid(i, j).land >= 11 And grid(i, j).track <> 0 Then
				what = 6
				x = i : y = j
				Exit Sub
			End If
		Next i
	Next j
	
	'Mountain side with illegal element on top
	For j = 1 To 30
		For i = 1 To 30
			Select Case grid(i, j).land
				Case 7
					If InStr(Chr(0, 4, 14, &H18, &H3B, &H27, &H62), Chr(grid(i, j).track)) = 0 Then _
						what = 7
				Case 8
					If InStr(Chr(0, 5, 15, &H19, &H38, &H24, &H5F), Chr(grid(i, j).track)) = 0 Then _
						what = 7
				Case 9
					If InStr(Chr(0, 4, 14, &H18, &H3A, &H26, &H61), Chr(grid(i, j).track)) = 0 Then _
						what = 7
				Case 10
					If InStr(Chr(0, 5, 15, &H19, &H39, &H25, &H60), Chr(grid(i, j).track)) = 0 Then _
						what = 7
			End Select
			If what Then
				x = i : y = j
				Exit Sub
			End If
		Next i
	Next j
	
	'Water with illegal element on top
	For j = 1 To 30
		For i = 1 To 30
			If grid(i, j).land >= 1 And grid(i, j).land <= 5 Then
				If InStr(Chr(0, &H68, &H23, &H67, &H22, &H69, &H6A, _
					&H6B, &H6C, &HAB, &HAE, &HAC, &HAD, &HFD, &HFE, _
					&HFF), Chr(grid(i, j).track)) = 0 Then
					
					what = 8
					x = i : y = j
					Exit Sub
				End If
			End If
		Next i
	Next j
	
	'It is still possible to draw a part of a big element, not
	'including the parent block, on water and go undetected!
End Sub


Sub DetectTerrainErrors(ByRef e As UByte, ByRef x As Byte, ByRef y As Byte)
	Dim As Byte i, j
	
	e = 0
	
	'Non-standard terrain elements - Error 40
	For j = 1 To 30
		For i = 1 To 30
			If grid(i, j).land > 18 Then
				e = 40
				x = i : y = j
				Exit Sub
			End If
		Next i
	Next j
	
	'Mountain borders mismatch - Error 41
	Dim tvert(0 To 18, 0 To 1, 0 To 1) As Byte
	
	'First create terrain element descriptors
	tvert(6, 0, 0) = 1 : tvert(6, 0, 1) = 1
	tvert(6, 1, 0) = 1 : tvert(6, 1, 1) = 1
	
	tvert(7, 0, 0) = 1 : tvert(7, 0, 1) = 1
	tvert(7, 1, 0) = 0 : tvert(7, 1, 1) = 0
	
	tvert(8, 0, 0) = 1 : tvert(8, 0, 1) = 0
	tvert(8, 1, 0) = 1 : tvert(8, 1, 1) = 0
	
	tvert(9, 0, 0) = 0 : tvert(9, 0, 1) = 0
	tvert(9, 1, 0) = 1 : tvert(9, 1, 1) = 1
	
	tvert(10, 0, 0) = 0 : tvert(10, 0, 1) = 1
	tvert(10, 1, 0) = 0 : tvert(10, 1, 1) = 1
	
	tvert(11, 0, 0) = 1 : tvert(11, 0, 1) = 0
	tvert(11, 1, 0) = 0 : tvert(11, 1, 1) = 0
	
	tvert(12, 0, 0) = 0 : tvert(12, 0, 1) = 0
	tvert(12, 1, 0) = 1 : tvert(12, 1, 1) = 0
	
	tvert(13, 0, 0) = 0 : tvert(13, 0, 1) = 0
	tvert(13, 1, 0) = 0 : tvert(13, 1, 1) = 1
	
	tvert(14, 0, 0) = 0 : tvert(14, 0, 1) = 1
	tvert(14, 1, 0) = 0 : tvert(14, 1, 1) = 0
	
	tvert(15, 0, 0) = 1 : tvert(15, 0, 1) = 1
	tvert(15, 1, 0) = 1 : tvert(15, 1, 1) = 0
	
	tvert(16, 0, 0) = 1 : tvert(16, 0, 1) = 0
	tvert(16, 1, 0) = 1 : tvert(16, 1, 1) = 1
	
	tvert(17, 0, 0) = 0 : tvert(17, 0, 1) = 1
	tvert(17, 1, 0) = 1 : tvert(17, 1, 1) = 1
	
	tvert(18, 0, 0) = 1 : tvert(18, 0, 1) = 1
	tvert(18, 1, 0) = 0 : tvert(18, 1, 1) = 1
	
	'Now check to the right and below
	For j = 1 To 30
		For i = 1 To 30
			If i < 30 Then
				If tvert(grid(i, j).land, 0, 1) <> tvert(grid(i + 1, j).land, 0, 0) _
					Or tvert(grid(i, j).land, 1, 1) <> tvert(grid(i + 1, j).land, 1, 0) Then
				
					e = 41
					x = i + 1 : y = j
					Exit Sub
				End If
			End If
			
			If j < 30 Then
				If tvert(grid(i, j).land, 1, 0) <> tvert(grid(i, j + 1).land, 0, 0) _
					Or tvert(grid(i, j).land, 1, 1) <> tvert(grid(i, j + 1).land, 0, 1) Then
				
					e = 41
					x = i : y = j + 1
					Exit Sub
				End If
			End If
		Next i
	Next j
	
	'Mountain corner with something on top - Error 50
	For j = 1 To 30
		For i = 1 To 30
			If grid(i, j).land >= 11 And grid(i, j).track <> 0 Then
				e = 50
				x = i : y = j
				Exit Sub
			End If
		Next i
	Next j
	
	'Mountain side with illegal element on top - Error 51
	For j = 1 To 30
		For i = 1 To 30
			Select Case grid(i, j).land
				Case 7
					If InStr(Chr(0, 4, 14, &H18, &H3B, &H27, &H62), Chr(grid(i, j).track)) = 0 Then _
						e = 51
				Case 8
					If InStr(Chr(0, 5, 15, &H19, &H38, &H24, &H5F), Chr(grid(i, j).track)) = 0 Then _
						e = 51
				Case 9
					If InStr(Chr(0, 4, 14, &H18, &H3A, &H26, &H61), Chr(grid(i, j).track)) = 0 Then _
						e = 51
				Case 10
					If InStr(Chr(0, 5, 15, &H19, &H39, &H25, &H60), Chr(grid(i, j).track)) = 0 Then _
						e = 51
			End Select
			If e Then
				x = i : y = j
				Exit Sub
			End If
		Next i
	Next j
End Sub


Sub DoNothing
End Sub


Sub DOSScreenTitle(title As String)
	Line (0, 0)-(1023, 31), RGB(60, 60, 100), BF
	Draw String (512 - Len(title) * 4, 8), title, RGB(255, 255, 255)
	PutIcon 24, 6, 16, 4
End Sub


Sub DrawBox(x1 As Short, y1 As Short, x2 As Short, y2 As Short)
	Line (x1, y1)-(x2, y2), RGB(30, 30, 50), BF
	Line (x1 + 2, y1 + 2)-(x2 - 2, y2 - 2), RGB(200, 200, 200), B
	Line (x1 + 3, y1 + 3)-(x2 - 3, y2 - 3), RGB(200, 200, 200), B
End Sub


Sub MenuBox(boxwidth As Short, boxheight As Short, title As String)
	Dim xcentre As Short, ycentre As Short
	
	xcentre = xoffs + 15 * bigwidth
	ycentre = yoffs + 15 * 22
	
	DrawBox xcentre - boxwidth * 8, _
			ycentre - boxheight * 8, _
			xcentre + boxwidth * 8, _
			ycentre + boxheight * 8
		
	If Len(title) Then
		Draw String (xcentre - Len(title) * 4, _
					ycentre - (boxheight - 1) * 8 + 2), _
					title, RGB(255, 255, 255)
		Line (xcentre - (boxwidth - 6) * 8, _
				ycentre - (boxheight - 4) * 8)- _
				(xcentre + (boxwidth - 6) * 8, _
				ycentre - (boxheight - 4) * 8 + 1), _
				RGB(200, 200, 200), B
		ceny = ycentre - (boxheight - 4) * 8 + 12
	Else
		ceny = ycentre - boxheight * 8 + 8
	End If
	
	cenx = xcentre
	lefx = xcentre - boxwidth * 8 + 16
End Sub


Sub SelectBackground
	Dim As Short i, j, centre
	Dim s As String, akey As String
	Dim As Integer xm, ym, wm, bm
	Dim As Byte current, previous = -1
	Dim custom_background As UByte = 5, update_custom As Byte = 0
	Dim text(0 To 5) As String
	
	text(0) = "Desert"
	text(1) = "Tropical"
	text(2) = "Alpine"
	text(3) = "City"
	text(4) = "Country"
	text(5) = "Custom/Chaotic"
	
	MenuBox 25, 25 - 3 * (allow_errors <> 0), "Select Background"
	centre = xoffs + 15 * bigwidth
	
	For i = 0 To 4 - (allow_errors <> 0)
		s = text(i)
		Draw String (centre - Len(s) * 4, i * 50 + ceny + 16), s, RGB(70, 200, 240)
		If i = 5 Then
			Draw String (centre - 56, ceny + 286), "Background #" + Hex(custom_background, 2), RGB(240, 160, 50)
		Else
			For j = 0 To 9
				PutIcon j, i + 22, centre + (j - 5) * bigwidth, i * 50 + ceny + 32
			Next j
		End If
	Next i
	
	buttons = 0 : ceny += (292 - 50 * (allow_errors <> 0))
	StackButton " Cancel ", 1
	EndOfButtonStack
	ceny -= (292 - 50 * (allow_errors <> 0))
		
	Do
		GetMouse xm, ym, wm, bm
		
		If xm >= centre - 5 * bigwidth - 16 And xm < centre + 5 * bigwidth + 16 _
			AndAlso ym >= ceny + 8 And ym < ceny + 256 - (50 * (allow_errors <> 0)) Then
		
			current = (ym - ceny - 8) \ 50
		Else
			current = -1
		End If
		
		If current <> previous Then
			If previous >= 0 Then
				ScreenLock
				Line (centre - 5 * bigwidth - 16, 50 * previous + ceny + 8)- _
					Step (10 * bigwidth + 31, 49), RGB(30, 30, 50), BF
				s = text(previous)
				Draw String (centre - Len(s) * 4, previous * 50 + ceny + 16), s, RGB(70, 200, 240)
				If previous = 5 Then
					Draw String (centre - 56, ceny + 286), "Background #" + Hex(custom_background, 2), RGB(240, 160, 50)
				Else
					For j = 0 To 9
						PutIcon j, previous + 22, centre + (j - 5) * bigwidth, previous * 50 + ceny + 32
					Next j
				End If
				ScreenUnlock
			End If
			If current >= 0 Then
				ScreenLock
				Line (centre - 5 * bigwidth - 16, 50 * current + ceny + 8)- _
					Step (10 * bigwidth + 31, 49), RGB(10, 10, 10), BF
				s = text(current)
				Draw String (centre - Len(s) * 4, current * 50 + ceny + 16), s, RGB(70, 200, 240)
				If current = 5 Then
					Draw String (centre - 56, ceny + 286), "Background #" + Hex(custom_background, 2), RGB(240, 160, 50)
				Else
					For j = 0 To 9
						PutIcon j, current + 22, centre + (j - 5) * bigwidth, current * 50 + ceny + 32
					Next j
				End If
				ScreenUnlock
			End If
			
			previous = current
		End If
				
		If bm = 1 Then
			If current >= 0 Then
				landscape = current
				If landscape = 5 Then landscape = custom_background
				Exit Do
			End If
		ElseIf bm = 2 Then
			Exit Do
		End If
	
		If ManageButtons Then Exit Do
		
		akey = InKey
		Select Case akey
			Case "=", "+"
				custom_background += 1
				update_custom = -1
			Case "-"
				custom_background -= 1
				update_custom = -1
			Case "0" To "9"
				custom_background ShL= 4
				custom_background Or= ValInt(akey)
				update_custom = -1
			Case "A" To "F", "a" To "f"
				custom_background ShL= 4
				custom_background Or= (ASC(UCase(akey)) - 55)
				update_custom = -1
			Case Chr(27) : Exit Do
		End Select
		
		If update_custom Then
			If allow_errors Then
				If current = 5 Then
					Line (centre - 56, ceny + 286)- Step (111, 15), RGB(10, 10, 10), BF
				Else
					Line (centre - 56, ceny + 286)- Step (111, 15), RGB(30, 30, 50), BF
				End If
				Draw String (centre - 56, ceny + 286), "Background #" + Hex(custom_background, 2), RGB(240, 160, 50)
			End If
			update_custom = 0
		End If
	Loop
	
	buttons = 0
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	Do : Loop Until Len(InKey) = 0
End Sub


Sub TCentre(y As Short = -1, text As String = "", colour As ULong = RGB(255, 255, 255))
	If y >= 0 Then ceny = y
	cenx = xoffs + 15 * bigwidth - 4 * Len(text)
	If Len(text) Then Draw String (cenx, ceny), text, colour
	cenx = xoffs + 15 * bigwidth
	ceny += 16
	conx = lefx : cony = ceny
End Sub


Sub TCont(text As String, eol As Byte = 0)
	Draw String (conx, cony), text, concolour
	If eol Then
		cony += 16
		conx = lefx
	Else
		conx += 8 * Len(text)
	End If
End Sub


Function Timey(t As Long) As String
	Dim As Byte c, s, m, h
	Dim As String r, result
	
	c = t Mod 100 : t \= 100
	s = t Mod 60 : t \= 60
	m = t Mod 60 : t \= 60
	h = t
	
	r = Str(c) : r = String(2 - Len(r), "0") + r
	result = r
	r = Str(s) : r = String(2 - Len(r), "0") + r
	result = r + "." + result
	r = Str(m)
	If h Then r = String(2 - Len(r), "0") + r
	result = r + ":" + result
	If h Then
		result = Str(h) + ":" + result
	End If
	
	Return result
End Function


Sub TLeft(y As Short = -1, text As String = "", colour As ULong = RGB(255, 255, 255))
	If y >= 0 Then ceny = y
	If Len(text) Then
		#ifdef RENDER_TO_CP437
			Draw String (lefx, ceny), text, colour
		#else
			PutString lefx, ceny, text, colour
		#endif
	End If
	cenx = xoffs + 15 * bigwidth
	ceny += 16
	conx = lefx : cony = ceny
End Sub


Sub AddButton(x As Short = -1, y As Short = -1, title As String, value As Short)
	buttons += 1
	If x >= 0 Then button(buttons).x1 = x Else button(buttons).x1 = cenx
	If y >= 0 Then button(buttons).y1 = y Else button(buttons).y1 = ceny
	button(buttons).x2 = button(buttons).x1 + 8 * Len(title) + 16
	button(buttons).y2 = button(buttons).y1 + 31
	button(buttons).title = title
	button(buttons).value = value
	
	Line (button(buttons).x1, button(buttons).y1)-(button(buttons).x2, button(buttons).y1 + 31), RGB(200, 200, 200), B
	Line (button(buttons).x1 + 1, button(buttons).y1 + 1)-(button(buttons).x2 - 1, button(buttons).y1 + 30), RGB(200, 200, 200), B
	Line (button(buttons).x1 + 2, button(buttons).y1 + 2)-(button(buttons).x2 - 2, button(buttons).y1 + 29), RGB(30, 30, 50), BF
	Draw String (button(buttons).x1 + 8, button(buttons).y1 + 8), title, RGB(200, 200, 200)
	
	ceny = button(buttons).y1
	cenx = button(buttons).x2 + 8
End Sub


Sub StackButton(title As String, value As Short = -1, direction As Byte = 0, separation As UShort = 16)
	Static lastvalue As Short = 0, first As Short = 0
	Dim i As Short

	If Len(title) = 0 Then
		first = 0
		Exit Sub
	End If
	
	buttons += 1
	If first = 0 Then first = buttons : lastvalue = 0
	
	If value Then
		button(buttons).value = value
		lastvalue = value
	Else
		lastvalue += 1
		button(buttons).value = lastvalue
	End If
	
	button(buttons).title = title
	button(buttons).y1 = ceny : button(buttons).y2 = ceny + 31
	If direction > 0 Then	'Stack to the right
		If buttons = first Then
			button(buttons).x1 = cenx
		Else
			button(buttons).x1 = button(buttons - 1).x2 + separation
		End If
		button(buttons).x2 = button(buttons).x1 + 8 * Len(title) + 16
	ElseIf direction < 0 Then	'Stack to the left
		If buttons = first Then
			button(buttons).x2 = cenx
		Else
			button(buttons).x2 = button(buttons - 1).x1 - separation
		End If
		button(buttons).x1 = button(buttons).x2 - 8 * Len(title) - 16
	Else	'Stack around a centre
		If buttons = first Then
			button(buttons).x1 = cenx - Len(title) * 4 - 8
			button(buttons).x2 = cenx + Len(title) * 4 + 8
		Else
			For i = first To buttons - 1
				button(i).x1 -= (Len(title) * 4 + 8 + separation \ 2)
				button(i).x2 -= (Len(title) * 4 + 8 + separation \ 2)
			Next i
			button(buttons).x1 = button(buttons - 1).x2 + separation
			button(buttons).x2 = button(buttons).x1 + Len(title) * 8 + 16
		End If
	End If
End Sub


Sub EndOfButtonStack
	Dim i As Short
	
	StackButton ""
	For i = 1 To buttons
		Line (button(i).x1, button(i).y1)-(button(i).x2, button(i).y1 + 31), RGB(200, 200, 200), B
		Line (button(i).x1 + 1, button(i).y1 + 1)-(button(i).x2 - 1, button(i).y1 + 30), RGB(200, 200, 200), B
		Line (button(i).x1 + 2, button(i).y1 + 2)-(button(i).x2 - 2, button(i).y1 + 29), RGB(30, 30, 50), BF
		Draw String (button(i).x1 + 8, button(i).y1 + 8), button(i).title, RGB(200, 200, 200)
	Next i
End Sub


Function ManageButtons As Short
	Static ex As Byte = 0
	Dim As Integer xm, ym, wm, bm
	Dim i As Byte, active As Byte = 0
	
	GetMouse xm, ym, wm, bm
	
	For i = 1 To buttons
		If xm >= button(i).x1 And xm <= button(i).x2 And ym >= button(i).y1 And ym <= button(i).y2 Then
			active = i
			Exit For
		End If
	Next i
	
	If active <> ex Then
		If ex <> 0 And ex <= buttons Then
			Line (button(ex).x1, button(ex).y1)-(button(ex).x2, button(ex).y2), RGB(200, 200, 200), B
			Line (button(ex).x1 + 1, button(ex).y1 + 1)-(button(ex).x2 - 1, button(ex).y2 - 1), RGB(200, 200, 200), B
			Line (button(ex).x1 + 2, button(ex).y1 + 2)-(button(ex).x2 - 2, button(ex).y2 - 2), RGB(30, 30, 50), BF
			Draw String (button(ex).x1 + 8, button(ex).y1 + 8), button(ex).title, RGB(200, 200, 200)
		End If
		If active <> 0 And active <= buttons Then
			Line (button(active).x1, button(active).y1)-(button(active).x2, button(active).y2), RGB(200, 200, 200), BF
			Draw String (button(active).x1 + 8, button(active).y1 + 8), button(active).title, RGB(30, 30, 50)
		End If
		ex = active
	End If
	
	If bm = 1 And active <> 0 And active <= buttons Then
		Return button(active).value
	Else
		Return 0
	End If
End Function


Sub ManageIcons
	Dim As Integer xm, ym, wm, bm
	Dim As Short xi, yi
	Dim and_now As Byte
	Dim tempboard As String
	Static highlit As Byte = -1, exwm As Integer
	Static internal_bgc As Byte = 0
	Dim oname(0 To 19) As String
	
	oname(0) = "New Track"
	oname(1) = "Save Track"
	oname(2) = "Load Track"
	oname(3) = "Exit Bliss"
	
	oname(4) = "Select"
	oname(5) = "Copy"
	oname(6) = "Cut"
	oname(7) = "Paste"
	
	oname(8) = "Flip Horizontally"
	oname(9) = "Flip Vertically"
	oname(10) = "Rotate Clockwise"
	oname(11) = "Rotate Ctr-clockwise"
	
	oname(12) = "Track Information"
	oname(13) = "Undo"
	oname(14) = "Redo"
	oname(15) = "Help"
	
	oname(16) = "Generate Scenery"
	oname(17) = "Track Analysis"
	oname(18) = "Tournaments"
	oname(19) = "Settings"
	
	GetMouse xm, ym, wm, bm
	
	'Calculate menu item position
	If xm < xpanel + xpicons Or xm >= xpanel + xpicons + 176 Or _
			ym < ypanel + ypicons Or ym >= ypanel + ypicons + 220 Then
		and_now = -1
	Else
		xi = (xm - xpanel - xpicons) \ 44
		yi = (ym - ypanel - ypicons) \ 44
		and_now = yi * 4 + xi
	End If
	
	If highlit <> and_now Then
		ScreenLock
		If highlit <> -1 Then
			Line ((highlit Mod 4) * 44 + xpanel + xpicons, (highlit \ 4) * 44 + ypanel + ypicons)- STEP (43, 43), RGB(30, 30, 50), BF
			If dosbox Then
				Put ((highlit Mod 4) * 44 + xpanel + xpicons, (highlit \ 4) * 44 + ypanel + ypicons), bigicons, (13 * bigwidth + (highlit Mod 4) * 44, 12 * 22 + (highlit \ 4) * 44)- STEP(bigwidth * 2 - 1, 43), Trans
			Else
				Put ((highlit Mod 4) * 44 + xpanel + xpicons, (highlit \ 4) * 44 + ypanel + ypicons), bigicons, (13 * bigwidth + (highlit Mod 4) * 44, 12 * 22 + (highlit \ 4) * 44)- STEP(bigwidth * 2 - 1, 43), Alpha
			End If
		End If
		Line (xpanel + xpicons, ypanel + ypicons + 236)- Step (175, 15), RGB(30, 30, 50), BF
		If and_now <> -1 Then
			Line (xi * 44 + xpanel + xpicons, yi * 44 + ypanel + ypicons)- STEP (43, 43), RGB(30, &HF2, &HF3), BF
			If dosbox Then
				Put (xi * 44 + xpanel + xpicons, yi * 44 + ypanel + ypicons), bigicons, (13 * bigwidth + (and_now Mod 4) * 44, 12 * 22 + (and_now \ 4) * 44)- STEP(bigwidth * 2 - 1, 43), Trans
			Else
				Put (xi * 44 + xpanel + xpicons, yi * 44 + ypanel + ypicons), bigicons, (13 * bigwidth + (and_now Mod 4) * 44, 12 * 22 + (and_now \ 4) * 44)- STEP(bigwidth * 2 - 1, 43), Alpha
			End If
			Draw String (xpanel + xpicons + 4 * bigwidth - Len(oname(and_now)) * 4, ypanel + ypicons + 236), oname(and_now), RGB(200, 200, 200)
		End If
		ScreenUnlock
		highlit = and_now
	End If
	
	'Activate menu icon option
	If highlit <> -1 And bm = 1 Then
		Select Case highlit
			Case 0 : Menu_StartNewTrack
			Case 1 : Menu_SaveTrack
			Case 2 : Menu_LoadTrack
			Case 3 : QuitProgram
			Case 4
				selecting = -1 : xselect = 0 : DrawTrack
				Do
					GetMouse xm, ym, wm, bm
				Loop Until bm <> 1
			Case 5, 6
				CopyOrCut highlit = 6
				If highlit = 6 Then PushUndo
				DrawPanel
				Do
					GetMouse xm, ym, wm, bm
				Loop Until bm <> 1
			Case 7
				Paste
				Do
					GetMouse xm, ym, wm, bm
				Loop Until bm <> 1
			Case 8	'Flip horizontally
				If pasting Then
					clipboard = HFlipTrack(clipboard)
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				ElseIf xselect Then
					tempboard = GetTrack(xselect, yselect, x2select, y2select)
					tempboard = HFlipTrack(tempboard)
					PutTrack xselect, yselect, tempboard, -1
					DrawTrack
					PushUndo
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				Else
					tempboard = GetTrack(1, 1, 30, 30)
					tempboard = HFlipTrack(tempboard)
					PutTrack 1, 1, tempboard, -1
					DrawTrack
					PushUndo
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				End If
			Case 9	'Flip vertically
				If pasting Then
					clipboard = VFlipTrack(clipboard)
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				ElseIf xselect Then
					tempboard = GetTrack(xselect, yselect, x2select, y2select)
					tempboard = VFlipTrack(tempboard)
					PutTrack xselect, yselect, tempboard, -1
					DrawTrack
					PushUndo
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				Else
					tempboard = GetTrack(1, 1, 30, 30)
					tempboard = VFlipTrack(tempboard)
					PutTrack 1, 1, tempboard, -1
					DrawTrack
					PushUndo
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				End If
			Case 10	'Rotate clockwise
				If pasting Then
					clipboard = CRotate(clipboard)
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				ElseIf xselect Then
					If x2select - xselect = y2select - yselect Then
						tempboard = GetTrack(xselect, yselect, x2select, y2select)
						tempboard = CRotate(tempboard)
						PutTrack xselect, yselect, tempboard, -1
						DrawTrack
						PushUndo
						Do
							GetMouse xm, ym, wm, bm
						Loop Until bm <> 1
					Else
						NotASquare
					End If
				Else
					tempboard = GetTrack(1, 1, 30, 30)
					tempboard = CRotate(tempboard)
					PutTrack 1, 1, tempboard, -1
					DrawTrack
					PushUndo
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				End If
			Case 11	'Rotate counter-clockwise
				If pasting Then
					clipboard = CCRotate(clipboard)
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				ElseIf xselect Then
					If x2select - xselect = y2select - yselect Then
						tempboard = GetTrack(xselect, yselect, x2select, y2select)
						tempboard = CCRotate(tempboard)
						PutTrack xselect, yselect, tempboard, -1
						DrawTrack
						PushUndo
						Do
							GetMouse xm, ym, wm, bm
						Loop Until bm <> 1
					Else
						NotASquare
					End If
				Else
					tempboard = GetTrack(1, 1, 30, 30)
					tempboard = CCRotate(tempboard)
					PutTrack 1, 1, tempboard, -1
					DrawTrack
					PushUndo
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				End If
			Case 12	: Menu_TrackInfo
			Case 13	'Undo
				If Not pasting Then
					Undo
					DrawTrack
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				End If
			Case 14	'Redo
				If Not pasting Then
					Redo
					DrawTrack
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 1
				End If
			Case 15 : Menu_Help
			Case 16	: Menu_Scenery
			Case 17 : Menu_Analysis
			Case 18 : Menu_Tournaments
			Case 19	: Menu_Settings
		End Select
	End If
	
	'Check to update switch indicators
	If xm >= xpanel + xswitches AndAlso _
		xm < xpanel + xswitches + 9 * 22 AndAlso _
		ym >= ypanel + yswitches AndAlso _
		ym < ypanel + yswitches + 22 Then
	
		If bm = 1 Then
			xi = (xm - xswitches - xpanel) \ 22
			PutIcon 21 + xi, 13, xpanel + xswitches + bigwidth * xi, ypanel + yswitches
		
			Select Case xi
				Case 0 : clipboard = ""
				Case 1 : show_errors = Not show_errors
				Case 2 : allow_errors = Not allow_errors
				Case 3 : data_codes = Not data_codes
				Case 4 : show_grid = Not show_grid
				Case 5 : affect_track = Not affect_track
				Case 6 : affect_terrain = Not affect_terrain
				Case 7 : colouring_mode = Not colouring_mode
				Case 8 : Menu_TrackShot
			End Select
			
			Do
				GetMouse xm, ym, wm, bm
			Loop Until bm <> 1
			DrawTrack
			DrawPanel
		End If
	End If
	
	If colouring_mode Then
		'Colouration - Background colour
		If xm >= xpanel + xpalette + 16 AndAlso ym >= ypanel + 434 AndAlso _
				xm < xpanel + xpalette + 38 AndAlso ym < ypanel + 456 Then
		
			Dim As ULong r, g, b
			If wm > exwm Then
				internal_bgc = (internal_bgc + 1) Mod 24
				current_bgc = cpal(internal_bgc)
				If internal_bgc Then
					r = .7 * ((current_bgc ShR 16) And 255)
					g = .7 * ((current_bgc ShR 8) And 255)
					b = .7 * (current_bgc And 255)
					current_bgc = RGB(r, g, b)
				Else
					current_bgc = 0
				End If
				DrawPanel
			ElseIf wm < exwm Then
				internal_bgc = (internal_bgc + 23) Mod 24
				current_bgc = cpal(internal_bgc)
				If internal_bgc Then
					r = .7 * ((current_bgc ShR 16) And 255)
					g = .7 * ((current_bgc ShR 8) And 255)
					b = .7 * (current_bgc And 255)
					current_bgc = RGB(r, g, b)
				Else
					current_bgc = 0
				End If
				DrawPanel
			ElseIf bm = 2 Then
				current_bgc = 0
				internal_bgc = 0
				DrawPanel
				
				Do
					GetMouse xm, ym, wm, bm
				Loop Until bm <> 2
			ElseIf bm = 1 Then
				Menu_Colouring
			End If
		'Colouration - Border colour
		ElseIf xm >= xpanel + xpalette + 48 AndAlso ym >= ypanel + 434 AndAlso _
				xm < xpanel + xpalette + 70 AndAlso ym < ypanel + 456 Then
		
			Dim q As Byte
			
			If wm > exwm Then
				For i As Byte = 0 To 23
					If cpal(i) = current_border Then q = i
				Next i
				
				q = (q + 1) Mod 24
				current_border = cpal(q)
				DrawPanel
			ElseIf wm < exwm Then
				For i As Byte = 0 To 23
					If cpal(i) = current_border Then q = i
				Next i
				
				q = (q + 23) Mod 24
				current_border = cpal(q)
				DrawPanel
			ElseIf bm = 2 Then
				current_border = 0
				DrawPanel
				
				Do
					GetMouse xm, ym, wm, bm
				Loop Until bm <> 2
			ElseIf bm = 1 Then
				Menu_Colouring
			End If
		End If
	End If
	
	exwm = wm
End Sub


Sub ManageKeyboardCursor(forceupdate As Byte = 0)
	Static As Byte oldx = 1, oldy = 1
	Dim As Integer xm, ym, wm, bm
	
	If forceupdate OrElse oldx <> xcursor Or oldy <> ycursor Then
		drawkeyboardcursor = -1
		For j As Byte = oldy - 1 To oldy + 1
			For i As Byte = oldx - 1 To oldx + 1
				If i >= 1 AndAlso i <= 30 AndAlso j >= 1 AndAlso j <= 30 _
					Then DrawSpot i, j
			Next i
		Next j
				
		
		Dim As Short xx, yy
		xx = xoffs + 22 * (xcursor - 1)
		yy = yoffs + 22 * (ycursor - 1)
		
		If current_page = 10 Or allow_errors <> 0 Then
			Line (xx, yy)- Step (21, 21), RGB(250, 250, 250), B
			Line (xx + 1, yy + 1)- Step (19, 19), RGB(250, 250, 250), B
		ElseIf current_page < 10 Then
			Dim As Byte w, h
			Dim v As UByte
			Dim As Byte ex, ey
			
			Select Case grid(xcursor, ycursor).track
				Case 255 : ex = xcursor - 1 : ey = ycursor
				Case 254 : ex = xcursor : ey = ycursor - 1
				Case 253 : ex = xcursor - 1 : ey = ycursor - 1
				Case Else : ex = xcursor : ey = ycursor
			End Select
			If ex < 1 Then ex = 1
			If ey < 1 Then ey = 1
			v = grid(ex, ey).track
			If v Then
				DrawSpot ex, ey, -1
				If tr(v).w = 2 And ex < 30 Then
					DrawSpot ex + 1, ey, -1
					If tr(v).h = 2 And ey < 30 Then DrawSpot ex + 1, ey + 1, -1
				End If
				If tr(v).h = 2 And ey < 30 Then DrawSpot ex, ey + 1, -1
			End If
		
			If xcursor < 30 Then w = tr(current_brush).w Else w = 1
			If ycursor < 30 Then h = tr(current_brush).h Else h = 1
			Line (xx, yy)- Step (22 * w - 1, 22 * h - 1), RGB(200, 200, 200), B
			Line (xx, yy)- Step (21, 21), RGB(250, 250, 250), B
			Line (xx + 1, yy + 1)- Step (19, 19), RGB(250, 250, 250), B
		End If
		
		Dim s As String, coorpos As Short
		
		coorpos = xpanel + 170
		If colouring_mode Then coorpos -= 72
		
		ScreenLock
		Line (coorpos - 74, ypanel + 432)- STEP (155, 31), RGB(30, 30, 50), BF
		s = "[" + Trim(Str(xcursor)) + ", " + Trim(Str(ycursor)) + "]"
		If selecting = -3 Then s = "[" + Trim(Str(xselect)) + ", " + Trim(Str(yselect)) + "]-" + s
		Draw String (coorpos - Len(s) * 4, ypanel + 430), s, RGB(200, 200, 200)
		If data_codes Then
			s = "Ter[" + Hex(grid(xcursor, ycursor).land) + "h]  Trk[" + Hex(grid(xcursor, ycursor).track) + "h]"
		Else
			s = Trim(tr(GetParent(xcursor, ycursor)).id)
		End If
		Draw String (coorpos - Len(s) * 4, ypanel + 446), s, RGB(200, 200, 200)
		ScreenUnlock
		
		oldx = xcursor : oldy = ycursor
	End If
	
	If drawkeyboardcursor Then
		GetMouse xm, ym, wm, bm
		
		If bm <> 0 Then
			drawkeyboardcursor = 0
			For j As Byte = ycursor - 1 To ycursor + 1
				For i As Byte = xcursor - 1 To xcursor + 1
					If i >= 1 AndAlso i <= 30 AndAlso j >= 1 AndAlso j <= 30 _
						Then DrawSpot i, j
				Next i
			Next j
		End If
	End If
End Sub


Function ManageSelector(sel As SelectorType) As Byte
	Dim isin As Byte
	Dim As Integer xm, ym, wm, bm
	
	If sel.current = 0 Then sel.current = 1
		
	GetMouse xm, ym, wm, bm
	If xm >= sel.x1 And xm <= sel.x2 And ym >= sel.y1 And ym <= sel.y2 Then
		isin = -1
	Else
		isin = 0
	End If
	
	'Redraw
	If sel.redraw <> 0 Or isin <> sel.wasinlasttime Then
		ScreenLock
		With sel
		If isin Then
			Line (.x1, .y1)-(.x2, .y2), RGB(200, 200, 200), BF
			Draw String (.x1 + 8, (.y1 + .y2) \ 2 - 8), .opt(.current), RGB(30, 30, 50)
			Draw String (.x2 - 16, (.y1 + .y2) \ 2 - 8), Chr(25), RGB(30, 30, 50)
		Else
			Line (.x1, .y1)-(.x2, .y2), RGB(200, 200, 200), B
			Line (.x1 + 1, .y1 + 1)-(.x2 - 1, .y2 - 1), RGB(200, 200, 200), B
			LIne (.x1 + 2, .y1 + 2)-(.x2 - 2, .y2 - 2), RGB(30, 30, 50), BF
			Draw String (.x1 + 8, (.y1 + .y2) \ 2 - 8), .opt(.current), RGB(200, 200, 200)
			Draw String (.x2 - 16, (.y1 + .y2) \ 2 - 8), Chr(25), RGB(200, 200, 200)
		End If
		End With
		ScreenUnlock
		sel.redraw = 0
		If isin Then sel.wasinlasttime = -1 Else sel.wasinlasttime = 0
	End If
	
	'Clicked on the pull-down selector
	If bm = 1 And isin Then
		Dim bgcopy As UByte Ptr, newy1 As Short, newy2 As Short
		Dim thisone As Byte, thelastone As Byte
		
		With sel
		newy1 = .y1
		newy2 = .y1 + 32 * .options + 3
		If newy2 > 700 Then
			newy1 = newy1 - newy2 + 700
			newy2 = 700
		End If
		
		bgcopy = ImageCreate(.x2 - .x1 + 1, 32 * .options + 4)
		Get (.x1, newy1)-(.x2, newy2), bgcopy
		
		ScreenLock
		Line (.x1, newy1)-(.x2, newy2), RGB(200, 200, 200), B
		Line (.x1 + 1, newy1 + 1)-(.x2 - 1, newy2 - 1), RGB(200, 200, 200), B
		Line (.x1 + 2, newy1 + 2)-(.x2 - 2, newy2 - 2), RGB(30, 30, 50), BF
		
		For i As Byte = 1 To .options
			Draw String (.x1 + 8, newy1 + 32 * i - 24), .opt(i), RGB(200, 200, 200)
		Next i
		ScreenUnlock
		
		Do
			GetMouse xm, ym, wm, bm
		Loop Until bm = 0
		
		thelastone = -1
		Do
			GetMouse xm, ym, wm, bm
			If xm < .x1  Or xm > .x2 Or ym < newy1 + 2 Or ym > newy2 - 2 Then Exit Do
			thisone = (ym - newy1) \ 32 + 1
			
			If thisone <> thelastone Then
				If thelastone >= 1 And thelastone <= .options Then
					Line (.x1 + 2, newy1 + 32 * (thelastone - 1) + 2)-(.x2 - 2, newy1 + 32 * thelastone + 1), RGB(30, 30, 50), BF
					Draw String (.x1 + 8, newy1 + 32 * thelastone - 24), .opt(thelastone), RGB(200, 200, 200)
				End If
				If thisone >= 1 And thisone <= .options Then
					Line (.x1 + 2, newy1 + 32 * (thisone - 1) + 2)-(.x2 - 2, newy1 + 32 * thisone + 1), RGB(200, 200, 200), BF
					Draw String (.x1 + 8, newy1 + 32 * thisone - 24), .opt(thisone), RGB(30, 30, 50)
				End If
				thelastone = thisone
			End If
			
			If bm = 1 And thisone >= 1 And thisone <= .options Then
				sel.current = thisone
				Exit Do
			End If
		Loop
		
		Do
			GetMouse xm, ym, wm, bm
		Loop Until bm = 0
		
		Put (.x1, newy1), bgcopy, PSet
		ImageDestroy bgcopy
		End With
		
		sel.redraw = -1
		Return sel.current
	End If
	
	Return 0
End Function


Function ManageString(ByRef s As String) As String
	Dim akey As String
	
	If stringer.init <> 0 Or stringer.last <> s Then
		stringer.t = Timer
		stringer.cursor = -1
		stringer.redraw = -1
		stringer.init = 0
		stringer.s32 = Enc_UTF8_to_UTF32(s)
		stringer.last = s
		#ifdef RENDER_TO_CP437
			stringer.sr = Enc_UTF32_to_CP437(stringer.s32)
		#else
			stringer.sr = Enc_UTF32_to_Latin1(stringer.s32)
		#endif
		stringer.cursor_pos = Len(stringer.sr)
	End If
	
	#ifdef __FB_LINUX__
		akey = LinKey
	#else
		akey = InKey
	#endif
	
	Select Case akey
		Case "A" To "Z", "a" To "z", "0" To "9", "_", "."
			If Len(stringer.sr) < stringer.maxlength Then
				stringer.sr = Left(stringer.sr, stringer.cursor_pos) + akey + Mid(stringer.sr, stringer.cursor_pos + 1)
				stringer.s32 = Left(stringer.s32, 4 * stringer.cursor_pos) + akey + String(3, 0) + Mid(stringer.s32, 4 * stringer.cursor_pos + 1)
				
				stringer.cursor_pos += 1
				stringer.redraw = -1
			End If
		Case " " To Chr(126)
			If Len(stringer.sr) < stringer.maxlength And stringer.fileonly = 0 Then
				stringer.sr = Left(stringer.sr, stringer.cursor_pos) + akey + Mid(stringer.sr, stringer.cursor_pos + 1)
				stringer.s32 = Left(stringer.s32, 4 * stringer.cursor_pos) + akey + String(3, 0) + Mid(stringer.s32, 4 * stringer.cursor_pos + 1)
				
				stringer.cursor_pos += 1
				stringer.redraw = -1
			End If
		Case Chr(8)
			If stringer.cursor_pos > 0 Then
				stringer.sr = Left(stringer.sr, stringer.cursor_pos - 1) + Mid(stringer.sr, stringer.cursor_pos + 1)
				stringer.s32 = Left(stringer.s32, 4 * stringer.cursor_pos - 4) + Mid(stringer.s32, 4 * stringer.cursor_pos + 1)
				
				stringer.cursor_pos -= 1
				stringer.redraw = -1
			End If
		Case Chr(13), Chr(27)
			Return akey
		Case Chr(255) + Chr(77), Chr(0, &H53, &HFF, 0, 0)
			If stringer.cursor_pos < Len(stringer.sr) Then
				stringer.cursor_pos += 1
				stringer.redraw = -1
			End If
		Case Chr(255) + Chr(75), Chr(0, &H51, &HFF, 0, 0)
			If stringer.cursor_pos > 0 Then
				stringer.cursor_pos -= 1
				stringer.redraw = -1
			End If
		Case Chr(255) + Chr(83), Chr(127)
			If Len(stringer.sr) >= stringer.cursor_pos Then
				stringer.sr = Left(stringer.sr, stringer.cursor_pos) + Mid(stringer.sr, stringer.cursor_pos + 2)
				stringer.s32 = Left(stringer.s32, 4 * stringer.cursor_pos) + Mid(stringer.s32, 4 * stringer.cursor_pos + 5)
				stringer.redraw = -1
			End If
		Case Chr(255) + Chr(71), Chr(0, &H50, &HFF, 0, 0)
			stringer.cursor_pos = 0
			stringer.redraw = -1
		Case Chr(255) + Chr(79), Chr(0, &H57, &HFF, 0, 0)
			stringer.cursor_pos = Len(stringer.sr)
			stringer.redraw = -1
		Case Else
			If Len(akey) > 1 Then
				Dim akey32 As Long
				
				If Left(akey, 1) = Chr(0) Then Return akey
				
				akey32 = CvL(Enc_UTF8_to_UTF32(akey))
				If akey32 >= 128 And stringer.fileonly = 0 Then
					If Len(stringer.sr) < stringer.maxlength Then
						#ifdef RENDER_TO_CP437
							'Use CP437 as target codepage (to render with Draw String)
							stringer.sr = Left(stringer.sr, stringer.cursor_pos) + Enc_UTF32_to_CP437(MkL(akey32)) + Mid(stringer.sr, stringer.cursor_pos + 1)
						#else
							'Use Latin-1 as target codepage (to render with PutString)
							stringer.sr = Left(stringer.sr, stringer.cursor_pos) + Enc_UTF32_to_Latin1(MkL(akey32)) + Mid(stringer.sr, stringer.cursor_pos + 1)
						#endif
						stringer.s32 = Left(stringer.s32, 4 * stringer.cursor_pos) + MkL(akey32) + Mid(stringer.s32, 4 * stringer.cursor_pos + 1)
						
						stringer.cursor_pos += 1
						stringer.redraw = -1
					End If
				End If
				
				'This line was here, but looks terribly wrong!!!
				'Return akey
			ElseIf Len(akey) = 1 And stringer.fileonly = 0 Then	'DOS extended CP437
				Dim akey32 As Long
				
				akey32 = fromCP437(Asc(akey) - 128)
				If Len(stringer.sr) < stringer.maxlength Then
					stringer.sr = Left(stringer.sr, stringer.cursor_pos) + Enc_UTF32_to_CP437(MkL(akey32)) + Mid(stringer.sr, stringer.cursor_pos + 1)
					stringer.s32 = Left(stringer.s32, 4 * stringer.cursor_pos) + MkL(akey32) + Mid(stringer.s32, 4 * stringer.cursor_pos + 1)
					
					stringer.cursor_pos += 1
					stringer.redraw = -1
				End If
			End If
	End Select
	
	If Timer - stringer.t > .2 Then
		stringer.cursor = Not stringer.cursor
		stringer.t = Timer
		stringer.redraw = -1
	End If
	
	If stringer.redraw <> 0 Or Len(akey) <> 0 Then _
		s = Enc_UTF32_to_UTF8(stringer.s32)
	
	If stringer.redraw Then
		ScreenLock
		Line(stringer.x, stringer.y)- STEP (8 * stringer.maxlength + 7, 15), stringer.background, BF
		#ifdef RENDER_TO_CP437
			Draw String (stringer.x, stringer.y), stringer.sr, RGB(160, 160, 240)
		#else
			PutString stringer.x, stringer.y, stringer.sr, RGB(160, 160, 240)
		#endif
		If stringer.cursor Then
			Line (stringer.x + 8 * stringer.cursor_pos, stringer.y)- STEP (7, 15), RGB(200, 200, 200), BF
		End If
		ScreenUnlock
		
		stringer.redraw = 0
	End If
	
	Return akey
End Function


Sub Error_Message(text As String, title As String = "Error!")
	Dim v As Short
	Dim As Integer xm, ym, wm, bm
	
	MenuBox 28, 10, title
	ceny += 16
	TCentre , text, RGB(200, 200, 240)
	ceny += 24
	
	buttons = 0
	StackButton "   OK   "
	EndOfButtonStack
	
	Do
		v = ManageButtons
	Loop Until v <> 0 Or Len(InKey) <> 0
	buttons = 0
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0

	Do : Loop Until Len(InKey) = 0

	DrawTrack
End Sub


Sub Menu_Scenery
	Dim update As Byte = -1, i As Short, j As Short
	Dim As Integer xm, ym, wm, bm
	Dim bar(0 To 9) As UByte, sel(0 To 9) As SelectorType
	Dim thing(0 To 9) As UByte
	Dim tempy As Short, v As Short, scratch As SelectorType
	Dim akey As String, lastchanged As Byte
	
	'Define what tile each bar represents
	thing(0) = &H99 : thing(1) = &H98 : thing(2) = &H97
	thing(3) = &H9A : thing(4) = &HA3 : thing(5) = &H9F
	thing(6) = &H9B : thing(7) = &HA7 : thing(8) = &HAF : thing(9) = &HAB
	
	Select Case landscape
		Case 0	'Desert
			bar(1) = 20 : bar(6) = 5
		Case 1	'Tropical
			bar(2) = 20 : bar(4) = 7 : bar(6) = 6
			bar(8) = 3 : bar(9) = 10
		Case 2	'Alpine
			bar(0) = 20 : bar(4) = 5 : bar(5) = 8
		Case 3	'City
			bar(0) = 15 : bar(4) = 15 : bar(6) = 7
			bar(8) = 9 : bar(9) = 10 : bar(3) = 2
		Case 4	'Country
			bar(0) = 15 : bar(3) = 5 : bar(5) = 7
			bar(6) = 3 : bar(7) = 8 : bar(9) = 5
	End Select
	
	MenuBox 35, 28, "Generate Scenery"
	lefx += 16 : ceny += 16
	PutIcon 24, 6, lefx, ceny
	PutIcon 24, 7, lefx, ceny + 32
	PutIcon 24, 8, lefx, ceny + 64
	PutIcon 24, 9, lefx, ceny + 96
	PutIcon 26, 6, lefx, ceny + 128
	PutIcon 26, 7, lefx, ceny + 160
	PutIcon 26, 8, lefx, ceny + 192
	PutIcon 26, 9, lefx, ceny + 224
	PutIcon 26, 11, lefx, ceny + 256
	PutIcon 26, 10, lefx, ceny + 288
	
	For i = 0 To 9
		sel(i).redraw = -1
		If i < 3 Then
			sel(i).current = 1
		Else
			sel(i).current = 2
		End If
		sel(i).options = 2
		sel(i).opt(1) = "Everywhere"
		If i = 9 Then
			sel(i).opt(2) = "On water"
		Else
			sel(i).opt(2) = "By the road"
		End If
		sel(i).x1 = lefx + 360
		sel(i).y1 = ceny + 32 * i
		sel(i).x2 = lefx + 487
		sel(i).y2 = ceny + 32 * i + 19
	Next i
	
	buttons = 0
	tempy = ceny
	ceny += 330 : cenx += 240
	StackButton "Generate", 1, -1
	StackButton " Cancel ", 2, -1
	EndOfButtonStack
	scratch.redraw = -1
	scratch.x1 = lefx
	scratch.y1 = ceny
	scratch.x2 = lefx + 199
	scratch.y2 = ceny + 31
	scratch.current = 1
	scratch.options = 2
	scratch.opt(1) = "Use free space"
	scratch.opt(2) = "Remove old scenery"
	ceny = tempy
	
	Do
		If update Then
			ScreenLock
			For i = 0 To 9
				Dim p As String
				
				p = Str(bar(i)) + "%"
				Line (lefx + 30, 32 * i + ceny)- Step(304, 19), RGB(160, 160, 160), B
				Line (lefx + 31, 32 * i + ceny + 1)- Step(302, 17), RGB(160, 160, 160), B
				Line (lefx + 32, 32 * i + ceny + 2)- Step(300, 15), RGB(30, 30, 50), BF
				Line (lefx + 32, 32 * i + ceny + 2)- Step(3 * bar(i), 15), RGB(0, 160, 160), BF
				Draw String (lefx + 192 - 4 * Len(p), 32 * i + ceny + 3), p, RGB(250, 250, 250)
			Next i
			ScreenUnlock
			update = 0
		End If
		
		GetMouse xm, ym, wm, bm
		akey = InKey
		
		If bm = 1 Then
			If xm >= lefx + 32 And xm <= lefx + 332 AndAlso _
				ym >= ceny + 2 And (ym - ceny - 2) Mod 32 < 22 AndAlso _
				(ym - ceny - 2) \ 32 <= 9 Then
				
				Dim n As Short
				lastchanged = (ym - ceny - 2) \ 32
				n = bar(lastchanged)
				bar(lastchanged) = (xm - lefx - 32) \ 3
								
				If bar(lastchanged) <> n Then update = -1
			End If
		End If
		
		Select Case akey
			Case Chr(13) : v = 1 : Exit Do
			Case Chr(27) : v = 2 : Exit Do
		End Select
		
		For i = 0 To 9
			Dim test As Short
			
			test = sel(i).current
			ManageSelector sel(i)
			If sel(i).current <> test Then update = -1
		Next i
		ManageSelector scratch
		v = ManageButtons
		
		'Ensure values don't add up greater than 100%
		If update Then
			Dim total As Short, howmany As Short
			Dim factor As Double, topvalue As Byte
			
			If sel(9).current = 2 Then topvalue = 8 Else topvalue = 9
			
			For j = 1 To 2
				total = 0 : howmany = 0
				For i = 0 To topvalue
					If sel(i).current = j Then
						total += bar(i)
						If i <> lastchanged Then howmany += 1
					End If
				Next i
				If total > 100 Then
					If sel(lastchanged).current = j Then
						factor = (100 - bar(lastchanged)) / (total - bar(lastchanged))
					Else
						factor = 100 / total
					End If
					
					For i = 0 To topvalue
						If sel(i).current = j And i <> lastchanged Then
							bar(i) *= factor
						End If
					Next i
				End If
			Next j
		End If
	Loop Until v <> 0
	buttons = 0
	
	'Generate the requested scenery
	If v = 1 Then
		Dim As Short openfield, water, bytheroad
		Dim map(1 To 30, 1 To 30) As Byte, s As String
		
		'First remove old scenery if requested
		If scratch.current = 2 Then
			For j = 1 To 30
				For i = 1 To 30
					If grid(i, j).track >= &H97 And grid(i, j).track <= &HB2 Then _
						grid(i, j).track = 0
				Next i
			Next j
		End If
		
		'Calculate how many tiles of each type are available
		For j = 1 To 30
			For i = 1 To 30
				Select Case grid(i, j).land
					Case 1 To 5 	'Water
						If grid(i, j).track Then
							map(i, j) = -1	'Cannot be used
						Else
							map(i, j) = 2	'Water
							water += 1
						End If
					Case Is >= 7	'Mountain borders
						map(i, j) = -1	'Cannot be used
					Case Else		'Grass or mountain top
						If grid(i, j).track Then
							map(i, j) = -1	'Cannot be used
						Else
							Dim isbytheroad As Byte
							
							s = ""
							If j < 30 Then s &= Chr(grid(i, j + 1).track) Else s &= Chr(0)
							If j > 1 Then s &= Chr(grid(i, j - 1).track) Else s &= Chr(0)
							If i < 30 Then s &= Chr(grid(i + 1, j).track) Else s &= Chr(0)
							If i > 1 Then s &= Chr(grid(i - 1, j).track) Else s &= Chr(0)
														
							For n As Byte = 1 To 4
								Dim k As UByte
								
								k = ASC(Mid(s, n, 1))
								If (k > 0 And k < &H97) Or k >= &HFD Then
									isbytheroad = -1
									map(i, j) = 10 + n - 1	'By the road (with direction)
									bytheroad += 1
									Exit For
								End If
							Next n
							
							If Not isbytheroad Then
								map(i, j) = 1	'Open field
								openfield += 1
							End If
						End If
				End Select
			Next i
		Next j
	
		'Fill with scenery
		For round As Byte = 1 To 2
			For i = 0 To 9
				Dim amount As Short, whichfrom As Byte, whichto As Byte
				
				'Calculate how many of each type to place
				If sel(i).current = 1 Then 'Everywhere (open field)
					whichfrom = 1 : whichto = 1
					amount = openfield * bar(i) / 100 - 1
				ElseIf i = 9 Then	'Water
					whichfrom = 2 : whichto = 2
					amount = water * bar(i) / 100 - 1
				Else	'By the road
					whichfrom = 10 : whichto = 13
					amount = bytheroad * bar(i) / 100 - 1
				End If
				
				'Place the scenery (open field goes last)
				If (whichfrom = 1 And round = 2) Or _
					(whichfrom <> 1 And round = 1) Then
				
					For j = 1 To amount
						Dim As Byte x, y
						
						Do
							x = Int(Rnd * 30) + 1
							y = Int(Rnd * 30) + 1
							
							If map(x, y) >= whichfrom And map(x, y) <= whichto Then
								If i < 4 Then
									grid(x, y).track = thing(i)
								ElseIf i = 9 Then
									grid(x, y).track = thing(i) + Int(Rnd * 4)
								ElseIf whichfrom = 10 Then
									grid(x, y).track = thing(i) + map(x, y) - 10
								Else
									grid(x, y).track = thing(i) + Int(Rnd * 4)
								End If
								map(x, y) = -1
								Exit Do
							ElseIf whichfrom = 1 And map(x, y) >= 10 Then
								If i < 4 Then
									grid(x, y).track = thing(i)
								Else
									grid(x, y).track = thing(i) + Int(Rnd * 4)
								End If
							ElseIf i = 9 And whichfrom = 1 And map(x, y) = 2 Then
								grid(x, y).track = thing(i) + Int(Rnd * 4)
							End If
						Loop
					Next j
				End If
			Next i
		Next round
	
		'We've done changes so push to the undo buffer
		PushUndo
		
		modified = -1
	End If
	
	DrawTrack
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	Do : Loop Until Len(InKey) = 0
End Sub


Sub Menu_Settings
	Dim top As Short, v As Short, akey As String, i As Short
	Dim As SelectorType theformat, thegrid, theconfview, theconfgen
	Dim As Integer xm, ym, wm, bm
	Dim content(1 To 4, 0 To 1) As String
	Dim pointed As Byte, previous As Byte, update As Byte
	Dim validtrack As Byte = -1, briefestwinning As Long
	Dim e As Byte, s As String, t As Double
	
	GenerateSections
	If sections > 254 Or paths >= MAXPATHS Then
		validtrack = 0
	ElseIf paths = 0 Then
		validtrack = 0
	End If
	DetectTerrainErrors e, 0, 0
	
	If e >= 40 And e <= 49 Then validtrack = 0
	
	briefestwinning = 100000
	For i = 1 To paths
		If path(i).finishes Then
			Dim m As Long
			
			m = PathLength(i, 1)
			If m < briefestwinning Then briefestwinning = m
		End If
	Next i
	If briefestwinning = 100000 Then validtrack = 0
	
	MenuBox 37, 27, "Settings"
	
	lefx += 8 : ceny += 16
	top = ceny
	
	theformat.x1 = lefx
	theformat.x2 = lefx + 239
	theformat.y1 = top + 208
	theformat.y2 = top + 239
	theformat.options = 3
	theformat.opt(1) = "Def. format: One file"
	theformat.opt(2) = "Def. format: Split binary"
	theformat.opt(3) = "Def. format: Split text"
	Select Case default_format
		Case FORMAT_BINARY_SPLIT : theformat.current = 2
		Case FORMAT_TEXT_SPLIT : theformat.current = 3
		Case Else : theformat.current = 1
	End Select
	theformat.redraw = -1
	
	thegrid.x1 = lefx
	thegrid.x2 = lefx + 239
	thegrid.y1 = top + 256
	thegrid.y2 = top + 287
	thegrid.options = 2
	thegrid.opt(1) = "Grid ON at start up"
	thegrid.opt(2) = "Grid OFF at start up"
	If show_grid Then
		thegrid.current = 1
	Else
		thegrid.current = 2
	End If
	thegrid.redraw = -1
	
	theconfview.x1 = lefx + 304
	theconfview.x2 = lefx + 543
	theconfview.y1 = top + 208
	theconfview.y2 = top + 239
	theconfview.options = 2
	theconfview.opt(1) = "Show track tile conflicts"
	theconfview.opt(2) = "Hide track tile conflicts"
	If show_errors Then
		theconfview.current = 1
	Else
		theconfview.current = 2
	End If
	theconfview.redraw = -1
	
	theconfgen.x1 = lefx + 304
	theconfgen.x2 = lefx + 543
	theconfgen.y1 = top + 256
	theconfgen.y2 = top + 287
	theconfgen.options = 2
	theconfgen.opt(1) = "Allow producing conflicts"
	theconfgen.opt(2) = "Prevent tile conflicts"
	If allow_errors Then
		theconfgen.current = 1
	Else
		theconfgen.current = 2
	End If
	theconfgen.redraw = -1
	
	
	content(1, 0) = "Track path:"
	For i = 1 To dirlinks
		If Trim(LCase(dirlink(i).text)) = "tracks" Then
			content(1, 1) = Trim(dirlink(i).directory)
			Exit For
		End If
	Next i
	If content(1, 1) = "" Then content(1, 1) = track_path
	
	content(2, 0) = "Stunts path:"
	For i = 1 To dirlinks
		If Trim(LCase(dirlink(i).text)) = "stunts" Then
			content(2, 1) = Trim(dirlink(i).directory)
			Exit For
		End If
	Next i
	
	content(3, 0) = "Default author:"
	content(3, 1) = default_author
	
	If validtrack Then
		content(4, 0) = "Racer calibration time with THIS track (OWOOT, Porsche March Indy)"
		content(4, 1) = Timey(briefestwinning * racer_weigh)
	Else
		content(4, 0) = "Racer calibration time (race 4am.trk in OWOOT):"
		content(4, 1) = Timey(LENGTHOF4AM * racer_weigh)
	End If
	
	For i = 1 To 4
		s = content(i, 1)
		TLeft , content(i, 0), RGB(200, 200, 200)
		TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
		TLeft
	Next i
		
	ceny += 128
	buttons = 0
	StackButton " Cancel ", 2
	StackButton "  Save  ", 1
	EndOfButtonStack
	
	previous = -1
	Do
		GetMouse xm, ym, wm, bm
		
		If xm >= lefx - 4 And xm < lefx + 547 And ym >= top - 8 And ym < top + 184 Then
			pointed = (ym - top + 8) \ 48
		Else
			pointed = -1
		End If
		
		If pointed <> previous Or update <> 0 Then
			ScreenLock
			If previous <> -1 Then
				Line (lefx - 4, 48 * previous + top - 8)- Step (551, 47), RGB(30, 30, 50), BF
				ceny = 48 * previous + top
				s = content(previous + 1, 1)
				TLeft , content(previous + 1, 0), RGB(200, 200, 200)
				#ifdef RENDER_TO_CP437
					TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
				#else
					TLeft , Enc_UTF32_to_Latin1(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
				#endif
			End If
			If pointed <> -1 Then
				Line (lefx - 4, 48 * pointed + top - 8)- Step (551, 47), RGB(10, 10, 10), BF
				ceny = 48 * pointed + top
				s = content(pointed + 1, 1)
				TLeft , content(pointed + 1, 0), RGB(200, 200, 200)
				#ifdef RENDER_TO_CP437
					TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
				#else
					TLeft , Enc_UTF32_to_Latin1(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
				#endif
			End If
			ScreenUnlock
			previous = pointed
		End If
		
		If bm = 1 And pointed <> -1 Then
			stringer.init = -1
			stringer.maxlength = 64
			stringer.fileonly = 0
			stringer.x = lefx
			stringer.y = 48 * pointed + top + 16
			stringer.background = RGB(10, 10, 10)
			
			Do
				akey = ManageString(content(pointed + 1, 1))
				v = ManageButtons
			Loop Until akey = Chr(13) Or akey = Chr(27) Or v <> 0
			t = Timer
			Do : Loop Until Timer >= t + .3
			Do : Loop Until Len(InKey) = 0
			
			content(pointed + 1, 1) = Trim(content(pointed + 1, 1))
			If pointed = 0 And content(1, 1) = "" Then content(1, 1) = ExePath
			If pointed = 3 Then
				Dim cents As Long
				
				cents = AntiTimey(content(4, 1))
				If cents < 6000 Then cents = 6000
				If cents > 60000 Then cents = 60000
				content(4, 1) = Timey(cents)
			End If
			update = -1
		End If
		
		ManageSelector theformat
		ManageSelector thegrid
		ManageSelector theconfview
		ManageSelector theconfgen
		v = ManageButtons
		akey = InKey
		
		Select Case akey
			Case Chr(13) : v = 1 : Exit Do
			Case Chr(27) : v = 2 : Exit DO
		End Select
	Loop Until v
	buttons = 0
	
	DrawTrack
	
	'Update configuration
	If v = 1 Then
		'Update track directory
		content(1, 1) = Trim(content(1, 1))
		If Len(content(1, 1)) <> 0 And Right(content(1, 1), 1) <> DIR_DIVISOR Then content(1, 1) &= DIR_DIVISOR
		For i = 1 To dirlinks
			If Trim(LCase(dirlink(i).text)) = "tracks" Then
				dirlink(i).directory = content(1, 1)
				Exit For
			End If
		Next i
		If i > dirlinks Then
			dirlinks += 1
			dirlink(dirlinks).text = "Tracks"
			dirlink(dirlinks).directory = content(1, 1)
		End If
		
		'Update Stunts directory
		If Len(content(1, 1)) <> 0 And Right(content(2, 1), 1) <> DIR_DIVISOR Then content(2, 1) &= DIR_DIVISOR
		For i = 1 To dirlinks
			If Trim(LCase(dirlink(i).text)) = "stunts" Then
				dirlink(i).directory = content(2, 1)
				Exit For
			End If
		Next i
		If i > dirlinks Then
			dirlinks += 1
			dirlink(dirlinks).text = "Stunts"
			dirlink(dirlinks).directory = content(2, 1)
		End If
		
		'Update author
		default_author = Trim(content(3, 1))
		
		Dim ff As Integer
		
		ff = FreeFile
		Open program_path + "bliss.cfg" For Output As ff
		Print #ff, "; Bliss configuration file"
		Print #ff,
		Print #ff, "; Starting track directory. Bliss home directory by default"
		Print #ff, "tracks=" + content(1, 1)
		Print #ff,
		If dirlinks Then
			If dirlinks > 1 Or LCase(dirlink(1).text) <> "tracks" Then
				Print #ff, "; Other directories"
				For i = 1 To dirlinks
					Select Case Trim(LCase(dirlink(i).text))
						Case "tracks"
						Case "stunts" : Print #ff, "stunts=" + Trim(dirlink(i).directory)
						Case Else
							Print #ff, "dirlink=" + Trim(dirlink(i).text) + ":" + Trim(dirlink(i).directory)
					End Select
				Next i
				Print #ff,
			End If
		End If
		Print #ff, "; Default author name"
		If Len(content(3, 1)) = 0 Then
			Print #ff, "; author=Your Name"
		Else
			Print #ff, "author=" + content(3, 1)
		End If
		Print #ff,
		Print #ff, "; Racer speed calibration quotient"
		If validtrack Then
			racer_weigh = AntiTimey(content(4, 1)) / briefestwinning
		Else
			racer_weigh = AntiTimey(content(4, 1)) / LENGTHOF4AM
		End If
		Print #ff, "calibration="; racer_weigh
		Print #ff,
		Print #ff, "; Default format for new tracks"
		Select Case theformat.current
			Case 1 : Print #ff, "format=bliss"
			Case 2 : Print #ff, "format=split"
			Case 3 : Print #ff, "format=text"
		End Select
		
		Print #ff,
		Print #ff, "; Show grid at start time"
		If thegrid.current = 1 Then
			Print #ff, "grid=yes"
		Else
			Print #ff, "grid=no"
		End If
		Print #ff,
		Print #ff, "; Enable generating track conflicts (superpositions)"
		If theconfgen.current = 1 Then
			Print #ff, "superpositions=yes"
		Else
			Print #ff, "superpositions=no"
		End If
		Print #ff,
		Print #ff, "; Show terrain conflict warnings"
		If theconfview.current = 1 Then
			Print #ff, "warnings=yes"
		Else
			Print #ff, "warnings=no"
		End If
		Print #ff,
		Print #ff, "; Image format when saving a track-shot"
		Print #ff, "imageformat=" + imageformat
		Close ff
	End If
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	Do : Loop Until Len(InKey) = 0
End Sub


Sub Menu_StartNewTrack
	Dim v As Short, akey As String
	Dim As Short x, y, i, top
	Dim As Integer xm, ym, wm, bm
	Dim ttitle(1 To 100) As String, terrain(1 To 100) As String
	Dim terrains As Short, first_terrain As Short
	
	MenuBox 28, 10, "Start New Track"
	ceny += 16
	TCentre , "Current track data will be lost!", RGB(200, 200, 240)
	TCentre , "Are you sure you want to clear the map?", RGB(200, 200, 240)
	ceny += 16
	
	StackButton "   OK   ", 1
	StackButton " Cancel ", 2, , 50
	EndOfButtonStack
	Do
		v = ManageButtons
		akey = InKey
	Loop Until v <> 0 Or akey <> ""
	buttons = 0
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	DrawTrack
	DrawPanel
	If v <> 1 Then Exit Sub		'User cancelled
	
	
	'---------- Terrain selection
	MenuBox 35, 37, "Select Terrain"
	
	'Load terrains
	Open program_path + "terrains.dat" For Binary Access Read As 100
	Get #100, 5, terrains
	For i = 1 To terrains
		Get #100, 2 * i + 5, v
		Seek #100, v
		Get #100, , v
		ttitle(i) = Space(v)
		Get #100, , ttitle(i)
		Get #100, , v
		terrain(i) = Space(v)
		Get #100, , terrain(i)
	Next i
	Close 100
	
	
	'Clear grid
	For y = 1 To 30
		For x = 1 To 30
			grid(x, y).land = 0
			grid(x, y).track = 0
			grid(x, y).bgc = 0
			grid(x, y).border = 0
		Next x
	Next y
	
	
	Dim As Short selected_t, pointed_t, old_t
	
	ceny += 16
	top = ceny
	lefx += 240
	first_terrain = 1
	For i = first_terrain To first_terrain + 27
		TLeft , ttitle(i), RGB(160, 160, 240)
	Next i
	TLeft
	StackButton " Create Track ", 1
	EndOfButtonStack
	
	ScreenLock
	DrawBox lefx - 184, top, lefx - 184 + 135, top + 135
	Line (lefx - 176, top + 8)- Step (119, 119), RGB(0, 100, 0), BF
	ScreenUnlock
	
	old_t = 1
	selected_t = 1
	Do
		GetMouse xm, ym, wm, bm
		If xm >= lefx - 8 And xm < lefx + 272 And ym >= top And ym < top + 16 * terrains Then
			pointed_t = (ym - top) \ 16 + 1
		Else
			pointed_t = -1
		End If
		
		If pointed_t <> old_t Then
			ScreenLock
			If old_t <> -1 Then
				If old_t = selected_t Then Color RGB(160, 160, 240) Else Color RGB(30, 30, 50)
				Line (lefx - 8, top + 16 * (old_t - 1))- Step (271, 15), , BF
				If old_t = selected_t Then Color RGB(10, 10, 10) Else Color RGB(160, 160, 240)
				Draw String (lefx, top + 16 * (old_t - 1)), ttitle(old_t)
			End If
			If pointed_t <> -1 Then
				If pointed_t = selected_t Then Color RGB(160, 160, 240) Else Color RGB(30, 30, 50)
				Line (lefx - 8, top + 16 * (pointed_t - 1))- Step (271, 15), , BF
				If pointed_t = selected_t Then Color RGB(250, 250, 250) Else Color RGB(200, 200, 250)
				Draw String (lefx, top + 16 * (pointed_t - 1)), ttitle(pointed_t)
			End If
			ScreenUnlock
			old_t = pointed_t
		End If
		
		If bm = 1 And pointed_t <> -1 Then
			old_t = selected_t
			selected_t = pointed_t
			UnRLETerrain terrain(selected_t)
			ScreenLock
			For y = 1 To 30
				For x = 1 To 30
					Dim As ULong c1, c2
					Dim style As Byte
					
					Select Case grid(x, y).land
						Case 0 : style = 0 : c1 = RGB(0, 100, 0)
						Case 1 : style = 0 : c1 = RGB(0, 0, 100)
						Case 2 : style = 1 : c1 = RGB(0, 0, 100) : c2 = RGB(0, 100, 0)
						Case 3 : style = 2 : c1 = RGB(0, 100, 0) : c2 = RGB(0, 0, 100)
						Case 4 : style = 1 : c1 = RGB(0, 100, 0) : c2 = RGB(0, 0, 100)
						Case 5 : style = 2 : c1 = RGB(0, 0, 100) : c2 = RGB(0, 100, 0)
						Case 6 : style = 0 : c1 = RGB(0, 200, 0)
						Case 7, 10 : style = 0 : c1 = RGB(0, 160, 0)
						Case 8, 9 : style = 0 : c1 = RGB(0, 240, 0)
						Case 11 : style = 2 : c1 = RGB(0, 200, 0) : c2 = RGB(0, 100, 0)
						Case 12 : style = 1 : c1 = RGB(50, 255, 50) : c2 = RGB(0, 100, 0)
						Case 13 : style = 2 : c1 = RGB(0, 100, 0) : c2 = RGB(0, 200, 0)
						Case 14 : style = 1 : c1 = RGB(0, 100, 0) : c2 = RGB(0, 130, 0)
						Case 15 : style = 2 : c1 = RGB(0, 200, 0) : c2 = RGB(0, 220, 0)
						Case 16 : style = 1 : c1 = RGB(0, 200, 0) : c2 = RGB(50, 255, 50)
						Case 17 : style = 2 : c1 = RGB(0, 220, 0) : c2 = RGB(0, 200, 0)
						Case 18 : style = 1 : c1 = RGB(0, 130, 0) : c2 = RGB(0, 200, 0)
						Case Else : style = 0 : c1 = RGB(100, 0, 0)
					End Select
					
					Select Case style
						Case 0 : Line (lefx - 180 + 4 * x, top + 4 * y + 4)- Step (3, 3), c1, BF
						Case 1
							Line (lefx - 180 + 4 * x, top + 4 * y + 4)- Step (0, 3), c1
							Line - Step (3, 0), c1
							Line - Step (-3, -3), c1
							Paint Step (1, 2), c1
							Line (lefx - 180 + 4 * x, top + 4 * y + 4)- Step (3, 0), c2
							Line - Step (0, 3), c2
							Line - Step (-3, -3), c2
							Paint Step (2, 1), c2
						Case Else
							Line (lefx - 180 + 4 * x, top + 4 * y + 4)- Step (0, 3), c1
							Line - Step (3, -3), c1
							Line - Step (-3, 0), c1
							Paint Step (1, 1), c1
							Line (lefx - 180 + 4 * x + 3, top + 4 * y + 4)- Step (0, 3), c2
							Line - Step (-3, 0), c2
							Line - Step (3, -3), c2
							Paint Step (-1, 2), c2
					End Select
				Next x
			Next y
			ScreenUnlock
		End If
	
		v = ManageButtons
	Loop Until v <> 0 Or InKey = Chr(13)
	buttons = 0
	
	
	modified = 0
	track_file = ""
	landscape = 4
	thisfileformat = default_format
	format_byte = 152	'Fixed for Bliss

	Dim today As Double
	today = Now
	meta.title = ""
	meta.author = default_author
	meta.cyear = DatePart("yyyy", today)
	meta.cmonth = DatePart("m", today)
	meta.cday = DatePart("d", today)
	meta.tool = "Bliss"
	meta.toolversion = THISVERSION_NOPERIOD
	meta.comment = ""
	meta.championship = ""
	meta.editing_time = 0
	track_file = ""
	UpdateTitleBar
	started_editing = Timer
	PushUndo
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	Do : Loop Until Len(InKey) = 0
	
	DrawTrack
	DrawPanel
End Sub


Sub Menu_LoadTrack
	Dim v As Short
	Dim As Integer xm, ym, wm, bm
	Dim As Short bx1, bx2, by1, by2
	Dim s As String, selected_track As String, akey As String
	Dim olds As String
	
	buttons = 0
	MenuBox 28, 28, "Load Track"
	
	bx1 = lefx + 8 : by1 = ceny : bx2 = lefx + 407 : by2 = ceny + 31
	DrawBox bx1, by1, bx2, by2
	InitFiles "trk;rpl", lefx + 8, ceny + 40, lefx + 407, ceny + 351
			
	cenx -= 16 : ceny += 40
	
	StackButton "  Load  ", 1, -1
	StackButton " Cancel ", 2, -1
	EndOfButtonStack
		
	selected_track = ""
	stringer.init = -1
	stringer.maxlength = 40
	stringer.fileonly = -1
	stringer.x = bx1 + 16
	stringer.y = by1 + 8
	stringer.background = RGB(30, 30, 50)
	
	olds = ""
	Do
		v = ManageButtons
		s = selected_track
		akey = ManageString(selected_track)
		If s <> selected_track And Len(selected_track) <> 0 Then akey = Chr(1) + selected_track
		s = ManageFiles(akey)
				
		Select Case akey
			Case Chr(13) : v = 1
			Case Chr(27) : v = 2
		End Select
		
		If Len(s) <> 0 And olds <> s Then
			selected_track = s
			stringer.cursor_pos = Len(s)
			ScreenLock
			DrawBox bx1, by1, bx2, by2
			Draw String (bx1 + 16, by1 + 8), s + Space(30 - Len(s)), RGB(160, 160, 240)
			ScreenUnlock
			olds = s
		End If
	Loop Until v = 1 Or v = 2
	buttons = 0
	Do : Loop Until Len(InKey) = 0
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	If v = 1 And Len(selected_track) <> 0 Then
		If InStr(selected_track, ".") = 0 Then selected_track = selected_track + ".trk"
		If FileExists(track_path + selected_track) Then
			LoadTrack track_path + selected_track
			track_file = selected_track
			DrawTrack
			PushUndo
		Else
			DrawTrack
			buttons = 0
			Error_Message "File not found or wrong name"
		End If
	Else
		DrawTrack
	End If
	
	DrawPanel
End Sub


Sub Menu_Analysis
	Dim As Integer xm, ym, wm, bm
	Dim As Short i, v
	Dim As Byte x, y, page
	Dim e As UByte
	Dim As Short winning, safe, cycles
	Dim As Short shortestsafe, shortestwinning, n
	Dim As Long briefestsafe, briefestwinning, m
	Dim As Byte flow_fatal, wrong_way
	Dim As Byte terrain_crash, terrain_fatal, terrain_warning
	Dim As ULong normal = RGB(160, 160, 240), bright = RGB(180, 180, 80)
	Dim As SelectorType current_car
	
	GenerateSections
	If sections > 254 Or paths >= MAXPATHS Then
		Error_Message "Track is too complex. Too many splits!"
		Exit Sub
	ElseIf paths = 0 Then
		Error_Message "Track has no valid path", "Track Analysis"
		Exit Sub
	End If
	DetectTerrainErrors e, x, y
	
	Select Case e
		Case 40 : terrain_crash = -1
		Case 41 To 49 : terrain_fatal = -1
		Case 50 To 59 : terrain_warning = -1
	End Select
	
	DetectNotStunts x, y, e
	
	shortestsafe = 10000
	shortestwinning = 10000
	briefestsafe = 1000000
	briefestwinning = 100000
	For i = 1 To paths
		If path(i).finishes Then
			winning += 1
			n = PathLength(i)
			m = PathLength(i, 1)
			If n < shortestwinning Then shortestwinning = n
			If m < briefestwinning Then briefestwinning = m
			If path(i).e = 0 Then
				safe += 1
				If n < shortestsafe Then shortestsafe = n
				If m < briefestsafe Then briefestsafe = m
			End If
		End If
		If path(i).e = 82 Then
			cycles += 1
		ElseIf path(i).e >= 70 And path(i).e <= 79 Then
			flow_fatal = -1
		End If
	Next i
	
	'Load car selector
	current_car.options = cars
	current_car.current = activecar
	For i = 1 To UBound(current_car.opt)
		If i > cars Then Exit For
		current_car.opt(i) = car(i).cname
	Next i
	current_car.redraw = -1
	current_car.x1 = 512
	current_car.x2 = 719
	current_car.y1 = 200
	current_car.y2 = 223
		
	Do
		Select Case page
			Case 0	'----------------------------------------------
				Dim As Short xrecalc, yrecalc, sxrecalc, syrecalc
				
				MenuBox 30, 23, "Track Analysis"
				lefx += 8
				TLeft
								
				concolour = normal : TCont "Total paths: "
				concolour = bright : TCont Str(paths), -1
				concolour = normal : TCont "Winning paths: "
				concolour = bright : TCont Str(winning), -1
				If winning Then
					concolour = normal : TCont "Shortest winning path: "
					concolour = bright : TCont Str(shortestwinning) + " tiles", -1
					concolour = normal : TCont "Estimated winning time: "
					xrecalc = conx : yrecalc = cony
					concolour = bright : TCont Timey(briefestwinning * racer_weigh * car(activecar).handicap) + " (" + Trim(Str(briefestwinning)) + " tokens)", -1
				End If
				concolour = normal : TCont "Safe paths: "
				concolour = bright : TCont Str(safe), -1
				If safe Then
					concolour = normal : TCont "Shortest safe path: "
					concolour = bright : TCont Str(shortestsafe) + " tiles", -1
					concolour = normal : TCont "Estimated winning time on a safe path: "
					sxrecalc = conx : syrecalc = cony
					concolour = bright : TCont Timey(briefestsafe * racer_weigh * car(activecar).handicap), -1
				End If
				concolour = normal : TCont "Cycles: "
				concolour = bright : TCont Str(cycles), -1
				TCont "", -1
				concolour = normal : TCont "Prognosis:", -1
				concolour = bright
				If terrain_crash Then
					TCont "Stunts will crash because of terrain errors", -1
				ElseIf terrain_fatal Then
					TCont "Track will not run, due to terrain errors", -1
				ElseIf winning Then
					If flow_fatal Then
						TCont "Track will fail because of path flow error", -1
					ElseIf terrain_warning Then
						TCont "Track will run, but terrain problems may occur", -1
					ElseIf e Then
						TCont "Stunts will run the track. Using the internal", -1
						TCont "editor will corrupt it, though", -1
					Else
						TCont "Track will run fine", -1
					End If
				Else
					TCont "Track will fail because there's no winning path", -1
				End If
				
				concolour = normal : cony += 16
				current_car.y1 = cony - 3
				If winning Then TCont "Times estimated based on:"
				
				ceny += 248
				buttons = 0
				StackButton "See paths", 1
				If winning Then StackButton "See times", 2
				StackButton "  OK  ", 3
				EndOfButtonStack
						
				Do
					GetMouse xm, ym, wm, bm
				Loop Until bm = 0
				
				current_car.redraw = -1
				current_car.x1 = 680
				current_car.x2 = current_car.x1 + 219
				current_car.y2 = current_car.y1 + 23
				Do
					If winning Then ManageSelector current_car
					If current_car.current <> activecar Then
						conx = xrecalc : cony = yrecalc
						Line (conx, cony)- Step (200, 15), RGB(30, 30, 50), BF
						concolour = bright
						activecar = current_car.current
						TCont Timey(briefestwinning * racer_weigh * car(activecar).handicap) + " (" + Trim(Str(briefestwinning)) + " tokens)", -1
						If sxrecalc Then
							conx = sxrecalc : cony = syrecalc
							Line (conx, cony)- Step (100, 15), RGB(30, 30, 50), BF
							TCont Timey(briefestsafe * racer_weigh * car(activecar).handicap), -1
						End If
					End If
					v = ManageButtons
				Loop Until v <> 0 Or Len(InKey) <> 0
				buttons = 0
				DrawTrack
				If v = 3 Or v = 0 Then Exit Do
				page = v
			Case 1	'------------------------------------------------
				Dim As Short first = 1, current = 1, top, ex, exfirst
				Dim pathdata(1 To paths, 2) As String
				Dim paintcol As ULong, update As Byte = -1
				Dim akey As String, exw As Integer
				Dim knob As Double, plen1 As Long, plen2 As Long
				
				For i = 1 To paths
					plen1 = PathLength(i)
					plen2 = PathLength(i, 1)
					pathdata(i, 0) = "Path " + Str(i) + ": " + Str(plen1) + " tiles - " + Timey(plen2 * racer_weigh * car(activecar).handicap)
					If path(i).finishes Then
						pathdata(i, 1) = "Complete"
						If path(i).e Then
							pathdata(i, 1) &= ", with warnings"
						Else
							pathdata(i, 1) &= ", safe"
						End If
						If plen1 = shortestwinning Then pathdata(i, 1) &= " (opp's path)"
						If plen2 = briefestwinning Then pathdata(i, 1) &= " - Fastest"
					Else
						pathdata(i, 1) = "Incomplete"
						If path(i).e = 72 Then
							pathdata(i, 1) &= ", wrong way"
						ElseIf section(ASC(Right(path(i).p, 1))).cycle Then
							pathdata(i, 1) &= ", cyclic"
						End If
					End If
				Next i
				
				MenuBox 30, 32, "Track Analysis"
				
				ceny += 16 : top = ceny
				lefx += 8
				
				ceny += 392
				buttons = 0
				StackButton "Follow path", 1
				If winning Then StackButton "See times", 2
				StackButton "Main page", 3
				StackButton "  OK  ", 4
				EndOfButtonStack
				Do
					GetMouse xm, ym, exw, bm
				Loop Until bm = 0
				Do
					If update Then
						ceny = top
						ScreenLock
						Line (lefx + 416, top)- Step (15, 359), RGB(30, 30, 50), BF
						Line (lefx + 423, top)- Step (1, 359), RGB(200, 200, 200), B
						If paths > 1 Then knob = (current - 1) / (paths - 1) Else knob = 0
						Line (lefx + 416, top + 344 * knob)- Step (15, 15), RGB(200, 200, 200), BF
						For i = first To first + 7
							If i > paths Then Exit For
							If i = current Then
								paintcol = RGB(10, 10, 10)
							Else
								paintcol = RGB(30, 30, 50)
							End If
							Line (lefx - 4, top + 48 * (i - first) - 4)- Step (379, 39), paintcol, BF
							TLeft , pathdata(i, 0), normal
							TLeft , pathdata(i, 1), bright
							TLeft
						Next i
						ScreenUnlock
						update = 0
					End If
					
					akey = InKey
					GetMouse xm, ym, wm, bm
					
					If bm = 1 Then
						ex = current : exfirst = first
						If xm >= lefx - 4 And xm < lefx + 376 And _
							ym >= top And ym < top + 380 Then
						
							current = first + (ym - top + 4) \ 48
							If current > paths Then current = paths
							If current > first + 7 Then current = first + 7
						End If
						If xm >= lefx + 416 And xm < lefx + 432 And _
							ym >= top And ym < top + 360 Then
						
							current = (ym - top) * paths / 360 + 1
							If current > paths Then current = paths
							If first > current Then
								first = current
							ElseIf current > first + 7 Then
								first = current - 7
							End If
						End If
						If current <> ex Or first <> exfirst Then update = -1
					End If
					If wm <> exw Then
						If wm > exw Then
							If first > 1 Then
								first -= 1
								If current > first + 7 Then current -= 1
								update = -1
							End If
						Else
							If first + 7 < paths Then
								first += 1
								If current < first Then current = first
								update = -1
							End If
						End If
						exw = wm
					End If
					
					Select Case akey
						Case Chr(255, 72)
							If current > 1 Then
								current -= 1
								If first > current Then first = current
								update = -1
							End If
						Case Chr(255, 80)
							If current < paths Then
								current += 1
								If first + 7 < current Then first += 1
								update = -1
							End If
						Case Chr(27) : v = 0 : Exit Do
						Case Chr(13) : v = 1 : Exit Do
					End Select
				
					v = ManageButtons
					If v <> 0 Then Exit Do
				Loop
				buttons = 0
				DrawTrack
				Select Case v
					Case 1
						FollowPath path(current).p
						drawkeyboardcursor = -1
						ManageKeyboardCursor -1
						Exit Do
					Case 2 : page = 2
					Case 3 : page = 0
					Case Else : Exit Do
				End Select
			Case 2	'----------------------------------------------
				Dim As Short xtimes, ytimes, ynorh
				Dim As String rhname(1 To 8), norhname(1 To 3)
				Dim As Single rhratio(1 To 8), norhratio(1 To 3)
				Dim i As Byte
				
				rhname(1) = "Duplode" : rhratio(1) = 6.8815
				rhname(2) = "Marco" : rhratio(2) = 6.8863
				rhname(3) = "FinRok" : rhratio(3) = 7.0758
				rhname(4) = "Zak McKracken" : rhratio(4) = 7.6161
				rhname(5) = "Cas" : rhratio(5) = 7.6255
				rhname(6) = "Nach" : rhratio(6) = 7.6588
				rhname(7) = "AbuRaf70" : rhratio(7) = 7.9953
				rhname(8) = "Shoegazing Leo" : rhratio(8) = 9.2796
				
				norhname(1) = "Marco" : norhratio(1) = 7.4313
				norhname(2) = "Duplode" : norhratio(2) = 7.6066
				norhname(3) = "Cas" : norhratio(3) = 8.3744
				
				MenuBox 30, 31, "Track Analysis"
				
				lefx += 112
				TLeft
				TCentre , "Estimated OWOOT times for famous racers", RGB(200, 200, 200)
				TLeft
				ytimes = cony : xtimes = conx + 160
				For i = 1 To 8
					concolour = normal
					TCont rhname(i)
					conx = xtimes
					concolour = bright
					TCont Timey(briefestwinning * rhratio(i) * car(activecar).handicap), 1
				Next i
				
				ceny += 144
				TLeft
				TCentre , "Estimated OWOOT NoRH times for famous racers", RGB(200, 200, 200)
				TLeft
				ynorh = cony
				For i = 1 To 3
					concolour = normal
					TCont norhname(i)
					conx = xtimes
					concolour = bright
					TCont Timey(briefestwinning * norhratio(i) * car(activecar).handicap), 1
				Next i
				
				ceny += 64
				TLeft
				TCentre , "Estimations based on selected car", RGB(200, 200, 200)
				TLeft
				
				current_car.options = cars
				For i = 1 To cars
					current_car.opt(i) = car(i).cname
				Next i
				
				current_car.redraw = -1
				current_car.x1 = cenx - 100
				current_car.x2 = current_car.x1 + 199
				current_car.y1 = ceny
				current_car.y2 = current_car.y1 + 23
				
				ceny += 48
				buttons = 0
				StackButton "Main page", 1
				StackButton "See paths", 2
				StackButton "  OK  ", 3
				EndOfButtonStack
				Do
					GetMouse xm, ym, wm, bm
				Loop Until bm = 0
				Do
					ManageSelector current_car
					If current_car.current <> activecar Then
						activecar = current_car.current
						concolour = bright
						cony = ytimes
						lefx = xtimes : conx = xtimes
						Line (xtimes, ytimes)- Step(79, 127), RGB(30, 30, 50), BF
						For i = 1 To 8
							TCont Timey(briefestwinning * rhratio(i) * car(activecar).handicap), 1
						Next i
						cony = ynorh
						Line (xtimes, ynorh)- Step(79, 47), RGB(30, 30, 50), BF
						For i = 1 To 3
							TCont Timey(briefestwinning * norhratio(i) * car(activecar).handicap), 1
						Next i
					End If
					v = ManageButtons
				Loop Until Len(InKey) <> 0 Or v <> 0
				buttons = 0
				DrawTrack
				Select Case v
					Case 1 : page = 0
					Case 2 : page = 1
					Case Else : Exit Do
				End Select
		End Select
	Loop
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	Do : Loop Until Len(InKey) = 0
End Sub


Sub Menu_Colouring
	Dim updatecols As Byte, bluish As ULong
	Dim tempcol(0 To 23, 0 To 1) As ULong
	Dim As ULong r, g, b
	Dim As ULong temp_bgc, temp_border
	Dim As Integer xm, ym, bm, wm
	Dim As Byte kx, ky, usekeyboard
	Dim v As Short, akey As String, dy As Short
	
	temp_bgc = current_bgc
	temp_border = current_border
	
	For i As Short = 0 To 23
		tempcol(i, 0) = cpal(i)
		r = (cpal(i) ShR 16) And 255
		g = (cpal(i) ShR 8) And 255
		b = cpal(i) And 255
		
		tempcol(i, 1) = RGB(Int(.7 * r), Int(.7 * g), Int(.7 * b))
	Next i
	tempcol(0, 1) = 0
	
	bluish = RGB(160, 160, 240)
	
	MenuBox 36, 24, "Colouring"
	
	dy = ceny
	
	TLeft
	TLeft , "  Border colour"
	TLeft
	TLeft , "  Red", bluish
	TLeft , "  Green", bluish
	TLeft , "  Blue", bluish
	TLeft
	TLeft
	
	TLeft , "  Background colour"
	TLeft
	TLeft , "  Red", bluish
	TLeft , "  Green", bluish
	TLeft , "  Blue", bluish
	TLeft
	TLeft
	TLeft , "  Preview"
	TLeft
	TLeft
	
	buttons = 0
	StackButton " Set colours ", 1
	StackButton " Clear ", 2
	StackButton " Uncolour map ", 3
	StackButton " Cancel ", 4
	EndOfButtonStack
	
	updatecols = -1
	Do
		If updatecols <> 0 Then
			ScreenLock
			For i As Short = 0 To 1
				Dim c As ULong
				
				If i Then c = temp_bgc Else c = temp_border
			
				For j As Short = 0 To 2
					Dim reg As UByte
					
					Line (494, 112 * i + 20 * j + 46 + dy)- Step (266, 10), RGB(30, 30, 50), BF
					Line (500, 112 * i + 20 * j + 50 + dy)- Step (255, 2), RGB(128, 128, 128), BF
					Select Case j
						Case 0 : reg = (c And &HFF0000) ShR 16
						Case 1 : reg = (c And &HFF00) ShR 8
						Case Else : reg = c And 255
					End Select
					Circle (500 + reg, 112 * i + 20 * j + 51 + dy), 4, RGB(200, 200, 200), , , , F
				Next j
				
				For j As Short = 0 To 3
					For k As Short = 0 To 5
						If j + k = 0 Then
							Line (24 * k + 790, 112 * i + 24 * j + 16 + dy)- Step (22, 22), RGB(128, 128, 128), B
							Line (24 * k + 791, 112 * i + 24 * j + 17 + dy)- Step (20, 20), RGB(30, 30, 50), BF
						Else
							Line (24 * k + 790, 112 * i + 24 * j + 16 + dy)- Step (22, 22), tempcol(6 * j + k, i), BF
						End If
						If usekeyboard AndAlso kx = k And ky = 4 * i + j Then
							Line (24 * k + 795, 112 * i + 24 * j + 21 + dy)- Step (12, 12), RGB(200, 200, 200), B
							Line (24 * k + 796, 112 * i + 24 * j + 22 + dy)- Step (10, 10), RGB(0, 0, 0), B
						End If
					Next k
				Next j
			Next i
			
			If temp_bgc Then
				Line (510, 238 + dy)- Step (21, 21), temp_bgc, BF
				If temp_border Then
					Line (510, 238 + dy)- Step (21, 21), temp_border, B
					Line (511, 239 + dy)- Step (19, 19), temp_border, B
				End If
			Else
				Line (510, 238 + dy)- Step (21, 21), RGB(30, 30, 50), BF
				If temp_border = 0 Then
					Line (510, 238 + dy)- Step (21, 21), RGB(128, 128, 128), B, &H5555
				Else
					Line (510, 238 + dy)- Step (21, 21), temp_border, B
					Line (511, 239 + dy)- Step (19, 19), temp_border, B
				End If
			End If
			ScreenUnlock
			
			updatecols = 0
		End If
		
		GetMouse xm, ym, wm, bm
		akey = InKey
		
		If bm = 1 Then
			If xm >= 500 And xm <= 755 Then
				Select Case ym
					Case 46 + dy To 65 + dy	'Update border red
						temp_border And= &HFF00FFFF
						r = (xm - 500) ShL 16
						temp_border Or= r
						updatecols = -1
					Case 66 + dy To 85 + dy 'Update border green
						temp_border And= &HFFFF00FF
						g = (xm - 500) ShL 8
						temp_border Or= g
						updatecols = -1
					Case 86 + dy To 105 + dy 'Update border blue
						temp_border And= &HFFFFFF00
						b = (xm - 500)
						temp_border Or= b
						updatecols = -1
					Case 158 + dy To 177 + dy	'Update background red
						temp_bgc And= &HFF00FFFF
						r = (xm - 500) ShL 16
						temp_bgc Or= r
						updatecols = -1
					Case 178 + dy To 197 + dy	'Update background green
						temp_bgc And= &HFFFF00FF
						g = (xm - 500) ShL 8
						temp_bgc Or= g
						updatecols = -1
					Case 198 + dy To 217 + dy	'Update background blue
						temp_bgc And= &HFFFFFF00
						b = (xm - 500)
						temp_bgc Or= b
						updatecols = -1
				End Select
			Elseif xm >= 790 And ym >= 16 + dy And xm < 934 And ym < 112 + dy Then
				temp_border = tempcol((xm - 790) \ 24 + 6 * ((ym - 16 - dy) \ 24), 0)
				updatecols = -1
			ElseIf xm >= 790 And ym >= 332 And xm < 934 And ym < 224 + dy Then
				temp_bgc = tempcol((xm - 790) \ 24 + 6 * ((ym - 128 - dy) \ 24), 1)
				updatecols = -1
			End If
		End If
		
		v = ManageButtons
		
		Select Case akey
			Case Chr(255, 72)
				If ky > 0 Then ky -= 1 : updatecols = -1
				usekeyboard = -1
			Case Chr(255, 80)
				If ky < 7 Then ky += 1 : updatecols = -1
				usekeyboard = -1
			Case Chr(255, 77)
				If kx < 5 Then kx += 1 : updatecols = -1
				usekeyboard = -1
			Case Chr(255, 75)
				If kx > 0 Then kx -= 1 : updatecols = -1
				usekeyboard = -1
			Case Chr(13)
				Dim tb As ULong
				If ky >= 4 Then
					tb = tempcol(6 * (ky - 4) + kx, 1)
					
					If temp_bgc = tb Then
						v = 1
					Else
						temp_bgc = tb
					End If
				Else
					tb = tempcol(6 * ky + kx, 0)
					
					If temp_border = tb Then
						v = 1
					Else
						temp_border = tb
					End If
				End If
				updatecols = -1
				usekeyboard = -1
			Case "C", "c", Chr(3) : v = 2
			Case Chr(0, 83), Chr(8) : v = 3
			Case Chr(27) : v = 4
		End Select
		
		If v Then
			Select Case v
				Case 1	'Set colours
					current_bgc = temp_bgc
					current_border = temp_border
					Exit Do
				Case 2	'Clear
					temp_bgc = 0
					temp_border = 0
					updatecols = -1
				Case 3	'Uncolour map
					For j As Byte = 1 To 30
						For i As Byte = 1 To 30
							grid(i, j).border = 0
							grid(i, j).bgc = 0
						Next i
					Next j
					PushUndo
					Exit Do
				Case 4	'Cancel
					Exit Do
			End Select
		End If
	Loop
	
	buttons = 0
	
	Do
		GetMouse 0, 0, 0, bm
	Loop Until bm = 0
	
	DrawTrack
	DrawPanel
	
	Do : Loop Until Len(InKey) = 0
End Sub


Sub Menu_Help
	Dim top As Short, page As Byte = 3
	Dim v As Short, f AS Integer, fline As Short = 1
	Dim maline(1 To 1500) As String, malines As Short = 0
	
	If FileExists(program_path & "manual.txt") Then
		f = FreeFile
		Open program_path & "manual.txt" For Input As f
		Do Until EoF(f)
			malines += 1
			Line Input #f, maline(malines)
			maline(malines) = Left(maline(malines), 72)
		Loop
		Close f
	End If
	
	Do
		Select Case page
			Case 1
				MenuBox 30, 38, "Help - Option keys"
				
				TLeft
				
				top = ceny
				lefx += 8
				
				TLeft , "F1 - F12", RGB(180, 180, 80)
				TLeft , "  Ctrl-Q", RGB(180, 180, 80)
				TLeft , "  Ctrl-E", RGB(180, 180, 80)
				TLeft , "  Ctrl-D", RGB(180, 180, 80)
				TLeft , "  Ctrl-G", RGB(180, 180, 80)
				TLeft , "  Ctrl-R", RGB(180, 180, 80)
				TLeft , "  Ctrl-S", RGB(180, 180, 80)
				TLeft , "  Ctrl-T", RGB(180, 180, 80)
				TLeft , "  Ctrk-K", RGB(180, 180, 80)
				TLeft , "  Ctrl-O", RGB(180, 180, 80)
				TLeft , "    Ctrl", RGB(180, 180, 80)
				TLeft , "  Ctrl-W", RGB(180, 180, 80)
				TLeft , "  Ctrl-C", RGB(180, 180, 80)
				TLeft , "  Ctrl-X", RGB(180, 180, 80)
				TLeft , "  Ctrl-V", RGB(180, 180, 80)
				TLeft , "       F", RGB(180, 180, 80)
				TLeft , " Shift-F", RGB(180, 180, 80)
				TLeft , "       R", RGB(180, 180, 80)
				TLeft , " Shift-R", RGB(180, 180, 80)
				TLeft , "  Ctrl-Z", RGB(180, 180, 80)
				TLeft , "  Ctrl-Y", RGB(180, 180, 80)
				TLeft , "       U", RGB(180, 180, 80)
				TLeft , "       C", RGB(180, 180, 80)
				TLeft , "  Arrows", RGB(180, 180, 80)
				TLeft , "     Tab", RGB(180, 180, 80)
				TLeft , "   Enter", RGB(180, 180, 80)
				TLeft , "     Del", RGB(180, 180, 80)
				TLeft , "       P", RGB(180, 180, 80)
				TLeft , "       \", RGB(180, 180, 80)
				
				ceny = top
				lefx += 80
				
				TLeft , "Select palette page (2xF1 for help)", RGB(160, 160, 240)
				TLeft , "Toggle debug mode", RGB(160, 160, 240)
				TLeft , "Allow/disallow conflict generation", RGB(160, 160, 240)
				TLeft , "Toggle conflict-warning display", RGB(160, 160, 240)
				TLeft , "Display/hide grid", RGB(160, 160, 240)
				TLeft , "Redraw track", RGB(160, 160, 240)
				TLeft , "Take a track-shot", RGB(160, 160, 240)
				TLeft , "Toggle terrain affected by paste", RGB(160, 160, 240)
				TLeft , "Toggle track affected by paste", RGB(160, 160, 240)
				TLeft , "Toggle colouring mode", RGB(160, 160, 240)
				TLeft , "Select by dragging with left mouse button", RGB(160, 160, 240)
				TLeft , "Select/Deselect the whole grid", RGB(160, 160, 240)
				TLeft , "Copy selection", RGB(160, 160, 240)
				TLeft , "Cut selection", RGB(160, 160, 240)
				TLeft , "Paste clipboard", RGB(160, 160, 240)
				TLeft , "Flip horizontally", RGB(160, 160, 240)
				TLeft , "Flip vertically", RGB(160, 160, 240)
				TLeft , "Rotate clockwise", RGB(160, 160, 240)
				TLeft , "Rotate counter-clockwise", RGB(160, 160, 240)
				TLeft , "Undo", RGB(160, 160, 240)
				TLeft , "Redo", RGB(160, 160, 240)
				TLeft , "Link tiles at pointer/keyboard cursor", RGB(160, 160, 240)
				TLeft , "Check track for errors", RGB(160, 160, 240)
				TLeft , "Move keyboard cursor", RGB(160, 160, 240)
				TLeft , "Switch between the grid and the palette", RGB(160, 160, 240)
				TLeft , "Paste current element/Create closed-circuit", RGB(160, 160, 240)
				TLeft , "Delete at keyboard cursor or selection", RGB(160, 160, 240)
				TLeft , "Pick element at keyboard cursor position", RGB(160, 160, 240)
				TLeft , "In manual mode, select element by hex typing", RGB(160, 160, 240)
				
				TLeft
				TLeft
				buttons = 0
				StackButton " User Manual ", 3
				StackButton " Tile Shorcuts ", 2
				StackButton "  Back  ", 1
				EndOfButtonStack
				
				Do
					v = ManageButtons
					Select Case InKey
						Case Chr(27) : v = 1 : Exit Do
						Case Chr(13) : v = 2 : Exit Do
					End Select
				Loop Until v <> 0
				buttons = 0
				
				If v = 1 Then page = -1 Else page = v
			Case 2
				MenuBox 30, 29, "Help - Tile shortcuts"
				
				TLeft
				
				top = ceny
				lefx += 8
				
				TLeft , "   Space", RGB(180, 180, 80)
				TLeft , "       A", RGB(180, 180, 80)
				TLeft , "       B", RGB(180, 180, 80)
				TLeft , "       D", RGB(180, 180, 80)
				TLeft , "       E", RGB(180, 180, 80)
				TLeft , "       G", RGB(180, 180, 80)
				TLeft , "       H", RGB(180, 180, 80)
				TLeft , "       I", RGB(180, 180, 80)
				TLeft , "       J", RGB(180, 180, 80)
				TLeft , "       K", RGB(180, 180, 80)
				TLeft , "       L", RGB(180, 180, 80)
				TLeft , "       M", RGB(180, 180, 80)
				TLeft , "       N", RGB(180, 180, 80)
				TLeft , "       O", RGB(180, 180, 80)
				TLeft , "       Q", RGB(180, 180, 80)
				TLeft , "       S", RGB(180, 180, 80)
				TLeft , "       T", RGB(180, 180, 80)
				TLeft , "       V", RGB(180, 180, 80)
				TLeft , "       W", RGB(180, 180, 80)
				TLeft , " X, Y, Z", RGB(180, 180, 80)
				
				ceny = top
				lefx += 80
				
				TLeft , "Find element by name", RGB(160, 160, 240)
				TLeft , "Banked road", RGB(160, 160, 240)
				TLeft , "Boulevard (highway)", RGB(160, 160, 240)
				TLeft , "Split (detour)", RGB(160, 160, 240)
				TLeft , "Elevated road", RGB(160, 160, 240)
				TLeft , "Spin (cork up/down)", RGB(160, 160, 240)
				TLeft , "Chicane", RGB(160, 160, 240)
				TLeft , "Pipe", RGB(160, 160, 240)
				TLeft , "Ramp (jump)", RGB(160, 160, 240)
				TLeft , "Crossroad", RGB(160, 160, 240)
				TLeft , "Loop", RGB(160, 160, 240)
				TLeft , "Change material", RGB(160, 160, 240)
				TLeft , "Scenery", RGB(160, 160, 240)
				TLeft , "Start/Finish line", RGB(160, 160, 240)
				TLeft , "Corner", RGB(160, 160, 240)
				TLeft , "Straightway", RGB(160, 160, 240)
				TLeft , "Tunnel and slalom", RGB(160, 160, 240)
				TLeft , "Transitions", RGB(160, 160, 240)
				TLeft , "Corkscrew", RGB(160, 160, 240)
				TLeft , "Side, bottom and corner fillers", RGB(160, 160, 240)
				
				
				TLeft
				TLeft
				buttons = 0
				StackButton " User Manual ", 3
				StackButton " Option Keys ", 2
				StackButton "  Back  ", 1
				EndOfButtonStack
				
				Do
					v = ManageButtons
					Select Case InKey
						Case Chr(27) : v = 1 : Exit Do
						Case Chr(13) : v = 3 : Exit Do
					End Select
				Loop Until v <> 0
				buttons = 0
				
				If v = 1 Then
					page = -1
				ElseIf v = 2 Then
					page = 1
				Else
					page = 3
				End If
			Case 3
				Dim As Integer w, exw, x, y, b
				Dim akey As String, drlines As Short = 33
				Dim exfline As Short = 0, s As String, n As Short
				
				MenuBox 38, 40, "Help - User Manual"
				
				top = ceny
				ceny += 512
				TLeft
				TLeft
				buttons = 0
				StackButton " Tile Shortcuts ", 2
				StackButton " Option Keys ", 1
				StackButton "  Back  ", 3
				EndOfButtonStack
				
				GetMouse x, y, w
				exw = w
				
				Do
					If fline <> exfline Then
						ceny = top
						Line (xoffs + 40, top)- Step (73 * 8, 33 * 16), RGB(30, 30, 50), BF
						For i As Short = 0 To drlines - 1
							s = Trim(maline(fline + i))
							n = InStr(s, ".")
							If n > 1 And n <= 4 And ValInt(s) > 0 Then
								TLeft , maline(fline + i), RGB(180, 180, 80)
							Else
								TLeft , maline(fline + i), RGB(160, 160, 240)
							End If
						Next i
						exfline = fline
					End If
					
					v = ManageButtons
					GetMouse x, y, w, b
					akey = InKey
					If w <> - 1 AndAlso w <> exw Then
						If w < exw Then akey = Chr(255, 80) Else akey = Chr(255, 72)
						exw = w
					End If
					
					If b = 1 AndAlso x > xoffs + 330 - 80 _
						AndAlso x < xoffs + 300 + 80 AndAlso y >= top Then
						
						Dim m As Short
						
						m = (y - top) \ 16 + fline
						If m <= malines Then
							s = Trim(maline(m))
							n = InStr(s, ".")
							If n > 0 And n <= 3 And ValInt(s) Then
								For i As Short = 1 To malines
									If Trim(maline(i)) = "." & ValInt(s) & "." Then
										fline = i
										Exit For
									End If
								Next i
								If fline + drlines - 1 > malines Then fline = malines - drlines + 1
							End If
						End If
					End If
					
					Select Case akey
						Case Chr(255, 72)
							If fline > 1 Then fline -= 1
						Case Chr(255, 80)
							If fline + drlines - 1 < malines Then fline += 1
						Case Chr(255, 73)
							fline -= 30
							If fline < 1 Then fline = 1
						Case Chr(255, 81)
							fline += 30
							If fline + drlines - 1 > malines Then fline = malines - drlines + 1
						Case Chr(255, 71)
							fline = 1
						Case Chr(255, 79)
							fline = malines - drlines + 1
						Case Chr(27) : v = 3 : Exit Do
						Case Chr(13) : v = 1 : Exit Do
					End Select
				Loop Until v <> 0
				
				buttons = 0
				If v = 3 Then page = -1 Else page = v
		End Select
	
		Dim bm As Integer
		Do
			GetMouse 0, 0, 0, bm
		Loop Until bm = 0
		
		Do : Loop Until Len(InKey) = 0

		DrawTrack
	Loop Until page = -1
End Sub


Sub Menu_License
	Dim As Integer xm, ym, wm, bm
	Dim v As Short
	
	MenuBox 27, 32, "Bliss " + THISVERSION '+ Chr(225)
	
	TCentre , "Track editor for Stunts v1.0 and v1.1", RGB(160, 160, 240)
	TCentre , "Copyright (c) 2016-2020 - Lucas Pedrosa", RGB(160, 160, 240)
	TCentre
	TCentre , "This program comes with ABSOLUTELY NO WARRANTY.", RGB(160, 160, 240)
	TCentre , "This is free software and you are welcome", RGB(160, 160, 240)
	TCentre , "to redistribute it under the conditions of the", RGB(160, 160, 240)
	TCentre , "GNU GPLv3 license. For more information on this", RGB(160, 160, 240)
	TCentre , "license, read the file license.txt that comes", RGB(160, 160, 240)
	TCentre , "included or visit http://www.gnu.org/licenses/", RGB(160, 160, 240)
	TCentre
	TCentre , "Thank you for using my editor!!", RGB(160, 160, 240)
	TCentre , "Bliss is dedicated especially to the", RGB(160, 160, 240)
	TCentre , "Stunts racing community, to the tournament", RGB(160, 160, 240)
	TCentre , "organisers and to the people of DSI, creators", RGB(160, 160, 240)
	TCentre , "of Stunts, a magnificent game.", RGB(160, 160, 240)
	TCentre , "Special thanks go to Duplode, for the amazing", RGB(160, 160, 240)
	TCentre , "feedback and effort in beta-testing during the", RGB(160, 160, 240)
	TCentre , "different stages of development.", RGB(160, 160, 240)
	TCentre
	TCentre
	TCentre , "Contact:", RGB(160, 160, 240)
	TCentre , "xlucas@mailo.com", RGB(50, 200, 240)
	TCentre
	TCentre , "Website:", RGB(160, 160, 240)
	TCentre , "http://www.raceforkicks.com/bliss", RGB(50, 200, 240)
	TCentre
	
	buttons = 0
	StackButton " Continue ", 1
	EndOfButtonStack
	Do
		v = ManageButtons
	Loop Until v = 1 Or InKey <> ""
	buttons = 0
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	DrawTrack
End Sub


Sub Menu_SaveTrack
	Dim v As Short
	Dim As Integer xm, ym, wm, bm
	Dim As Short bx1, bx2, by1, by2
	Dim s As String, selected_track As String, akey As String
	Dim olds As String
	Dim sel As SelectorType
	
	buttons = 0
	MenuBox 28, 28, "Save Track"

	bx1 = lefx + 8 : by1 = ceny : bx2 = lefx + 407 : by2 = ceny + 31
	DrawBox bx1, by1, bx2, by2
	InitFiles "trk;rpl", lefx + 8, ceny + 40, lefx + 407, ceny + 351
		
	cenx -= 16 : ceny += 40
	
	sel.redraw = -1
	Select Case thisfileformat
		Case FORMAT_RAW : sel.current = 1  'Not 4. Never default to raw!
		Case FORMAT_COMBINED : sel.current = 1
		Case FORMAT_BINARY_SPLIT : sel.current = 2
		Case FORMAT_TEXT_SPLIT : sel.current = 3
		Case Else	'Just in case I add a format or something
			sel.current = 1
	End Select
	
	sel.opt(1) = "One file"
	sel.opt(2) = "Binary split"
	sel.opt(3) = "Text split"
	sel.opt(4) = "Raw (no metadata)"
	sel.options = 4
	sel.y1 = ceny
	sel.y2 = sel.y1 + 31
	sel.x1 = cenx - 24 * 16
	sel.x2 = sel.x1 + 160
	
	StackButton "  Save  ", 1, -1
	StackButton " Cancel ", 2, -1
	EndOfButtonStack
			
	selected_track = track_file
	stringer.init = -1
	stringer.maxlength = 40
	stringer.fileonly = -1
	stringer.x = bx1 + 16
	stringer.y = by1 + 8
	stringer.background = RGB(30, 30, 50)
	
	olds = ""
	Do
		v = ManageButtons
		ManageSelector sel
		
		s = selected_track
		akey = ManageString(selected_track)
		If s <> selected_track And Len(selected_track) <> 0 Then akey = Chr(1) + selected_track
		s = ManageFiles(akey)
		
		Select Case akey
			Case Chr(13) : v = 1
			Case Chr(27) : v = 2
		End Select
		
		If Len(s) <> 0 And olds <> s Then
			selected_track = s
			stringer.cursor_pos = Len(s)
			ScreenLock
			DrawBox bx1, by1, bx2, by2
			Draw String (bx1 + 16, by1 + 8), s + Space(30 - Len(s)), RGB(160, 160, 240)
			ScreenUnlock
			olds = s
		End If
	Loop Until (v = 1 And Len(selected_track) <> 0) Or v = 2
	buttons = 0
	Do : Loop Until Len(InKey) = 0
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	If v = 1 And Len(selected_track) <> 0 Then
		If InStr(selected_track, ".") = 0 Then selected_track = selected_track + ".trk"
		If FileExists(track_path + selected_track) Then
			DrawTrack
			MenuBox 28, 10, "Warning!"
			ceny += 16
			If sel.current = 4 Then
				TCentre , "An image with that name already exists.", RGB(200, 200, 240)
			Else
				TCentre , "A track with that name already exists.", RGB(200, 200, 240)
			End If
			TCentre , "Are you sure you want to OVERWRITE it?", RGB(200, 200, 240)
			ceny += 16
			
			buttons = 0
			StackButton "  SAVE  ", 1, , 50
			StackButton " Cancel ", 2, , 50
			EndOfButtonStack
			Do
				v = ManageButtons
				akey = InKey
			Loop Until v <> 0 Or akey <> ""
			buttons = 0
			
			Do
				GetMouse xm, ym, wm, bm
			Loop Until bm = 0
			If v = 1 Then
				If sel.current = 1 Then
					thisfileformat = FORMAT_COMBINED
				ElseIf sel.current = 2 Then
					thisfileformat = FORMAT_BINARY_SPLIT
				ElseIf sel.current = 3 Then
					thisfileformat = FORMAT_TEXT_SPLIT
				Else
					thisfileformat = FORMAT_RAW	'Do not save meta-data
				End If
				SaveTrack track_path + selected_track
				track_file = selected_track
			End If
		Else
			If Open(track_path + selected_track For Output As 101) Then
				DrawTrack
				buttons = 0
				Error_Message "Invalid track file name"
			Else
				Close 101
				If Right(LCase(selected_track), 4) = ".rpl" Then
					DrawTrack
					buttons = 0
					Error_Message "Can't write to replay if it doesn't exist"
				Else
					If sel.current = 1 Then
						thisfileformat = FORMAT_COMBINED
					ElseIf sel.current = 2 Then
						thisfileformat = FORMAT_BINARY_SPLIT
					ElseIf sel.current = 3 Then
						thisfileformat = FORMAT_TEXT_SPLIT
					Else
						thisfileformat = FORMAT_RAW	'Do not save meta-data
					End If
					SaveTrack track_path + selected_track
					track_file = selected_track
				End If
			End If
		End If
	End If
	
	DrawTrack
	DrawPanel
End Sub


Sub LinkTiles
	Dim As Short xr, yr, i, j, n, v
	Dim As Integer xm, ym, wm, bm
	Dim connector(0 To 3) As Byte, connectors As Byte
	
	GetMouse xm, ym, wm, bm
	If drawkeyboardcursor Then
		xr = xcursor
		yr = ycursor
	ElseIf xm >= xoffs And ym >= yoffs Then
		xr = (xm - xoffs) \ bigwidth + 1
		yr = (ym - yoffs) \ 22 + 1
		If xr > 30 Or yr > 30 Then Exit Sub
	End If
	
	v = GetParent(xr, yr)
		
	'First try to find a 1x1 tile
	If yr > 1 Then
		connector(0) = tr(GetParent(xr, yr - 1)).ctype(2)
		If connector(0) Then connectors += 1
	End If
	If xr < 30 Then
		connector(1) = tr(GetParent(xr + 1, yr)).ctype(3)
		If connector(1) Then connectors += 1
	End If
	If yr < 30 Then
		connector(2) = tr(GetParent(xr, yr + 1)).ctype(0)
		If connector(2) Then connectors += 1
	End If
	If xr > 1 Then
		connector(3) = tr(GetParent(xr - 1, yr)).ctype(1)
		If connector(3) Then connectors += 1
	End If
	If connectors >= 2 Then
		For i = v + 1 To v + 256
			n = i Mod 256
			If tr(n).w = 1 And tr(n).h = 1 Then
				If tr(n).ctype(0) = connector(0) And tr(n).ctype(1) = connector(1) _
					And tr(n).ctype(2) = connector(2) And tr(n).ctype(3) = connector(3) Then
					
					PushUndo
					SetTrack xr, yr, n
					Exit Sub
				End If
			End If
		Next i
	End If
End Sub


#ifdef __FB_LINUX__
Sub LKinitialise
	ScreenControl FB.GET_WINDOW_HANDLE, LKwindow
	LKdisp = XOpenDisplay(0)
	XSelectInput LKdisp, LKwindow, KeyPressMask Or KeyReleaseMask
End Sub


Function Linkey As String
	Dim tempkey As Long		'KeySym type seems to be a Long
	
	If Not LKinitialised Then
		LKinitialise
		LKinitialised = -1
	End If
	
	If XPending(LKdisp) Then
		XNextEvent LKdisp, @LKevent
		If LKevent.type = KeyPress Then
			'XLookupString fills only as many bytes as necessary, so
			'the string has to be zeroed to prevent trailing bytes from
			'the last query to populate in the next if shorter
			LKstring = String(25, 0)
			XLookupString @LKevent.xkey, @LKstring, 25, @LKsym, 0
			
			tempkey = XLookupKeysym(@LKevent.xkey, 0)
			'If tempkey = XK_Alt_L Or tempkey = XK_Alt_R Then LKaltpressed = -1
		
			If Len(LKstring) Then
				Return LKstring
			Else
				Return Chr(0) & MkL(tempkey)
			End If
		End If
	Else
		Return ""
	End If
End Function
#endif


Sub LoadConfiguration
	Dim s As String, n As UShort
	Dim As String param, value
	
	default_author = "Anonymous"
	imageformat = "tga"
	Open program_path + "bliss.cfg" For Input As 100
	Do While Not EOF(100)
		Line Input #100, s
		s = Trim(s)
		n = InStr(s, ";")
		If n Then s = Left(s, n - 1)
		n = InStr(s, "=")
		If n Then
			param = LCase(Trim(Left(s, n - 1)))
			value = Trim(Mid(s, n + 1))
			
			Select Case param
				Case "track", "tracks"
					track_path = value
					If Right(track_path, 1) <> "/" And Right(track_path, 1) <> "\" Then
						#ifdef __FB_LINUX__
						track_path = track_path + "/"
						#else
						track_path = track_path + "\"
						#endif
					End If
					If dirlinks < 10 Then
						dirlinks += 1
						dirlink(dirlinks).text = "Tracks"
						dirlink(dirlinks).directory = track_path
					End If
				Case "dirlink"
					Dim mysplit As UByte
					
					If dirlinks < 10 Then
						dirlinks += 1
						mysplit = InStr(value, ":")
						If mysplit Then
							dirlink(dirlinks).text = Trim(Left(value, mysplit - 1))
							dirlink(dirlinks).directory = Trim(Mid(value, mysplit + 1))
						Else
							dirlink(dirlinks).text = Trim(value)
							dirlink(dirlinks).directory = Trim(value)
						End If
					
						If Right(dirlink(dirlinks).directory, 1) <> "/" And Right(dirlink(dirlinks).directory, 1) <> "\" Then
							#ifdef __FB_LINUX__
							dirlink(dirlinks).directory &= "/"
							#else
							dirlink(dirlinks).directory &= "\"
							#endif
						End If
					End If
				Case "stunts"
					If dirlinks < 10 Then
						dirlinks += 1
						dirlink(dirlinks).text = "Stunts"
						dirlink(dirlinks).directory = Trim(value)
					End If
					
					If Right(dirlink(dirlinks).directory, 1) <> "/" And Right(dirlink(dirlinks).directory, 1) <> "\" Then
						#ifdef __FB_LINUX__
						dirlink(dirlinks).directory &= "/"
						#else
						dirlink(dirlinks).directory &= "\"
						#endif
					End If
				Case "superposition", "superpositions"
					If LCase(value) = "true" Or LCase(value) = "yes" Or LCase(value) = "on" Then
						allow_errors = -1
					Else
						allow_errors = 0
					End If
				Case "warning", "warnings"
					If LCase(value) = "true" Or LCase(value) = "yes" Or LCase(value) = "on" Then
						show_errors = -1
					Else
						show_errors = 0
					End If
				Case "grid"
					If LCase(value) = "true" Or LCase(value) = "yes" Or LCase(value) = "on" Then
						show_grid = -1
					Else
						show_grid = 0
					End If
				Case "format"
					Select Case LCase(Trim(value))
						Case "stunts", "none" : default_format = FORMAT_RAW
						Case "tb", "trackblaster" : default_format = FORMAT_RAW
						Case "bliss", "overlay", "combined"
							default_format = FORMAT_COMBINED
						Case "companion", "split", "binary"
							default_format = FORMAT_BINARY_SPLIT
						Case "text", "text-split", "human", "human-readable"
							default_format = FORMAT_TEXT_SPLIT
					End Select
				Case "author"
					default_author = Trim(value)
				Case "calibration", "speed"	'Racer speed weigh value
					racer_weigh = Val(value)
					
					If racer_weigh < 4 Then racer_weigh = 4
					If racer_weigh > 20 Then racer_weigh = 20
				Case "imageformat"
					value = LCase(Trim(value))
					If Left(value, 1) = "." Then value = Mid(value, 2)
					Select Case value
						Case "bmp", "gif", "pcx", "jpg", "png"
							imageformat = value
						Case "jpeg"
							imageformat = "jpg"
						Case Else
							imageformat = "tga"
					End Select
			End Select
		End If
	Loop
	Close 100

	
	If FileExists(program_path + "handicap.cfg") Then
		Open program_path + "handicap.cfg" For Input As 100
		Do While Not EOF(100)
			Line Input #100, s
			s = Trim(s)
			If Len(s) <> 0 And Left(s, 1) <> ";" Then
				cars += 1
				If Mid(s, 5, 1) = ";" Then
					car(cars).id = Left(s, 4)
					s = Mid(s, 6)
				Else
					car(cars).id = "****"
				End If
				
				n = InStr(s, ";")
				If n = 0 Then
					cars -= 1
				Else
					car(cars).cname = Trim(Left(s, n - 1))
					If Len(car(cars).cname) = 0 Then car(cars).cname = "Unknown"
					car(cars).handicap = Val(Mid(s, n + 1))
					If car(cars).handicap < 0.005 Or car(cars).handicap > 5 Then _
						car(cars).handicap = 1.5
				End If
			End If
			
			If car(cars).handicap = 1 Then activecar = cars
			If cars = MAXCARS Then Exit Do
		Loop
		Close 100
		
		If activecar = 0 Then activecar = 1
	Else
		'No handicap file. Only one car: Porsche March Indy
		cars = 1 : activecar = 1
		car(1).id = "PMIN" : car(1).cname = "Porsche March Indy"
		car(1).handicap = 1
	End If
End Sub


Sub LoadFont (fontfile As String)
	Dim As Byte x, y
	Dim f As String, b As UByte, i As Short
	Dim ff As String
	
	ff = fontfile
	If InStr(ff, ".") = 0 Then ff &= ".fnt"

	Open program_path + ff For Binary Access Read As 1
	f = Space(Lof(1))
	Get #1, , f
	Close 1

	font = ImageCreate(8, 16 * 256)
	mask = ImageCreate(8, 16 * 256)

	Line font, (0, 0)-(15, 16 * 256 -1), RGB(0, 0, 0), BF
	Line mask, (0, 0)-(15, 16 * 256 -1), RGB(255, 0, 255), BF
	For i = 0 To 255
		For y = 0 To 15
			b = Asc(Mid(f, 16 * i + y, 1))
			For x = 0 To 7
				If b And 2 ^ x Then
					PSet font, (x, 16 * i + y), RGB(255, 255, 255)
					PSet mask, (x, 16 * i + y), RGB(0, 0, 0)
				End If
			Next x
		Next y
	Next i
End Sub


Sub LoadGraphics
	Dim gf As String
	Dim f As Integer
	Dim As UShort gwidth, gheight, x, y, i
	Dim As UByte r, g, b, a, count
	Dim rle As Byte
	Dim col As ULong
	Dim buffer As UByte Ptr, fp As ULong
	
	f = FreeFile
	
	gf = big_graphics_file
	If InStr(gf, ".") = 0 Then gf = gf + ".tga"
	
	Open program_path + gf For Binary Access Read As f
	Get #f, 13, gwidth
	Get #f, , gheight
	
	bigicons = ImageCreate(gwidth, gheight)
	buffer = Allocate(LOF(f) + 1024)
	Get #f, 1, *buffer, LOF(f)
	
	If dosbox Then
		fp = 18
		x = 0 : y = 0
		Do
			count = buffer[fp]
			rle = count And 128
			count = count And 127
			fp += 1
					
			If rle Then
				b = buffer[fp]
				g = buffer[fp + 1]
				r = buffer[fp + 2]
				a = buffer[fp + 3]
				fp += 4
				
				For i = 0 To count
					If a >= 128 Then
						PSet bigicons, (x, y), RGB(r, g, b)
					Else
						PSet bigicons, (x, y), RGB(255, 0, 255)
					End If
					x += 1
					If x = gwidth Then x = 0 : y += 1
				Next i
			Else
				For i = 0 To count
					b = buffer[fp]
					g = buffer[fp + 1]
					r = buffer[fp + 2]
					a = buffer[fp + 3]
					fp += 4
					
					If a >= 128 Then
						PSet bigicons, (x, y), RGB(r, g, b)
					Else
						PSet bigicons, (x, y), RGB(255, 0, 255)
					End If
					x += 1
					If x = gwidth Then x = 0 : y += 1
				Next i
			End If
		Loop Until y >= gheight
	Else
		fp = 18
		x = 0 : y = 0
		Do
			count = buffer[fp]
			rle = count And 128
			count = count And 127
			fp += 1
					
			If rle Then
				b = buffer[fp]
				g = buffer[fp + 1]
				r = buffer[fp + 2]
				a = buffer[fp + 3]
				fp += 4
				
				For i = 0 To count
					PSet bigicons, (x, y), RGBA(r, g, b, a)
					x += 1
					If x = gwidth Then x = 0 : y += 1
				Next i
			Else
				For i = 0 To count
					b = buffer[fp]
					g = buffer[fp + 1]
					r = buffer[fp + 2]
					a = buffer[fp + 3]
					fp += 4
					
					PSet bigicons, (x, y), RGBA(r, g, b, a)
					x += 1
					If x = gwidth Then x = 0 : y += 1
				Next i
			End If
		Loop Until y >= gheight
	End If
	
	Close f
	Deallocate buffer
End Sub


Sub LoadMetaData(filenumber As Short = 0, content As String = "")
	Dim s As String, l As Short
	Dim c As String
	
	If Len(content) Then
		c = content
	Else
		l = LOF(filenumber) - Seek(filenumber) + 1
		c = Space(l)
		Get #filenumber, , c
	End If
	
	'Initialise meta-data
	meta.title = ""
	meta.author = ""
	meta.comment = ""
	meta.championship = ""
	meta.cyear = 0
	meta.cmonth = 0
	meta.cday = 0
	meta.editing_time = -1
	started_editing = Timer
	
	'Read meta-data
	s = Left(c, 8)
		
	If Left(s, 4) = "smdf" Then
		thisfileformat = FORMAT_BINARY_SPLIT
		c = Mid(c, 9)		'Skip obsolete track hash
			
		Do While Len(c)
			s = Left(c, 4) : l = CvShort(Mid(c, 5, 2))
			c = Mid(c, 7)
					
			'Field length has to be positive
			If l <= 0 Then Exit Do
			
			Select Case s
				Case "Titl"
					meta.title = Left(c, l)
					c = Mid(c, l + 1)
				Case "Autr"
					meta.author = Left(c, l)
					c = Mid(c, l + 1)
				Case "Comm"
					meta.comment = Left(c, l)
					c = Mid(c, l + 1)
				Case "Chmp"
					meta.championship = Left(c, l)
					c = Mid(c, l + 1)
				Case "Date"
					If l <> 4 Then Exit Do
					meta.cyear = CvShort(Left(c, 2))
					meta.cmonth = ASC(Mid(c, 3, 1))
					meta.cday = ASC(Mid(c, 4, 1))
					c = Mid(c, 5)
				Case "Tool"
					l -= 4
					If l <= 0 Then Exit Do
					meta.tool = Left(c, l) : c = Mid(c, l + 1)
					meta.toolversion = CvL(Left(c, 4))
					c = Mid(c, 5)
				Case "Etim"
					If l <> 4 Then Exit Do
					meta.editing_time = CvL(Left(c, 4))
					c = Mid(c, 5)
				Case "Colr"
					Dim t As String, last_bgc As ULong, last_border As ULong
					Dim As UByte i, j
					Dim pending As UByte
					
					t = Left(c, l)
					c = Mid(c, l + 1)
					
					i = 1 : j = 1 : pending = 0
					Do While Len(t) <> 0 Or pending
						If pending = 0 Then
							pending = ASC(Left(t, 1))
							last_border = CvL(ConvertColour(Mid(t, 2, 2)))
							last_bgc = CvL(ConvertColour(Mid(t, 4, 2)))
							t = Mid(t, 6)
						End If
						
						grid(i, j).border = last_border
						grid(i, j).bgc = last_bgc
						pending -= 1
						i += 1
						If i = 31 Then i = 1 : j += 1
						If j = 31 Then Exit Do
					Loop
				Case Else
					c = Mid(c, l + 1)
			End Select
		Loop
	ElseIf Left(s, 7) = "[smdf]" + Chr(13) Or Left(s, 7) = "[smdf]" + Chr(10) Then
		thisfileformat = FORMAT_TEXT_SPLIT
		
		If Right(s, 1) = Chr(10) Or Right(s, 1) = Chr(13) Then
			'Using CRLF or LFCR for end of line. Skip both characters
			c = Mid(c, 9)
		Else
			'Using either CR or LF for end of line. Skip it
			c = Mid(c, 8)
		End If
		
		'Make sure the string ends with an end-of-line character
		If Right(c, 1) <> Chr(13) And Right(c, 1) <> Chr(10) Then c &= Chr(10)
		
		Dim titi As Double
		s = ""
		Do While Len(c)
			If Left(c, 1) = Chr(13) Or Left(c, 1) = Chr(10) Then
				'EOL character. Process the accumulated string if any
				Dim n As Short, id As String, vval As String
				
				s = Trim(s)
				n = InStr(s, "=")
				If n Then	'Ignore lines that don't contain a "="
					id = LCase(RTrim(Left(s, n - 1)))
					vval = LTrim(Mid(s, n + 1))
					
					Select Case id
						Case "title", "titl" : meta.title = Left(vval, 64)
						Case "author", "autr" : meta.author = Left(vval, 64)
						Case "comment", "comm" : meta.comment = Left(vval, 64)
						Case "tour_info", "chmp" : meta.championship = Left(vval, 64)
						Case "creation_date", "date"
							meta.cyear = ValInt(vval)
							meta.cmonth = 0 : meta.cday = 0
							If meta.cyear < 1900 Then meta.cyear = 1900
							If meta.cyear > 3000 Then meta.cyear = 3000
							n = InStr(vval, "-")
							If n Then
								vval = Mid(vval, n + 1)
								meta.cmonth = ValInt(vval)
								If meta.cmonth < 0 Or meta.cmonth > 12 Then meta.cmonth = 0
								n = InStr(vval, "-")
								If n Then
									vval = Mid(vval, n + 1)
									meta.cday = ValInt(vval)
									If meta.cday < 0 Or meta.cday > 31 Then meta.cday = 0
								End If
							End If
						Case "tool" : meta.tool = Left(vval, 64)
						Case "tool_version"
							n = InStr(vval, ".")
							If n Then
								meta.toolversion = ValInt(Left(vval, n - 1))
								meta.toolversion *= 10000
								vval = Mid(vval, n + 1)
								n = InStr(vval, ".")
								If n Then
									meta.toolversion += 100 * ValInt(Left(vval, n - 1))
									meta.toolversion += ValInt(Mid(vval, n + 1))
								Else
									meta.toolversion += 100 * ValInt(vval)
								End If
							Else
								meta.toolversion = ValInt(vval)
								meta.toolversion *= 10000
							End If
						Case "editing_time", "etim" : meta.editing_time = ValInt(vval)
					End Select
				End If
				
				s = ""
			Else
				'Not and EOL character, add the character to the line
				s &= Left(c, 1)
			End If
			c = Mid(c, 2)
		Loop
	Else
		Exit Sub	'No valid metadata found
		'Notice that the metadata file has to be UTF-8 or ASCII with
		'no BOM. Otherwise, we'll get here and no metadata will be loaded.
	End If
End Sub


Sub LoadTrack(trk As String)
	Dim As Short x, y, n, i
	Dim ticks As UShort, s As String
	Dim mytrack As String
	Dim pathtofile As String
	Dim isrpl As Byte = 0
	
	#ifdef __FB_LINUX__
		n = InStrRev(trk, "/")
	#else
		n = InStrRev(trk, "\")
	#endif
	pathtofile = Left(trk, n)
	
	mytrack = trk
	If InStr(mytrack, ".") = 0 Then mytrack = mytrack + ".trk"
		
	Open mytrack For Binary Access Read As 100
	If Right(LCase(mytrack), 4) = ".rpl" Then
		isrpl = -1
		track_file = Space(9)
		Get #100, 14, track_file
		n = InStr(track_file, Chr(0))
		track_file = LCase(Left(track_file, n - 1)) + ".trk"
		Get #100, 25, ticks
		If ticks + 1828 = LOF(100) Then		'v1.1 replay file
			Seek #100, 27
		Else
			Get #100, 23, ticks
			If ticks + 1826 = LOF(100) Then		'v1.0 replay file
				Seek #100, 25
			Else		'Track file renamed as replay?  Or corrupt?
				Seek #100, 1
			End If
		End If
	Else
		track_file = ""
		For i = Len(mytrack) To 1 Step -1
			If Mid(mytrack, i, 1) = "/" Or Mid(mytrack, i, 1) = "\" Then
				track_file = Mid(mytrack, i + 1)
				Exit For
			End If
		Next i
		If Len(track_file) = 0 Then track_file = mytrack
	End If
	
	'Load track contents
	For y = 30 To 1 Step -1
		For x = 1 To 30
			Get #100, , grid(x, y).track
		Next x
	Next y
	Get #100, , landscape
	For y = 1 To 30
		For x = 1 To 30
			Get #100, , grid(x, y).land
			
			grid(x, y).bgc = 0
			grid(x, y).border = 0
		Next x
	Next y
	Get #100, , format_byte
	
	'Initialise meta-data
	meta.title = ""
	meta.author = ""
	meta.comment = ""
	meta.championship = ""
	meta.cyear = 0
	meta.cmonth = 0
	meta.cday = 0
	meta.editing_time = -1
	started_editing = Timer
	
	'Load meta-data
	s = pathtofile + Left(track_file, InStr(track_file, ".")) + "smd"
	If isrpl = 0 And LOF(100) > 1802 Then
		Seek #100, 1803
		LoadMetaData 100
		thisfileformat = FORMAT_COMBINED	'Overlay
	ElseIf FileExists(s) Then
		Close 100
		Open s For Binary Access Read As 100
		'We know this is a split format, but still don't know if
		'it's text or binary. We'll assume binary and if it isn't,
		'LoadMetaData will change the global variable thisfileformat
		'accordingly.
		thisfileformat = FORMAT_BINARY_SPLIT	'Split
		LoadMetaData 100
	ElseIf GetMetaDataFromRegistry Then
		Select Case format_byte
			Case 150
				'Format 150 was used in earlier versions of Bliss,
				'before metadata were introduced
				meta.tool = "Bliss"
				meta.toolversion = 20100
				thisfileformat = FORMAT_COMBINED	'Use overlay if modified
			Case 151, 152, 2
				'Format 151 was used previously for tracks including
				'their metadata in the same file. It was discontinued
				'after version 2.3.3
				'Format 152 corresponded originally to tracks whose
				'metadata was being stored separately. Currently, all
				'Bliss tracks are generated with format code 152
				'Format 2 was being issued as a bug on new tracks
				'made with versions prior to 2.5.4, maybe even several
				'versions before. This was fixed, but tracks still
				'exist carrying this value
				meta.tool = "Probably, Bliss"
				meta.toolversion = 20303
				thisfileformat = FORMAT_COMBINED	'Bliss, so also use overlay
			Case 0
				'Stunts produces files with format 0 and so does
				'Track Blaster. Other tools might issue this same
				'format code, but I can't tell if that's the case.
				Dim mywhat As Byte
				
				'Here verify the elements on the track to see if
				'it was made with Track Blaster
				DetectNotStunts 0, 0, mywhat
				If mywhat Then
					meta.tool = "Track Blaster Pro"
					meta.toolversion = 50300
				Else
					meta.tool = "Stunts"
					meta.toolversion = 10100
				End If
				thisfileformat = FORMAT_BINARY_SPLIT	'Prefer split for these files
			Case Else
				meta.tool = "Unknown"
				meta.toolversion = 0
				thisfileformat = FORMAT_BINARY_SPLIT	'Unknown format - prefer split
		End Select
	End If
	Close 100
	
	UpdateTitleBar
	
	modified = 0
End Sub


Sub Redo
	'If already at the head, then there's nothing to redo
	If undopointer = undohead Then Exit Sub
	
	'Increment the pointer
	undopointer = (undopointer + 1) Mod UNDOLEVEL
	
	'Recover the grid
	If Len(undobuffer(undopointer)) = 0 Then _
		undobuffer(undopointer) = Chr(30, 30) + String(1800, 0)
	PutTrack 1, 1, undobuffer(undopointer), -1
	DrawTrack
End Sub


Sub SaveMetaData(filenumber As Short)
	Dim l As Short
	
	If thisfileformat = FORMAT_BINARY_SPLIT Or _
		thisfileformat = FORMAT_COMBINED Then
		
		'Stunts Meta-Data Format
		Put #filenumber, , "smdf"	'Magic word
		Put #filenumber, , MKL(0)	'Skip obsolete track hash
		
		l = Len(meta.title)
		If l Then
			Put #filenumber, , "Titl"
			Put #filenumber, , l : Put #filenumber, , meta.title
		End If
		l = Len(meta.author)
		If l Then
			Put #filenumber, , "Autr"
			Put #filenumber, , l : Put #filenumber, , meta.author
		End If
		l = Len(meta.comment)
		If l Then
			Put #filenumber, , "Comm"
			Put #filenumber, , l : Put #filenumber, , meta.comment
		End If
		l = Len(meta.championship)
		If l Then
			Put #filenumber, , "Chmp"
			Put #filenumber, , l : Put #filenumber, , meta.championship
		End If
		If meta.cyear Then
			Put #filenumber, , "Date" : l = 4
			Put #filenumber, , l
			Put #filenumber, , meta.cyear
			Put #filenumber, , meta.cmonth
			Put #filenumber, , meta.cday
		End If
		l = Len(meta.tool)
		If l = 0 Then
			meta.tool = "Bliss"
			meta.toolversion = THISVERSION_NOPERIOD
			l = 10
		End If
		l += 4
		Put #filenumber, , "Tool"
		Put #filenumber, , l
		Put #filenumber, , meta.tool
		Put #filenumber, , meta.toolversion
		If meta.editing_time >= 0 Then
			If started_editing < Timer Then _
				meta.editing_time += (Timer - started_editing)
			started_editing = Timer
			Put #filenumber, , "Etim"
			l = 4
			Put #filenumber, , l
			Put #filenumber, , meta.editing_time
		End If
		
		'Save colouration if any
		Dim As Byte i, j, q = 0
		Dim p As String, last_bgc As ULong, last_border As ULong
		Dim count As Short
		
		last_bgc = grid(1, 1).bgc
		last_border = grid(1, 1).border
		For j = 1 To 30
			For i = 1 To 30
				If grid(i, j).border <> 0 OrElse grid(i, j).bgc <> 0 Then q = -1
				If grid(i, j).border <> last_border OrElse _
					grid(i, j).bgc <> last_bgc OrElse count = 255 Then
				
					p &= Chr(count) & ConvertColour(MkL(last_border)) & ConvertColour(MkL(last_bgc))
					last_bgc = grid(i, j).bgc
					last_border = grid(i, j).border
					count = 1
				Else
					count += 1
				End If
			Next i
		Next j
		p &= Chr(count) & ConvertColour(MkL(last_border)) & ConvertColour(MkL(last_bgc))
		
		If q Then
			Put #filenumber, , "Colr"
			Put #filenumber, , MkShort(Len(p))
			Put #filenumber, , p
		End If
	ElseIf thisfileformat = FORMAT_TEXT_SPLIT Then
		'Stunts text meta-data format
		Put #filenumber, , "[smdf]" + Chr(13, 10)
		
		If Len(meta.title) Then
			Put #filenumber, , "title="
			Put #filenumber, , meta.title + Chr(13, 10)
		End If
		
		If Len(meta.author) Then
			Put #filenumber, , "author="
			Put #filenumber, , meta.author + Chr(13, 10)
		End If
		
		If Len(meta.comment) Then
			Put #filenumber, , "comment="
			Put #filenumber, , meta.comment + Chr(13, 10)
		End If
		
		If Len(meta.championship) Then
			Put #filenumber, , "tour_info="
			Put #filenumber, , meta.championship + Chr(13, 10)
		End If
		
		If meta.cyear Then
			Dim s As String
			
			s = "creation_date="
			s &= Trim(Str(meta.cyear)) & "-" & Trim(Str(meta.cmonth))
			s &= "-" & Trim(Str(meta.cday))
			
			Put #filenumber, , s + Chr(13, 10)
		End If
		l = Len(meta.tool)
		If l = 0 Then
			meta.tool = "Bliss"
			meta.toolversion = THISVERSION_NOPERIOD
		End If

		Put #filenumber, , "tool="
		Put #filenumber, , meta.tool + Chr(13, 10)
		Put #filenumber, , "tool_version="
		
		Dim As UByte v1, v2, v3
		v1 = meta.toolversion \ 10000
		v2 = (meta.toolversion Mod 10000) \ 100
		v3 = meta.toolversion Mod 100
		Put #filenumber, , Trim(Str(v1))
		If v2 Or v3 Then Put #filenumber, , "." & Trim(Str(v2))
		If v3 Then Put #filenumber, , "." & Trim(Str(v3))
		Put #filenumber, , Chr(13, 10)
		
		If meta.editing_time >= 0 Then
			If started_editing < Timer Then _
				meta.editing_time += (Timer - started_editing)
			
			started_editing = Timer
			Put #filenumber, , "editing_time="
			Put #filenumber, , Trim(Str(meta.editing_time)) + Chr(13, 10)
		End If
	End If
End Sub


Sub SaveTrack(trk As String)
	Dim mytrack As String, otherfile As String
	Dim ticks As Short, isrpl As Byte = 0
	Dim As Short x, y, i
	Dim hashvalue As ULong
	
	
	mytrack = trk
	If InStr(mytrack, ".") = 0 Then mytrack = mytrack + ".trk"
	otherfile = Left(mytrack, InStr(mytrack, ".")) + "smd"
	
	If Right(LCase(mytrack), 4) = ".rpl" Then
		isrpl = -1
		Open mytrack For Binary As 100
		Get #100, 25, ticks
		If ticks + 1828 = LOF(100) Then		'v1.1 replay file
			Seek #100, 27
		Else
			Get #100, 23, ticks
			If ticks + 1826 = LOF(100) Then		'v1.0 replay file
				Seek #100, 25
			Else		'Track file renamed as replay?  Or corrupt?
				Seek #100, 1
			End If
		End If
	Else
		Open mytrack For Output As 100 : Close 100
		Open mytrack For Binary As 100
		track_file = ""
		For i = Len(mytrack) To 1 Step -1
			If Mid(mytrack, i, 1) = "/" Or Mid(mytrack, i, 1) = "\" Then
				track_file = Mid(mytrack, i + 1)
				Exit For
			End If
		Next i
		If Len(track_file) = 0 Then track_file = mytrack
	End If

	For y = 30 To 1 Step -1
		For x = 1 To 30
			Put #100, , grid(x, y).track
		Next x
	Next y
	Put #100, , landscape
	For y = 1 To 30
		For x = 1 To 30
			Put #100, , grid(x, y).land
		Next x
	Next y
	Put #100, , format_byte
	
	'Write meta-data
	If Not isrpl AndAlso (thisfileformat <> FORMAT_RAW) Then
		If thisfileformat = FORMAT_BINARY_SPLIT Or _
			thisfileformat = FORMAT_TEXT_SPLIT Then
			
			Close 100 : Open otherfile For Output As 100
			Close 100 : Open otherfile For Binary As 100
		End If
		
		SaveMetaData 100
	End If
	Close 100
	
	'Delete SMD file if saved in a format other than split
	If thisfileformat <> FORMAT_BINARY_SPLIT And _
		thisfileformat <> FORMAT_TEXT_SPLIT And _
		FileExists(otherfile) Then Kill otherfile
	
	UpdateTitleBar
	
	modified = 0
End Sub


Sub SaveTrackImage(trk As String)
	Dim n As Short, extension As String, tfile As String
	Dim As Short iwidth, iheight, ixstart, iystart
	Dim finalbuffer As ULong Ptr
	Dim tempxselect As Byte
	
	'If there's a selection, save selection, otherwise, whole track
	If xselect Then
		ixstart = bigwidth * (xselect - 1)
		iystart = bigwidth * (yselect - 1)
		iwidth = bigwidth * (Abs(x2select - xselect) + 1)
		iheight = bigwidth * (Abs(y2select - yselect) + 1)
	Else
		iwidth = 660 : iheight = 660
		ixstart = 0 : iystart = 0
	End If
	
	If show_grid Then
		track_image_buffer = ImageCreate(661, 661, 32)
		iwidth += 1 : iheight += 1
	Else
		track_image_buffer = ImageCreate(660, 660, 32)
	End If
	finalbuffer = ImageCreate(iwidth, iheight, 32)
	
	tempxselect = xselect
	xselect = 0
	DrawTrack
	xselect = tempxselect

	'Kill alpha channel
	Dim pvalue As ULong
	For j As Short = 0 To 659
		For i As Short = 0 To 659
			pvalue = Point(i, j, track_image_buffer)
			PSet track_image_buffer, (i, j), pvalue Or &HFF000000ul
		Next i
	Next j

	'Get the part that's being saved
	Get track_image_buffer, (ixstart, iystart)-_
		(ixstart + iwidth - 1, iystart + iheight - 1), finalbuffer
	
	n = InStrRev(trk, ".")
	If n Then
		extension = LCase(Mid(trk, n))
		tfile = trk
	Else
		extension = ".tga"
		tfile = trk + extension
	End If
	
	Select Case extension
		Case ".bmp" : BSave tfile, finalbuffer
		Case ".tga" : TargaSave tfile, finalbuffer
		Case ".jpg", ".png", ".pcx", ".gif"
			BSave track_path + "temp.bmp", finalbuffer
			Shell "convert " + track_path + "temp.bmp " + tfile
			Kill track_path + "temp.bmp"
	End Select
	
	ImageDestroy track_image_buffer
	ImageDestroy finalbuffer
	track_image_buffer = 0
End Sub


Sub FollowPath(s As String, e As Byte = 0)
	Dim As TrackVector slot, oldslot
	Dim As Byte w, h, juststarted = -1, gofast = 0
	Dim As Short n, pointed = 0
			
	'Null tracks are not to be followed
	If section(1).final = section(1).initial And section(1).finishes = 0 Then Exit Sub
	
	Do
		pointed += 1
		n = ASC(Mid(s, pointed, 1))
		slot.coors = section(n).initial
		slot.bearing = section(n).bearing
				
		Do
			If slot.coors = section(n).final And juststarted = 0 Then
				xcursor = slot.x
				ycursor = slot.y
				Exit Do
			End If
			
			'This part may cause artifacts when big items are used
			'across the border of the grid
			If gofast = 0 Then
				w = tr(grid(slot.x, slot.y).track).w
				h = tr(grid(slot.x, slot.y).track).h
				Line (xoffs + (slot.x - 1) * 22, yoffs + (slot.y - 1) * 22)- _
					Step(bigwidth * w - 1, 22 * h - 1), RGB(100, 240, 240), BF
				
				For j As Byte = 1 To h
					For i As Byte = 1 To w
						PutIcon tr(grid(slot.x, slot.y).track).x + i - 1, tr(grid(slot.x, slot.y).track).y + j - 1, _
							xoffs + bigwidth * (slot.x + i - 2), yoffs + 22 * (slot.y + j - 2)
					Next i
				Next j
				Sleep 100
			End If
			
			DrawSpot slot.x, slot.y
			If w = 2 And slot.x < 30 Then
				DrawSpot slot.x + 1, slot.y
				If h = 2 And slot.y < 30 Then DrawSpot slot.x + 1, slot.y + 1
			End If
			If h = 2 And slot.y < 30 Then DrawSpot slot.x, slot.y + 1
		
			oldslot = slot
			slot = GetNext(slot)
			If e Then	'e <> 0 means follow to error
				If slot.e = e Then
					xcursor = oldslot.x
					ycursor = oldslot.y
					Exit Do, Do
				End If
			End If
			juststarted = 0
						
			If Len(InKey) Then gofast = -1
		Loop
	Loop Until pointed = Len(s)
	
	DrawTrack
	ManageKeyboardCursor -1
End Sub


Sub GenerateSections
	Dim vector As TrackVector
	Dim lastchecked As Short = 0
	
	vector = FindStart
	If vector.e Then
		sections = 0
		paths = 0
		terrors = 0
		Exit Sub
	End If
	
	terrors = 0
	
	sections = 1
	section(1).initial = vector.coors
	section(1).bearing = vector.bearing
	section(1).origin = vector.bearing XOr 2
	
	SolveSection 1
	
	paths = 1
	
	path(1).p = Chr(1)
	path(1).e = 0
	path(1).finishes = 0
	SolvePath 1
End Sub


Function GetMetaDataFromRegistry As Byte
	Dim s As String, h As ULong
	Dim As Byte original
	
	h = Hash32
	
	Select Case h
		Case &H57F9D21A : meta.title = "Default" : original = -1
		Case &H9F88F8DB : meta.title = "Bernie's" : original = -1
		Case &H142A0681 : meta.title = "Cherry's" : original = -1
		Case &HC3477BBF : meta.title = "Helen's" : original = -1
		Case &H843C5C7F : meta.title = "Joe's" : original = -1
		Case &H2C1625C5 : meta.title = "Skid's" : original = -1
		Case &HF8680B47
			meta.title = "4:00am"
			meta.author = "Cas"
			meta.cyear = 2006 : meta.cmonth = 2 : meta.cday = 28
			meta.tool = "Cas-Stunts" : meta.toolversion = 10000
			meta.editing_time = -1
			meta.comment = "Always raced with Porsche March Indy"
			meta.championship = "Paleke's WSC - March 2006; ZakStunts - May 2015"
		Case &H136836E9
			meta.title = "Napalm"
			meta.author = "Cas"
			meta.cyear = 0
			meta.tool = "Bliss" : meta.toolversion = 20100
			meta.editing_time = -1
			meta.comment = ""
			meta.championship = "ZakStunts ZCT183 - September 2016"
		Case &HA6460C26
			meta.title = "Bliss"
			meta.author = "Cas"
			meta.cyear = 2016 : meta.cmonth = 10 : meta.cday = 17
			meta.tool = "Bliss" : meta.toolversion = 20500
			meta.editing_time = 1066
			meta.comment = "Race this track to measure your relative lap"
			meta.championship = "Race for Immortality - October/November 2016"
		Case Else	'Use external registry
			Dim found As Short = -1
			If FileExists("bliss.reg") Then
				Dim f As Integer, n As Short, s As String, rs As Short
				Dim shash As String, fpointer As Long
				
				f = FreeFile
				Open "bliss.reg" For Binary Access Read As f
				'First four bytes are magic number (not checked now)
				Get #f, 5, rs	'Get registry size (max number of tracks)
				Get #f, , n	'Get number of registry entries
				s = Space(4 * n)
				Get #f, , s		'Read hash table
				
				shash = MkL(h) : found = -1
				For i As Short = 0 To n - 1
					If Mid(s, 4 * i + 1, 4) = shash Then
						found = i
						Exit For
					End If
				Next i
				
				If found >= 0 Then
					Get #f, 5 + 4 * rs + 4 * found, fpointer
					Get #f, fpointer + 1, n		'Get metadata length
					s = Space(n)
					Get #f, , s
					LoadMetadata , s
				End If
				Close f
			End If
			#ifndef __FB_DOS__
				'If it looks like a ZakStunts track, try to retrieve
				'the metadata from the site
				If found = -1 AndAlso UCase(Left(track_file, 3)) = "ZCT" Then
					Dim length As Long, trackaddr As String
					Dim content As ZString Ptr, s As String
					Dim dif As Boolean = False
					
					DrawTrack
					MenuBox 25, 9, "Identifying Track"
					
					lefx += 8
					TLeft , "Identifying ZakStunts champ track...", RGB(160, 160, 240)
					TLeft , "Verifying track content...", RGB(160, 160, 240)
					trackaddr = "zak.stunts.hu/tracks/" & UCase(Left(track_file, Len(track_file) - 4)) & ".trk"
					length = HTTP_Download(trackaddr, content)
					If length > 0 Then
						Dim As Byte x, y
						Dim u As Short = 0
						
						For y = 30 To 1 Step -1
							For x = 1 To 30
								If content[u] <> grid(x, y).track Then
									dif = True
									Exit For, For
								End If
								u += 1
							Next x
						Next y
						If content[u] <> landscape Then dif = True
						u += 1
						For y = 1 To 30
							For x = 1 To 30
								If content[u] <> grid(x, y).land Then
									dif = True
									Exit For, For
								End If
								u += 1
							Next x
						Next y
						If Not dif Then
							TLeft , "Verified.", RGB(160, 160, 240)
						Else
							TLeft , "Couldn't verify!", RGB(160, 160, 240)
							Sleep 300
						End If
						If content <> 0 Then Deallocate content : content = 0
					End If
					If Not dif Then
						TLeft , "Retrieving metadata from ZakStunts...", RGB(160, 160, 240)
						trackaddr = UCase(Left(track_file, Len(track_file) - 4))
						trackaddr = "zak.stunts.hu/tracks/" & trackaddr
						length = HTTP_Download(trackaddr, content)
						If length > 0 Then
							Dim n As Long
							
							'Get the track name in form "Cxxx"
							s = Left(track_file, Len(track_file) - 4)
							s = "C" + Mid(s, 4) + " - "
							n = InStr(*content, s)
							
							If n Then
								s = Mid(*content, n + Len(s), 255)
								n = InStr(s, "<")
								If n Then s = Left(s, n - 1)
								n = InStr(s, "(by ")
								If n Then
									'Title and author
									meta.title = Trim(Left(s, n - 1))
									s = Mid(s, n + 4)
									n = InStr(s, ")")
									If n Then s = Left(s, n - 1)
									meta.author = Trim(s)
								Else
									'Title only
									meta.title = Trim(s)
									meta.author = ""
								End If
								meta.championship = "ZakStunts " & UCase(Left(track_file, Len(track_file) - 4))
							End If
							Deallocate content
						End If
					End If
				End If
			#endif
	End Select
	
	If original Then
		meta.author = "DSI"
		meta.comment = "Original track"
		meta.championship = ""
		meta.tool = "Stunts"
		meta.toolversion = 10000
		meta.editing_time = -1
		meta.cyear = 1990
		meta.cmonth = 10
		meta.cday = 0
		Return 0
	Else
		Return -1
	End If
End Function


Function GetNext(slot As TrackVector, detour As Byte = 0) As TrackVector
	Dim newslot As TrackVector
	Dim As UByte curel, newel, isaramp = 0, isabridge = 0
	
	'Current element is the nature of the element at current slot
	curel = grid(slot.x, slot.y).track
	newslot.origin = slot.bearing XOr 2
	
	Select Case slot.bearing
		Case 0	'North
			If tr(curel).cisalt(slot.bearing) Then
				newslot.x = slot.x + 1
			Else
				newslot.x = slot.x
			End If
			newslot.y = slot.y - 1
			
			'See if this is a ramp or bridge
			If tr(curel).ctype(0) = 2 Then
				If tr(curel).w = 1 And tr(curel).h = 1 AndAlso _
					tr(curel).ctype(2) = 1  Then
			
					isaramp = -1
				Else
					isabridge = -1
				End If
				
				If detour Then newslot.y -= 1	'Skip one slot (jump)
			End If
			
			If newslot.x > 30 Or newslot.y < 1 Then _
				newslot.e = 80 : Return newslot	'Grid border reached
			
			newel = grid(newslot.x, newslot.y).track
			Select Case newel
				Case 255 : newslot.x -= 1
				Case 254 : newslot.y -= 1
				Case 253 : newslot.x -= 1 : newslot.y -= 1
			End Select
			If newslot.x < 1 Or newslot.y < 1 Then _
				newslot.e = 80 : Return newslot	'Grid border reached
			
			newel = grid(newslot.x, newslot.y).track
			
			If tr(newel).ctype(2) = 0 Then	'New tile does not accept
				If (isaramp <> 0 Or isabridge <> 0) And detour = 0 Then
					newslot = GetNext(slot, -1)
					Return newslot
				ElseIf isaramp Then
					newslot.e = 73	'Jump distance is too long
					Return newslot
				ElseIf tr(curel).ctype(slot.bearing) = 2 Then
					newslot.e = 74	'Stunts won't allow interrupting this bridge
					Return newslot
				Else
					newslot.e = 81	'End of track, interrupted
					Return newslot
				End If
			ElseIf tr(newel).ctype(2) <> tr(curel).ctype(0) Then
				newslot.e = 70	'Track types mismatch
			End If
			
			'Accepted, but... is the jump feasible?
			If isaramp And detour Then
				If grid(slot.x, slot.y).land = 9 Then
					newslot.e = 21	'Jump is not feasible
				ElseIf grid(slot.x, slot.y).land = 0 And grid(newslot.x, newslot.y).land = 6 Then
					newslot.e = 21	'Jump is not feasible
				End If
			ElseIf isabridge And detour Then
				If grid(slot.x, slot.y).land <> 6 Or grid(newslot.x, newslot.y).land = 6 Then
					newslot.e = 21	'Jump is not feasible
				End If
			End If
				
			If newslot.x - (tr(newel).cisalt(2) <> 0) _
				<> slot.x - (tr(curel).cisalt(0) <> 0) Then
								
				newslot.e = 81	'Track not properly connected
			End If
			
			'Straightway before jump is too short?
			If slot.origin <> (slot.bearing XOr 2) _
				Or tr(curel).entity = ASC("t") _
				Or tr(curel).entity = ASC("h") Then	'Turn, tunnel or chicane?
				
				'Yes, it's one of those. Ramp ahead?
				If tr(newel).w = 1 And tr(newel).h = 1 AndAlso _
					tr(newel).ctype(2) = 1 And tr(newel).ctype(0) = 2 Then
				
					'Yes. Is it a jump?
					If newslot.y - 1 >= 1 AndAlso _
						tr(GetParent(newslot.x, newslot.y - 1)).ctype(2) <> 2 Then
						
						newslot.e = 71	'Straightway before jump is too short
					End If
				End If
			End If
			
			Select Case tr(newel).cto(2)
				Case 0 : newslot.e = 4	'No exit!
				Case 1 : newslot.bearing = 0	'North
				Case 2 : newslot.bearing = 1	'East
				Case 4 : newslot.bearing = 2	'South
				Case 8 : newslot.bearing = 3	'West
				Case 3	'Split: North or East
					If detour Then
						newslot.bearing = 1
					Else
						newslot.bearing = 0
					End If
				Case 9	'Split: North or West
					If detour Then
						newslot.bearing = 3
					Else
						newslot.bearing = 0
					End If
				Case Else : newslot.bearing = 0	'This should never happen
			End Select
		Case 1	'East
			If tr(curel).cisalt(slot.bearing) Then
				newslot.y = slot.y + 1
			Else
				newslot.y = slot.y
			End If
			newslot.x = slot.x + tr(curel).w
			
			'See if this is a ramp or bridge
			If tr(curel).ctype(1) = 2 Then
				If tr(curel).w = 1 And tr(curel).h = 1 AndAlso _
					tr(curel).ctype(3) = 1  Then
			
					isaramp = -1
				Else
					isabridge = -1
				End If
				
				If detour Then newslot.x += 1	'Skip one slot (jump)
			End If
			
			If newslot.x > 30 Or newslot.y > 30 Then _
				newslot.e = 80 : Return newslot	'Grid border reached
				
			newel = grid(newslot.x, newslot.y).track
			Select Case newel
				Case 255 : newslot.x -= 1
				Case 254 : newslot.y -= 1
				Case 253 : newslot.x -= 1 : newslot.y -= 1
			End Select
			If newslot.x < 1 Or newslot.y < 1 Then _
				newslot.e = 80 : Return newslot	'Grid border reached
			
			newel = grid(newslot.x, newslot.y).track
			
			If tr(newel).ctype(3) = 0 Then	'New tile does not accept
				If (isaramp <> 0 Or isabridge <> 0) And detour = 0 Then
					newslot = GetNext(slot, -1)
					Return newslot
				ElseIf isaramp Then
					newslot.e = 73	'Jump distance is too long
					Return newslot
				ElseIf tr(curel).ctype(slot.bearing) = 2 Then
					newslot.e = 74	'Stunts won't allow interrupting this bridge
					Return newslot
				Else
					newslot.e = 81	'End of track, interrupted
					Return newslot
				End If
			ElseIf tr(newel).ctype(3) <> tr(curel).ctype(1) Then
				newslot.e = 70	'Track types mismatch
			End If

			'Accepted, but... is the jump feasible?
			If isaramp And detour Then
				If grid(slot.x, slot.y).land = 8 Then
					newslot.e = 21	'Jump is not feasible
				ElseIf grid(slot.x, slot.y).land = 0 And grid(newslot.x, newslot.y).land = 6 Then
					newslot.e = 21	'Jump is not feasible
				End If
			ElseIf isabridge And detour Then
				If grid(slot.x, slot.y).land <> 6 Or grid(newslot.x, newslot.y).land = 6 Then
					newslot.e = 21	'Jump is not feasible
				End If
			End If
				
			If newslot.y - (tr(newel).cisalt(3) <> 0) _
				<> slot.y - (tr(curel).cisalt(1) <> 0) Then
								
				newslot.e = 81	'Track not properly connected
			End If
			
			'Straightway before jump is too short?
			If slot.origin <> (slot.bearing XOr 2) _
				Or tr(curel).entity = ASC("t") _
				Or tr(curel).entity = ASC("h") Then	'Turn, tunnel or chicane?
				
				'Yes, it's one of those. Ramp ahead?
				If tr(newel).w = 1 And tr(newel).h = 1 AndAlso _
					tr(newel).ctype(3) = 1 And tr(newel).ctype(1) = 2 Then
				
					'Yes. Is it a jump?
					If newslot.x + 1 <= 30 AndAlso _
						tr(GetParent(newslot.x + 1, newslot.y)).ctype(3) <> 2 Then
						
						newslot.e = 71	'Straightway before jump is too short
					End If
				End If
			End If
			
			Select Case tr(newel).cto(3)
				Case 0 : newslot.e = 4	'No exit!
				Case 1 : newslot.bearing = 0	'North
				Case 2 : newslot.bearing = 1	'East
				Case 4 : newslot.bearing = 2	'South
				Case 8 : newslot.bearing = 3	'West
				Case 3	'Split: East or North
					If detour Then
						newslot.bearing = 0
					Else
						newslot.bearing = 1
					End If
				Case 6	'Split: East or South
					If detour Then
						newslot.bearing = 2
					Else
						newslot.bearing = 1
					End If
				Case Else : newslot.bearing = 1	'This should never happen
			End Select
		Case 2	'South
			If tr(curel).cisalt(slot.bearing) Then
				newslot.x = slot.x + 1
			Else
				newslot.x = slot.x
			End If
			newslot.y = slot.y + tr(curel).h
			
			'See if this is a ramp or bridge
			If tr(curel).ctype(2) = 2 Then
				If tr(curel).w = 1 And tr(curel).h = 1 AndAlso _
					tr(curel).ctype(0) = 1  Then
			
					isaramp = -1
				Else
					isabridge = -1
				End If
				
				If detour Then newslot.y += 1	'Skip one slot (jump)
			End If
			
			If newslot.x > 30 Or newslot.y > 30 Then _
				newslot.e = 80 : Return newslot	'Grid border reached
				
			newel = grid(newslot.x, newslot.y).track
			Select Case newel
				Case 255 : newslot.x -= 1
				Case 254 : newslot.y -= 1
				Case 253 : newslot.x -= 1 : newslot.y -= 1
			End Select
			If newslot.x < 1 Or newslot.y < 1 Then _
				newslot.e = 80 : Return newslot	'Grid border reached
			
			newel = grid(newslot.x, newslot.y).track
			
			If tr(newel).ctype(0) = 0 Then	'New tile does not accept
				If (isaramp <> 0 Or isabridge <> 0) And detour = 0 Then
					newslot = GetNext(slot, -1)
					Return newslot
				ElseIf isaramp Then
					newslot.e = 73	'Jump distance is too long
					Return newslot
				ElseIf tr(curel).ctype(slot.bearing) = 2 Then
					newslot.e = 74	'Stunts won't allow interrupting this bridge
					Return newslot
				Else
					newslot.e = 81	'End of track, interrupted
					Return newslot
				End If
			ElseIf tr(newel).ctype(0) <> tr(curel).ctype(2) Then
				newslot.e = 70	'Track types mismatch
			End If

			'Accepted, but... is the jump feasible?
			If isaramp And detour Then
				If grid(slot.x, slot.y).land = 7 Then
					newslot.e = 21	'Jump is not feasible
				ElseIf grid(slot.x, slot.y).land = 0 And grid(newslot.x, newslot.y).land = 6 Then
					newslot.e = 21	'Jump is not feasible
				End If
			ElseIf isabridge And detour Then
				If grid(slot.x, slot.y).land <> 6 Or grid(newslot.x, newslot.y).land = 6 Then
					newslot.e = 21	'Jump is not feasible
				End If
			End If
				
			If newslot.x - (tr(newel).cisalt(0) <> 0) _
				<> slot.x - (tr(curel).cisalt(2) <> 0) Then
								
				newslot.e = 81	'Track not properly connected
			End If
			
			'Straightway before jump is too short?
			If slot.origin <> (slot.bearing XOr 2) _
				Or tr(curel).entity = ASC("t") _
				Or tr(curel).entity = ASC("h") Then	'Turn, tunnel or chicane?
				
				'Yes, it's one of those. Ramp ahead?
				If tr(newel).w = 1 And tr(newel).h = 1 AndAlso _
					tr(newel).ctype(0) = 1 And tr(newel).ctype(2) = 2 Then
				
					'Yes. Is it a jump?
					If newslot.y + 1 <= 30 AndAlso _
						tr(GetParent(newslot.x, newslot.y + 1)).ctype(0) <> 2 Then
						
						newslot.e = 71	'Straightway before jump is too short
					End If
				End If
			End If
			
			Select Case tr(newel).cto(0)
				Case 0 : newslot.e = 4	'No exit!
				Case 1 : newslot.bearing = 0	'North
				Case 2 : newslot.bearing = 1	'East
				Case 4 : newslot.bearing = 2	'South
				Case 8 : newslot.bearing = 3	'West
				Case 6	'Split: South or East
					If detour Then
						newslot.bearing = 1
					Else
						newslot.bearing = 2
					End If
				Case 12	'Split: South or West
					If detour Then
						newslot.bearing = 3
					Else
						newslot.bearing = 2
					End If
				Case Else : newslot.bearing = 2	'This should never happen
			End Select
		Case 3	'West
			If tr(curel).cisalt(slot.bearing) Then
				newslot.y = slot.y + 1
			Else
				newslot.y = slot.y
			End If
			newslot.x = slot.x - 1
			
			'See if this is a ramp or bridge
			If tr(curel).ctype(3) = 2 Then
				If tr(curel).w = 1 And tr(curel).h = 1 AndAlso _
					tr(curel).ctype(1) = 1  Then
			
					isaramp = -1
				Else
					isabridge = -1
				End If
				
				If detour Then newslot.x -= 1	'Skip one slot (jump)
			End If
			
			If newslot.x < 1 Or newslot.y > 30 Then _
				newslot.e = 80 : Return newslot	'Grid border reached
				
			newel = grid(newslot.x, newslot.y).track
			Select Case newel
				Case 255 : newslot.x -= 1
				Case 254 : newslot.y -= 1
				Case 253 : newslot.x -= 1 : newslot.y -= 1
			End Select
			If newslot.x < 1 Or newslot.y < 1 Then _
				newslot.e = 80 : Return newslot	'Grid border reached
			
			newel = grid(newslot.x, newslot.y).track
			
			If tr(newel).ctype(1) = 0 Then	'New tile does not accept
				If (isaramp <> 0 Or isabridge <> 0) And detour = 0 Then
					newslot = GetNext(slot, -1)
					Return newslot
				ElseIf isaramp Then
					newslot.e = 73	'Jump distance is too long
					Return newslot
				ElseIf tr(curel).ctype(slot.bearing) = 2 Then
					newslot.e = 74	'Stunts won't allow interrupting this bridge
					Return newslot
				Else
					newslot.e = 81	'End of track, interrupted
					Return newslot
				End If
			ElseIf tr(newel).ctype(1) <> tr(curel).ctype(3) Then
				newslot.e = 70	'Track types mismatch
			End If

			'Accepted, but... is the jump feasible?
			If isaramp And detour Then
				If grid(slot.x, slot.y).land = 10 Then
					newslot.e = 21	'Jump is not feasible
				ElseIf grid(slot.x, slot.y).land = 0 And grid(newslot.x, newslot.y).land = 6 Then
					newslot.e = 21	'Jump is not feasible
				End If
			ElseIf isabridge And detour Then
				If grid(slot.x, slot.y).land <> 6 Or grid(newslot.x, newslot.y).land = 6 Then
					newslot.e = 21	'Jump is not feasible
				End If
			End If
				
			If newslot.y - (tr(newel).cisalt(1) <> 0) _
				<> slot.y - (tr(curel).cisalt(3) <> 0) Then
								
				newslot.e = 81	'Track not properly connected
			End If
			
			'Straightway before jump is too short?
			If slot.origin <> (slot.bearing XOr 2) _
				Or tr(curel).entity = ASC("t") _
				Or tr(curel).entity = ASC("h") Then	'Turn, tunnel or chicane?
				
				'Yes, it's one of those. Ramp ahead?
				If tr(newel).w = 1 And tr(newel).h = 1 AndAlso _
					tr(newel).ctype(1) = 1 And tr(newel).ctype(3) = 2 Then
				
					'Yes. Is it a jump?
					If newslot.x - 1 >= 1 AndAlso _
						tr(GetParent(newslot.x - 1, newslot.y)).ctype(1) <> 2 Then
						
						newslot.e = 71	'Straightway before jump is too short
					End If
				End If
			End If
			
			Select Case tr(newel).cto(1)
				Case 0 : newslot.e = 4	'No exit!
				Case 1 : newslot.bearing = 0	'North
				Case 2 : newslot.bearing = 1	'East
				Case 4 : newslot.bearing = 2	'South
				Case 8 : newslot.bearing = 3	'West
				Case 9	'Split: West or North
					If detour Then
						newslot.bearing = 0
					Else
						newslot.bearing = 3
					End If
				Case 12	'Split: West or South
					If detour Then
						newslot.bearing = 2
					Else
						newslot.bearing = 3
					End If
				Case Else : newslot.bearing = 3	'This should never happen
			End Select
	End Select
	
	Return newslot
End Function


'Get the code of the parent element
Function GetParent(x As UByte, y As UByte) As UByte
	Select Case grid(x, y).track
		Case 255
			If x > 1 Then
				If tr(grid(x - 1, y).track).w = 2 Then
					Return grid(x - 1, y).track
				End If
			End If
		Case 254
			If y > 1 Then
				If tr(grid(x, y - 1).track).h = 2 Then
					Return grid(x, y - 1).track
				End If
			End If
		Case 253
			If x > 1 And y > 1 Then
				If tr(grid(x - 1, y - 1).track).w = 2 And tr(grid(x - 1, y - 1).track).h = 2 Then
					Return grid(x - 1, y - 1).track
				End If
			End If
	End Select
	
	Return grid(x, y).track
End Function


'Read a rectangle from the grid into a string
Function GetTrack(x As UByte, y As UByte, x2 As UByte, y2 As UByte) As String
	Dim As Byte i, j, c
	Dim s As String, t As String
	
	s = Chr(x2 - x + 1, y2 - y + 1)
	For j = y To y2
		For i = x To x2
			c = 0 : t = ""
			If grid(i, j).track Then
				c Or= 1
				t &= Chr(grid(i, j).track)
			End If
			If grid(i, j).land Then
				c Or= 2
				t &= Chr(grid(i, j).land)
			End If
			If grid(i, j).border Then
				c Or= 4
				t &= MkL(grid(i, j).border)
			End If
			If grid(i, j).bgc Then
				c Or= 8
				t &= MkL(grid(i, j).bgc)
			End If
			
			s &= Chr(c) & t
		Next i
	Next j
	
	Return s
End Function


'Generate a 32bit hash value out of the given string
Function Hash32 Overload (content As String) As ULong
	Dim i As Short, hash As ULong
	Dim u As UByte
	
	For i = 1 To Len(content)
		u = ASC(Mid(content, i, 1))
		
		If u Then
			hash XOr= (u + 11)
			hash XOr= (121 * i)
		Else
			hash *= 31
			hash += 3
		End If
	Next i
	
	Return hash
End Function


Function Hash32 Overload As ULong
	Dim trackstring As String, h As ULong
				
	For j As Byte = 30 To 1 Step - 1
		For i As Byte = 1 To 30
			trackstring &= Chr(grid(i, j).track)
		Next i
	Next j
	trackstring &= Chr(landscape)
	For j As Byte = 1 To 30
		For i As Byte = 1 To 30
			trackstring &= Chr(grid(i, j).land)
		Next i
	Next j
	
	Return Hash32(trackstring)
End Function


'Flip buffer rectangle horizontally
Function HFlipTrack(t As String) As String
	Dim As Byte w, h, c
	w = ASC(Left(t, 1))
	h = ASC(Mid(t, 2, 1))
	
	Dim temp(1 To w, 1 To h) As SGrid
	Dim s As String, i As Byte, j As Byte, n As Short
	
	'Read and flip
	n = 3 : i = w : j = 1
	Do While n <= Len(t)
		c = ASC(Mid(t, n, 1))
		n += 1
		If c And 1 Then		'Track is non-zero
			temp(i, j).track = tr(ASC(Mid(t, n, 1))).hflip
			n += 1
		End If
		If c And 2 Then		'Terrain is non-zero
			temp(i, j).land = ttr(ASC(Mid(t, n, 1))).hflip
			n += 1
		End If
		If c And 4 Then		'Border colour is non-zero
			temp(i, j).border = CvL(Mid(t, n, 4))
			n += 4
		End If
		If c And 8 Then		'Background colour is non-zero
			temp(i, j).bgc = CvL(Mid(t, n, 4))
			n += 4
		End If
		
		i -= 1
		If i = 0 Then i = w : j += 1
	Loop
	
	'Fix
	For j = 1 To h
		For i = 1 To w - 1
			If temp(i, j).track = 255 Or temp(i, j).track = 253 Then
				Swap temp(i, j).track, temp(i + 1, j).track
				i += 1
			End If
		Next i
	Next j

	'Put back into the string
	s = PackedClip(temp())
	
	Return s
End Function


Sub InitFiles(mask As String, x1 As Short, y1 As Short, x2 As Short, y2 As Short)
	Dim As String s, t
	Dim attr As Integer
	Dim i As Short
	Dim justreloading As Byte
	
	filer.mask = mask
	If x1 Then
		filer.x1 = x1
		filer.y1 = y1
		filer.x2 = x2
		filer.y2 = y2
		justreloading = 0
		DrawBox x1, y1, x2, y2
	Else
		x1 = filer.x1
		y1 = filer.y1
		x2 = filer.x2
		y2 = filer.y2
		justreloading = -1
		Line (x1 + 4, y1 + 4)-(x2 - 32, y2 - 4), RGB(30, 30, 50), BF
	End If
	filer.reread = -1
		
	fileys = 0
	#ifdef __FB_LINUX__
	If track_path <> "/" And track_path <> "\" Then
		fileys = 1
		filey(1) = "*.."
	End If
	#else
		DetectDrives
	#endif
	
	For i As UByte = 1 To dirlinks
		If track_path <> dirlink(i).directory Then
			fileys += 1
			filey(fileys) = "* - " + dirlink(i).text + " - "
		End If
	Next i
	
	s = Dir(track_path + "*.*", 49, attr)
	Do
		If Len(s) Then
			If attr And 16 Then
				If s <> "." Then
					fileys += 1
					filey(fileys) = "*" + s
				End If
			Else
				i = 1
				Do
					t = LCase(Mid(mask, i))
					i = InStr(t, ";")
					If i Then t = Left(t, i - 1)
					t = Trim(t)
					If Len(t) = 0 Then Exit Do
					If Right(LCase(s), Len(t) + 1) = "." + t Then
						fileys += 1
						filey(fileys) = s
						Exit Do
					End If
					If i = 0 Then Exit Do Else i += 1
				Loop
			End If
			s = Dir(attr)
		Else
			Exit Do
		End If
	Loop Until fileys = 512
	
	SortFiles
	
	If Not justreloading Then
		AddButton x2 - 24, y1, Chr(24), 101
		AddButton x2 - 24, y2 - 32, Chr(25), 102
	End If
End Sub


Function ManageFiles(akey As String = "") As String
	Static first As Short, exfirst As Short = -1
	Static lit As Short, exlit As Short
	Static wheel As Integer
	Dim i As Short, v As Short
	Dim As Integer xm, ym, wm, bm
	
	If filer.reread Then
		first = 1 : exfirst = -1
		lit = -1 : exlit = -1
		filer.reread = 0
		
		Do
			GetMouse xm, ym, wm, bm
		Loop Until bm = 0
		wheel = wm
	End If
	
	GetMouse xm, ym, wm, bm
	v = ManageButtons
	If v = 101 Or wm > wheel Then
		If first > 1 Then first -= 1 Else v = 0
		wheel = wm
	ElseIf v = 102 Or wm < wheel Then
		If first + 17 < fileys Then first += 1 Else v = 0
		wheel = wm
	End If
	
	If Len(akey) Then
		If ASC(akey) = 1 Then	'Search for file name in list
			For i = 1 To fileys
				If Left(LCase(filey(i)), Len(akey) - 1) = LCase(Mid(akey, 2)) Then
					first = i
					If first + 17 > fileys Then first = fileys - 17
					If first < 1 Then first = 1
					Exit For
				End If
			Next i
		Else
			Select Case akey
				Case Chr(255) + Chr(73)
					If first > 18 Then first -= 18 Else first = 1
				Case Chr(255) + Chr(81)
					If first + 35 < fileys Then first += 18 Else first = fileys - 17
			End Select
		End If
	End If
		
	If first <> exfirst Then
		ScreenLock
		Line (filer.x1 + 4, filer.y1 + 4)-(filer.x2 - 32, filer.y2 - 4), RGB(30, 30, 50), BF
		For i = 0 To 17
			If first + i > fileys Then Exit For
			If Left(filey(first + i), 1) = "*" Then
				Draw String (filer.x1 + 16, filer.y1 + 12 + i * 16), "[" + Mid(filey(first + i), 2) + "]", RGB(160, 240, 160)
			Else
				Draw String (filer.x1 + 16, filer.y1 + 12 + i * 16), filey(first + i), RGB(160, 160, 240)
			End If
		Next i
		ScreenUnlock
		exfirst = first
	End If
	
	If xm >= filer.x1 + 16 And xm <= filer.x2 - 32 And ym >= filer.y1 + 12 And ym <= filer.y2 - 12 Then
		lit = (ym - filer.y1 - 12) \ 16
	Else
		lit = -1
	End If
	
	If lit <> exlit Then
		If exlit <> -1 And first + exlit <= fileys Then
			If Left(filey(first + exlit), 1) = "*" Then
				Draw String (filer.x1 + 16, filer.y1 + 12 + exlit * 16), "[" + Mid(filey(first + exlit), 2) + "]", RGB(160, 240, 160)
			Else
				Draw String (filer.x1 + 16, filer.y1 + 12 + exlit * 16), filey(first + exlit), RGB(160, 160, 240)
			End If
		End If
		If lit <> -1 And first + lit <= fileys Then
			If Left(filey(first + lit), 1) = "*" Then
				Draw String (filer.x1 + 16, filer.y1 + 12 + lit * 16), "[" + Mid(filey(first + lit), 2) + "]", RGB(255, 255, 255)
			Else
				Draw String (filer.x1 + 16, filer.y1 + 12 + lit * 16), filey(first + lit), RGB(255, 255, 255)
			End If
		End If
		exlit = lit
	End If
		
	If bm = 1 And lit <> -1 And first + lit <= fileys Then
		If Left(filey(first + lit), 1) = "*" Then
			If Mid(filey(first + lit), 2, 1) = " " Then
				For i As UByte = 1 To dirlinks
					If Mid(filey(first + lit), 5, Len(dirlink(i).text)) = dirlink(i).text Then
						track_path = dirlink(i).directory
						Exit For
					End If
				Next i
			Else
				ChangeTrackDirectory Mid(filey(first + lit), 2)
			End If
			InitFiles filer.mask, 0, 0, 0, 0
		Else
			Return filey(first + lit)
		End If
	End If
	
	If v = 101 Or v = 102 Then Sleep 30, 1
	Return ""
End Function


Sub CreatePath
	Dim As Byte firstx, firsty, bearing, material = 1
	Dim As UByte addthing
	Dim As String buffer, akey
	Dim As Integer bm
	
	Do
		GetMouse 0, 0, 0, bm
	Loop Until bm = 0
	
	firstx = xcursor
	firsty = ycursor
	
	PushUndo
	
	Do
		GetMouse 0, 0, 0, bm
		If bm Then Exit Do
		
		akey = InKey
		Select Case akey
			Case Chr(255, 72)
			Case Chr(27) : Undo : Exit Do
			Case Chr(13)
				If Len(buffer) = 0 Then Undo
				Exit Do
		End Select
		
		If addthing Then
			addthing = 0
		End If
	Loop
	
	Do
		GetMouse 0, 0, 0, bm
	Loop Until bm = 0
End Sub


Sub ChangeTrackDirectory(d As String)
	Dim i As Short
	
	#ifndef __FB_LINUX__
	If Len(d) = 2 And Right(d, 1) = ":" Then
		track_path = UCase(d) + "\"
		Exit Sub
	End If
	#endif
	
	If d <> ".." Then
		#ifdef __FB_LINUX__
		track_path = track_path + d + "/"
		#else
		track_path = track_path + d + "\"
		#endif
		Exit Sub
	End If
	
	For i = Len(track_path) - 1 To 1 Step -1
		#ifdef __FB_LINUX__
		If Mid(track_path, i, 1) = "/" Then
		#else
		If Mid(track_path, i, 1) = "\" Then
		#endif
			track_path = Left(track_path, i)
			Exit Sub
		End If
	Next i
	
	track_path = CurDir
	#ifdef __FB_LINUX__
	If Right(track_path, 1) <> "/" Then track_path = track_path + "/"
	#else
	If Right(track_path, 1) <> "\" Then track_path = track_path + "\"
	#endif
End Sub

'Convert 32bit to 16bit colour or vice-versa in string form
Function ConvertColour(c As String) As String
	Dim As UByte r, g, b
	Dim s As UShort, l As ULong
	Dim t As String
	
	If Len(c) = 2 Then
		s = CvShort(c)
		If s = &B1111100000011111 Then
			'Transparent colour translates to no colouration
			t = MkL(0)
		Else
			r = (s ShR 8) And &B11111000
			g = (s ShR 3) And &B11111100
			b = (s ShL 3) And &B11111000
		
			t = MkL(RGB(r, g, b))
		End If
	ElseIf Len(c) = 4 Then
		l = CvL(c)
		If l Then
			r = (l ShR 19) And &B11111
			g = (l ShR 10) And &B111111
			b = (l ShR 3) And &B11111
			
			s = r
			s = (s ShL 6) Or g
			s = (s ShL 5) Or b
			t = MkShort(s)
		Else
			'No colouration translates as transparent colour
			t = MkShort(&B1111100000011111)
		End If
	End If
	
	Return t
End Function


Sub CopyOrCut(cut As Byte = 0)
	If xselect Then
		If xselect > x2select Then Swap xselect, x2select
		If yselect > y2select Then Swap yselect, y2select
		
		clipboard = GetTrack(xselect, yselect, x2select, y2select)
		
		'Update temporary file so as to export clipboard --------
		Dim cllen As Short, f As Integer
		
		f = FreeFile
		If FileExists(program_path & "clip.tmp") Then
			cllen = FileLen(program_path & "clip.tmp")
		Else
			cllen = 0
		End If
		
		'Make sure new length is different from previous one
		'This will allow Bliss instances to realise the clipboard
		'was changed without having to open the file
		Open program_path & "clip.tmp" For Output As f : Close f
		Open program_path & "clip.tmp" For Binary Access Write As f
		If cllen <> Len(clipboard) + 1 Then
			Dim b As Byte = 0
			Put #f, , b
		Else
			Dim b As Byte = 1
			Put #f, , b
			b = 0
			Put #f, , b
		End If
		Put #f, , clipboard
		
		last_cb_file_length = LOF(f)
		Close f
		'------------------------------
		
		'xclip = xselect : x2clip = x2select
		'yclip = yselect : y2clip = y2select
		
		If cut Then
			For j As Byte = yselect To y2select
				For i As Byte = xselect To x2select
					If affect_terrain Then grid(i, j).land = 0
					If affect_track Then
						If allow_errors Then
							ClearTrack i, j
						Else
							grid(i, j).track = 0
						End If
					End If
				Next i
			Next j
			modified = -1
		End If
		
		xselect = 0 : DrawTrack
	End If
End Sub


Function AntiTimey(t As String) As Long
	Dim n As Short, cents As Long
	Dim s As String
	
	s = t
	
	n = InStr(s, ":")
	If n = 0 Then
		cents = 100 * Val(s)
		Return cents
	End If
	
	cents = ValInt(s) * 6000
	s = Mid(s, n + 1)
	n = InStr(s, ":")
	If n = 0 Then
		cents += 100 * Val(s)
		Return cents
	End If
	
	cents *= 24
	cents += 6000 * ValInt(s)
	s = Mid(s, n + 1)
	cents += 100 * Val(s)
	Return cents
End Function


Sub BuildClosedCircuit
	Dim As UByte corner, straightway, tile
	Dim material As Byte, i As Short, j As Short
	
	If xselect = 0 Then
		Error_Message "First select a region to build a closed-circuit"
		DrawTrack
		Exit Sub
	End If
	
	If xselect > x2select Then Swap xselect, x2select
	If yselect > y2select Then Swap yselect, y2select
	
	If tr(current_brush).material >= 1 And tr(current_brush).material <= 3 Then
		material = tr(current_brush).material
	
		tile = grid(xselect, yselect).track
		If x2select - xselect > 1 And y2select - yselect > 1 _
			And tr(tile).ctype(0) = 0 And tr(tile).ctype(1) <> 0 _
			And tr(tile).ctype(2) <> 0 And tr(tile).ctype(3) = 0 Then
			'It's a North-West corner. Find big or small corner of the
			'same material. If the selection is too narrow, always use
			'small corners
			
			For i = 1 To 190
				If tr(i).h = 3 - tr(tile).h And tr(i).ctype(0) = 0 And tr(i).ctype(1) <> 0 _
					And tr(i).ctype(2) <> 0 And tr(i).ctype(3) = 0 _
					And tr(i).material = material Then Exit For
			Next i
			corner = i		
		Else
			'If still not drawn, use small corners
			
			For i = 1 To 190
				If tr(i).h = 1 And tr(i).ctype(0) = 0 And tr(i).ctype(1) <> 0 _
					And tr(i).ctype(2) <> 0 And tr(i).ctype(3) = 0 _
					And tr(i).material = material Then Exit For
			Next i
			corner = i
		End If
		
		'Find a straightway
		For i = 1 To 190
			If Chr(tr(i).entity) = "s" And tr(i).material = material And tr(i).ctype(0) <> 0 Then _
				Exit For
		Next i
		straightway = i
	ElseIf tr(current_brush).entity = ASC("e") Then
		'It's going to be an elevated circuit
		straightway = current_brush
		If tr(straightway).ctype(0) = 0 Then straightway = tr(straightway).cr
		corner = &H69
	ElseIf tr(current_brush).entity = ASC("a") Then
		'It's going to be a banked circuit
		straightway = &H30
		corner = &H34
	ElseIf tr(current_brush).entity = ASC("Q") Then
		If tr(current_brush).ctype(0) = 2 OrElse tr(current_brush).ctype(1) = 2 _
			OrElse tr(current_brush).ctype(2) = 2 Then
				straightway = &H67	'It's elevated
				corner = &H69
		Else
			straightway = &H30	'It's banked
			corner = &H34
		End If
	ElseIf tr(current_brush).entity = ASC("b") Then
		straightway = &H6D		'Boulevard (no corners)
		corner = 0
	ElseIf current_brush >= &H97 And current_brush <= &HB2 Then
		ScreenLock	'Scenery - fill with it
		For j = yselect To y2select
			For i = xselect To x2select
				SetTrack i, j, current_brush
			Next i
		Next j
		ScreenUnlock
		Exit Sub
	Else
		Exit Sub	'Can't do anything with this item
	End If
	
	'Push to undo buffer
	PushUndo
	
	If xselect = x2select Then
		'Draw vertical straightway
		For i = yselect To y2select
			SetTrack xselect, i, straightway
		Next i
	ElseIf yselect = y2select Then
		'Draw horizontal straightway
		straightway = tr(straightway).cr
		For i = xselect To x2select
			SetTrack i, yselect, straightway
		Next i
	Else
		'Draw corners
		SetTrack xselect, yselect, corner : corner = tr(corner).cr
		SetTrack x2select - tr(corner).w + 1, yselect, corner : corner = tr(corner).cr
		SetTrack x2select - tr(corner).w + 1, y2select - tr(corner).h + 1, corner : corner = tr(corner).cr
		SetTrack xselect, y2select - tr(corner).h + 1, corner
		
		'Draw straightways
		For i = yselect + tr(corner).h To y2select - tr(corner).h
			SetTrack xselect, i, straightway
		Next i
		straightway = tr(straightway).cr
		For i = xselect + tr(corner).w To x2select - tr(corner).w
			SetTrack i, yselect, straightway
		Next i
		straightway = tr(straightway).cr
		For i = yselect + tr(corner).h To y2select - tr(corner).h
			SetTrack x2select, i, straightway
		Next i
		straightway = tr(straightway).cr
		For i = xselect + tr(corner).w To x2select - tr(corner).w
			SetTrack i, y2select, straightway
		Next i
	End If
	
	DrawTrack
End Sub


Function CCRotate(t As String) As String
	Dim As Byte w, h, c
	h = ASC(Left(t, 1))
	w = ASC(Mid(t, 2, 1))
	
	Dim temp(1 To w, 1 To h) As SGrid
	Dim s As String, i As Byte, j As Byte, n As Short
	
	'Read and rotate
	n = 3 : i = 1 : j = h
	Do While n <= Len(t)
		c = ASC(Mid(t, n, 1))
		n += 1
		If c And 1 Then		'Track is non-zero
			temp(i, j).track = tr(ASC(Mid(t, n, 1))).ccr
			n += 1
		End If
		If c And 2 Then		'Terrain is non-zero
			temp(i, j).land = ttr(ASC(Mid(t, n, 1))).ccr
			n += 1
		End If
		If c And 4 Then		'Border colour is non-zero
			temp(i, j).border = CvL(Mid(t, n, 4))
			n += 4
		End If
		If c And 8 Then		'Background colour is non-zero
			temp(i, j).bgc = CvL(Mid(t, n, 4))
			n += 4
		End If
		
		j -= 1
		If j = 0 Then i += 1 : j = h
	Loop
	
	'Fix
	For j = 1 To h
		For i = 1 To w
			If tr(temp(i, j).track).w = 2 And tr(temp(i, j).track).h = 2 Then
				If j > 1 Then
					temp(i, j - 1).track = temp(i, j).track
					If i < w then temp(i + 1, j - 1).track = 255
				End If
				If i < w Then temp(i + 1, j).track = 253
				temp(i, j).track = 254
			ElseIf tr(temp(i, j).track).w = 2 Then
				If i < w Then temp(i + 1, j).track = 255
			ElseIf tr(temp(i, j).track).h = 2 Then
				If j > 1 Then temp(i, j - 1).track = temp(i, j).track
				temp(i, j).track = 254
			End If
		Next i
	Next j

	'Put back into the string
	s = PackedClip(temp())
	
	Return s
End Function


Function CRotate(t As String) As String
	Dim As Byte w, h, c
	h = ASC(Left(t, 1))
	w = ASC(Mid(t, 2, 1))
	
	Dim temp(1 To w, 1 To h) As SGrid
	Dim s As String, i As Byte, j As Byte, n As Short
	
	'Read and rotate
	n = 3 : i = w : j = 1
	Do While n <= Len(t)
		c = ASC(Mid(t, n, 1))
		n += 1
		If c And 1 Then		'Track is non-zero
			temp(i, j).track = tr(ASC(Mid(t, n, 1))).cr
			n += 1
		End If
		If c And 2 Then		'Terrain is non-zero
			temp(i, j).land = ttr(ASC(Mid(t, n, 1))).cr
			n += 1
		End If
		If c And 4 Then		'Border colour is non-zero
			temp(i, j).border = CvL(Mid(t, n, 4))
			n += 4
		End If
		If c And 8 Then		'Background colour is non-zero
			temp(i, j).bgc = CvL(Mid(t, n, 4))
			n += 4
		End If
		
		j += 1
		If j > h Then i -= 1 : j = 1
	Loop
	
	'Fix
	For j = 1 To h
		For i = 1 To w
			If tr(temp(i, j).track).w = 2 And tr(temp(i, j).track).h = 2 Then
				If i > 1 Then
					temp(i - 1, j).track = temp(i, j).track
					If j < h then Temp(i - 1, j + 1).track = 254
				End If
				If j < h Then temp(i, j + 1).track = 253
				temp(i, j).track = 255
			ElseIf tr(temp(i, j).track).w = 2 Then
				If i > 1 Then temp(i - 1, j).track = temp(i, j).track
				temp(i, j).track = 255
			ElseIf tr(temp(i, j).track).h = 2 Then
				If j < h Then temp(i, j + 1).track = 254
			End If
		Next i
	Next j

	'Put back into the string
	s = PackedClip(temp())
	
	Return s
End Function


Sub DetectDrives
	Dim i As Byte
	
	For i = 3 To 26
		Open Chr(64 + i) + ":\NUL" For Input As 101
		If Err = 0 Then
			fileys += 1
			filey(fileys) = "*" + Chr(64 + i) + ":"
			Close 101
		End If
	Next i
End Sub


Sub SmartSelect(etype As String, direction As Byte = 1)
	Dim As Byte checkconnections = -1, checkmaterial = -1
	Dim As Byte connector(0 To 3), material
	Dim i As Short
	
	'Check to see if there are open connectors
	If vlast.x = 0 Or vlast.y = 0 OrElse grid(vlast.x, vlast.y).track = 0 Then
		'Nothing has yet been placed or there's an empty space there
		checkconnections = 0
		material = tr(current_brush).material
		If material = 0 Then checkmaterial = 0
	Else
		Dim n As UByte = GetParent(vlast.x, vlast.y)
		
		material = tr(n).material
		If material = 0 Then checkmaterial = 0
		
		'Check North
		If vlast.y > 1 AndAlso grid(vlast.x + tr(n).cisalt(0), vlast.y - 1).track = 0 Then _
			connector(2) = tr(n).ctype(0)
		'Check East
		If vlast.x <= 30 - tr(n).w AndAlso grid(vlast.x + tr(n).w, vlast.y + tr(n).cisalt(1)).track = 0 Then _
			connector(3) = tr(n).ctype(1)
		'Check South
		If vlast.y <= 30 - tr(n).h AndAlso grid(vlast.x + tr(n).cisalt(2), vlast.y + tr(n).h).track = 0 Then _
			connector(0) = tr(n).ctype(2)
		'Check West
		If vlast.x > 1 AndAlso grid(vlast.x - 1, vlast.y + tr(n).cisalt(3)).track = 0 Then _
			connector(1) = tr(n).ctype(3)
			
		'If no connector, then don't check
		If connector(0) + connector(1) + connector(2) + connector(3) = 0 Then checkconnections = 0
	End If
	
	
	Dim As Short searchstart, searchend
	Dim q As Byte
	
	'Prepare to search left or right
	If direction > 0 Then
		searchstart = current_brush + 1
		searchend = current_brush + 256
	Else
		searchstart = current_brush + 255
		searchend = current_brush
	End If
	
	'Try to find a coincidence. First for everything, then go
	'eliminating restrictions
	Do		
		For i = searchstart To searchend Step direction
			Dim n As UByte
			
			n = i Mod 256
			
			'See if type matches
			If UCase(Chr(tr(n).entity)) = UCase(etype) Then
				q = 0
				If checkconnections Then
					For j As Byte = 0 To 3
						If connector(j) <> 0 And tr(n).ctype(j) = connector(j) Then
							q = -1		'At least one connector matches
							Exit For
						End If
					Next j
				Else
					q = -1	'Not checking connections, so assume OK
				End If
			
				If checkmaterial Then
					If tr(n).material <> material Then q = 0
				End If
				
				If q Then Exit For	'Everything matches
			End If
		Next i
	
		If q Then Exit Do
		
		'See what we can give up to obtain a result
		If checkmaterial Then
			checkmaterial = 0	'Give up checking material
		ElseIf checkconnections Then
			checkconnections = 0	'Give up checking connections
		Else
			Exit Do		'Couldn't find anything
		End If
	Loop
	
	If q Then
		current_brush = i
		DrawPanel
	End If
End Sub


Sub SolvePath(pn As Short)
	Dim As UByte thissection, c1, c2
	
	thissection = ASC(Right(path(pn).p, 1))
	
	'Propagate errors
	If section(thissection).errors Then
		If section(thissection).e >= 40 Then
			'Path-fatal errors. Path ends here
			path(pn).e = section(thissection).e
			Exit Sub
		Else
			'Prioritise early errors if not path-fatal (warnings)
			If path(pn).e = 0 Then path(pn).e = section(thissection).e
		End If
	End If
	
	'See if it ends at the finish line
	If section(thissection).final = section(1).initial Then
		path(pn).finishes = -1
		Exit Sub
	End If
	
	c1 = section(thissection).child(0)
	c2 = section(thissection).child(1)
			
	If c2 = 0 Then
		If c1 = 0 Then	'No child (weird)
			Exit Sub
		Else	'Joint (one child)
			If InStr(path(pn).p, Chr(c1)) Then	'It's a cycle!
				path(pn).e = 82
				Exit Sub
			End If
			path(pn).p &= Chr(c1)
			SolvePath pn
		End If
	Else	'Split (two children)
		If paths = MAXPATHS Then Exit Sub
		paths += 1
		path(paths).p = path(pn).p
		path(paths).e = path(pn).e
		path(paths).finishes = path(pn).finishes
		path(pn).p &= Chr(c1)
		path(paths).p &= Chr(c2)
		SolvePath paths
		SolvePath pn
	End If
End Sub


'Analyse a section of the track, finding errors and handling
'split points recursively. Save information in the sections array
Sub SolveSection(sn As Short)
	Dim As TrackVector v, oldv
	Dim As Short i, n
	
	v.coors = section(sn).initial
	v.bearing = section(sn).bearing
	v.origin = section(sn).origin
	section(sn).e = 0
	section(sn).errors = 0
	section(sn).solving = -1
	section(sn).finishes = 0
	section(sn).cycle = 0
	section(sn).final = 0
	section(sn).wrongway = 0
	Do
		oldv = v
		v = GetNext(v)
		
		If v.e Then
			terrors += 1
			terror(terrors).coors = oldv.coors
			terror(terrors).e = v.e
			terror(terrors).section = sn
		End If
		
		Select Case v.e
			Case 70 To 79	'Path flow fatal errors
				If section(sn).e < 40 Then section(sn).e = v.e
				section(sn).errors = -1
				section(sn).solving = 0
				section(sn).final = oldv.coors
				section(sn).child(0) = 0
				section(sn).child(1) = 0
				Exit Do
			Case 80 To 89	'Path flow non-fatal errors
				If section(sn).e = 0 Then section(sn).e = v.e
				section(sn).errors = -1
				section(sn).solving = 0
				section(sn).final = oldv.coors
				section(sn).child(0) = 0
				section(sn).child(1) = 0
				Exit Do
			Case 20 To 39	'Warnings
				If section(sn).e = 0 Then section(sn).e = v.e
				section(sn).errors = -1
		End Select
		
		'Found finish line
		If v.coors = section(1).initial Then
			section(sn).final = v.coors
			section(sn).child(0) = 0
			section(sn).child(1) = 0
			section(sn).finishes = -1
			Exit Do
		End If
		
		'Reached a split?
		n = 0
		For i = 0 To 3	'Count number of connectors
			If tr(grid(v.x, v.y).track).ctype(i) Then n += 1
		Next i
		
		'Yes. It's a split
		If n = 3 Then
			section(sn).final = v.coors
			
			'See if this is a previously solved node
			For i = 1 To sections
				If v.coors = section(i).initial Then
					If 2 ^ section(i).bearing <> tr(grid(v.x, v.y).track).cto(v.bearing XOr 2) Then
						'Make sure it ends here
						section(sn).child(0) = 0
						section(sn).child(1) = 0
						
						'Wrong way!
						section(sn).wrongway = -1
						section(sn).errors = -1
						If section(sn).e < 40 Then section(sn).e = 72
						terrors += 1
						terror(terrors).coors = v.coors
						terror(terrors).e = 72
						terror(terrors).section = sn
					ElseIf section(i).solving Then
						'Make sure it ends here
						section(sn).child(0) = 0
						section(sn).child(1) = 0
						
						'This is a cycle!
						section(sn).cycle = -1
						If section(sn).e < 40 Then section(sn).e = 82
						terrors += 1
						terror(terrors).coors = v.coors
						terror(terrors).e = 82
						terror(terrors).section = sn
					Else
						'Connect sections
						section(sn).child(0) = i
						section(sn).child(1) = 0
						section(i).parent(1) = sn
						
						'Inherit child's errors and finish status
						section(sn).finishes = section(i).finishes
						section(sn).cycle = section(i).cycle
						section(sn).wrongway = section(i).wrongway
						If section(sn).e < 40 Then section(sn).e = section(i).e
					End If
					section(sn).solving = 0
					Exit Do
				End If
			Next i
			
			'It's a new node
			n = tr(grid(v.x, v.y).track).cto(v.bearing XOr 2)
			Select Case n
				Case 1, 2, 4, 8		'Only one direction
					Dim daughter As Short
					
					'Create new section and solve it
					sections += 1
					section(sections).initial = v.coors
					section(sections).bearing = Log(n) / Log(2)
					section(sections).origin = v.origin
					daughter = sections
					SolveSection daughter
					
					'Inherit new section's status
					section(sn).finishes = section(daughter).finishes
					section(sn).cycle = section(daughter).cycle
					section(sn).wrongway = section(daughter).wrongway
					If section(sn).e = 0 Then section(sn).e = section(daughter).e
				
					'Create parental links
					section(sn).child(0) = daughter
					section(sn).child(1) = 0
					section(sections).parent(0) = sn
					section(sections).parent(1) = 0
				Case Else			'Two directions
					Dim As Short daughter(1 To 2), daughters = 0
					
					'Always solve the straight node first					
					For i = v.bearing To v.bearing + 3
						Dim direction As Byte
						
						direction = i Mod 4
						If direction <> (v.bearing XOr 2) And _
							tr(grid(v.x, v.y).track).ctype(direction) <> 0 Then
						
							'Create each node and solve it
							sections += 1
							If sections > 254 Then Exit Sub
							section(sections).initial = v.coors
							section(sections).bearing = direction
							section(sections).origin = v.origin
							daughters += 1
							daughter(daughters) = sections
							SolveSection sections
						End If
					Next i
					
					'Carefully inherit the status
					If section(daughter(1)).finishes Or section(daughter(2)).finishes Then _
						section(sn).finishes = -1
					If section(daughter(1)).cycle And section(daughter(2)).cycle Then _
						section(sn).cycle = -1
					If section(sn).e = 0 Then
						section(sn).e = section(daughter(1)).e
						If section(sn).e = 0 Or section(daughter(2)).e = 4 Then section(sn).e = section(daughter(2)).e
					End If
					If section(daughter(1)).wrongway And section(daughter(2)).wrongway Then _
						section(sn).wrongway = -1
					
					'Create parental links
					section(sn).child(0) = daughter(1)
					section(sn).child(1) = daughter(2)
					section(daughter(1)).parent(0) = sn
					section(daughter(1)).parent(1) = 0
					section(daughter(2)).parent(0) = sn
					section(daughter(2)).parent(1) = 0
			End Select
			Exit Do
		End If
	Loop
	
	section(sn).solving = 0
End Sub


Sub SortFiles
	Dim changes As Byte
	Dim i As Short
	
	'For now, we use bubble sort
	
	'First sort by name
	Do
		changes = 0
		For i = 1 To fileys - 1
			If LCase(filey(i)) > LCase(filey(i + 1)) Then
				Swap filey(i), filey(i + 1)
				changes = -1
			End If
		Next i
	Loop Until changes = 0	
	
	'Then place directories first
	Do
		changes = 0
		For i = 1 To fileys - 1
			If Left(filey(i), 1) <> "*" And Left(filey(i + 1), 1) = "*" Then
				Swap filey(i), filey(i + 1)
				changes = -1
			End If
		Next i
	Loop Until changes = 0
	
	#ifndef __FB_LINUX__
	'Finally, place drives after directories
	Do
		changes = 0
		For i = 1 To fileys - 1
			If Left(filey(i), 1) = "*" And Left(filey(i + 1), 1) = "*" Then
				If Right(filey(i), 1) = ":" And Right(filey(i + 1), 1) <> ":" Then
					Swap filey(i), filey(i + 1)
					changes = -1
				End If
			Else
				Exit For
			End If
		Next i
	Loop Until changes = 0
	#endif
End Sub


Sub LoadTransformations
	Dim As Short x, y, p
	
	Open program_path + "xlation.dat" For Binary Access Read As 100
	For x = 0 To 255
		Get #100, , tr(x)
	Next x
	For x = 0 To 18
		Get #100, , ttr(x)
	Next x
	For p = 0 To 11
		For y = 0 To 5
			For x = 0 To 5
				Get #100, , itr(x, y, p)
			Next x
		Next y
	Next p
	Close 100
End Sub


Sub SaveTransformations
	Dim As Short x, y, p
	
	Open program_path + "xlation.dat" For Binary Access Write As 100
	For x = 0 To 255
		Put #100, , tr(x)
	Next x
	For x = 0 To 18
		Put #100, , ttr(x)
	Next x
	For p = 0 To 11
		For y = 0 To 5
			For x = 0 To 5
				Put #100, , itr(x, y, p)
			Next x
		Next y
	Next p
	Close 100
End Sub


#ifndef __FB_DOS__
Function TMT_GetCurrentTrack(taddress As String) As Byte
	Dim mysite As ZString Ptr, length As Long
	Dim baseaddr As String, descfile As ZString Ptr
	Dim As Short n, m
	Dim trackfn As String, tbinary As UByte Ptr
	Dim As Byte i, j
	Dim As String title, author
	
	MenuBox 25, 10, "Getting current track"
	
	lefx += 16
	TLeft
	
	If LCase(Trim(taddress)) = "zak.stunts.hu" Then
		TLeft , "Trying to connect to ZakStunts...", RGB(160, 160, 240)
'		
'		length = HTTP_Download("zak.stunts.hu/", mysite, -1)
'
'		If length < 0 Then
'			DrawTrack
'			Error_Message "Could not connect to ZakStunts"
'			Return -1
'		End If
'		
'		n = InStr(*mysite, "zct")
'		trackfn = "ZCT" + Mid(*mysite, n + 3, 3) + ".trk"
'		n = InStr(n + 1, *mysite, "-")
'		m = InStr(n, *mysite, "<")
'		If n <> 0 And m <> 0 And m > n Then
'			title = Mid(*mysite, n + 2, m - n - 2)
'			title = Trim(title)
'			n = InStr(m, *mysite, ">")
'			If n Then
'				m = InStr(n, *mysite, ";")
'				If m Then
'					n = InStr(m, *mysite, "<")
'					If n Then author = Trim(Mid(*mysite, m + 1, n - m - 1))
'				End If
'			End If
'		End If

		length = HTTP_Download("zak.stunts.hu/track.json", mysite)
		If length < 0 Then Return -1
		
		n = InStr(*mysite, "ZCT")
		trackfn = "ZCT" + Mid(*mysite, n + 3, 3) + ".trk"
		m = InStr(n + 1, *mysite, "-")
		If m Then
			n = InStr(m + 1, *mysite, Chr(34))
			title = Mid(*mysite, m + 2, n - m - 2)
		End If
		n = InStr(*mysite, "author" + Chr(34))
		If n Then
			n += 9
			m = InStr(n, *mysite, Chr(34))
			author = Mid(*mysite, n, m - n)
		End If
				
		Deallocate mysite : mysite = 0
	Else
		TLeft , "Trying to connect to the server...", RGB(160, 160, 240)
			
		If LCase(Left(taddress, 4)) = "btp:" Then
			baseaddr = Mid(taddress, 5)
		Else
			baseaddr = taddress
		End If
		
		length = HTTP_Download(baseaddr + "/tour.cfg", descfile)
		If length < 0 Then Return -1
		If LCase(Left(*descfile, 4)) <> "tour" Then Return -1
		
		n = InStr(*descfile, "tracktitle=")
		If n Then
			n += 11
			m = InStr(n, *descfile, Chr(10))
			If m Then
				title = Mid(*descfile, n, m - n)
				If Right(title, 1) = Chr(13) Then title = Left(title, Len(title) - 1)
			End If
		End If
		
		n = InStr(*descfile, "trackauthor=")
		If n Then
			n += 12
			m = InStr(n, *descfile, Chr(10))
			If m Then
				author = Mid(*descfile, n, m - n)
				If Right(author, 1) = Chr(13) Then author = Left(author, Len(author) - 1)
			End If
		End If
		
		n = InStr(*descfile, "trackfile=")
		If n Then
			n += 10
			m = InStr(n, *descfile, Chr(10))
			If m Then
				trackfn = Mid(*descfile, n, m - n)
				If Right(trackfn, 1) = Chr(13) Then trackfn = Left(trackfn, Len(trackfn) - 1)
			End If
		End If
		If InStr(trackfn, ".") = 0 Then trackfn &= ".trk"
		
		Deallocate descfile : descfile = 0
	End If
	
	TLeft , "Downloading '" + trackfn + "'...", RGB(160, 160, 240)
	
	If LCase(Trim(taddress)) = "zak.stunts.hu" Then
		length = HTTP_Download("zak.stunts.hu/tracks/" + trackfn, tbinary)
	Else
		length = HTTP_Download(baseaddr + "/" + trackfn, tbinary)
	End If

	If length < 0 Then
		DrawTrack
		Error_Message "Could not download the track " + trackfn
		Return -1
	End If
	
	n = 0
	For j = 30 To 1 Step -1
		For i = 1 To 30
			grid(i, j).track = tbinary[n]
			n += 1
		Next i
	Next j
	landscape = tbinary[n]
	n += 1
	For j = 1 To 30
		For i = 1 To 30
			grid(i, j).land = tbinary[n]
			n += 1
		Next i
	Next j
	format_byte = tbinary[n]
	PushUndo
	
	If length > 1802 Then
		Dim s As String
		
		For i = 1 To length - 1802
			s &= Chr(tbinary[i + 1801])
		Next i
		
		LoadMetaData , s
	Else
		meta.title = title
		meta.author = author
		meta.cyear = 0
		meta.cmonth = 0
		meta.cday = 0
		Select Case format_byte
			Case 0
				meta.tool = "Track Blaster"
				meta.toolversion = 50300
			Case 150
				meta.tool = "Bliss"
				meta.toolversion = 20100
			Case 151, 152
				meta.tool = "Bliss"
				meta.toolversion = 20400
			Case Else
				meta.tool = "Unknown"
				meta.toolversion = 0
		End Select
		meta.comment = ""
		If LCase(Trim(taddress)) = "zak.stunts.hu" Then
			meta.championship = "ZakStunts " + UCase(Left(trackfn, 6))
		Else
			meta.championship = ""
		End If
		meta.editing_time = -1
	End If

	
	Deallocate tbinary
	
	modified = -1
	track_file = LCase(trackfn)
	UpdateTitleBar
	DrawTrack

	Return 0
End Function


Function TMT_GetMain(taddress As String, ByRef curtrack As String, ByRef deadline As String) As Byte
	Dim zaksite As ZString Ptr, length As Long
	Dim As String title, author
	Dim As Short n, m
	Dim trackfn As String
	
	If LCase(Trim(taddress)) = "zak.stunts.hu" Then	'Use Zak's Protocol
'		length = HTTP_Download("zak.stunts.hu/", zaksite, -1)
'
'		If length < 0 Then Return -1
'		
'		n = InStr(*zaksite, "zct")
'		trackfn = "ZCT" + Mid(*zaksite, n + 3, 3)
'		n = InStr(n + 1, *zaksite, "-")
'		m = InStr(n, *zaksite, "<")
'		If n <> 0 And m <> 0 And m > n Then
'			title = Mid(*zaksite, n + 2, m - n - 2)
'			title = Trim(title)
'			n = InStr(m, *zaksite, ">")
'			If n Then
'				m = InStr(n, *zaksite, ";")
'				If m Then
'					n = InStr(m, *zaksite, "<")
'					If n Then author = Trim(Mid(*zaksite, m + 1, n - m - 1))
'				End If
'			End If
'		End If
'		
'		n = InStr(*zaksite, "deadline" + Chr(34) + ">")
'		If n = 0 Then
'			deadline = "Unknown"
'		Else
'			n += 10
'			m = InStr(n, *zaksite, "<")
'			deadline = Trim(Mid(*zaksite, n, m - n))
'		End If

		length = HTTP_Download("zak.stunts.hu/track.json", zaksite)
		If length < 0 Then Return -1
		
		n = InStr(*zaksite, "ZCT")
		trackfn = "ZCT" + Mid(*zaksite, n + 3, 3) + ".trk"
		m = InStr(n + 1, *zaksite, "-")
		If m Then
			n = InStr(m + 1, *zaksite, Chr(34))
			title = Mid(*zaksite, m + 2, n - m - 2)
		End If
		n = InStr(*zaksite, "author" + Chr(34))
		If n Then
			n += 9
			m = InStr(n, *zaksite, Chr(34))
			author = Mid(*zaksite, n, m - n)
		End If
		n = InStr(*zaksite, "deadline" + Chr(34))
		If n Then
			n += 11
			m = InStr(n, *zaksite, Chr(34))
			deadline = Mid(*zaksite, n, m - n)
		End If
		
		Deallocate zaksite : zaksite = 0
		If Len(title) Then
			curtrack = title
			If Len(author) Then curtrack &= " (by " + author + ")"
		Else
			curtrack = trackfn
		End If
		
		Return 0
	Else	'Use Bliss Tournament Protocol
		Dim baseaddr As String, descfile As ZString Ptr
		
		If LCase(Left(taddress, 4)) = "btp:" Then
			baseaddr = Mid(taddress, 5)
		Else
			baseaddr = taddress
		End If
		
		length = HTTP_Download(baseaddr + "/tour.cfg", descfile)
		If length < 0 Then Return -1
		If LCase(Left(*descfile, 4)) <> "tour" Then Return -1
		
		n = InStr(*descfile, "tracktitle=")
		If n Then
			n += 11
			m = InStr(n, *descfile, Chr(10))
			If m Then
				title = Mid(*descfile, n, m - n)
				If Right(title, 1) = Chr(13) Then title = Left(title, Len(title) - 1)
			End If
		End If
		
		n = InStr(*descfile, "trackauthor=")
		If n Then
			n += 12
			m = InStr(n, *descfile, Chr(10))
			If m Then
				author = Mid(*descfile, n, m - n)
				If Right(author, 1) = Chr(13) Then author = Left(author, Len(author) - 1)
			End If
		End If
		
		n = InStr(*descfile, "trackfile=")
		If n Then
			n += 10
			m = InStr(n, *descfile, Chr(10))
			If m Then
				trackfn = Mid(*descfile, n, m - n)
				If Right(trackfn, 1) = Chr(13) Then trackfn = Left(trackfn, Len(trackfn) - 1)
			End If
		End If
		If InStr(trackfn, ".") = 0 Then trackfn &= ".trk"
		
		n = InStr(*descfile, "deadline=")
		If n Then
			n += 9
			m = InStr(n, *descfile, Chr(10))
			If m Then
				deadline = Mid(*descfile, n, m - n)
				If Right(author, 1) = Chr(13) Then deadline = Left(deadline, Len(deadline) - 1)
			End If
		End If
		
		Deallocate descfile : descfile = 0
		If Len(title) Then
			curtrack = title
			If Len(author) Then curtrack &= " (by " + author + ")"
		Else
			curtrack = trackfn
		End If
		
		Return 0
	End If
End Function


Function TMT_GetScoreboard(taddress As String, item() As Scoreboard, ByRef items As Byte) As Byte
	If LCase(Trim(taddress)) = "zak.stunts.hu" Then
		Dim zaksite As ZString Ptr, length As Long
		Dim trackfn As String
		Dim As Long n, m
		
		length = HTTP_Download("zak.stunts.hu/", zaksite, -1)
		If length < 0 Then Return -1
		
		n = InStr(*zaksite, "ZCT")
		trackfn = "ZCT" + Mid(*zaksite, n + 3, 3)
		
		length = HTTP_Download("zak.stunts.hu/tracks/" + trackfn, zaksite, -1)
		If length < 0 Then Return -1
		
		If InStr(*zaksite, "No results") Then
			items = 0
			Return 0
		End If
		
		m = 1
		For i As Byte = 1 To 100
			n = InStr(m, *zaksite, "rank" + Chr(34) + ">" + Trim(Str(i)) + "<")
			If n = 0 Then items = i - 1 : Exit For
			
			m = InStr(n, *zaksite, "racer" + Chr(34)) + 8
			n = InStr(m, *zaksite, ">") + 1
			m = InStr(n, *zaksite, "<")
			item(i).racer = Trim(Mid(*zaksite, n, m - n))
						
			n = InStr(m, *zaksite, "time" + Chr(34)) + 6
			m = InStr(n, *zaksite, "<")
			item(i).hctime = Trim(Mid(*zaksite, n, m - n))
			
			n = InStr(m, *zaksite, "car-image" + Chr(34)) + 16
			m = InStr(n, *zaksite, Chr(34))
			item(i).car = Trim(Mid(*zaksite, n, m - n))
			
			n = InStr(m, *zaksite, "(") + 1
			m = InStr(n, *zaksite, "%")
			item(i).handicap = ValInt(Mid(*zaksite, n, m - n))
			
			n = InStr(m, *zaksite, "original-time" + Chr(34)) + 15
			m = InStr(n, *zaksite, "<")
			item(i).realtime = Trim(Mid(*zaksite, n, m - n))
			n = m + 1
			
			If i = UBound(item) Then items = i : Exit For
		Next i
	Else
		Dim baseaddr As String, scfile As UByte Ptr, tourfile As ZString Ptr
		Dim length As Long, i As Short, n As Short, s As String
		Dim sbfname As String
		
		If LCase(Left(taddress, 5)) = "btp:" Then
			baseaddr = Mid(taddress, 6)
		Else
			baseaddr = taddress
		End If
		
		length = HTTP_Download(baseaddr + "/tour.cfg", tourfile)
		If length < 0 Then Return -1
		If LCase(Left(*tourfile, 4)) <> "tour" Then Return -1
		
		Dim m As Long
		n = InStr(*tourfile, "scoreboard=")
		If n Then
			n += 11
			m = InStr(n, *tourfile, Chr(10))
			If m Then
				sbfname = Mid(*tourfile, n, m - n)
				If Right(sbfname, 1) = Chr(13) Then sbfname = Left(sbfname, Len(sbfname) - 1)
			End If
		End If
		
		Deallocate tourfile : tourfile = 0
		length = HTTP_Download(baseaddr + "/" + sbfname, scfile)
		
		If length = 0 Then
			items = 0
			Return 0
		End if
		
		'Skip all control characters other than character 10
		s = ""
		For i = 0 To length - 1
			If scfile[i] >= 32 Or scfile[i] = 10 Then s &= Chr(scfile[i])
		Next i
				
		Deallocate scfile : scfile = 0

		Dim sbx As Scoreboard, l As String
		Dim pass As Byte
		
		items = 0
		Do While Len(s)
			n = InStr(s, Chr(10))
			If n Then
				l = Left(s, n - 1)
				s = Mid(s, n + 1)
			Else
				l = s
				s = ""
			End If
			
			l = Trim(l)
			If Left(l, 1) = "[" And Right(l, 1) = "]" Then
				If pass <> 0 Then
					items += 1
					item(items) = sbx
				Else
					sbx.racer = "Unknown"
					sbx.car = "????"
					sbx.realtime = ""
					sbx.hctime = ""
					sbx.style = ""
					sbx.handicap = 0
					sbx.verified = 0
					pass = -1
				End If
			ElseIf InStr(l, "=") Then
				Dim As String varid, v
				
				varid = LCase(RTrim(Left(l, InStr(l, "=") - 1)))
				v = LTrim(Mid(l, InStr(l, "=") + 1))
				
				Select Case varid
					Case "carid", "car" : sbx.car = v
					Case "name" : sbx.racer = v
					Case "lap" : sbx.realtime = Timey((ValInt(v) - 1828 - 20) * 5)
					Case "laptime" : sbx.realtime = v
					Case "style" : sbx.style = v
					Case "handicap" : sbx.handicap = ValInt(v)
					Case "competing"
						If LCase(v) <> "yes" Then pass = 0
					Case "status"
						If LCase(v) = "verified" Then
							sbx.verified = -1
						ElseIf LCase(v) = "rejected" Then
							pass = 0
						End If
				End Select
			End If
		Loop
	End If
	
	'Make sure the values are valid
	For i As Byte = 1 To items
		item(i).racer = Left(item(i).racer, 20)
		item(i).hctime = Left(item(i).hctime, 8)
		item(i).car = Left(item(i).car, 10)
		item(i).realtime = Left(item(i).realtime, 8)
		If item(i).handicap < -100 Then
			item(i).handicap = -100
		ElseIf item(i).handicap > 100 Then
			item(i).handicap = 100
		End If
	Next i
	
	Return 0
End Function
#endif


Sub TrackErrorMessage(e As UByte)
	Select Case e
		Case 20 : Error_Message "Dangerous path on mountain border", "Warning!"
		Case 21 : Error_Message "Jump is not feasible in OWOOT", "Warning!"
		Case 22 : Error_Message "Inverted jump. Not feasible in OWOOT", "Warning!"
		Case 23 : Error_Message "Water will display as grass", "Warning!"
		Case 24 : Error_Message "Water will be solid here", "Warning!"
		Case 40 : Error_Message "Non-standard terrain element", "Fatal Error!"
		Case 41 : Error_Message "Mountain borders mismatch"
		Case 50 : Error_Message "Objects on mountain corners will not display correctly", "Warning"
		Case 51 : Error_Message "Only straightways and ramps will display on mountain borders", "Warning"
		Case 60 : Error_Message "Start/Finish line not found"
		Case 61 : Error_Message "Too many start/finish lines!"
		Case 62 : Error_Message "Terrain at track start is inadequeate"
		Case 70 : Error_Message "Track type mismatch"
		Case 71 : Error_Message "Straightway before jump is too short"
		Case 72 : Error_Message "Wrong way"
		Case 73 : Error_Message "Jump distance is too long"
		Case 74 : Error_Message "Stunts won't allow interrupting this bridge"
		Case 80 : Error_Message "Grid border reached"
		Case 81 : Error_Message "Path interrupted"
		Case 82 : Error_Message "Cyclic path"		
		Case Else : Error_Message "Error #" + Str(e)
	End Select
	
	DrawTrack
End Sub


Sub Undo
	'If the pointer would be at the tail, then there's no undo.
	If ((undotail + 1) Mod UNDOLEVEL) = undopointer Then Exit Sub
	
	'Decrement the pointer for the next undo
	If undopointer Then undopointer -= 1 Else undopointer = UNDOLEVEL - 1
	
	'Restore grid
	If Len(undobuffer(undopointer)) = 0 Then _
		undobuffer(undopointer) = Chr(30, 30) + String(1800, 0)
	PutTrack 1, 1, undobuffer(undopointer), -1
	DrawTrack
End Sub


Sub UnRLETerrain(ter As String)
	Dim As Short x = 1, y = 1, n = 0
	Dim what As UByte, s As String
	
	s = ter
	
	Do
		If n Then
			grid(x, y).land = what
			n -= 1
			x += 1
			If x = 31 Then x = 1 : y += 1
		Else
			what = ASC(Left(s, 1))
			If what And 128 Then
				n = what And 127
				what = ASC(Mid(s, 2, 1))
				s = Mid(s, 3)
			Else
				grid(x, y).land = what
				s = Mid(s, 2)
				x += 1
				If x = 31 Then x = 1 : y += 1
			End If
		End If
	Loop Until y = 31
End Sub


Sub UpdateTitleBar
	Dim s As String
	If Len(Trim(meta.title)) Then
		s = Trim(meta.title)
		If Len(Trim(meta.author)) Then _
			s &= " (by " + Trim(meta.author) + ")"
	ElseIf Len(Trim(track_file)) Then
		s = Trim(track_file)
		If Len(Trim(meta.author)) Then _
			s &= " (by " + Trim(meta.author) + ")"
	End If
	
	If Len(s) Then
		s = ptitle + " - " + s
	Else
		s = ptitle
	End If
	
	#ifdef __FB_DOS__
		WindowTitle Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s))
	#else
		changing_title = -1
		WindowTitle Enc_UTF32_to_Latin1(Enc_UTF8_to_UTF32(s))
		changing_title = 0
	#endif
End Sub


'Flip buffer rectangle horizontally
Function VFlipTrack(t As String) As String
	Dim As Byte w, h, c
	w = ASC(Left(t, 1))
	h = ASC(Mid(t, 2, 1))
	
	Dim temp(1 To w, 1 To h) As SGrid
	Dim s As String, i As Byte, j As Byte, n As Short
	
	'Read and flip
	n = 3 : i = 1 : j = h
	Do While n <= Len(t)
		c = ASC(Mid(t, n, 1))
		n += 1
		If c And 1 Then		'Track is non-zero
			temp(i, j).track = tr(ASC(Mid(t, n, 1))).vflip
			n += 1
		End If
		If c And 2 Then		'Terrain is non-zero
			temp(i, j).land = ttr(ASC(Mid(t, n, 1))).vflip
			n += 1
		End If
		If c And 4 Then		'Border colour is non-zero
			temp(i, j).border = CvL(Mid(t, n, 4))
			n += 4
		End If
		If c And 8 Then		'Background colour is non-zero
			temp(i, j).bgc = CvL(Mid(t, n, 4))
			n += 4
		End If
		
		i += 1
		If i > w Then i = 1 : j -= 1
	Loop	
	
	'Fix
	For i = 1 To w
		For j = 1 To h - 1
			If temp(i, j).track = 254 Or temp(i, j).track = 253 Then
				Swap temp(i, j).track, temp(i, j + 1).track
				j += 1
			End If
		Next j
	Next i

	'Put back into the string
	s = PackedClip(temp())
	
	Return s
End Function


'Push the current grid into the undo buffer
Sub PushUndo
	'Advance the pointer
	undopointer = (undopointer + 1) Mod UNDOLEVEL
	
	'If there were pending redos (the user has been undoing),
	'drop them, as they are now obsolete
	If undohead <> undopointer Then undohead = undopointer
	
	'Store current grid
	undobuffer(undohead) = GetTrack(1, 1, 30, 30)
		
	'If the head reaches the tail (there are already UNDOLEVEL undos
	'in the buffer), then drop the oldest undo by advancing the tail
	If undohead = undotail Then undotail = (undotail + 1) Mod UNDOLEVEL
End Sub


Sub PutIcon(u As UByte, v As UByte, x As UShort, y As UShort)
	If track_image_buffer = 0 Then
		If dosbox Then
			Put (x, y), bigicons, (u * bigwidth, v * 22)- STEP (bigwidth - 1, 21), Trans
		Else
			Put (x, y), bigicons, (u * bigwidth, v * 22)- STEP (bigwidth - 1, 21), Alpha
		End If
	Else
		If dosbox Then
			Put track_image_buffer, (x, y), bigicons, (u * bigwidth, v * 22)- STEP (bigwidth - 1, 21), Trans
		Else
			Put track_image_buffer, (x, y), bigicons, (u * bigwidth, v * 22)- STEP (bigwidth - 1, 21), Alpha
		End If
	End If
End Sub


Sub PutSmallIcon(u As UByte, v As UByte, x As UShort, y As UShort)
	If dosbox Then
		Put (x, y), bigicons, (u * graphic_size, v * graphic_size)- STEP (graphic_size - 1, graphic_size - 1), Trans
	Else
		Put (x, y), bigicons, (u * graphic_size, v * graphic_size)- STEP (graphic_size - 1, graphic_size - 1), Alpha
	End If
End Sub


Sub CheckClipboardImport
	'Do not import clipboard if currently pasting (it would mess up)
	If pasting Then Exit Sub
	
	'Do not delete the clipboard if nothing to import
	If FileExists(program_path & "clip.tmp") AndAlso _
		FileLen(program_path & "clip.tmp") <> last_cb_file_length Then
		
		Dim f As Integer, b As Byte
		
		f = FreeFile
		Open program_path & "clip.tmp" For Binary Access Read As f
		Get #f, , b
		If b Then Get #f, , b
		
		clipboard = Space(LOF(f) - Seek(f) + 1)
		Get #f, , clipboard
		
		last_cb_file_length = LOF(f)
		Close f
		DrawPanel
	End If
End Sub


Sub CheckTrack
	Dim v As TrackVector
	Dim As Short i, j
	Dim track_closed As Byte = 0
	Dim e As UByte
	Dim As Byte ex, ey
		
	DetectTerrainErrors e, ex, ey
	If e >= 40 And e <= 49 Then
		TrackErrorMessage e
		DrawTrack
		xcursor = ex : ycursor = ey
		ManageKeyboardCursor -1
		Exit Sub
	End If
	
	v = FindStart
	Select Case v.e
		Case 60
			Error_Message "Start/finish line not found"
			DrawTrack
			Exit Sub
		Case 61
			Error_Message "Too many start/finish lines!"
			DrawTrack
			Exit Sub
		Case 62
			Error_Message "Terrain at track start is inadequate"
			DrawTrack
			Exit Sub
	End Select
	
	GenerateSections
	If sections > 254 Or paths >= MAXPATHS Then
		Error_Message "Track is too complex. Too many splits!"
		Exit Sub
	End If
	
	'Find track-fatal errors
	For i = 1 To paths
		If path(i).e >= 70 And path(i).e <= 79 Then
			TrackErrorMessage path(i).e
			
			FollowPath path(i).p, path(i).e
			Exit Sub
		End If
	Next i
	
	'So, track is OK, but is there any warning?
	For i = 1 To paths
		If path(i).e >= 20 And path(i).e <= 29 Then
			TrackErrorMessage path(i).e
			
			FollowPath path(i).p, path(i).e
			Exit Sub
		End If
	Next i
	
	'See if there's a path to the finish-line with no errors
	For i = 1 To paths
		If path(i).finishes Then
			Error_Message "Found winning path", "Track OK"
			DrawTrack
			FollowPath path(i).p
			Exit Sub
		End If
	Next i
	
	'Reject tracks that are not closed
	Error_Message "At least one path must be closed"
	DrawTrack
	
	For i = 1 To paths
		FollowPath path(i).p
	Next i
	
	v.coors = section(ASC(Right(path(1).p, 1))).final
	xcursor = v.x : ycursor = v.y
	ManageKeyboardCursor -1
End Sub


Sub ClearTrack(x As UByte, y As UByte)
	Dim As Short alterx, altery
	Dim element As UByte
	
	If allow_errors Then
		alterx = x : altery = y
	Else
		Select Case grid(x, y).track
			Case 255 : alterx = x - 1 : altery = y
			Case 254 : alterx = x : altery = y - 1
			Case 253 : alterx = x - 1 : altery = y - 1
			Case Else : alterx = x : altery = y
		End Select
	End If
	
	If alterx < 1 Then alterx = 1
	If altery < 1 Then altery = 1
	element = grid(alterx, altery).track
	grid(alterx, altery).track = 0
	DrawSpot alterx, altery
	
	'Tunnels
	If element = &H42 Then
		If y > 1 Then DrawSpot x, y - 1
		If y < 30 Then DrawSpot x, y + 1
	ElseIf element = &H43 Then
		If x > 1 Then DrawSpot x - 1, y
		If x < 30 Then DrawSpot x + 1, y
	End If
		
	If Not allow_errors Then
		If tr(element).w > 1 Then
			If alterx + 1 <= 30 Then grid(alterx + 1, altery).track = 0 : DrawSpot alterx + 1, altery
			If tr(element).h > 1 Then
				If alterx + 1 <= 30 And altery + 1 <= 30 Then grid(alterx + 1, altery + 1).track = 0 : DrawSpot alterx + 1, altery + 1
			End if
		End If
		If tr(element).h > 1 Then
			If altery + 1 <= 30 Then grid(alterx, altery + 1).track = 0 : DrawSpot alterx, altery + 1
		End If

		If grid(x, y).track <> 0 Then grid(x, y).track = 0 : DrawSpot x, y
	End If
End Sub


Sub Menu_TrackInfo
	Dim As Integer xm, ym, wm, bm
	Dim v As Short, s As String
	Dim et As Long, top As Short
	Dim content(0 To 6, 1 To 2) As String
	Dim As Byte current = -1, former = -1, update = -1
	Dim akey As String
	
	MenuBox 36, 29, "Track Information"
	
	top = ceny
	lefx += 8
	TLeft
	content(0, 1) = "Track title:"
	TLeft , content(0, 1), RGB(200, 200, 200)
	If Len(meta.title) Then
		s = Left(meta.title, 64)
	ElseIf Len(track_file) Then
		s = UCase(Left(track_file, 1)) + LCase(Mid(track_file, 2))
		v = InStr(s, ".")
		If v Then s = Left(s, v - 1)
	Else
		s = "Untitled"
	End If
	content(0, 2) = s
	#ifdef RENDER_TO_CP437
		TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
	#else
		TLeft , Enc_UTF32_to_Latin1(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
	#endif
	TLeft
	content(1, 1) = "Author:"
	TLeft , content(1, 1), RGB(200, 200, 200)
	If Len(meta.author) Then
		s = Left(meta.author, 64)
	Else
		s = "Anonymous"
	End If
	content(1, 2) = s
	#ifdef RENDER_TO_CP437
		TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
	#else
		TLeft , Enc_UTF32_to_Latin1(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
	#endif
	TLeft
	TLeft , "Date created:", RGB(200, 200, 200)
	If meta.cyear Then
		s = Str(meta.cyear)
		If meta.cmonth Then
			s = Str(meta.cmonth) + "-" + s
			If meta.cday Then s = Str(meta.cday) + "-" + s
		End If
	Else
		s = "Unknown"
	End If
	TLeft , s, RGB(160, 160, 240)
	TLeft
	TLeft , "Created with:", RGB(200, 200, 200)
	If meta.toolversion Then
		s = Left(meta.tool, 32) + " " + Str(meta.toolversion \ 10000)
		s &= "." + Str((meta.toolversion Mod 10000) \ 100)
		If meta.toolversion Mod 100 Then s &= "." + Str(meta.toolversion Mod 100)
	Else
		s = Left(meta.tool, 32)
	End If
	TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
	TLeft
	TLeft , "Editing time:", RGB(200, 200, 200)
	If meta.editing_time >= 0 THen
		et = meta.editing_time + Timer - started_editing
		s = ""
		If et >= 3600 Then s = Str(et \ 3600) + "h "
		s &= Str((et Mod 3600) \ 60) + "' "
		s &= Str(et Mod 60) + Chr(34)
	Else
		s = "Unknown"
	End If
	TLeft , s, RGB(160, 160, 240)
	TLeft
	content(5, 1) = "Comment:"
	TLeft , content(5, 1), RGB(200, 200, 200)
	If Len(meta.comment) Then
		s = Left(meta.comment, 64)
	Else
		s = "No comment"
	End If
	content(5, 2) = s
	#ifdef RENDER_TO_CP437
		TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
	#else
		TLeft , Enc_UTF32_to_Latin1(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
	#endif
	TLeft
	content(6, 1) = "Championship information:"
	TLeft , content(6, 1), RGB(200, 200, 200)
	If Len(meta.championship) Then
		s = Left(meta.championship, 64)
	Else
		s = "No info"
	End If
	content(6, 2) = s
	#ifdef RENDER_TO_CP437
		TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
	#else
		TLeft , Enc_UTF32_to_Latin1(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
	#endif
	TLeft
	TLeft
	
	buttons = 0
	StackButton " Cancel ", 2
	StackButton " Update ", 1
	EndOfButtonStack
		
	Do
		GetMouse xm, ym, wm, bm
		If xm < lefx - 8 Or xm >= lefx + 8 * 67 Or ym < top + 8 Or ym >= top + 7 * 48 + 8 Then
			current = -1
		Else
			current = (ym - top - 8) \ 48
		End If
		
		If current <> former Or update = -1 Then
			ScreenLock
			If former = 0 Or former = 1 Or former = 5 Or former = 6 Then
				Line (lefx - 4, top + 8 + 48 * former)- Step (8 * 67 - 1,  47), RGB(30, 30, 50), BF
				ceny = top + 16 + 48 * former
				s = content(former, 2)
				TLeft , content(former, 1), RGB(200, 200, 200)
				#ifdef RENDER_TO_CP437
					TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
				#else
					TLeft , Enc_UTF32_to_Latin1(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
				#endif
			End If
			If current = 0 Or current = 1 Or current = 5 Or current = 6 Then
				Line (lefx - 4, top + 8 + 48 * current)- Step (8 * 67 - 1,  47), RGB(10, 10, 10), BF
				ceny = top + 16 + 48 * current
				s = content(current, 2)
				TLeft , content(current, 1), RGB(200, 200, 200)
				#ifdef RENDER_TO_CP437
					TLeft , Enc_UTF32_to_CP437(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
				#else
					TLeft , Enc_UTF32_to_Latin1(Enc_UTF8_to_UTF32(s)), RGB(160, 160, 240)
				#endif
			End If
			ScreenUnlock
			
			former = current
			update = 0
		End If
		
		v = ManageButtons
		
		If bm = 1 And (current = 0 Or current = 1 Or current = 5 Or current = 6) Then
			stringer.init = -1
			stringer.maxlength = 64
			stringer.fileonly = 0
			stringer.x = lefx
			stringer.y = 48 * current + top + 32
			stringer.background = RGB(10, 10, 10)
			If current = 0 And content(current, 2) = "Untitled" Then content(current, 2) = ""
			If current = 1 And content(current, 2) = "Anonymous" Then content(current, 2) = ""
			If current = 5 And content(current, 2) = "No comment" Then content(current, 2) = ""
			If current = 6 And content(current, 2) = "No info" Then content(current, 2) = ""
			Do
				akey = ManageString(content(current, 2))
				v = ManageButtons
			Loop Until akey = Chr(13) Or akey = Chr(27) Or v <> 0
			If current = 0 And Trim(content(current, 2)) = "" Then content(current, 2) = "Untitled"
			If current = 1 And Trim(content(current, 2)) = "" Then content(current, 2) = "Anonymous"
			If current = 5 And Trim(content(current, 2)) = "" Then content(current, 2) = "No comment"
			If current = 6 And Trim(content(current, 2)) = "" Then content(current, 2) = "No info"
			update = -1
			
			Dim t As Double
			t = Timer
			Do : Loop Until Timer >= t + .3
			Do : Loop Until Len(InKey) = 0
		End If
	
		akey = InKey
	Loop Until v Or akey = Chr(13) Or akey = Chr(27)
	buttons = 0
	
	If v = 1 Or akey = Chr(13) Then
		meta.title = Trim(content(0, 2))
		If meta.title = "Untitled" Then meta.title = ""
		meta.author = Trim(content(1, 2))
		If meta.author = "Anonymous" Then meta.author = ""
		meta.comment = Trim(content(5, 2))
		If meta.comment = "No comment" Then meta.comment = ""
		meta.championship = Trim(content(6,2))
		If meta.championship = "No info" Then meta.championship = ""
	End If
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	Do : Loop Until Len(InKey) = 0
	
	UpdateTitleBar
	DrawTrack
End Sub


Sub Menu_Tournaments
	#ifdef __FB_DOS__
		Error_Message "Tournament option not available in DOS"
	#else
		Dim tmt(1 To 10) As String, addr(1 To 10) As String, tmts As Byte
		Dim n As Short, v As Short, current_tournament As Byte, top As Short
		Dim page As Byte, reload_tournament As Byte = -1
		Dim As Integer xm, ym, wm, bm
		Dim As String curtrack, deadline
		
		If FileExists(program_path + "tours.dat") Then
			Dim ff As Integer
			
			ff = FreeFile
			Open program_path + "tours.dat" For Binary Access Read As ff
			While Not EOF(ff)
				tmts += 1
				Get #ff, , n
				tmt(tmts) = Space(n)
				Get #ff, , tmt(tmts)
				Get #ff, , n
				addr(tmts) = Space(n)
				Get #ff, , addr(tmts)
			WEnd
			Close ff
		End If
		
		Do
			Select Case page
				Case 0	'Main page, list of tournaments
					Dim update As Byte = -1
					
					Do
						If update Then
							ScreenLock
							DrawTrack
							If tmts Then
								MenuBox 30, 8 + 3 * tmts, "Tournaments"
								If current_tournament = 0 Then current_tournament = 1
								Line (lefx - 4, ceny + 48 * current_tournament - 40)- Step (455, 47), RGB(10, 10, 10), BF
							Else
								MenuBox 30, 11, "Tournaments"
							End If
							
							top = ceny : lefx += 8
							TLeft
							If tmts Then
								For n = 1 To tmts
									TLeft , tmt(n), RGB(200, 200, 200)
									TLeft , addr(n), RGB(160, 160, 240)
									TLeft
								Next n
							Else
								TLeft
								TCentre , "No tournaments found", RGB(160, 160, 240)
								TLeft
							End If
							TLeft
							
							buttons = 0
							If tmts Then
								StackButton " Connect ", 1
								StackButton " Remove ", 2
							End If
							If tmts < 10 Then StackButton " Add New ", 3
							StackButton " Done ", 10
							EndOfButtonStack
							ScreenUnlock
							
							update = 0
						End If
						
						GetMouse xm, ym, wm, bm
						
						If bm = 1 And tmts <> 0 Then
							If xm >= lefx - 4 And xm <= lefx + 459 And ym >= top + 8 Then
								n = (ym - top - 8) \ 48 + 1
								If n <= tmts And n <> current_tournament Then
									current_tournament = n
									update = -1
								End If
							End If
						End If
						
						v = ManageButtons
					Loop Until v
					buttons = 0
					
					If v = 2 And tmts <> 0 Then	'Remove
						For i As Byte = current_tournament To tmts - 1
							tmt(i) = tmt(i + 1)
							addr(i) = addr(i + 1)
						Next i
						tmts -= 1
						If current_tournament > tmts Then current_tournament = tmts
					ElseIf v = 3 Then
						page = 1
					ElseIf v = 1 Then
						reload_tournament = -1
						page = 2	'Connect
					End If
					
					DrawTrack
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm = 0
				Case 1	'Add tournament
					Dim As String title, address, akey
					Dim As Byte which = 0, old = 1
					
					MenuBox 30, 14, "Add Tournament"
					
					lefx += 8 : top = ceny
					TLeft
					TLeft , "Tournament name:", RGB(200, 200, 200)
					TLeft
					TLeft
					TLeft , "Domain name (base address):", RGB(200, 200, 200)
					TLeft
					TLeft
					TLeft
					
					buttons = 0
					StackButton " Cancel ", 2
					StackButton " Create ", 1
					EndOfButtonStack
					
					stringer.x = lefx
					stringer.fileonly = 0
					stringer.maxlength = 40
					stringer.background = RGB(30, 30, 50)
					Do
						If old <> which Then
							Line (lefx, top + 48 * old + 32)- Step (449, 15), RGB(30, 30, 50), BF
							If old Then
								Draw String (lefx, top + 80), address + Space(40 - Len(address)), RGB(160, 160, 240)
								stringer.y = top + 32
							Else
								Draw String (lefx, top + 32), title + Space(40 - Len(title)), RGB(160, 160, 240)
								stringer.y = top + 80
							End If
							stringer.init = -1
							old = which
						End If
						
						v = ManageButtons
						If which Then
							akey = ManageString(address)
						Else
							akey = ManageString(title)
						End If
						
						Select Case akey
							Case Chr(9) : which = 1 - which
							Case Chr(13) 
								If which Then
									v = 1 : Exit Do
								Else
									which = 1 - which
								End If
							Case Chr(27) : v = 2 : Exit DO
						End Select
					Loop Until v
					Do : Loop Until Len(InKey) = 0
					
					If v = 1 Then
						tmts += 1
						tmt(tmts) = Trim(title)
						addr(tmts) = Trim(address)
					End If
					page = 0
					
					DrawTrack
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm = 0
				Case 2	'Attempt connection to tournament
					Dim result As Byte
					
					If reload_tournament Then
						MenuBox 20, 7, "Please wait"
						TLeft
						TCentre , "Attempting connection...", RGB(160, 160, 240)
						
						result = TMT_GetMain(addr(current_tournament), curtrack, deadline)
						DrawTrack
					Else
						result = 0
					End If						
					
					If result Then
						Error_Message "Failed to connect to " + tmt(current_tournament)
						v = 3
					Else
						MenuBox 35, 14, tmt(current_tournament)
						
						lefx += 8
						TLeft
						TLeft , "Current race:", RGB(200, 200, 200)
						TLeft , curtrack, RGB(160, 160, 240)
						TLeft
						TLeft , "Deadline:", RGB(200, 200, 200)
						TLeft , deadline, RGB(160, 160, 240)
						TLeft
						TLeft
						
						buttons = 0
						StackButton " Back ", 3
						StackButton " Get Track ", 1
						StackButton " Scoreboard ", 2
						EndOfButtonStack
						
						Do
							v = ManageButtons
						Loop Until v
						buttons = 0
						reload_tournament = 0
					End If
					
					If v = 1 Then
						DrawTrack
						TMT_GetCurrentTrack addr(current_tournament)
					ElseIf v = 2 Then
						page = 3
					ElseIf v = 3 Then
						page = 0
					End If
					
					DrawTrack
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm = 0
				Case 3	'Scoreboard
					Dim item(1 To 100) As Scoreboard, items As Byte
					Dim result As Byte
					
					MenuBox 20, 7, "Please wait"
					TLeft
					TCentre , "Loading scoreboard...", RGB(160, 160, 240)
										
					result = TMT_GetScoreboard(addr(current_tournament), item(), items)
					DrawTrack
					
					If result Then
						Error_Message "Failed to get the scoreboard"
					ElseIf items = 0 Then
						Error_Message "Scoreboard is empty", tmt(current_tournament) + " - Scoreboard"
					Else
						Dim top As Short
						Dim As ULong normal = RGB(160, 160, 240), bright = RGB(180, 180, 80)
						
						Do : Loop Until Len(InKey) = 0
						
						ScreenLock
						MenuBox 35, 9 + items, tmt(current_tournament) + " - Socreboard"
						lefx += 8
						TLeft
						top = ceny
						
						For i As Byte = 1 To items
							Dim s As String
							
							s = Str(i) : If Len(s) = 1 Then s = " " + s
							concolour = normal
							TCont s + " - " + item(i).racer + " (" + item(i).car + ")"
							concolour = bright : conx = lefx + 320
							If Len(item(i).hctime) = 0 Then
								TCont Space(8) + item(i).realtime
							Else
								TCont item(i).realtime + "  " + item(i).hctime
							End If
							If item(i).handicap Then
								s = "(" + Str(item(i).handicap) + "%)"
							ElseIf Len(item(i).style) Then
								s = "(" + item(i).style + ")"
							Else
								s = ""
							End If
							If item(i).verified Then s &= Chr(251)
							concolour = normal
							TCont s, -1
						Next i
						ScreenUnlock
						
						buttons = 0
						ceny = top + 16 * items + 32
						StackButton "  OK  ", 1
						EndOfButtonStack
						
						Do
							v = ManageButtons
						Loop Until v <> 0 Or Len(InKey) <> 0
						
						DrawTrack
						Do
							GetMouse xm, ym, wm, bm
						Loop Until bm = 0
					End If
					
					page = 2
			End Select
		Loop Until v = 10
		
		Do : Loop Until Len(InKey) = 0
		
		If tmts Then
			Dim ff As Integer
			ff = FreeFile
			Open program_path + "tours.dat" For Output As ff : Close ff
			Open program_path + "tours.dat" For Binary As ff
			For i As Byte = 1 To tmts
				n = Len(tmt(i))
				Put #ff, , n
				Put #ff, , tmt(i)
				n = Len(addr(i))
				Put #ff, , n
				Put #ff, , addr(i)
			Next i
			Close ff
		Else
			If FileExists(program_path + "tours.dat") Then Kill program_path + "tours.dat"
		End If
	#endif
End Sub


Sub Menu_TrackShot
	Dim tiname As String, tsformat As SelectorType

	MenuBox 28, 12, "Track-shot"
	
	TLeft ,
	
	tsformat.redraw = -1
	tsformat.options = 6
	tsformat.opt(1) = "TGA"
	tsformat.opt(2) = "BMP"
	tsformat.opt(3) = "PCX"
	tsformat.opt(4) = "GIF"
	tsformat.opt(5) = "JPG"
	tsformat.opt(6) = "PNG"
	tsformat.current = 1
	For i As Byte = 1 To 6
		If LCase(tsformat.opt(i)) = LCase(imageformat) Then
			tsformat.current = i
			Exit For
		End If
	Next i
	tsformat.x1 = cenx + 8
	tsformat.x2 = cenx + 108
	tsformat.y1 = ceny - 8
	tsformat.y2 = ceny + 23

	TCentre , "Image format:             ", RGB(160, 160, 240)
	TLeft ,
	TCentre , "Only TGA and BMP are supported natively", RGB(160, 160, 240)
	TCentre , "Other formats require ImageMagick", RGB(160, 160, 240)
	TLeft ,
	
	buttons = 0
	StackButton " Cancel ", 1
	StackButton "  Save  ", 2
	EndOfButtonStack
	
	Dim v As Short, akey As String
	Do
		v = ManageButtons
		ManageSelector tsformat
	
		akey = InKey
		Select Case akey
			Case Chr(13) : v = 2 : Exit Do
			Case Chr(27) : v = 1 : Exit Do
		End Select
	Loop Until v <> 0
	
	buttons = 0
	DrawTrack
	
	If v = 2 Then
		imageformat = LCase(tsformat.opt(tsformat.current))
		If Len(Trim(track_file)) Then
			If LCase(Right(track_file, 4)) = ".trk" Then
				tiname = Left(track_file, Len(track_file) - 3) + imageformat
			Else
				tiname = track_file + "." + imageformat
			End If
		Else
			tiname = "track." + imageformat
		End If
		
		SaveTrackImage program_path + tiname
		If FileExists(program_path + tiname) Then
			Error_Message "Track image saved as '" & tiname & "'", "Track-shot"
		Else
			Error_Message "Track image could not be saved!"
		End If
	End If
End Sub


Sub NotASquare
	Dim v As Short

	MenuBox 32, 10, "Cannot rotate!"
	ceny += 8
	TCentre , "The selected area has to be a square to rotate in-situ", RGB(200, 200, 240)
	TCentre , "Adjust dimensions or copy the selection to the", RGB(200, 200, 240)
	TCentre , "clipboard and then paste before rotating.", RGB(200, 200, 240)
	TCentre
	
	If xselect > x2select Then Swap xselect, x2select
	If yselect > y2select Then Swap yselect, y2select
	If xselect + y2select - yselect <= 30 Then _
		StackButton " Adjust width ", 1
	If yselect + x2select - xselect <= 30 Then _
		StackButton " Adjust height ", 2
	StackButton " Cancel ", 3
	EndOfButtonStack
	Do
		v = ManageButtons
	Loop Until v <> 0 Or InKey <> ""
	buttons = 0
	
	Select Case v
		Case 1 : x2select = xselect + y2select - yselect
		Case 2 : y2select = yselect + x2select - xselect
	End Select
	
	Dim As Integer xm, ym, wm, bm
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	Do : Loop Until Len(InKey) = 0
	DrawTrack
End Sub


Sub Paste
	If Len(clipboard) Then pasting = -1
End Sub


Function PathLength(n As Short, weighed As Byte = 0) As Long
	Dim l As Long = 0, i As Short
	Dim v As TrackVector, s As Short
	Dim juststarted As Byte
	
	For i = 1 To Len(path(n).p)
		s = ASC(Mid(path(n).p, i, 1))
		v.coors = section(s).initial
		v.bearing = section(s).bearing
		If sections = 1 And section(1).finishes <> 0 Then juststarted = -1
		While v.coors <> section(s).final Or juststarted
			v = GetNext(v)
			juststarted = 0
			If weighed Then
				l += tr(grid(v.x, v.y).track).length
			Else
				If tr(grid(v.x, v.y).track).w > 1 Or tr(grid(v.x, v.y).track).h > 1 Then
					l += 2
				Else
					l += 1
				End If
			End If
		WEnd
	Next i
	
	If weighed Then
		l += 10
	Else
		l += 1
	End If
	
	Return l
End Function


Function PathToError(te As Short) As String
	Dim s As String, i As Short, current As Short
	
	'I believe this function may hang when executed to find
	'a cycle that's made of more than one section if it happens
	'to choose the same two or more sections over and over.
	
	current = terror(te).section
	s = Chr(current)
	While current <> 1
		For i = 1 To sections
			If i <> current And section(i).final = section(current).initial Then
				current = i
				s = Chr(i) + s
				Exit For
			End If
		Next i
	Wend
	
	Return s
End Function


Function PathToFinishLine(which As Byte = 0) As String
	Dim i As Short
	
		
	Return ""
End Function


Sub PickTrack(x As UByte, y As UByte)
	Dim As Short alterx, altery
	
	Select Case grid(x, y).track
		Case 255 : alterx = x - 1 : altery = y
		Case 254 : alterx = x : altery = y - 1
		Case 253 : alterx = x - 1 : altery = y - 1
		Case Else : alterx = x : altery = y
	End Select
	
	If alterx < 1 Then alterx = 1
	If altery < 1 Then altery = 1
	
	current_brush = grid(alterx, altery).track
End Sub


Sub SelectByTyping
	Dim As String akey, s
	Dim As Integer xm, ym, wm, bm, xo, yo
	Dim t As Double
	
	GetMouse xo, yo, wm, bm
	t = Timer
	
	Do
		akey = InKey
		
		Select Case akey
			Case ""
			Case Chr(27) : s = "" : Exit Do
			Case Chr(8)
				If Len(s) > 1 Then s = Left(s, Len(s) - 1)
			Case " " To Chr(126) : s = s + akey
			Case Else : Exit Do
		End Select
		
		If Len(akey) And Len(s) Then
			For i As Short = 1 To current_brush + 255
				Dim n As Byte, namey As String
				
				namey = Trim(tr(i).id)
				n = InStr(LCase(namey), LCase(s))
				If n Then
					current_brush = i
					ScreenLock
					DrawPanel
					Draw String (xpanel + 99 - Len(namey) * 4 + (n - 1) * 8, ypanel + ypalette + 82), Mid(namey, n, Len(s)), RGB(50, 200, 250)
					ScreenUnlock
					Exit For
				End If
			Next i
		End If
		
		GetMouse xm, ym, wm, bm
		If bm <> 0 Then
			Exit Do
		ElseIf xo = -1 And xm <> -1 Then
			Exit Do
		ElseIf xo <> -1 And xm = -1 Then
			Exit Do
		ElseIf Sqr((xm - xo) ^ 2 + (ym - yo) ^ 2) > 10 Then
			Exit Do
		End If
	Loop Until Timer > t + 5
	DrawPanel
		
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
End Sub


Sub SetTrack(x As UByte, y As UByte, code As UByte)
	
	'First, check that the new element can be placed
	If Not allow_errors Then
		If (x = 30 And tr(code).w > 1) Or (y = 30 And tr(code).h > 1) Then
			Exit Sub
		End If
	End If

	ScreenLock

	'Then make sure to leave no incomplete track elements
	If Not allow_errors Then
		ClearTrack x, y
		If tr(code).w > 1 And x < 30 Then
			ClearTrack x + 1, y
			If tr(code).h > 1 And y < 30 Then ClearTrack x + 1, y + 1
		End If
		If tr(code).h > 1 And y < 30 Then ClearTrack x, y + 1
	End If
	
	'Finally, place the new element
	grid(x, y).track = code : DrawSpot x, y
	
	'If it's a tunnel, make it look good
	If code = &H42 Then		'Vertical tunnel
		If y > 1 Then DrawSpot x, y - 1
		If y < 30 Then DrawSpot x, y + 1
	ElseIf code = &H43 Then	'Horizontal tunnel
		If x > 1 Then DrawSpot x - 1, y
		If x < 30 Then DrawSpot x + 1, y
	End If
	
	'... and draw the complemental sub-elements, if any
	If tr(code).w > 1 Then
		If x + 1 <= 30 Then grid(x + 1, y).track = 255 : DrawSpot x + 1, y
		If tr(code).h > 1 Then
			If x + 1 <= 30 And y + 1 <= 30 Then
				grid(x + 1, y + 1).track = 253
				DrawSpot x + 1, y + 1
			End If
		End If
	End If
	If tr(code).h > 1 Then
		If y + 1 <= 30 Then grid(x, y + 1).track = 254 : DrawSpot x, y + 1
	End If
	
	ScreenUnlock
	
End Sub


Sub RaiseTerrain(x As UByte, y As UByte)
	ScreenLock
	If x < 30 And y < 30 Then
		Select Case grid(x + 1, y + 1).land
			Case 0 To 5: grid(x + 1, y + 1).land = 11
			Case 9 : grid(x + 1, y + 1).land = 16
			Case 10 : grid(x + 1, y + 1).land = 18
			Case 12 : grid(x + 1, y + 1).land = 8
			Case 13
				grid(x + 1, y + 1).land = 6
				RaiseTerrain x + 1, y
				RaiseTerrain x, y + 1
			Case 14 : grid(x + 1, y + 1).land = 7
			Case 17 : grid(x + 1, y + 1).land = 6
		End Select
		DrawSpot x + 1, y + 1
	End If
	If x > 0 And y > 0 Then
		Select Case grid(x, y).land
			Case 0 To 5 : grid(x, y).land = 13
			Case 7 : grid(x, y).land = 18
			Case 8 : grid(x, y).land = 16
			Case 11
				grid(x, y).land = 6
				RaiseTerrain x - 1, y
				RaiseTerrain x, y - 1
			Case 12 : grid(x, y).land = 9
			Case 14 : grid(x, y).land = 10
			Case 15 : grid(x, y).land = 6
		End Select
		DrawSpot x, y
	End If
	If x > 0 And y < 30 Then
		Select Case grid(x, y + 1).land
			Case 0 To 5: grid(x, y + 1).land = 14
			Case 8 : grid(x, y + 1).land = 15
			Case 9 : grid(x, y + 1).land = 17
			Case 11 : grid(x, y + 1).land = 7
			Case 12
				grid(x, y + 1).land = 6
				RaiseTerrain x - 1, y
				RaiseTerrain x, y + 1
			Case 13 : grid(x, y + 1).land = 10
			Case 16 : grid(x, y + 1).land = 6
		End Select
		DrawSpot x, y + 1
	End If
	If x < 30 And y > 0 Then
		Select Case grid(x + 1, y).land
			Case 0 To 5: grid(x + 1, y).land = 12
			Case 7 : grid(x + 1, y).land = 15
			Case 10 : grid(x + 1, y).land = 17
			Case 11 : grid(x + 1, y).land = 8
			Case 13 : grid(x + 1, y).land = 9
			Case 14
				grid(x + 1, y).land = 6
				RaiseTerrain x, y - 1
				RaiseTerrain x + 1, y
			Case 18 : grid(x + 1, y).land = 6
		End Select
		DrawSpot x + 1, y
	End If
	ScreenUnlock
End Sub


Sub LowerTerrain(x As UByte, y As UByte)
	ScreenLock
	If x < 30 And y < 30 Then
		Select Case grid(x + 1, y + 1).land
			Case 6 : grid(x + 1, y + 1).land = 17
			Case 7 : grid(x + 1, y + 1).land = 14
			Case 8 : grid(x + 1, y + 1).land = 12
			Case 11 : grid(x + 1, y + 1).land = 0
			Case 15
				grid(x + 1, y + 1).land = 0
				LowerTerrain x + 1, y
				LowerTerrain x, y + 1
			Case 16 : grid(x + 1, y + 1).land = 9
			Case 18 : grid(x + 1, y + 1).land = 10
		End Select
		DrawSpot x + 1, y + 1
	End If
	If x > 0 And y > 0 Then
		Select Case grid(x, y).land
			Case 6 : grid(x, y).land = 15
			Case 9 : grid(x, y).land = 12
			Case 10 : grid(x, y).land = 14
			Case 13 : grid(x, y).land = 0
			Case 16 : grid(x, y).land = 8
			Case 17
				grid(x, y).land = 0
				LowerTerrain x, y - 1
				LowerTerrain x - 1, y
			Case 18 : grid(x, y).land = 7
		End Select
		DrawSpot x, y
	End If
	If x > 0 And y < 30 Then
		Select Case grid(x, y + 1).land
			Case 6 : grid(x, y + 1).land = 16
			Case 7 : grid(x, y + 1).land = 11
			Case 10 : grid(x, y + 1).land = 13
			Case 14 : grid(x, y + 1).land = 0
			Case 15 : grid(x, y + 1).land = 8
			Case 17 : grid(x, y + 1).land = 9
			Case 18
				grid(x, y + 1).land = 0
				LowerTerrain x - 1, y
				LowerTerrain x, y + 1
		End Select
		DrawSpot x, y + 1
	End If
	If x < 30 And y > 0 Then
		Select Case grid(x + 1, y).land
			Case 6 : grid(x + 1, y).land = 18
			Case 8 : grid(x + 1, y).land = 11
			Case 9 : grid(x + 1, y).land = 13
			Case 12 : grid(x + 1, y).land = 0
			Case 15 : grid(x + 1, y).land = 7
			Case 16
				grid(x + 1, y).land = 0
				LowerTerrain x + 1, y
				LowerTerrain x, y - 1
			Case 17 : grid(x + 1, y).land = 10
		End Select
		DrawSpot x + 1, y
	End If
	ScreenUnlock
End Sub


Function Enc_UTF8_to_UTF32 (s As String) As String
	Dim s1 As String, s2 As String, w As Long
	Dim As UByte v1, v2, v3, v4
	
	If Len(s) = 0 Then Return ""
	s1 = s
	s2 = ""
	Do
		v1 = Asc(Left(s1, 1))
		
		'Note: This code does not check the validity of trailing bytes
		'for multi-byte codes, that is, it does not make sure that the
		'highest two bits are 1 and 0. It will work well as long as the
		'input code is well-behaved. Otherwise, it would be crap anyway.
		'The check is not performed to avoid making the code longer and
		'slower unnecessarily.		
		If v1 < 128 Then	'ASCII
			w = v1
			s1 = Mid(s1, 2)
			s2 &= MkL(w)
		ElseIf (v1 And 64) = 0 Then		'Invalid. Skip
			s1 = Mid(s1, 2)
		ElseIf (v1 And 32) = 0 Then		'Two-byte code
			v2 = Asc(Mid(s1, 2, 1))
			s1 = Mid(s1, 3)
			w = (v2 And 63) Or ((v1 And 31) ShL 6)
			s2 &= MkL(w)
		ElseIf (v1 And 16) = 0 Then		'Three-byte code
			v2 = Asc(Mid(s1, 2, 1))
			v3 = Asc(Mid(s1, 3, 1))
			s1 = Mid(s1, 4)
			w = (v3 And 63) Or ((v2 And 63) ShL 6) Or ((v1 And 15) ShL 12)
			s2 &= MkL(w)
		ElseIf (v1 And 8) = 0 Then		'Four-byte code
			v2 = Asc(Mid(s1, 2, 1))
			v3 = Asc(Mid(s1, 3, 1))
			v4 = Asc(Mid(s1, 4, 1))
			s1 = Mid(s1, 5)
			w = (v4 And 63) Or ((v3 And 63) ShL 6) Or ((v2 And 63) ShL 12) Or ((v1 And 7) ShL 18)
			s2 &= MkL(w)
		Else		'Invalid leading byte. Skip
			s1 = Mid(s1, 2)
		End If
	Loop Until Len(s1) = 0
	
	Return s2
End Function


Function Enc_UTF32_to_UTF8 (s As String) As String
	Dim As UByte v1, v2, v3, v4
	Dim w As Long, s1 As String, s2 As String
	
	If Len(s) = 0 Then Return ""
	s1 = s : s2 = ""
	Do
		w = CvL(Left(s1, 4))
		s1 = Mid(s1, 5)
		
		If w < 128 Then		'ASCII
			v1 = w
			s2 &= Chr(v1)
		ElseIf w < 2 ^ 11 Then	'Two-byte code
			v2 = (w And 63) Or 128
			v1 = ((w ShR 6) And 31) Or &B11000000
			s2 &= Chr(v1) & Chr(v2)
		ElseIf w < 2 ^ 16 Then	'Three-byte code
			v3 = (w And 63) Or 128
			v2 = ((w ShR 6) And 63) Or 128
			v1 = ((w ShR 12) And 15) Or &B11100000
			s2 &= Chr(v1) & Chr(v2) & Chr(v3)
		Else	'Four-byte code
			v4 = (w And 63) Or 128
			v3 = ((w ShR 6) And 63) Or 128
			v2 = ((w ShR 12) And 63) Or 128
			v1 = ((w ShR 18) And 7) Or &B11110000
			s2 &= Chr(v1) & Chr(v2) & Chr(v3) & Chr(v4)
		End If
	Loop Until Len(s1) = 0
	
	Return s2
End Function


Function Enc_UTF32_to_CP437 (s As String) As String
	Dim s2 As String, w As Long, v As UByte
	
	If Len(s) = 0 Then Return ""
	For i As Short = 1 To Len(s) - 3 Step 4
		w = CvL(Mid(s, i, 4))
		
		If (w >= 32 And w <= 126) Then
			v = w
		Else
			v = 0
			For j As Short = 0 To UBound(toCP437)
				If toCP437(j).utf32 = 0 Then Exit For
				If w = toCP437(j).utf32 Then
					v = toCP437(j).o
					Exit For
				End If
			Next j
			If v = 0 And w <> 0 Then v = 254
		End If
		
		s2 &= Chr(v)
	Next i
	
	Return s2
End Function


Function Enc_UTF32_to_Latin1 (s As String) As String
	Dim s2 As String, w As Long, v As UByte
	
	If Len(s) = 0 Then Return ""
	For i As Short = 1 To Len(s) - 3 Step 4
		w = CvL(Mid(s, i, 4))
		
		If (w >= 32 And w <= 126) Or (w >= &HA0 And w <= &HFF) Then
			v = w
		ElseIf changing_title Then
			'We're chaning the window title, so Latin-2 codepoints
			'will be placed where Latin-2 usually puts them
			
			Select Case w
				'The following code points correspond to Latin-2
				'actually and are mapped so that Hungarian language
				'is supported
				Case &H150 : v = 213	'Capital O with double acute
				Case &H170 : v = 219	'Capital U with double acute
				Case &H151 : v = 245	'Lowercase o with double acute
				Case &H171 : v = 251	'Lowercase u with double acute
				
				'Everything else is a question mark
				Case Else : v = 63
			End Select
		Else
			'We're goint to map Latin-2 codepoints to special codes
			'in the font I designed, where I've located the corresponding
			'characters
			Select Case w
				'The following code points correspond to Latin-2
				'actually and are mapped so that Hungarian language
				'is supported
				Case &H150 : v = 133	'Capital O with double acute
				Case &H170 : v = 139	'Capital U with double acute
				Case &H151 : v = 149	'Lowercase o with double acute
				Case &H171 : v = 155	'Lowercase u with double acute
				
				'The Euro sign is supported here
				Case &H20AC: v = 132
				
				'Everything else is an empty rectangle
				Case Else : v = 128
			End Select
		End If
				
		s2 &= Chr(v)
	Next i
	
	Return s2
End Function


#ifdef __FB_DOS__
Sub FakeScreenLock
	If fake_screenlock_level = 0 Then
		ScreenCopy 0, 1
		ScreenSet 1, 0
	End If
	fake_screenlock_level += 1	
End Sub


Sub FakeScreenUnlock
	fake_screenlock_level -= 1
	If fake_screenlock_level = 0 Then
		ScreenCopy 1, 0
		ScreenSet 0, 0
	ElseIf fake_screenlock_level = -1 Then
		fake_screenlock_level = 0
	End If
End Sub
#endif

Function FindStart As TrackVector
	Dim As Byte i, j
	Dim starts As String, n As Short
	Dim slot As TrackVector
	
	starts = Chr(1, &HB3, &HB4, &HB5, &H86, &H87, &H88, &H89, &H93, &H94, &H95, &H96)
		
	For j = 1 To 30
		For i = 1 To 30
			If InStr(starts, Chr(grid(i, j).track)) Then
				slot.x = i : slot.y = j
				n += 1
			End If
		Next i
	Next j
	
	If n = 0 Then		'No start/finish line
		slot.e = 60
		Return slot
	ElseIf n > 1 Then	'Too many start/finish lines
		slot.e = 61
		Return slot
	Else
		Select Case grid(slot.x, slot.y).track
			Case 1, &H86, &H93 : slot.bearing = 0		'North
			Case &HB5, &H89, &H96 : slot.bearing = 3	'West
			Case &HB3, &H87, &H94 : slot.bearing = 2	'South
			Case Else : slot.bearing = 1				'East
		End Select
		slot.origin = slot.bearing XOr 2
		
		'Terrain at track start is inadequate
		If grid(slot.x, slot.y).land > 6 Then slot.e = 62
				
		Return slot
	End If
End Function


Sub Flood(x As UByte, y As UByte)
	ScreenLock
	If x < 30 And y < 30 Then
		Select Case grid(x + 1, y + 1).land
			Case 0 : grid(x + 1, y + 1).land = 5
			Case 2 To 4 : grid(x + 1, y + 1).land = 1
		End Select
		DrawSpot x + 1, y + 1
	End If
	If x > 0 And y > 0 Then
		Select Case grid(x, y).land
			Case 0 : grid(x, y).land = 3
			Case 2, 4, 5 : grid(x, y).land = 1
		End Select
		DrawSpot x, y
	End If
	If x > 0 And y < 30 Then
		Select Case grid(x, y + 1).land
			Case 0 : grid(x, y + 1).land = 4
			Case 2, 3, 5 : grid(x, y + 1).land = 1
		End Select
		DrawSpot x, y + 1
	End If
	If x < 30 And y > 0 Then
		Select Case grid(x + 1, y).land
			Case 0 : grid(x + 1, y).land = 2
			Case 3 To 5 : grid(x + 1, y).land = 1
		End Select
		DrawSpot x + 1, y
	End If
	ScreenUnlock
End Sub


Sub Dry(x As UByte, y As UByte)
	ScreenLock
	If x < 30 And y < 30 Then
		Select Case grid(x + 1, y + 1).land
			Case 1 : grid(x + 1, y + 1).land = 3
			Case 2, 4, 5 : grid(x + 1, y + 1).land = 0
		End Select
		DrawSpot x + 1, y + 1
	End If
	If x > 0 And y > 0 Then
		Select Case grid(x, y).land
			Case 1 : grid(x, y).land = 5
			Case 2 To 4 : grid(x, y).land = 0
		End Select
		DrawSpot x, y
	End If
	If x > 0 And y < 30 Then
		Select Case grid(x, y + 1).land
			Case 1 : grid(x, y + 1).land = 2
			Case 3 To 5 : grid(x, y + 1).land = 0
		End Select
		DrawSpot x, y + 1
	End If
	If x < 30 And y > 0 Then
		Select Case grid(x + 1, y).land
			Case 1 : grid(x + 1, y).land = 4
			Case 2, 3, 5 : grid(x + 1, y).land = 0
		End Select
		DrawSpot x + 1, y
	End If
	ScreenUnlock
End Sub


Sub StartUp
	Dim s As String, n As Short
	
	program_path = ""
	#ifdef __FB_LINUX__
		program_path = Environ("HOME") + "/.bliss/"
		If Not FileExists(program_path + "bliss.cfg") Then
			program_path = Environ("HOME") + "/bliss/"
			If Not FileExists(program_path + "bliss.cfg") Then program_path = ""
		End If
	#elseif defined (__FB_DOS__)
		If FileExists("bliss.cfg") Then
			program_path = ""
		Else
			s = Environ("PATH")
			Do
				s = Trim(s)
				If Len(s) = 0 Then
					Screen 0
					Print
					Print "Configuration file not found!"
					Print "Create an empty one or reinstall Bliss."
					Print
					End 1
				End If
				
				n = InStr(s, ";")
				If n Then
					program_path = Left(s, n - 1)
					s = Mid(s, n + 1)
				Else
					program_path = s
					s = ""
				End If
				If Right(program_path, 1) <> "\" Then program_path = program_path + "\"
				If FileExists(program_path + "bliss.cfg") Then Exit Do
			Loop
		End If
	#elseif defined (__FB_WIN32__)
		program_path = Environ("APPDATA") + "\bliss\"
		If Not FileExists(program_path + "bliss.cfg") Then program_path = ""
	#endif
	
	If Not FileExists(program_path + "bliss.cfg") Then
		ScreenRes 640, 64, 32
		Width 80, 4
		Line (0, 0)-(639, 63), RGB(30, 30, 50), BF
		Color RGB(200, 200, 200)
		Draw String (204, 16), "Configuration file not found!"
		Draw String (164, 32), "Create an empty one or reinstall Bliss."
		WindowTitle "Bliss - Error"
		
		Dim t As Double, k As String
		Dim As Integer xm, ym, bm
		
		t = Timer
		Do
			k = Inkey
			If Len(k) Then Exit Do
			GetMouse xm, ym, , bm
			If bm > 0 Then Exit Do
		Loop Until Timer > t + 10
		End 1
	End If
	
	track_path = CurDir
	#ifdef __FB_LINUX__
	If Right(track_path, 1) <> "/" Then track_path = track_path + "/"
	#else
	If Right(track_path, 1) <> "\" Then track_path = track_path + "\"
	#endif
End Sub


'Draw string using the font loaded with LoadFont (used for Latin-1)
Sub PutString (x As Short, y As Short, s As String, col As ULong, col2 As ULong = RGB(&HFF, 0, &HFF))
	Dim As Any Ptr mystring, mymask
	Dim c As UByte
	
	If Len(s) = 0 Then Exit Sub
	
	mystring = ImageCreate(8 * Len(s), 16, col)
	mymask = ImageCreate(8 * Len(s), 16, 0)
	
	For i As Short = 1 To Len(s)
		c = Asc(Mid(s, i, 1))
		Put mymask, (8 * i - 8, 0), mask, (0, 16 * c)-(7, 16 * c + 15), PSet
		Put mystring, (8 * i - 8, 0), font, (0, 16 * c)-(7, 16 * c + 15), And
	Next i
	
	If col2 <> RGB(&HFF, 0, &HFF) Then _
		Line (x, y)-(x + 8 * Len(s) - 1, y + 15), col2, BF
	
	Put mystring, (0, 0), mymask, Or
	Put (x, y), mystring, Trans
	ImageDestroy mystring
	ImageDestroy mymask
End Sub


'Paste a string based rectangular region on the grid
Sub PutTrack(x As UByte, y As UByte, t As String, forcefull As Byte = 0)
	Dim As Byte i, j, c
	Dim n As Short
	
	n = 3
	For j = y To y + ASC(Mid(t, 2, 1)) - 1
		For i = x To x + ASC(Left(t, 1)) - 1
			c = ASC(Mid(t, n, 1))
			n += 1
			
			If c And 1 Then
				If affect_track Or forcefull Then _
					grid(i, j).track = ASC(Mid(t, n, 1))
				
				n += 1
			Else
				If affect_track Or forcefull Then _
					grid(i, j).track = 0
			End If
			If c And 2 Then
				If affect_terrain Or forcefull Then _
					grid(i, j).land = ASC(Mid(t, n, 1))
				
				n += 1
			Else
				If affect_terrain Or forcefull Then _
					grid(i, j).land = 0
			End If
			If c And 4 Then
				If colouring_mode Or forcefull Then _
					grid(i, j).border = CvL(Mid(t, n, 4))
					
				n += 4
			Else
				If colouring_mode Or forcefull Then _
					grid(i, j).border = 0
			End If
			If c And 8 Then
				If colouring_mode Or forcefull Then _
					grid(i, j).bgc = CvL(Mid(t, n, 4))
				
				n += 4
			Else
				If colouring_mode Or forcefull Then _
					grid(i, j).bgc = 0
			End If
		Next i
	Next j
End Sub


Sub QuitProgram
	Dim v As Short, akey As String
	Dim As Integer xm, ym, wm, bm
	
	If modified Then
		MenuBox 28, 10, "Quit Bliss"
		ceny += 8
		TCentre , "Current track data will be lost!", RGB(200, 200, 240)
		TCentre , "Are you sure you want to leave the program?", RGB(200, 200, 240)
		TCentre
		
		buttons = 0
		StackButton "  Quit  ", 1
		StackButton "  Stay  ", 2, , 50
		EndOfButtonStack
		Do
			v = ManageButtons
			akey = InKey
		Loop Until v <> 0 Or akey <> ""
		buttons = 0
	Else
		v = 1
	End If
	
	If v = 1 Then
		'SaveTransformations		'-- COMMENT TO PUBLISH
		ImageDestroy bigicons
		
		#ifndef __FB_DOS__
			HTTP_End
		#endif
		End
	End If
	
	Do
		GetMouse xm, ym, wm, bm
	Loop Until bm = 0
	
	DrawTrack
	DrawPanel
End Sub


Sub Editor
	Dim As Integer xm, ym, wm, bm
	Dim akey As String, s As String
	Dim As Short xgrid, ygrid		'Coors within a box
	Dim As Short xcross, ycross		'Coors near a vertex
	Dim As Short xbrush, ybrush		'Coors in the centre of current brush
	Dim As Short exgrid, eygrid		'To check when it changes
	Dim As Short stylex, styley		'Last styling coordinates
	Dim As Byte exxcursor = 1, exycursor = 1
	Dim As Byte xpcursor = 0, ypcursor = 0	'Palette cursor
	Dim As Byte inpalette = 0, updatepalette = 0
	Dim tempboard As String		'Temporal rectangle on the grid
	Dim valuecopy As UByte, specialkeylock As Byte
	Dim idletimer As Double, ctrltoselect As Byte
	Dim updatepaste As Byte, stroking As Byte
	Dim previous_page As Byte = 0	'For returning after 2xF1

	
	idletimer = Timer
	Do
		'Read input
		akey = InKey
		GetMouse xm, ym, wm, bm
		
		'This is to prevent the program from hogging the CPU
		If xm <> -1 Or Len(akey) <> 0 Then idletimer = Timer
		If Timer > idletimer + 1 Then Sleep 500
		
		'Check on clipboard import, but not too often
		If Timer > idletimer + .1 Then CheckClipboardImport

		'CTRL key to select
		If MultiKey(&H1D) Then
			If selecting = 0 Then selecting = -1 : ctrltoselect = -1
		Else
			If (selecting = -1 Or selecting = -3) And ctrltoselect = -1 Then selecting = 0
			ctrltoselect = 0
		End If
		
		'Calculate coordinates
		xgrid = (xm - xoffs) \ bigwidth + 1
		ygrid = (ym - yoffs) \ 22 + 1
		'Invalidate border bogus
		If xgrid = 1 And xm < xoffs Then xgrid = -100
		If ygrid = 1 And ym < yoffs Then ygrid = -100
		xcross = (xm - xoffs + (bigwidth ShR 1)) \ bigwidth
		ycross = (ym - yoffs + 11) \ 22
		If tr(current_brush).h = 2 Then
			ybrush = ycross
		Else
			ybrush = ygrid
		End If
		If tr(current_brush).w = 2 Then
			xbrush = xcross
		Else
			xbrush = xgrid
		End If
		If ybrush < 1 Then ybrush = 1
		If ybrush > 30 Then ybrush = 30
		If xbrush < 1 Then xbrush = 1
		If xbrush > 30 Then xbrush = 30
		
		'Draw coordinates and content at the cursor
		If xgrid <> exgrid Or ygrid <> eygrid Or updatepaste Then
			If pasting Then
				Dim As Byte dx, dy
				
				'Paste old contents if any
				If Len(tempboard) <> 0 AndAlso exgrid >= 1 And exgrid <= 30 And eygrid >= 1 And eygrid <= 30 Then
					dx = ASC(Left(tempboard, 1)) : dy = ASC(Mid(tempboard, 2, 1))
					xselect = exgrid - dx \ 2
					If xselect < 1 Then xselect = 1
					If xselect + dx - 1 > 30 Then xselect = 30 - dx + 1
					yselect = eygrid - dy \ 2
					If yselect < 1 Then yselect = 1
					If yselect + dy - 1 > 30 Then yselect = 30 - dy + 1
					PutTrack xselect, yselect, tempboard, -1
				End If
				
				If xgrid >= 1 And xgrid <= 30 And ygrid >= 1 And ygrid <= 30 Then
					'Read new region into buffer
					dx = ASC(Left(clipboard, 1)) : dy = ASC(Mid(clipboard, 2, 1))
					xselect = xgrid - dx \ 2
					If xselect < 1 Then xselect = 1
					If xselect + dx - 1 > 30 Then xselect = 30 - dx + 1
					yselect = ygrid - dy \ 2
					If yselect < 1 Then yselect = 1
					If yselect + dy - 1 > 30 Then yselect = 30 - dy + 1
					x2select = xselect + dx - 1
					y2select = yselect + dy - 1
					tempboard = GetTrack(xselect, yselect, x2select, y2select)
					
					'And place clipboard contents on it
					PutTrack xselect, yselect, clipboard
				Else
					xselect = 0
				End If
				
				updatepaste = 0
			End If
			
			ScreenLock
			If pasting Then DrawTrack
			If xgrid >= 1 And ygrid >= 1 And xgrid <= 30 And ygrid <= 30 Then
				Dim coorpos As Short
				
				coorpos = xpanel + 170
				If colouring_mode Then coorpos -= 72
			
				Line (coorpos - 74, ypanel + 432)- STEP (152, 31), RGB(30, 30, 50), BF	
			
				s = "(" + Trim(Str(xgrid)) + ", " + Trim(Str(ygrid)) + ")"
				If selecting = -2 Then s = "(" + Trim(Str(xselect)) + ", " + Trim(Str(yselect)) + ")-" + s
				Draw String (coorpos - Len(s) * 4, ypanel + 430), s, RGB(200, 200, 200)
				If data_codes Then
					s = "Ter[" + Hex(grid(xgrid, ygrid).land) + "h]  Trk[" + Hex(grid(xgrid, ygrid).track) + "h]"
				Else
					s = Trim(tr(GetParent(xgrid, ygrid)).id)
				End If
				Draw String (coorpos - Len(s) * 4, ypanel + 446), s, RGB(200, 200, 200)
			End If
			ScreenUnlock
			
			exgrid = xgrid : eygrid = ygrid
		End If
		
		'Keyboard cursor
		ManageKeyboardCursor
		If inpalette <> 0 And updatepalette <> 0 Then
			ScreenLock
			DrawPanel
			Line (xpanel + xpalette + bigwidth * xpcursor, ypanel + ypalette + 22 * ypcursor) - Step (bigwidth - 1, 21), RGB(200, 200, 200), B
			ScreenUnlock
			updatepalette = 0
		End If
		
		'Mouse button actions
		If bm = 1 Then
			If selecting <> 0 AndAlso xgrid >= 1 And xgrid <= 30 And ygrid >= 1 And ygrid <= 30 Then
				selecting = -2	'Selecting with the mouse
				If xselect = 0 Then
					xselect = xgrid : yselect = ygrid
					x2select = xgrid : y2select = ygrid
					DrawTrack
				ElseIf xgrid <> x2select Or ygrid <> y2select Then
					x2select = xgrid : y2select = ygrid
					DrawTrack
				End If
			ElseIf pasting <> 0 AndAlso xgrid >= 1 And xgrid <= 30 And ygrid >= 1 And ygrid <= 30 Then
				tempboard = ""		'Accept paste
				pasting = 0
				xselect = 0
				DrawTrack
				PushUndo
				Do
					GetMouse xm, ym, wm, bm
				Loop Until bm <> 1
				modified = -1
			Else
				If xgrid >= 1 And xgrid <= 30 And ygrid >= 1 And ygrid <= 30 Then
					If xselect Then
						xselect = 0 : yselect = 0
						x2select = 0 : y2select = 0
						DrawTrack
						Do
							GetMouse xm, ym, wm, bm
						Loop Until bm <> 1 Or xm < xoffs
					ElseIf colouring_mode Then
						stroking = -1
						
						If stylex <> xgrid Or styley <> ygrid Then
							Dim As ULong last_bgc, last_border
							
							last_bgc = grid(xgrid, ygrid).bgc
							last_border = grid(xgrid, ygrid).border
							grid(xgrid, ygrid).bgc = current_bgc
							grid(xgrid, ygrid).border = current_border
							
							If last_border <> current_border Then
								DrawTrack
							ElseIf last_bgc <> current_bgc Then
								DrawSpot xgrid, ygrid
							End If
							
							stylex = xgrid : styley = ygrid
						End If
					Else
						stroking = -1
						
						'Update the track/terrain brush-like
						ScreenLock
						If current_page < 10 Then
							SetTrack xbrush, ybrush, current_brush
							vlast.x = xbrush : vlast.y = ybrush
						ElseIf current_page = 10 Then
							grid(xgrid, ygrid).land = current_terrain_brush
							DrawSpot xgrid, ygrid
						ElseIf current_page = 11 Then
							If current_terrain_brush >= 1 And current_terrain_brush <= 5 Then
								Flood xcross, ycross
							Else
								RaiseTerrain xcross, ycross
							End If
						End If
						ScreenUnlock
						modified = -1
					End If
				ElseIf xgrid >= -5 And xgrid <= -1 And ygrid >= 29 And ygrid <= 30 Then
					current_page = (xgrid + 5) + 5 * (ygrid - 29)
					DrawPanel
				ElseIf xgrid = 0 And ygrid >= 29 And ygrid <= 30 Then
					current_page = ygrid - 19
					DrawPanel
				ElseIf xgrid >= -5 And xgrid <= 0 And ygrid >= 22 And ygrid <= 27 Then
					If current_page < 10 Then
						current_brush = itr(xgrid + 5, ygrid - 22, current_page)
					ElseIf current_page >= 10 Then
						current_terrain_brush = itr(xgrid + 5, ygrid - 22, current_page)
					End If
					Sleep 200	'To avoid picking the wrong element
					DrawPanel
				ElseIf xgrid >= -13 And xgrid <= -7 And ygrid >= 29 And ygrid <= 30 Then
					SelectBackground
					DrawTrack
					DrawPanel
					modified = -1
				End If
			End If
		ElseIf bm = 2 Then
			If xgrid >= 1 And xgrid <= 30 And ygrid >= 1 And ygrid <= 30 Then
				If pasting Then
					Dim As Byte dx, dy
					
					'Cancel pasting
					dx = ASC(Left(tempboard, 1)) : dy = ASC(Mid(tempboard, 2, 1))
					xselect = xgrid - dx \ 2
					If xselect < 1 Then xselect = 1
					If xselect + dx - 1 > 30 Then xselect = 30 - dx + 1
					yselect = ygrid - dy \ 2
					If yselect < 1 Then yselect = 1
					If yselect + dy - 1 > 30 Then yselect = 30 - dy + 1
					PutTrack xselect, yselect, tempboard
					tempboard = ""
					pasting = 0
					xselect = 0
					DrawTrack
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 2
				ElseIf xselect Then
					xselect = 0 : yselect = 0
					x2select = 0 : y2select = 0
					DrawTrack
					Do
						GetMouse xm, ym, wm, bm
					Loop Until bm <> 2 Or xm < xoffs
				
				ElseIf colouring_mode Then
					stroking = -1
					
					If stylex <> xgrid Or styley <> ygrid Then
						Dim As ULong last_bgc, last_border
						
						last_bgc = grid(xgrid, ygrid).bgc
						last_border = grid(xgrid, ygrid).border
						grid(xgrid, ygrid).bgc = 0
						grid(xgrid, ygrid).border = 0
						
						If last_border <> 0 Then
							DrawTrack
						ElseIf last_bgc <> 0 Then
							DrawSpot xgrid, ygrid
						End If
						
						stylex = xgrid : styley = ygrid
					End If	
				Else
					stroking = -1
					
					ScreenLock
					If current_page < 10 Then
						ClearTrack xgrid, ygrid
					ElseIf current_page = 10 Then
						grid(xgrid, ygrid).land = 0
						DrawSpot xgrid, ygrid
					Else
						If current_terrain_brush >= 1 And current_terrain_brush <= 5 Then
							Dry xcross, ycross
						Else
							LowerTerrain xcross, ycross
						End If
					End If
					ScreenUnlock
					modified = -1
				End If
			End If
		ElseIf bm = 4 Then
			If xgrid >= 1 And xgrid <= 30 And ygrid >= 1 And ygrid <= 30 Then
				If colouring_mode Then
					current_bgc = grid(xgrid, ygrid).bgc
					current_border = grid(xgrid, ygrid).border
				Else
					PickTrack xgrid, ygrid
				End If
				DrawPanel
			End If
		ElseIf bm = 0 Then
			'Button released -> end of stroke
			If stroking Then
				stroking = 0
				PushUndo
			End If
			
			'Any styling is interrupted
			stylex = -1 : styley = -1
			
			'Final selection step
			If selecting = -2 Then selecting = 0
		End If
		
		ManageIcons
		
		'Handle F11 and F12 for GNU/Linux
		If specialkeylock = 0 Then
			If MultiKey(&H57) Then
				akey = Chr(255) + Chr(133)
				specialkeylock = -1
			ElseIf MultiKey(&H58) Then
				akey = Chr(255) + Chr(134)
				specialkeylock = -1
			End If
		ElseIf MultiKey(&H57) = 0 And MultiKey(&H58) = 0 Then
			specialkeylock = 0
		End If
	
		Select Case akey
			'--- Palette pages
			Case Chr(255) + Chr(59) To Chr(255) + Chr(68)
				'This check is necessary in GNU/Linux because the
				'FreeBasic compiler does not support Shift+Functional
				'keys in InKey for this platform.
				If MultiKey(&H2A) Or MultiKey(&H36) Then
					If ASC(Right(akey, 1)) = 59 Then
						current_page = 10
						DrawPanel
					ElseIf ASC(Right(akey, 1)) = 60 Then
						current_page = 11
						DrawPanel
					End If
				ElseIf current_page = 0 And akey = Chr(255, 59) Then
					current_page = previous_page
					DrawPanel
					Menu_Help
				Else
					previous_page = current_page
					current_page = ASC(Right(akey, 1)) - 59
					DrawPanel
				End If
			Case Chr(255) + Chr(133), Chr(255) + Chr(84)
				current_page = 10
				DrawPanel
			Case Chr(255) + Chr(134), Chr(255) + Chr(85)
				current_page = 11
				DrawPanel
			Case " "	'Switch between mountain and water
				If current_page = 11 Then
					If current_terrain_brush = 0 Or current_terrain_brush > 5 Then
						current_terrain_brush = 1
					Else
						current_terrain_brush = 6
					End If
					DrawPanel
				ElseIf current_page <= 10 Then
					SelectByTyping
				End If
			
			'---- Switches
			Case Chr(5)		'CTRL+E - Allow mixing track elements
				allow_errors = Not allow_errors
				DrawPanel
			Case Chr(4)		'CTRL+D - Display errors and warnings
				show_errors = Not show_errors
				DrawTrack : DrawPanel
			Case Chr(7)		'CTRL+G - Show grid
				show_grid = Not show_grid
				DrawTrack : DrawPanel
			Case Chr(17)	'CTRL+Q - Display codes instead of element names
				data_codes = Not data_codes
				DrawTrack : DrawPanel
			Case Chr(20)	'CTRL+T - Switch terrain affected by paste
				affect_terrain = Not affect_terrain
				If pasting Then updatepaste = -1
				DrawPanel
			Case Chr(11)	'CTRL+K - Switch track affected by paste
				affect_track = Not affect_track
				If pasting Then updatepaste = -1
				DrawPanel
			Case Chr(18)	'CTRL+R - Redraw track
				DrawTrack

				
			'---- Copying, pasting & co.
			Case Chr(3)		'CTRL+C - Copy
				CopyOrCut : DrawPanel
			Case Chr(24)	'CTRL+X - Cut
				CopyOrCut -1 : PushUndo : DrawPanel : modified = -1
			Case Chr(22)	'CTRL+V - Paste
				Paste : modified = -1
			Case Chr(26)	'CTRL+Z - Undo
				If Not pasting Then Undo : modified = -1
			Case Chr(25)	'CTRL+Y - Redo
				If Not pasting Then Redo : modified = -1
			Case Chr(23)	'CTRL+W - Whole-Track Selection / Deselection
				If pasting = 0 And bm = 0 Then
					If xselect Then
						xselect = 0
					Else
						xselect = 1 : yselect = 1
						x2select = 30 : y2select = 30
					End If
					DrawTrack
				End If
							
			'---- Check track
			Case "c", "C" :	CheckTrack
			
			'---- Save track image
			Case Chr(19)	'CTRL+S
				Menu_TrackShot
			Case Chr(8)		'CTRL+H - Provide hash value
				Error_Message "Current track hash value is " + Hex(Hash32), "Information requested"
				
			'---- Flipping and rotating
			Case "f"	'Horizontal flipping
				If pasting Then
					clipboard = HFlipTrack(clipboard)
					updatepaste = -1
				ElseIf xselect Then
					If selecting = 0 Then
						tempboard = GetTrack(xselect, yselect, x2select, y2select)
						tempboard = HFlipTrack(tempboard)
						PutTrack xselect, yselect, tempboard, -1
						DrawTrack
						PushUndo
					End If
				ElseIf current_page < 10 Then
					current_brush = tr(current_brush).hflip
					DrawPanel
				ElseIf current_page = 10 Then
					current_terrain_brush = ttr(current_terrain_brush).hflip
					DrawPanel
				End If
			Case "F"	'Vertical flipping
				If pasting Then
					clipboard = VFlipTrack(clipboard)
					updatepaste = -1
				ElseIf xselect Then
					If selecting = 0 Then
						tempboard = GetTrack(xselect, yselect, x2select, y2select)
						tempboard = VFlipTrack(tempboard)
						PutTrack xselect, yselect, tempboard, -1
						DrawTrack				
						PushUndo
					End If
				ElseIf current_page < 10 Then
					current_brush = tr(current_brush).vflip
					DrawPanel
				ElseIf current_page = 10 Then
					current_terrain_brush = ttr(current_terrain_brush).vflip
					DrawPanel
				End If
			Case "r"	'Clockwise rotation
				If pasting Then
					clipboard = CRotate(clipboard)
					updatepaste = -1
				ElseIf xselect Then
					If selecting = 0 Then
						If x2select - xselect = y2select - yselect Then
							tempboard = GetTrack(xselect, yselect, x2select, y2select)
							tempboard = CRotate(tempboard)
							PutTrack xselect, yselect, tempboard, -1
							DrawTrack
							PushUndo
							Do
								GetMouse xm, ym, wm, bm
							Loop Until bm <> 1
						Else
							NotASquare
						End If
					End If
				ElseIf current_page < 10 Then
					current_brush = tr(current_brush).cr
					DrawPanel
				ElseIf current_page = 10 Then
					current_terrain_brush = ttr(current_terrain_brush).cr
					DrawPanel
				End If
			Case "R"	'Counter-clockwise rotation
				If pasting Then
					clipboard = CCRotate(clipboard)
					updatepaste = -1
				ElseIf xselect Then
					If selecting = 0 Then
						If x2select - xselect = y2select - yselect Then
							tempboard = GetTrack(xselect, yselect, x2select, y2select)
							tempboard = CCRotate(tempboard)
							PutTrack xselect, yselect, tempboard, -1
							DrawTrack
							PushUndo
							Do
								GetMouse xm, ym, wm, bm
							Loop Until bm <> 1
						Else
							NotASquare
						End If
					End If
				ElseIf current_page < 10 Then
					current_brush = tr(current_brush).ccr
					DrawPanel
				ElseIf current_page = 10 Then
					current_terrain_brush = ttr(current_terrain_brush).ccr
					DrawPanel
				End If

			'---- Track element selectors
			Case "m", "M"	'Material (pavement, dirt, ice)
				Select Case current_brush
					Case 1 : current_brush = &H86
					Case &HB3 To &HB5 : current_brush -= &H2C
					Case &H86 To &H89 : current_brush += 13
					Case &H93 : current_brush = 1
					Case &H94 To &H96 : current_brush += &H1F
					Case 4, 5, 14, 15 : current_brush += 10
					Case 24, 25 : current_brush -= 20
					Case &H4A : current_brush = &H7D
					Case &H7D : current_brush = &H8A
					Case &H8A : current_brush = &H4A
					Case 6 To 9, &H10 To &H13 : current_brush += 10
					Case &H1A To &H1D : current_brush -= 20
					Case &HA To &HD, &H14 To &H17 : current_brush += 10
					Case &H1E To &H21 : current_brush -= 20
					Case &H4B To &H52 : current_brush += &H33
					Case &H7E To &H85 : current_brush += 13
					Case &H8B To &H92 : current_brush -= &H40
				End Select
				DrawPanel
			Case "u", "U" : LinkTiles
			Case "a" To "o", "q" To "w"
				SmartSelect akey
			Case "A" To "O", "Q" To "W"
				SmartSelect akey, -1
			'~ Case "s", "S"	'Street, straightway
				'~ If tr(current_brush).entity = ASC("s") Then
					'~ current_brush = tr(current_brush).cr
				'~ Else
					'~ Dim m As Byte
					'~ 
					'~ If tr(current_brush).material >= 1 And tr(current_brush).material <= 3 Then
						'~ m = tr(current_brush).material
					'~ Else
						'~ m = 1
					'~ End If
					'~ 
					'~ For i As UByte = 1 To 190
						'~ If tr(i).material = m And tr(i).entity = ASC("s") Then
							'~ current_brush = i
							'~ Exit For
						'~ End If
					'~ Next i
				'~ End If
				'~ DrawPanel
			Case "x", "X"
				current_brush = 255
				DrawPanel
			Case "y", "Y"
				current_brush = 254
				DrawPanel
			Case "z", "Z"
				current_brush = 253
				DrawPanel
			Case "\"
				If allow_errors Then
					Dim temp_s As String, temp_t As Double
					Dim temp_key As String
					
					Line (xpanel + 77, ypanel + ypalette + 33)- Step (43, 43), RGB(30, 30, 50), BF
					PutIcon 19, 18, xpanel + 77, ypanel + ypalette + 33
					PutIcon 20, 18, xpanel + 99, ypanel + ypalette + 33
					PutIcon 19, 19, xpanel + 77, ypanel + ypalette + 55
					PutIcon 20, 19, xpanel + 99, ypanel + ypalette + 55
					
					'You have to type a hex value in three seconds
					temp_t = Timer
					temp_s = ""
					Do
						temp_key = InKey
						Select Case temp_key
							Case "0" To "9", "a" To "f", "A" To "F"
								temp_s &= temp_key
								If Len(temp_s) = 2 Then Exit Do
							Case Chr(8), Chr(255, 83)
								temp_s = ""
								temp_t = Timer
							Case ""
							Case Else
								temp_s = ""
								Exit Do
						End Select
					Loop Until Timer >= temp_t + 3
					
					If Len(temp_s) Then
						If current_page = 10 Then
							current_terrain_brush = Abs(ValInt("&H" + temp_s))
						Else
							current_brush = Abs(ValInt("&H" + temp_s))
						End If
					End If
				End If
				DrawPanel
			'~ Case "a" To "o", "q" To "w"
				'~ For i As Short = current_brush + 1 To current_brush + 256
					'~ Dim ii As Short
					'~ 
					'~ ii = i Mod 256
					'~ If tr(ii).entity = ASC(LCase(akey)) Or tr(ii).entity = ASC(UCase(akey)) Then
						'~ current_brush = ii
						'~ Exit For
					'~ End If
				'~ Next i
				'~ DrawPanel
			'~ Case "A" To "O", "Q" To "W"
				'~ For i As Short = current_brush + 255 To current_brush Step -1
					'~ Dim ii As Short
					'~ 
					'~ ii = i Mod 256
					'~ If tr(ii).entity = ASC(LCase(akey)) Or tr(ii).entity = ASC(UCase(akey)) Then
						'~ current_brush = ii
						'~ Exit For
					'~ End If
				'~ Next i
				'~ DrawPanel
			
			'--- Move keyboard cursor
			Case Chr(255, 72), Chr(255, 141)
				If inpalette Then
					If ypcursor > 0 Then ypcursor -= 1  : updatepalette = -1
				Else
					If selecting = -1 Then
						xselect = xcursor : x2select = xcursor
						yselect = ycursor : y2select = ycursor
						selecting = -3	'Selecting with the keyboard
					End If
					If ycursor > 1 Then ycursor -= 1
					If selecting = -3 Then
						x2select = xcursor
						y2select = ycursor
						DrawTrack
					End If
					If pasting Then
						SetMouse xoffs + (xcursor - 1) * bigwidth + 11, yoffs + (ycursor - 1) * 22 + 11
						DrawTrack
					End If
				End If
			Case Chr(255, 80), Chr(255, 145)
				If inpalette Then
					If ypcursor < 5 Then ypcursor += 1  : updatepalette = -1
				Else
					If selecting = -1 Then
						xselect = xcursor : x2select = xcursor
						yselect = ycursor : y2select = ycursor
						selecting = -3	'Selecting with the keyboard
					End If
					If ycursor < 30 Then ycursor += 1
					If selecting = -3 Then
						x2select = xcursor
						y2select = ycursor
						DrawTrack
					End If
					If pasting Then
						SetMouse xoffs + (xcursor - 1) * bigwidth + 11, yoffs + (ycursor - 1) * 22 + 11
						DrawTrack
					End If
				End If
			Case Chr(255, 75), Chr(255, 115)
				If inpalette Then
					If xpcursor > 0 Then xpcursor -= 1  : updatepalette = -1
				Else
					If selecting = -1 Then
						xselect = xcursor : x2select = xcursor
						yselect = ycursor : y2select = ycursor
						selecting = -3	'Selecting with the keyboard
					End If
					If xcursor > 1 Then xcursor -= 1
					If selecting = -3 Then
						x2select = xcursor
						y2select = ycursor
						DrawTrack
					End If
					If pasting Then
						SetMouse xoffs + (xcursor - 1) * bigwidth + 11, yoffs + (ycursor - 1) * 22 + 11
						DrawTrack
					End If
				End If
			Case Chr(255, 77), Chr(255, 116)
				If inpalette Then
					If xpcursor < 5 Then xpcursor += 1 : updatepalette = -1
				Else
					If selecting = -1 Then
						xselect = xcursor : x2select = xcursor
						yselect = ycursor : y2select = ycursor
						selecting = -3	'Selecting with the keyboard
					End If
					If xcursor < 30 Then xcursor += 1
					If selecting = -3 Then
						x2select = xcursor
						y2select = ycursor
						DrawTrack
					End If
					If pasting Then
						SetMouse xoffs + (xcursor - 1) * bigwidth + 11, yoffs + (ycursor - 1) * 22 + 11
						DrawTrack
					End If
				End If
			Case Chr(13)	'Insert element
				If inpalette Then
					If current_page < 10 Then
						current_brush = itr(xpcursor, ypcursor, current_page)
					ElseIf current_page = 10 Then
						current_terrain_brush = itr(xpcursor, ypcursor, current_page)
					End If
					DrawPanel
				ElseIf pasting Then
					tempboard = ""		'Accept paste
					pasting = 0 : xselect = 0
					DrawTrack : PushUndo
					modified = -1
				ElseIf xselect Then
					modified = -1
					BuildClosedCircuit
				Else
					modified = -1
					If colouring_mode Then
						Dim As ULong last_bgc, last_border
							
						last_bgc = grid(xcursor, ycursor).bgc
						last_border = grid(xcursor, ycursor).border
						grid(xcursor, ycursor).bgc = current_bgc
						grid(xcursor, ycursor).border = current_border
						
						If last_border <> current_border Then
							DrawTrack
						ElseIf last_bgc <> current_bgc Then
							DrawSpot xcursor, ycursor
						End If
					ElseIf current_page < 10 Then
						SetTrack xcursor, ycursor, current_brush
						vlast.x = xcursor : vlast.y = ycursor
					ElseIf current_page = 10 Then
						grid(xcursor, ycursor).land = current_terrain_brush
						DrawSpot xcursor, ycursor
					End If
					PushUndo
					ManageKeyboardCursor -1
				End If
			Case Chr(255, 83)	'Delete element or block
				modified = -1
				If xselect Then
					If selecting = 0 Then
						For j As Byte = yselect To y2select
							For i As Byte = xselect To x2select
								If affect_track Then grid(i, j).track = 0
								If affect_terrain Then grid(i, j).land = 0
								If colouring_mode Then grid(i, j).border = 0 : grid(i, j).bgc = 0
							Next i
						Next j
						xselect = 0
						DrawTrack
					End If					
				Else
					If current_page < 10 Then
						ClearTrack xcursor, ycursor
					ElseIf current_page = 10 Then
						grid(xcursor, ycursor).land = 0
						DrawSpot xgrid, ygrid
					End If
					ManageKeyboardCursor -1
				End If
				PushUndo
			Case "p", "P"	'Pick element
				If drawkeyboardcursor Then
					If colouring_mode Then
						current_border = grid(xcursor, ycursor).border
						current_bgc = grid(xcursor, ycursor).bgc
					Else
						PickTrack xcursor, ycursor
					End If
					DrawPanel
				ElseIf xgrid >= 1 And xgrid <= 30 And ygrid >= 1 And ygrid <= 30 Then
					If colouring_mode Then
						current_border = grid(xgrid, ygrid).border
						current_bgc = grid(xgrid, ygrid).bgc
					Else
						PickTrack xgrid, ygrid
					End If
					DrawPanel
				End If
			Case Chr(9)		'Switch between track and palette
				If colouring_mode Then
					Menu_Colouring
				Else
					inpalette = Not inpalette
					updatepalette = -1
					DrawPanel
					ManageKeyboardCursor -1
				End If
			'~ Case Chr(255, 82)	'INSERT: Activate path creating tool
				'~ If drawkeyboardcursor Then
					'~ CreatePath
					'~ DrawTrack
				'~ End If
			Case Chr(15)	'Switch colouration
				colouring_mode = Not colouring_mode
				DrawPanel
			Case Chr(27), Chr(255) + "k" : QuitProgram
		End Select
	Loop
End Sub
