File: modWP_Levels.lua

package info (click to toggle)
freedroidrpg 1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, sid
  • size: 273,532 kB
  • sloc: ansic: 66,191; cpp: 2,033; sh: 766; makefile: 627; python: 322; xml: 94; perl: 87
file content (1110 lines) | stat: -rw-r--r-- 46,862 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
--[[

	Copyright (c) 2014 Scott Furry

	This file is part of Freedroid

	Freedroid 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; either version 2 of the License, or
	(at your option) any later version.

	Freedroid 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 Freedroid; see the file COPYING. If not, write to the
	Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
	MA  02111-1307  USA

]]--
--	lua module for parsing FDRPG data files for ship/level data
local modWP_Levels = {}
--	modWPCommon reference
modWP_Levels.modcommon = {}
--	Variable will contain all parsed level information after completion of ProcessLevelData()
modWP_Levels.AllLevelData = {}
--	levels.dat file parsed line-by-line into table
modWP_Levels.LevelFileData = {}
--	Storage of 2D grid map of levels at ground
modWP_Levels.GridData = {}
--	table of level numbers at ground stored by level number
modWP_Levels.LevelsGround = {}
--	table of level numbers found not at ground stored by level number
modWP_Levels.LevelsNonGround = {}
--	table of level numbers used as tutorial levels stored by level number
modWP_Levels.LevelsTutorial = {}
--	table of level numbers used for debugging/development stored by level number
modWP_Levels.LevelsDebug = {}
--	names in table format for looping
modWP_Levels.TypeNames = {
	{ name = "LevelsGround", display = "Ground Levels"},
	{ name = "LevelsNonGround", display = "Non-Ground Levels"},
	{ name = "LevelsTutorial", display = "Tutorial Levels"},
	{ name = "LevelsDebug", display = "Debug Levels"},
}
--	table of level numbers found to be unconnected/unused in FDRPG stored by level number
modWP_Levels.LevelsUnreferenced = {}
--	table of filepaths to FDRPG files used for parsing level information
modWP_Levels.files = { levels="" }
-- names of required modules - "modWP_Events"
modWP_Levels.requiredModules = { moduleNames[2].id }
--	Default grid center - value will change after trimming
modWP_Levels.grid_center = {
	x = 11,
	y = 11
}
--	Default grid size - value will change after trimming
modWP_Levels.grid_max = {
	x = 21,
	y = 21
}

--	"struct" of level information for each level
modWP_Levels.leveldata = {
	levelnumber	= -1,
	levelname="",
	bgSong = "",
	is_groundlvl = false,
	is_tutorial	= false,
	is_debug	= false,
	is_random	= false,
	levelnumber_north = -1,
	levelnumber_east = -1,
	levelnumber_south = -1,
	levelnumber_west = -1,
	levelnumber_above = -1,
	levelnumber_below = {-1},
	xlen = 0,
	ylen = 0
}

--	text items for Level parsing and presentation
--	id) key, label) text to display loop) can use in loop when search for text
--	srchPatn) text/pattern to use to find data
--	extrctPatn) how to extract found data
modWP_Levels.textLevel = {
	{ id = "levelnumber",       label = "Level Number",           loop = true,  srchPatn = "Levelnumber:%s*[%d]+",        extrctPatn = "[%d]+",  },
	{ id = "levelname",         label = "Level Name",             loop = true,  srchPatn = "Name of this level=",         extrctPatn = "[EOL]",  },
	{ id = "levelsize",         label = "Level Size",             loop = false, srchPatn = "" },
	{ id = "xlen",              label = "X: ",                    loop = true,  srchPatn = "xlen of this level:%s*[%d]+", extrctPatn = "[%d]+",  },
	{ id = "ylen",              label = "Y: ",                    loop = true,  srchPatn = "ylen of this level:%s*[%d]+", extrctPatn = "[%d]+",  },
	{ id = "bgSong",            label = "Background Song",        loop = true,  srchPatn = "BgSong=",                     extrctPatn = "[EOL]",  },
	{ id = "is_groundlvl",      label = "Level at Ground",        loop = false, srchPatn = "" },
	{ id = "is_tutorial",       label = "Tutorial Level",         loop = false, srchPatn = "tutorial",                    extrctPatn = "[TEXT]", },
	{ id = "is_debug",          label = "Debug Level",            loop = false, srchPatn = "debug level",                 extrctPatn = "[TEXT]", },
	{ id = "debugcomment",      label = "",                       loop = false, srchPatn = "%s+%-%-%s+",                  extrctPatn = "[TEXT]", },
	{ id = "is_unref",          label = "Unreferenced Level",     loop = false, srchPatn = "" },
	{ id = "is_random",         label = "Random Generated Level", loop = true,  srchPatn = "random dungeon:%s*[%d]+",     extrctPatn = "[%d]+",  },
	{ id = "levelnumber_north", label = "Level To North",         loop = true,  srchPatn = "jump target north:%s*[%d]+",  extrctPatn = "[%d]+",  },
	{ id = "levelnumber_east",  label = "Level To East",          loop = true,  srchPatn = "jump target east:%s*[%d]+",   extrctPatn = "[%d]+",  },
	{ id = "levelnumber_south", label = "Level To South",         loop = true,  srchPatn = "jump target south:%s*[%d]+",  extrctPatn = "[%d]+",  },
	{ id = "levelnumber_west",  label = "Level To West",          loop = true,  srchPatn = "jump target west:%s*[%d]+",   extrctPatn = "[%d]+",  },
	{ id = "levelnumber_above", label = "Level Above",            loop = false, srchPatn = "" },
	{ id = "levelnumber_below", label = "Levels Below",           loop = false, srchPatn = "" },
	{ id = "end_of_level",      label = "end_of_level",           loop = true,  srchPatn = "end_of_level",                extrctPatn = "[TEXT]", },
}

--	test if level is a debug level
--	[in]	levelnumber	level to be tested
--	[ret]	boolean - Y level is debug
function modWP_Levels.LevelIsDebug( levelnumber )
	local retval = false
	local levelindex = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelnumber, "levelnumber" )
	if (( levelindex ~= nil ) and ( levelindex > 0 )) then
		retval = modWP_Levels.AllLevelData[levelindex].is_debug
	end
	return retval
end

--	test if level is a tutorial level
--	[in]	levelnumber	level to be tested
--	[ret]	boolean - Y level is tutorial
function modWP_Levels.LevelIsTutorial( levelnumber )
	local retval = false
	local levelindex = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelnumber, "levelnumber" )
	if (( levelindex ~= nil ) and ( levelindex > 0 )) then
		retval = modWP_Levels.AllLevelData[levelindex].is_tutorial
	end
	return retval
end

--	Look up leveldata variable value and its presentation label
--	Assumes AllLevelData is populated and processed
--	[in]	levelitemdata	object under examination (type leveldata)
--	[in]	idvalue	search id - same as id value in textLevel
--	[ret]	pair of strings representing label|data from level item
function modWP_Levels.GetLevelItemStringsPair( levelitemdata, idvalue )
	local retLabel, retData = "", ""
	if (( idvalue == nil ) or ( type(idvalue) ~= 'string' ) or ( idvalue:len() <= 0 )) then
		return retLabel, retData
	end
	local labelitem = select(1,modWP_Levels.modcommon.Extract.GetTableItem( modWP_Levels.textLevel, "id", idvalue))
	if ( labelitem ~= nil ) then
		retLabel = labelitem.label
		if	( idvalue == "is_unref") then
			retData = ""
		elseif ( idvalue == "levelnumber_below" ) then
			local str = ""
			for key,value in pairs(levelitemdata[idvalue]) do
				if (key > 1) then
					str = str .. " " .. tostring(value)
				else
					str = tostring(value)
				end
			end
			retData = str
		elseif ( idvalue == "levelsize" ) then
			retData = string.format("%02d X %02d ", levelitemdata.xlen, levelitemdata.ylen )
		else
			retData = tostring(levelitemdata[idvalue])
		end
	end
	return retLabel, retData
end

--	Read in FDRPG levels and events data file.
--	Process extracted information for different level
--	information: ground, underground, debug and tutorial.
--	All level information is saved into lua tables.
--	A map of the levels at ground (GridData) is also produced.
function modWP_Levels.ProcessData()
	modWP_Levels.modcommon = require("modWPCommon")
	--	test for presence of source data files
	modWP_Levels.files.levels = tostring(modWP_Levels.modcommon.paths.srcData .. modWP_Levels.modcommon.datafiles["levels"])
	modWP_Levels.modcommon.Test.Files(modWP_Levels.files)
	--	read levels.dat and process into table objects
	modWP_Levels.LevelFileData = modWP_Levels.modcommon.Process.FileToLines(modWP_Levels.files["levels"])
	modWP_Levels.ParseLevel()
	--	generate grid from parsed Level Data
	modWP_Levels.GridProcess()
	--	update level data to reflect all found ground levels
	--	if its referenced in the "map" grid - that level is at the same level as town or "ground level"
	--	populate modWP_Levels.LevelsGround -> making 1D table of levels at ground from grid
	for y = 1, modWP_Levels.grid_max.y do
		for x = 1, modWP_Levels.grid_max.x do
			local value = tonumber(modWP_Levels.GridSquareRead(x,y))
			if (value >= 0) then
				table.insert(modWP_Levels.LevelsGround, value)
			end
		end
	end
	if (#modWP_Levels.LevelsGround > 0) then
		table.sort(modWP_Levels.LevelsGround)
	end
	local templeveldata = {}
	for key,value in pairs(modWP_Levels.LevelsGround) do
		local index = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, value, "levelnumber" )
		templeveldata = modWP_Levels.AllLevelData[index]
		templeveldata.is_groundlvl = true
	end
	--	flag the rest - except debug/tutorial - as NON-ground levels (i.e. subterrainian)
	for key, LevelItem in pairs(modWP_Levels.AllLevelData) do
		if (not(LevelItem.is_groundlvl) and not(LevelItem.is_debug) and not(LevelItem.is_tutorial)) then
			table.insert(modWP_Levels.LevelsNonGround, LevelItem.levelnumber)
		end
	end
	modWP_Levels.events = require(modWP_Levels.requiredModules[1])
	assert( modWP_Levels.events ~= nil )
	--	find all transport events - used to map the vertical connections between levels
	for key, event in pairs(modWP_Levels.events.AllEventData) do
		if (( event.trigger == "teleport" ) and ( event.teleport ~= nil )) then
			local indexP = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, event.teleport.lvlA, "levelnumber" )
			local indexQ = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, event.teleport.lvlB, "levelnumber" )
			modWP_Levels.ProcessParentChild( indexP, indexQ )
		end
	end
	--	found all ground/non-ground/debug/tutorial levels
	--	anything left over is "unreferenced" - flag and update tables with this data
	for key,LevelItem in pairs(modWP_Levels.AllLevelData) do
		if (not(modWP_Levels.hasreferenceIndex(key))) then
			modWP_Levels.LevelsUnreferenced[#modWP_Levels.LevelsUnreferenced + 1] = LevelItem.levelnumber
			--	remove reference of this level in other tables
			for subkey, tbl in pairs(modWP_Levels.TypeNames) do
				local levelindex = modWP_Levels.GetIndexByValue(modWP_Levels[tbl.name],LevelItem.levelnumber)
				if (levelindex > 0) then
					table.remove(modWP_Levels[tbl.name],levelindex)
				end
			end	--	loop through other tables
		end	--	Level as reference
		LevelItem["urlAnchor"] = modWP_Levels.modcommon.Wiki.HLink .. modWP_Levels.modcommon.Wiki.WikifyLink( "level" .. LevelItem.levelnumber )
	end	--	loop through AllLevelData
	modWP_Levels.GridTrim()
end

-- retrieve the anchortext associated with this id value
function modWP_Levels.GetItemUrlText( idvalue )
	local retText = ""
	if ( not idvalue ) then
		return retText
	end
	local index = 	modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, idvalue, "levelnumber" )
	if ( index ) then
		local item = modWP_Levels.AllLevelData[index]
		retText = modWP_Levels.modcommon.outputfilenames.levels .. item.urlAnchor
	end
	return retText
end

--	Process Level Above/Below for each level in AllLevelData table
--	Action to update AllLevelData - level above/below information.
--	Assumes AllLevelData is populated and processed.
--	[in]	indexP	index value of level in AllLevelData to be processed
--	[in]	indexQ	index value of level in AllLevelData to be processed
function modWP_Levels.ProcessParentChild( indexP, indexQ )
	local parentlvl = -1
	local childlvl = -1
	local parentdata = {}
	local childdata = {}
	local parentIsP = false
	--	determine which of these two numbers is the "parent" level (above)
	local isgroundP = (modWP_Levels.AllLevelData[indexP].is_groundlvl)
	local isgroundQ = (modWP_Levels.AllLevelData[indexQ].is_groundlvl)
	if ((isgroundP) and (not(isgroundQ))) then
		parentIsP = true
	elseif ((not(isgroundP)) and (isgroundQ)) then
		parentIsP = false
	else
		--	which level is closer to ground level?
		--	and would indicate it has been processed/touched
		if (       (modWP_Levels.AllLevelData[indexP].levelnumber_above >= 0)
			and not(modWP_Levels.AllLevelData[indexQ].levelnumber_above >= 0)) then
			parentIsP = true
		elseif (not(modWP_Levels.AllLevelData[indexP].levelnumber_above >= 0)
				and (modWP_Levels.AllLevelData[indexQ].levelnumber_above >= 0)) then
			parentIsP = false
		else
			--	unable to determine parent level
			parentIsP = nil
		end
	end
	if (parentIsP ~= nil) then
		if (parentIsP) then
			--	process modWP_Levels.AllLevelData[indexP] as parent
			--	process modWP_Levels.AllLevelData[indexQ] as child
			parentdata = modWP_Levels.AllLevelData[indexP]
			childdata = modWP_Levels.AllLevelData[indexQ]
		else
			--	process modWP_Levels.AllLevelData[indexP] as child
			--	process modWP_Levels.AllLevelData[indexQ] as parent
			parentdata = modWP_Levels.AllLevelData[indexQ]
			childdata = modWP_Levels.AllLevelData[indexP]
		end
		parentlvl = parentdata.levelnumber
		childlvl = childdata.levelnumber
		--process child/parent values
		local foundlvl = false
		if (not modWP_Levels.hasChildren(parentdata.levelnumber)) then
			parentdata.levelnumber_below = {}
			parentdata.levelnumber_below[#parentdata.levelnumber_below + 1] = childlvl
			foundlvl = true
		else
			for key,lvldata in pairs(parentdata.levelnumber_below) do
				if (lvldata == childlvl) then
					--	found the child level number in the list
					foundlvl = true
					break
				end
			end
		end
		if (not(foundlvl)) then
			parentdata.levelnumber_below[#parentdata.levelnumber_below + 1] = childlvl
		end
		childdata.levelnumber_above = parentlvl
		if (parentIsP) then
			modWP_Levels.AllLevelData[indexP] = parentdata
			modWP_Levels.AllLevelData[indexQ] = childdata
		else
			modWP_Levels.AllLevelData[indexP] = childdata
			modWP_Levels.AllLevelData[indexQ] = parentdata
		end
	end	--	test flag parentIsP
end

--	Find the location of a level number in a table
--	[in]	tabletosearch	table to search for levelnumbertosearch
--	[in]	levelnumbertosearch	level number of level to find
--	[ret]	table index or -1 if not found
function modWP_Levels.GetIndexByValue( tabletosearch, levelnumbertosearch, keyvalue )
	local index = -1
	for key,value in pairs(tabletosearch) do
		local match = false
		if ( keyvalue ~= nil ) then
			match = (value[keyvalue] == levelnumbertosearch)
		else
			match = (value == levelnumbertosearch)
		end
		if (match) then
			index = key
			break
		end
	end
	return index
end

--	Determine if level under examination has levels below ( using level number )
--	[in]	levelnumbertosearch level number of level under examination
--	[ret]	boolean (True = level has levels below)
function modWP_Levels.hasChildren( levelnumbertosearch )
	local index = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelnumbertosearch, "levelnumber" )
	return modWP_Levels.hasChildrenIndex(index)
end

--	Determine if level under examination has levels below ( using table index )
--	[in]	location index in AllLevelData table of level under examination
--	[ret]	boolean (True = level has levels below)
function modWP_Levels.hasChildrenIndex( index )
	local boolreturn = false
	if (modWP_Levels.AllLevelData[index].levelnumber ~= -1) then
		--	not a valid level item
		for key,value in pairs(modWP_Levels.AllLevelData[index].levelnumber_below) do
			if (value >= 0) then
				boolreturn = true
				break
			end
		end
	end
	return boolreturn
end
--	Determine if level is connected to any another levels ( using level number )
--	check the processed level data for connections N/S/E/W, above and below.
--	[in]	levelnumbertosearch	level number to be examined
--	[ret]	boolean - does level have any connections? (true = yes)
function modWP_Levels.hasreference( levelnumbertosearch )
	local index = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelnumbertosearch, "levelnumber" )
	return modWP_Levels.hasreferenceIndex(index)
end
--	Determine if level is connected to another level ( using table index )
--	check the processed level data for connections N/S/E/W, above and below
--	[in]	index	table index in AllLevelData of level to be examined
--	[ret]	boolean - does level have any connections? (true = yes)
function modWP_Levels.hasreferenceIndex( index )
	local hasref = false
	if (index >= 1) then
		hasref	=	((modWP_Levels.AllLevelData[index].levelnumber_north >= 0)
					or	(modWP_Levels.AllLevelData[index].levelnumber_east >= 0)
					or	(modWP_Levels.AllLevelData[index].levelnumber_south >= 0)
					or	(modWP_Levels.AllLevelData[index].levelnumber_west >= 0)
					or	(modWP_Levels.AllLevelData[index].levelnumber_above >= 0)
					or	(modWP_Levels.AllLevelData[index].is_tutorial)
					or	(modWP_Levels.AllLevelData[index].is_debug)
					or	modWP_Levels.hasChildrenIndex(index)
					)
	end
	return hasref
end

--	Process file data for all level information
--	Populates AllLevelData variable
function modWP_Levels.ParseLevel()
	local templeveldataitem = modWP_Levels.modcommon.Extract.TblDeepCopy(modWP_Levels.leveldata)
	for key,line in pairs(modWP_Levels.LevelFileData) do
		for key, textitem in pairs(modWP_Levels.textLevel) do
			if ( not textitem.loop ) then goto PARSE_LEVEL_NEXT_PATTERN end
			--	pattern can be used in a loop
			local value = modWP_Levels.modcommon.Extract.SearchText( line, textitem.srchPatn, textitem.extrctPatn )
			if ( not value ) then goto PARSE_LEVEL_NEXT_PATTERN end
			--	data returned from search/extract
			if ( textitem.id == "end_of_level" ) then
				if ( value == textitem.id ) then
					modWP_Levels.AllLevelData[#modWP_Levels.AllLevelData + 1] = templeveldataitem
					templeveldataitem = nil
					templeveldataitem = modWP_Levels.modcommon.Extract.TblDeepCopy(modWP_Levels.leveldata)
				end
			elseif ( textitem.id == "levelname" ) then
				templeveldataitem[textitem.id] = value
				value = string.lower(templeveldataitem.levelname)
				local subitem = select(1,modWP_Levels.modcommon.Extract.GetTableItem( modWP_Levels.textLevel, "id", "is_tutorial" ))
				local subvalue = modWP_Levels.modcommon.Extract.SearchText( value, subitem.srchPatn, subitem.extrctPatn )
				if ( subvalue ) then
					templeveldataitem.is_tutorial = true
					table.insert(modWP_Levels.LevelsTutorial, templeveldataitem.levelnumber)
				end
				local subitem = select(1,modWP_Levels.modcommon.Extract.GetTableItem( modWP_Levels.textLevel, "id", "is_debug" ))
				local subvalue = modWP_Levels.modcommon.Extract.SearchText( value, subitem.srchPatn, subitem.extrctPatn )
				if ( subvalue ) then
					templeveldataitem.is_debug = true
					table.insert(modWP_Levels.LevelsDebug, templeveldataitem.levelnumber)
					--	remove everything after " -- " -> debug commentary
					textpattern = select(1,modWP_Levels.modcommon.Extract.GetTableItem( modWP_Levels.textLevel, "id", "debugcomment"))
					local debug_start = select(1,templeveldataitem.levelname:find(textpattern.srchPatn))
					if (debug_start) then
						templeveldataitem.levelname = templeveldataitem.levelname:sub(1,(debug_start - 1))
					end
				end
			elseif ( textitem.id == "is_random" ) then
				templeveldataitem.is_random = (value ~= 0)
			else
				templeveldataitem[textitem.id] = value
			end --	process found data based on id
			break	--	goto next line of text
::PARSE_LEVEL_NEXT_PATTERN::
		end	--	loop through textLevel table looking for patterns
	end	--	loop through level file data
end

--	Grid Function - Process level data parsed from file into a 2D grid of levels at ground level
--	GridProcess takes the populated AllLevelData variable and attempts to build a 2D grid \"map\" of levels at ground.
--
--	Process starts with an arbitrary grid containing values all set to -1. The grid is sized to the values in grid_max.
--
--	Processing starts with the Level 0 (or \"Town" Level) located at variable grid_center. The level numbers in the
--	surrounding squares (N, E, S and W) are populated (GridSquarePopulateCross()). The \"focus\" (or CurGridLoc) is
--	changed and the process is repeated until the entire grid is populated.
--
--	Changing \"focus\" is done in a spiral movement outwards from Level 0 moving in the sequence of directions
--	S -> E -> N -> W - uses fn GridSquareChangeFocus(). Spiral movement is achieved by changing the value of curDirWriteLimit.
--	When curDirWriteLimit is equal to MaxWriteLimit(how far to write for a given direction), the direction of movement for
--	focus is changed (S -> E -> N -> W).
--
--	For example, after writing the town square, focus is moved south one, east one.
--	curDirWriteLimit is found to be equal to MaxWriteLimit. MaxWriteLimit is incremented. curDirWriteLimit is reset to 1.
--	Focus is moved north then west. curDirWriteLimit increments to two - equal to MaxWriteLimit. MaxWriteLimit is incremented.
--	curDirWriteLimit is reset to 1. Focus is moved south then east. The sequence is repeated until the grid is populated.
--
--	If grid square under focus does not have a level number( value is -1), focus is changed and processing continues with
--	the next square in sequence.
--
--	The function GridSquarePopulate() prevents bad data being stored by limiting stored values to be > 1.
--
--	Note: grid definition -> Y is row data and X is column data.
--	If accessing the grid directly, do so by using GridData[y][x], or use the function GridSquareRead().
function modWP_Levels.GridProcess()
	--	populate the grid with default value
	for y = 1, modWP_Levels.grid_max.y do
		modWP_Levels.GridData[y] = {}
		for x = 1, modWP_Levels.grid_max.x do
			modWP_Levels.GridSquarePopulate( x, y, -1 )
		end
	end
	--	fill in center square - town
	local CurGridLoc = { x = modWP_Levels.grid_center.x, y = modWP_Levels.grid_center.y }
	local CurLvlIndex = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, 0 , "levelnumber" )
	modWP_Levels.GridSquarePopulate(CurGridLoc.x, CurGridLoc.y, modWP_Levels.AllLevelData[CurLvlIndex].levelnumber)
	modWP_Levels.GridSquarePopulateCross(CurGridLoc.x, CurGridLoc.y, modWP_Levels.AllLevelData[CurLvlIndex])
	--	now fill in map levels for the rest of grid
	local MaxWriteLimit = 2 * (modWP_Levels.grid_center.x - 2) + 1
	local curDirWriteLimit = 1
	local curdir = 1	--	start filling in other grid squares going south
	CurGridLoc.x, CurGridLoc.y = modWP_Levels.GridSquareChangeFocus(CurGridLoc.x , CurGridLoc.y, curdir)
	local curlvl = modWP_Levels.GridSquareRead(CurGridLoc.x, CurGridLoc.y)
	--	we spiral outwards from center(town - level 0) going S(1)/E(2)/N(3)/W(4)
	local countOfWriteCurDir = 1
	local index = -1
	while (curDirWriteLimit < MaxWriteLimit) do
		if (curlvl >= 0) then
			index = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, curlvl , "levelnumber" )
			modWP_Levels.GridSquarePopulateCross(CurGridLoc.x, CurGridLoc.y, modWP_Levels.AllLevelData[index])
		end
		countOfWriteCurDir = countOfWriteCurDir + 1
		if (countOfWriteCurDir > curDirWriteLimit) then
			curdir = curdir + 1
			if (curdir > 4) then
				curdir = 1
			end
			if ((curdir == 1) or (curdir == 3)) then
				curDirWriteLimit = curDirWriteLimit + 1
			end
			countOfWriteCurDir = 1
		end
		if (curlvl < 0) then
			CurGridLoc.x, CurGridLoc.y = modWP_Levels.GridSquareChangeFocus(CurGridLoc.x , CurGridLoc.y, curdir)
			curlvl = modWP_Levels.GridSquareRead(CurGridLoc.x, CurGridLoc.y)
		else
			curlvl = modWP_Levels.GridGetNextSquare(modWP_Levels.AllLevelData[index], curdir)
			CurGridLoc.x, CurGridLoc.y = modWP_Levels.GridSquareChangeFocus(CurGridLoc.x , CurGridLoc.y, curdir)
		end
	end
end

--	Grid Function - Read Level Number at a grid location
--	This is a convenience function to hide the details of grid construction.
--	See description of GridProcess()
--	[in]	localx	X coordinate of grid square data to read
--	[in]	localy	Y coordinate  of grid square data to read
--	[ret]	Level number found stored at grid square(x,y)
function modWP_Levels.GridSquareRead( localx, localy )
	return modWP_Levels.GridData[localy][localx]
end
--	Grid Function - Write Level Number to a grid location
--	This is a convenience function to hide the details of grid construction.
--	See description of GridProcess()
--	[in]	localx	X coordinate of grid square data to write
--	[in]	localy	Y coordinate  of grid square data to write
--	[in]	value	Level number to be written at grid square(x,y)
function modWP_Levels.GridSquarePopulate( localx, localy, value )
	local gridvalue = modWP_Levels.GridData[localy][localx]
	if (gridvalue ~= value) then
		modWP_Levels.GridData[localy][localx] = value
	end
end

--	Grid Function - Populate grid data in a N/S/E/W direction from current grid square in focus
--	[in]	cross_center_x	X coordinate of current grid square being processed
--	[in]	cross_center_y	Y coordinate of current grid square being processed
--	[in]	lvldata	Level data associated with the grid square being processed
function modWP_Levels.GridSquarePopulateCross( cross_center_x, cross_center_y, lvldata )
	modWP_Levels.GridSquarePopulate(cross_center_x,			(cross_center_y + 1),	lvldata.levelnumber_south)
	modWP_Levels.GridSquarePopulate((cross_center_x + 1),	cross_center_y,			lvldata.levelnumber_east)
	modWP_Levels.GridSquarePopulate(cross_center_x,			(cross_center_y - 1),	lvldata.levelnumber_north)
	modWP_Levels.GridSquarePopulate((cross_center_x - 1),	cross_center_y,			lvldata.levelnumber_west)
end

--	Grid Function - Change the grid focus for next grid square to be processed
--	[in]	grid_locx	X coordinate of current grid square
--	[in]	grid_locy	Y coordinate of current grid square
--	[in]	curdir	direction of processing
--	== direction can be one of four values: 1 = south, 2 = east, 3 = north, 4 = west.
--	[ret]	table of x,y coordinates of grid square to be processed next
function modWP_Levels.GridSquareChangeFocus( grid_locx, grid_locy, curdir )
	if (curdir == 1) then
		--	south
		grid_locx = grid_locx
		grid_locy = grid_locy + 1
	elseif (curdir == 2) then
		--	east
		grid_locx = grid_locx + 1
		grid_locy = grid_locy
	elseif (curdir == 3) then
		--	north
		grid_locx = grid_locx
		grid_locy = grid_locy - 1
	elseif (curdir == 4) then
		--	west
		grid_locx = grid_locx - 1
		grid_locy = grid_locy
	else	--	shouldn't receive direction outsite of 1 <= curdir <= 4
		grid_locx = -1
		grid_locy = -1
	end
	return grid_locx, grid_locy
end

--	Grid Function - Determine the next level for grid processing
--	[in]	lvldata	current level being processed
--	[in]	curdir	direction of processing
--	== direction can be one of four values: 1 = south, 2 = east, 3 = north, 4 = west.
--	[ret]	level number that is to be processed next OR -1 if not determined
function modWP_Levels.GridGetNextSquare( lvldata, curdir )
	local nextlvl = -1
	if (curdir == 1) then
		nextlvl = lvldata.levelnumber_south
	elseif (curdir == 2) then
		nextlvl = lvldata.levelnumber_east
	elseif (curdir == 3) then
		nextlvl = lvldata.levelnumber_north
	elseif (curdir == 4) then
		nextlvl = lvldata.levelnumber_west
--	else	--	shouldn't receive direction outsite of 1 <= curdir <= 4
	end
	return nextlvl
end

--	Grid Function - Examine global GridData (ground levels) and trim excess rows/columns
--	see GridDataIsTrimmable() for definition of 'Trimmable'
function modWP_Levels.GridTrim()
	local trim_columns = {}
	local trim_rows = {}
	local size = {x = modWP_Levels.grid_max.x, y = modWP_Levels.grid_max.y}
	local center = {x = modWP_Levels.grid_center.x, y = modWP_Levels.grid_center.y}
	--	find all columns that can be trimmed
	for y = size.y, 1, -1 do
		if (modWP_Levels.GridDataIsTrimmable(y, false)) then
			table.insert(trim_rows, y)
		end
	end
	table.sort(trim_rows)
	--	find all rows that can be trimmed
	for x = size.x, 1, -1 do
		if (modWP_Levels.GridDataIsTrimmable(x, true)) then
			table.insert(trim_columns, x)
		end
	end
	table.sort(trim_columns)
	--	trim rows
	for value = #trim_rows, 1, -1 do
		local rowToRemove = trim_rows[value]
		table.remove(modWP_Levels.GridData, rowToRemove)
		if (rowToRemove < center.y) then
			center.y = center.y - 1
		end
		size.y = size.y - 1
	end
	--	trim columns
	for value = #trim_columns, 1, -1 do
		local columnToRemove = trim_columns[value]
		for n = 1, size.y do
			--	for each row - remove the column
			table.remove(modWP_Levels.GridData[n],columnToRemove )
		end
		if (columnToRemove < center.x) then
			center.x = center.x - 1
		end
		size.x = size.x - 1
	end
	modWP_Levels.grid_max = { x = size.x, y = size.y }
	modWP_Levels.grid_center = { x = center.x, y = center.y }
end

--	Grid Function - Determine if row/column in global GridData can be trimmed
--	Definition of a "Trimmable" column/row
--	element[j][i] = -1 for all j[1 to #col] or [1 to #row]i
--	[in]	index	row/column index number in grid to be examined
--	[in]	isrow	bool:	true - index is a row value
--							false - index is a column value
--	[ret]	bool - Is row/column "Trimmable" (True = Yes)
function modWP_Levels.GridDataIsTrimmable( index, isrow )
	local cantrim = false
	--	check incoming values are useful
	if ((index == nil) or (isrow == nil)) then
		return false
	end
	local valuefirst = -1
	if (isrow) then
		--	checking against row data
		if ((index <= modWP_Levels.grid_max.y) and
			(index >= 1)) then
			for dirY = 1, modWP_Levels.grid_max.y do
				valuefirst = modWP_Levels.GridSquareRead(index, dirY)
				if (valuefirst >= 0) then
					cantrim = false
					break
				else
					cantrim = true
				end	--	check grid data at {[index | secondIndex], dirY}
			end	--	for loop on grid Y dir
		end	--	check index value will not cause read beyond grid bounds
	else
		--	checking against column data
		if ((index <= modWP_Levels.grid_max.x) and
			(index >= 1)) then
			for dirX = 1, modWP_Levels.grid_max.x do
				valuefirst = modWP_Levels.GridSquareRead(dirX, index)
				if (valuefirst >= 0) then
					cantrim = false
					break
				else
					cantrim = true
				end --	check grid data at {dirX, [index | secondIndex]}
			end	--	for loop on grid X dir
		end	--	check index value will not cause read beyond grid bounds
	end	--	is index a row or column value
	return cantrim
end

--	Build up wiki string of underground FDRPG levels
--	This function is used recursively!
--	[in]	levelnumbertosearch	examine all children of this level
--	[in]	indent	amount of push in text from left margin
--	[ret]	underlevels array of underground levels for this levelnumber
function modWP_Levels.TraverseLevels( levelnumbertosearch, indent )
	local modWIKI = modWP_Levels.modcommon.Wiki
	indent = indent or 0
	local underlevels = {}
	if (levelnumbertosearch >= 0) then
		local leveltext = modWP_Levels.WikiEntryLevelAnchorText( levelnumbertosearch )
		local levelindex = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelnumbertosearch , "levelnumber" )
		local strindent = string.rep("&emsp;", indent)
		underlevels[#underlevels + 1] = strindent .. leveltext
		if (modWP_Levels.hasChildrenIndex(levelindex)) then
			for key, value in pairs(modWP_Levels.AllLevelData[levelindex].levelnumber_below) do
				local underchildren = modWP_Levels.TraverseLevels(value,(indent + 2))
				for k,v in pairs(underchildren) do
					underlevels[#underlevels + 1] = v
				end
			end
		end
	end
	return underlevels
end

--	Write FDRPG level information to file in a wiki format
function modWP_Levels.WikiWrite()
	local modWIKI = modWP_Levels.modcommon.Wiki
	local colourCryo = "#5f5fff"
	local colourTown = "green"
	local filename = modWP_Levels.modcommon.outputfilenames.levels .. ".html.md.eco"
	local filepath = tostring(modWP_Levels.modcommon.paths.destRootFile .. filename)

	local wikitbl = {
		{ name = "LevelsDebug", link = "lvlsmapdebug", head = "Debug Levels", verbage = "Following level(s) are used for game testing/debug only:"},
		{ name = "LevelsTutorial", link = "lvlsmaptutorial", head = "Tutorial Levels", verbage = "Following level(s) are used as tutorials for helping users learn how to play:"},
		{ name = "LevelsUnreferenced", link = "lvlsmapunref", head = "Unreferenced Levels", verbage = "Following level(s) were found to be unconnected in the level map:"}
	}

	local wikitext = {}
	wikitext[#wikitext + 1] = "---"
	wikitext[#wikitext + 1] = "layout: 'page'"
	wikitext[#wikitext + 1] = "title: 'Map Guide'"
	wikitext[#wikitext + 1] = "comment: 'Description of the subparts (levels) of the map.'"
	wikitext[#wikitext + 1] = ""

	--	convert 'trimmed' grid to table
	--	structured table
	wikitext[#wikitext + 1] = "map:"
	modWIKI.StartSequence()
	wikitext[#wikitext + 1] = modWIKI.AddAttr('nb_columns', modWP_Levels.grid_max.x)
	wikitext[#wikitext + 1] = modWIKI.AddAttr('nb_rows', modWP_Levels.grid_max.y)
	wikitext[#wikitext + 1] = modWIKI.AddAttr('rows', nil)
	for y = 1, modWP_Levels.grid_max.y do
		modWIKI.StartMapping()
		wikitext[#wikitext + 1] = modWIKI.AddAttr('columns', nil)
		for x = 1, modWP_Levels.grid_max.x do
			modWIKI.StartMapping()
			local levelnum = modWP_Levels.GridSquareRead(x,y)
			local levelindex = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelnum , "levelnumber" )
			local levelname = ""
			wikitext[#wikitext + 1] = modWIKI.AddAttr('num', modWP_Levels.LevelShortLinkText(levelnum))
			if levelnum ~= -1 then
				levelname = modWP_Levels.AllLevelData[levelindex].levelname
				wikitext[#wikitext + 1] = modWIKI.AddAttr('name', levelname)
			end
			if levelnum == 0 then
				wikitext[#wikitext + 1] = modWIKI.AddAttr('color', colourTown)
			elseif levelnum == 12 then
				wikitext[#wikitext + 1] = modWIKI.AddAttr('color', colourCryo)
			end
			modWIKI.EndMapping()
		end
		modWIKI.EndMapping()
	end
	modWIKI.EndSequence()

	wikitext[#wikitext + 1] = ""
	wikitext[#wikitext + 1] = "undergrounds:"
	for key, levelnumber in pairs(modWP_Levels.LevelsGround) do
		if (modWP_Levels.hasChildren(levelnumber)) then
			modWIKI.StartMapping()
			wikitext[#wikitext + 1] = modWIKI.AddAttrArray(nil, modWP_Levels.TraverseLevels(levelnumber))
			modWIKI.EndMapping()
		end
	end

	wikitext[#wikitext + 1] = ""
	wikitext[#wikitext + 1] = "special_categories:"
	for key, tbl in pairs(wikitbl) do
		modWIKI.StartMapping()
		wikitext[#wikitext + 1] = modWIKI.AddAttr('name', tbl.head)
		wikitext[#wikitext + 1] = modWIKI.AddAttr('descr', tbl.verbage)
		local lvls = {}
		for key, levelnumber in pairs(modWP_Levels[tbl.name]) do
			lvls[#lvls + 1] = modWP_Levels.WikiEntryLevelAnchorText(levelnumber)
		end
		wikitext[#wikitext + 1] = modWIKI.AddAttr('levels', lvls)
		modWIKI.EndMapping()
	end

	wikitext[#wikitext + 1] = ""
	wikitext[#wikitext + 1] = "levels:"
	for key, levelitem in pairs(modWP_Levels.AllLevelData)do
		--	processing for surrounding levels
		local index_north = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelitem.levelnumber_north, "levelnumber" )
		local index_east = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelitem.levelnumber_east, "levelnumber" )
		local index_south = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelitem.levelnumber_south, "levelnumber" )
		local index_west = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelitem.levelnumber_west, "levelnumber" )
		local level_northeast = -1
		local level_northwest = -1
		local level_southeast = -1
		local level_southwest = -1
		if index_north > 0 then
			level_northeast = modWP_Levels.AllLevelData[index_north].levelnumber_east
			level_northwest = modWP_Levels.AllLevelData[index_north].levelnumber_west
		else
			if index_east > 0 then
				level_northeast = modWP_Levels.AllLevelData[index_east].levelnumber_north
			end
			if index_west > 0 then
				level_northwest = modWP_Levels.AllLevelData[index_west].levelnumber_north
			end
		end
		if index_south > 0 then
			level_southeast = modWP_Levels.AllLevelData[index_south].levelnumber_east
			level_southwest = modWP_Levels.AllLevelData[index_south].levelnumber_west
		else
			if index_east > 0 then
				level_southeast = modWP_Levels.AllLevelData[index_east].levelnumber_south
			end
			if index_west > 0 then
				level_southwest = modWP_Levels.AllLevelData[index_west].levelnumber_south
			end
		end
		local levelcolour = nil
		if levelitem.levelnumber == 0 then
			--	town
			levelcolour = colourTown
		elseif levelitem.levelnumber == 12 then
			--	cryo facility
			levelcolour = colourCryo
		end
		--
		modWIKI.StartMapping()
		wikitext[#wikitext + 1] = modWIKI.AddAttr('id', modWIKI.WikifyLink( "level" .. levelitem.levelnumber ))
		wikitext[#wikitext + 1] = modWIKI.AddAttr('name', modWP_Levels.WikiEntryLevelText(levelitem.levelnumber))
		wikitext[#wikitext + 1] = modWIKI.AddAttrArray('size', { levelitem.xlen, levelitem.ylen })
		wikitext[#wikitext + 1] = modWIKI.AddAttr('song', levelitem.bgSong)
		if levelcolour then
			wikitext[#wikitext + 1] = modWIKI.AddAttr('name_color', levelcolour)
		end
		local specials = {}
		specials[#specials + 1] = modWIKI.AddAttr('specials', nil)
		if ( not modWP_Levels.hasreference(levelitem.levelnumber) ) then
			modWIKI.StartMapping()
			specials[#specials + 1] = modWIKI.AddAttr('text', select(1, modWP_Levels.GetLevelItemStringsPair( levelitem, "is_unref")))
			specials[#specials + 1] = modWIKI.AddAttr('color', modWIKI.ColourWarn)
			modWIKI.EndMapping()
		end --	level is unreferenced
		if (levelitem.is_random) then
			modWIKI.StartMapping()
			specials[#specials + 1] = modWIKI.AddAttr('text', select(1, modWP_Levels.GetLevelItemStringsPair( levelitem, "is_random")))
			specials[#specials + 1] = modWIKI.AddAttr('color', modWIKI.ColourCaution)
			modWIKI.EndMapping()
		end --	level is random
		if (levelitem.is_tutorial) then
			modWIKI.StartMapping()
			specials[#specials + 1] = modWIKI.AddAttr('text', select(1, modWP_Levels.GetLevelItemStringsPair( levelitem, "is_tutorial")))
			specials[#specials + 1] = modWIKI.AddAttr('color', modWIKI.ColourCaution)
			modWIKI.EndMapping()
		end --	level is tutorial
		if (levelitem.is_debug) then
			modWIKI.StartMapping()
			specials[#specials + 1] = modWIKI.AddAttr('text', select(1, modWP_Levels.GetLevelItemStringsPair( levelitem, "is_debug")))
			specials[#specials + 1] = modWIKI.AddAttr('color', modWIKI.ColourCaution)
			modWIKI.EndMapping()
		end --	level is debug
		if #specials > 1 then
			for k,text in ipairs(specials) do
				wikitext[#wikitext + 1] = text
			end
		end

		wikitext[#wikitext + 1] = modWIKI.AddAttrArray('neighbors',
		                                               { modWP_Levels.LevelShortLinkText(level_northwest),
		                                                 modWP_Levels.LevelShortLinkText(levelitem.levelnumber_north),
		                                                 modWP_Levels.LevelShortLinkText(level_northeast),
		                                                 modWP_Levels.LevelShortLinkText(levelitem.levelnumber_west),
		                                                 levelitem.levelnumber,
		                                                 modWP_Levels.LevelShortLinkText(levelitem.levelnumber_east),
		                                                 modWP_Levels.LevelShortLinkText(level_southwest),
		                                                 modWP_Levels.LevelShortLinkText(levelitem.levelnumber_south),
		                                                 modWP_Levels.LevelShortLinkText(level_southeast) })
		if levelitem.levelnumber_above >= 0 then
			wikitext[#wikitext + 1] = modWIKI.AddAttr('level_above', modWP_Levels.WikiEntryLevelAnchorText(levelitem.levelnumber_above))
		end
		if modWP_Levels.hasChildren(levelitem.levelnumber) then
			--	make a table of levels below
			local levelsbelow = {}
			for key, level in pairs(levelitem.levelnumber_below) do
				levelsbelow[#levelsbelow + 1] =  modWP_Levels.WikiEntryLevelAnchorText(level)
			end
			wikitext[#wikitext + 1] = modWIKI.AddAttrArray('levels_below', levelsbelow)
		end

		modWIKI.EndMapping()
	end
	wikitext[#wikitext + 1] = "---"
	wikitext[#wikitext + 1] = ""
	wikitext[#wikitext + 1] = [[

# FreedroidRPG Levels

## Map Guide

<div class="bordered-table">
<table width="100%">
 <tbody>
 <% for row in @document.map.rows: %>
  <tr>
  <% for cell in row.columns: %>
   <td align="center" width="<%- 100/@document.map.nb_columns %>%">
    <% if cell.num != "-1": %>
     <%- cell.num %><br/>
     <% if cell.color: %>
      <span style="color: <%- cell.color %>;"><%- cell.name %></span>
     <% else: %>
      <%- cell.name %>
     <% end %>
    <% else: %>
     &nbsp;
    <% end %>
   </td>
  <% end %>
  </tr>
 <% end %>
 </tbody>
</table>
</div>

# Map of Underground Levels

<% for lvl in @document.undergrounds: %>
 <ul><li>
 <% for s in lvl: %>
 <%- s %><br/>
 <% end %>
 </li></ul>
<% end %>

<% for category in @document.special_categories: %>
# <%- category.name %>

<%- category.descr %>
<ul><% for lvl in category.levels: %>
 <li><%- lvl %></li>
<% end %></ul>
<% end %>

# List of All FreedroidRPG Levels

<% for lvl in @document.levels: %>
<h3 id="<%- lvl.id %>" style="color: <% if lvl.name_color: %><%- lvl.name_color %><% else: %>white<% end %>;"><b><%- lvl.name %></b></h3>
<div class="row">
 <div class="col-md-3 bordered-table">
  <table width="100%"><tbody>
  <% for idx in [0..2]: %>
   <tr>
   <% for neighbor in lvl.neighbors[3*idx..3*idx+2]: %>
    <td align="center" width="30%">
     <% if neighbor == "-1": %><span class="glyphicon glyphicon-ban-circle"></span><% else: %><%- neighbor %><% end %>
    </td>
   <% end %>
   </tr>
  <% end %>
  </tbody></table>
 </div>
 <div class="col-md-9">
  <p>
   <% if lvl.specials: %>
    <% for special in lvl.specials: %>
    <b><span style="color: <%- special.color %>;"><%- special.text %></b><br/>
    <% end %>
   <% end %>
   <b>Level Size:</b> X: <%- lvl.size[0] %> Y: <%- lvl.size[1] %><br/>
   <b>Background Song:</b> <%- lvl.song %>
  </p>
  <% if lvl.level_above: %>
   <p><b>Level Above:</b><br/>&nbsp;&nbsp;<%- lvl.level_above %></p>
  <% end %>
  <% if lvl.levels_below: %>
   <p><b>Levels Below:</b><br/>
   <% for lvlb in lvl.levels_below: %>
    &nbsp;&nbsp;<%- lvlb %><br/>
   <% end %>
  </p>
 <% end %>
 </div>
</div>
<% end %>
]]
	modWP_Levels.modcommon.Process.DataToFile(filepath, table.concat(wikitext, "\n"))
end

--	convert level number and name to text format
--	[in]	levelnumber	level to be converted
--	[in]	label		text to be used for wiki presentation
--	[in]	withURL		false - return just formatted anchor
--						true - return full url#anchor to level information
--	[ret]	string	"XX - level name"
function modWP_Levels.WikiEntryLevelText( levelnumber, label, withURL )
	local asLink = false
	if (( label == nil) or ( not label )) then
		label = ""
	else
		asLink = true
	end
	local useURL = false
	if (( withURL == nil ) or ( not withURL )) then
		useURL = false
	else
		useURL = true
	end

	levelnumber = assert(tonumber(levelnumber), "WikiEntryLevelText: unable to convert " .. levelnumber)
	local levelindex = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelnumber, "levelnumber" )
	local levelname = modWP_Levels.AllLevelData[levelindex].levelname
	local strText = string.format("%02d", tostring(levelnumber)) .. "&nbsp;-&nbsp;"	.. levelname
	if ( not asLink ) then
		return strText
	else
		strText = "Level " .. strText
		local urltext = ""
		if ( not useURL ) then
			urltext = modWP_Levels.AllLevelData[levelindex].urlAnchor
		else
			urltext = modWP_Levels.modcommon.outputfilenames.levels .. modWP_Levels.AllLevelData[levelindex].urlAnchor
		end
		return modWP_Levels.modcommon.Wiki.LinkText( urltext, strText )
	end
end

function modWP_Levels.WikiEntryLevelAnchorText( levelnumber )
	levelnumber = assert(tonumber(levelnumber), "WikiEntryLevelAnchorText: unable to convert " .. tostring(levelnumber))
	local levelindex = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelnumber, "levelnumber" )
	local levelname = modWP_Levels.AllLevelData[levelindex].levelname
	local anchortext = modWP_Levels.AllLevelData[levelindex].urlAnchor
	local levelnum = string.format("%02d", tostring(levelnumber))
	return modWP_Levels.modcommon.Wiki.LinkText( anchortext, levelnum ) .. "&nbsp;-&nbsp;" .. levelname
end

function modWP_Levels.LevelShortLinkText( levelnumber )
	if (( levelnumber == nil ) or ( levelnumber < 0 )) then
		return tostring(levelnumber)
	end
	local index = modWP_Levels.GetIndexByValue( modWP_Levels.AllLevelData, levelnumber, "levelnumber" )
	if ( index ) then
		local levelnumformat = string.format("%02d", tostring(levelnumber))
		local leveltext = modWP_Levels.AllLevelData[index].urlAnchor
		return modWP_Levels.modcommon.Wiki.LinkText( leveltext, levelnumformat )
	else
		return tostring(levelnumber)
	end
end

--	Print out level information based on selected verbosity.
function modWP_Levels.Verbosity()
	if (( not modWP_Levels.modcommon.verbose) and ( not modWP_Levels.modcommon.doubleverbose)) then
		return
	end
	local copyType = modWP_Levels.TypeNames
	copyType[#copyType + 1] = { name = "LevelsUnreferenced", display = "Unreferenced Levels" }
	io.stdout:write(modWP_Levels.modcommon.VerboseHeader)
	io.stdout:write("All Level Data\n")
	io.stdout:write(modWP_Levels.modcommon.VerboseHeader)
	local sum = 0
	for key, tbl in pairs(copyType) do
		sum = sum + #modWP_Levels[tbl.name]
	end
	local printoutput	= "=== Level Check ===\n"
	for key, tbl in pairs(copyType) do
		printoutput = printoutput .. "size of " .. tbl.display .. ": " .. #modWP_Levels[tbl.name] .. "\n"
	end
	printoutput = printoutput .. "size of AllLevelData = sum of above: " .. tostring((#modWP_Levels.AllLevelData == sum )) .. "\n"
	io.stdout:write(printoutput)

	for key, tbl in pairs(copyType) do
		local str = modWP_Levels.modcommon.Extract.OneDTableToString(modWP_Levels[tbl.name])
		io.stdout:write("\n=== Listing " .. tbl.display .. ":\n " .. str .. "\n")
	end

	io.stdout:write("\n=== Printing Ground Level Map (Grid):\n")
	local writedata = ""
	for y = 1, modWP_Levels.grid_max.y do
		local row_data = ""
		for x = 1, modWP_Levels.grid_max.x do
			row_data = row_data .. string.format("%02d ", tostring(modWP_Levels.GridSquareRead(x,y)))
		end
		writedata = writedata .. row_data .. "\n"
	end
	io.stdout:write(writedata)
	io.stdout:write(modWP_Levels.modcommon.VerboseHeader)
	if (modWP_Levels.modcommon.doubleverbose) then
		modWP_Levels.modcommon.Process.TblPrint( modWP_Levels.AllLevelData, nil, nil, "AllLevelData: " .. #modWP_Levels.AllLevelData )
		for key, tbl in pairs(copyType) do
			modWP_Levels.modcommon.Process.TblPrint( modWP_Levels[tbl.name], nil, nil, tbl.name .. ": " .. #modWP_Levels[tbl.name])
		end
	end
	io.stdout:write(modWP_Levels.modcommon.VerboseHeader)
end

return modWP_Levels