-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathRossAsciiMap.xml
1131 lines (1008 loc) · 35.5 KB
/
RossAsciiMap.xml
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
<?xml version="1.0" encoding="iso-8859-1"?><!DOCTYPE muclient><muclient><script><![CDATA[ -- this is all on the first line (including this comment) so that lua error messages will have correct line numbers
require "json"
local SELF_ID = GetPluginID()
local GMCP_INTERFACE_ID = "c190b5fc9e83b05d8de996c3"
local winID = SELF_ID .. "RossAsciiMap"
local rossPluginsDir = GetPluginInfo(SELF_ID, 20)
local RGBToInt = dofile(rossPluginsDir .. "RGBToInt.lua")
local window = dofile(rossPluginsDir .. "window.lua")
local parseMDT = dofile(rossPluginsDir .. "MapDoorTextParser.lua")
local function copy(t, out)
out = out or {}
for k,v in pairs(t) do out[k] = v end
return out
end
-- Settings:
--------------------------------------------------
local COLORS = {
background = 0,
border = 12632256,
Normal = RGBToInt(192),
Yellow = RGBToInt(255, 255, 0),
Red = RGBToInt(255, 0, 0),
Green = RGBToInt(0, 255, 0),
Cyan = RGBToInt(0, 255, 255),
Blue = RGBToInt(0, 0, 255),
Magenta = RGBToInt(255, 0, 255),
White = RGBToInt(255),
Desert = 34815,
}
local DEFAULT_COLORS = {}
for k,v in pairs(COLORS) do DEFAULT_COLORS[k] = v end
local fontFamily = "fixedsys"
local fontSize = 9 -- NOTE: FixedSys only comes in this size.
local spacingX, spacingY = 0, 0
-- Other Vars:
--------------------------------------------------
local winRect = {x = 800, y = 0, width = 200, height = 200, z = 0}
local winLocked = false
local fontID = "font" -- Not the font name, just the ID it's registered to for our window.
local fontHeight
local fontCharWidth
local fontMaxCharWidth -- For the rect to draw each character within.
local loadMapFont, loadFilterGroupFont -- Upvalue for functions defined below.
local playerY, playerX = 1, 1
local mapLines = {}
local colorChangesAt = {}
local mxpColorRegex = rex.new("MXP<(.+?)MXP>")
local hexRegex = rex.new(".*? (#[0-9a-f]{6})") -- MXP<C #ff8700MXP>
local lastMapPacket, lastMDTPacket-- Save last raw packet string to disk so we can show something on load.
local MDTIndicatorsEnabled = true
local MDTPlayerPrefix = "P)"
local entityFilters = {}
local DEFAULT_FILTER_GROUP = {
name = "default",
color = RGBToInt(255, 0, 0),
fontFamily = "fixedsys",
fontSize = 9,
ox = 2,
oy = 0
}
local filterGroups = { default = copy(DEFAULT_FILTER_GROUP) }
local disabledGroups = {}
local CASE_INSENSITIVE = rex.flags().CASELESS
-- Loading & Saving Settings:
--------------------------------------------------
local function toboolean(v) -- Convert a value to a boolean. Always returns a boolean.
if v == nil then return false
elseif v == false or v == "false" then return false end
return true -- Anything other than nil, false, or "false" ==> true.
end
local function loadVar(name) -- Returns nil instead of an empty string.
local v = GetVariable(name)
if v == "" then return nil end
return v
end
local function loadSettings()
for k,v in pairs(winRect) do
winRect[k] = tonumber(GetVariable("window_" .. k)) or v
end
winLocked = toboolean(GetVariable("windowLocked"))
lastMapPacket = loadVar("lastMapPacket")
lastMDTPacket = loadVar("lastMDTPacket")
fontFamily = loadVar("fontFamily") or fontFamily
fontSize = loadVar("fontSize") or fontSize
spacingX = loadVar("spacingX") or spacingX
spacingY = loadVar("spacingY") or spacingY
for colorName in pairs(COLORS) do
COLORS[colorName] = tonumber(loadVar("color_" .. colorName)) or DEFAULT_COLORS[colorName]
end
local MDTEnabled = loadVar("MDTIndicatorsEnabled")
if MDTEnabled then MDTIndicatorsEnabled = toboolean(MDTEnabled) end
MDTPlayerPrefix = loadVar("MDTPlayerPrefix") or MDTPlayerPrefix
local filters = loadVar("entityFilters")
if filters then
entityFilters = json.decode(filters)
for i,f in ipairs(entityFilters) do
local regexFlags = f.doesIgnoreCase and CASE_INSENSITIVE or nil
f.regex = rex.new(f.pattern, regexFlags)
end
end
local groups = loadVar("filterGroups")
if groups then filterGroups = json.decode(groups) end
local disabled = loadVar("disabledGroups")
if disabled then disabledGroups = json.decode(disabled) end
end
local function saveVar(name, val, nilVal)
if val == nil then val = nilVal or false end
SetVariable(name, tostring(val))
end
local function saveSettings()
for k,v in pairs(winRect) do
saveVar("window_" .. k, v)
end
winLocked = window.getLocked(winID)
saveVar("windowLocked", winLocked)
if lastMapPacket then saveVar("lastMapPacket", lastMapPacket) end
if lastMDTPacket then saveVar("lastMDTPacket", lastMDTPacket) end
saveVar("fontFamily", fontFamily, "fixedsys")
saveVar("fontSize", fontSize, 9)
saveVar("spacingX", spacingX, 0)
saveVar("spacingY", spacingY, 0)
for colorName in pairs(COLORS) do
saveVar("color_" .. colorName, COLORS[colorName])
end
saveVar("MDTIndicatorsEnabled", MDTIndicatorsEnabled)
saveVar("MDTPlayerPrefix", MDTPlayerPrefix, "")
-- Save custom entity filters.
if next(entityFilters) then
-- Can't save regex object, so make a copy of the filters without it.
local saveData = {}
local filterPropsToSave = {"pattern", "score", "group", "keepEvaluating", "doesIgnoreCase"}
for i,f in ipairs(entityFilters) do
local v = {}
for _,prop in ipairs(filterPropsToSave) do v[prop] = f[prop] end
saveData[i] = v
end
saveVar("entityFilters", json.encode(saveData))
end
saveVar("filterGroups", json.encode(filterGroups))
saveVar("disabledGroups", json.encode(disabledGroups))
end
function OnPluginSaveState()
saveSettings()
end
local function refresh()
if lastMapPacket then
onGMCPReceived("room.map", lastMapPacket)
end
if lastMDTPacket and MDTIndicatorsEnabled then
onGMCPReceived("room.writtenmap", lastMDTPacket)
else
window.draw(winID)
end
end
-- Living Thing Filter Editing:
--------------------------------------------------
local curMDTData = nil
local getCountRegex = rex.new("^(\\d+)")
local function setPlayerPrefix()
local msg = [[The text you enter will be inserted before each player name so you can have a custom filter for them.
If you put in something weird like " and ", "four ", or "one northwest ", then you'll break things.]]
local prefix = utils.inputbox(msg, "Select Player Name Prefix", MDTPlayerPrefix)
if prefix then
MDTPlayerPrefix = prefix
window.setMenuItem(winID, 34, "Set player name prefix...(cur: "..MDTPlayerPrefix..")")
refresh()
end
end
local function addFilter(pattern, score, group, keepEvaluating, doesIgnoreCase)
local regexFlags = doesIgnoreCase and CASE_INSENSITIVE or nil
local filter = {
regex = rex.new(pattern, regexFlags),
pattern = pattern,
score = score,
group = group or "default",
keepEvaluating = keepEvaluating,
doesIgnoreCase = doesIgnoreCase
}
table.insert(entityFilters, filter)
end
local function setFilter(i, filter)
-- 1. Set the regex pattern.
local msg = [[Enter a regular expression to check against the living thing name.
If there are multiple identical things in the room, their text will start with a number.
i.e. "2 annoying children".
Capitalization is preserved, but you can set the regex to be case-insensitive.]]
local extras = { box_width = 800, box_height = 250, prompt_height = 100 }
local default = filter and filter.pattern
local pattern = utils.editbox(msg, "Define RegEx", default, nil, nil, extras)
if not pattern then return end
-- 2. Set the score.
local msg = [[Enter your desired score number.]]
local default = filter and filter.score or 1
local score = utils.inputbox(msg, "Select Score", default)
if not score then return end
score = tonumber(score)
if not score then Note("[RossAsciiMap] - Invalid score. Must be a number. Aborting.") return end
-- 3. Set the group.
local msg = [[Things in each group is counted separately and each group count can be drawn as a separate indicator.]]
local default = filter and filter.group or "default"
local list = {}
for k,v in pairs(filterGroups) do list[k] = k end
local group = utils.choose(msg, "Select Group", list, default)
if not group then return end
-- 4. Set 'keepEvaluating'.
local msg = [[Should we keep evaluating if this filter matches?
i.e. should subsequent filters be able to override this one?]]
local default = filter and (filter.keepEvaluating and 1 or 2) or 2
local keepEvaluating = utils.msgbox(msg, "Keep Evaluating?", "yesno", "?", default)
if not keepEvaluating then return end
keepEvaluating = keepEvaluating == "yes" and true or nil
-- 5. Set 'doesIgnoreCase'
local msg = [[Is the regex pattern case-sensitive?]]
local default = filter and (filter.doesIgnoreCase and 2 or 1) or 1
local doesIgnoreCase = utils.msgbox(msg, "Case-sensitive?", "yesno", "?", 1)
if not doesIgnoreCase then return end
doesIgnoreCase = doesIgnoreCase == "no" and true or nil
if filter then
local regexFlags = doesIgnoreCase and CASE_INSENSITIVE or nil
filter.regex = rex.new(pattern, regexFlags)
filter.pattern = pattern
filter.score = score
filter.group = group
filter.keepEvaluating = keepEvaluating
filter.doesIgnoreCase = doesIgnoreCase
else
addFilter(pattern, score, group, keepEvaluating, doesIgnoreCase)
end
refresh()
end
local function chooseFilter(msg, title, isMulti)
local list = {}
for i,f in ipairs(entityFilters) do
local str = '%s: %s, %s, "%s", %s, %s %s'
local isDisabled = disabledGroups[f.group or "default"] and "(group disabled)" or ""
str = str:format(
i, f.pattern, f.score, f.group or "default",
tostring(not not keepEvaluating), tostring(not doesIgnoreCase),
isDisabled
)
table.insert(list, str)
end
local message = 'Shown as: #: regex pattern, score, "group", keepEvaluating, caseSensitive'
if msg then
message = message .. "\n\n" .. msg
end
local fn = isMulti and utils.multilistbox or utils.listbox
return fn(message, title, list), list -- Return the filter index.
end
local function modifyFilter()
local filterIdx = chooseFilter(nil, "Choose a Filter to Modify")
if filterIdx then
setFilter(nil, entityFilters[filterIdx])
end
end
local function deleteFilters()
local filterIndices, list = chooseFilter("You may select multiple.", "Choose Filters to Delete", true)
if filterIndices then
for filterIdx in pairs(filterIndices) do
local idStr = list[filterIdx]
local msg = "Are you sure you want to delete the filter:\n" .. idStr
local confirm = utils.msgbox(msg, "Really Delete?", "okcancel")
if confirm == "ok" then
table.remove(entityFilters, filterIdx)
end
end
refresh()
end
end
local function setFilterOrder()
local filterIdx = chooseFilter(nil, "Choose a Filter to Reorder")
if filterIdx then
local minI, maxI = 1, #entityFilters
local list = {}
for i=minI,maxI do list[i] = i end
local toIdx = utils.choose("", "Choose a New Index", list, filterIdx)
if toIdx then
local filter = entityFilters[filterIdx]
table.remove(entityFilters, filterIdx)
table.insert(entityFilters, toIdx, filter)
refresh()
end
end
end
-- Group Editing:
--------------------------------------------------
local function addGroup(name, color, fontFamily, fontSize, ox, oy)
local group = {
name = name,
color = color,
fontFamily = fontFamily,
fontSize = fontSize,
ox = ox,
oy = oy
}
filterGroups[name] = group
loadFilterGroupFont(winID, group)
end
local function setGroup(i, group)
-- 1. Set the group name.
local msg = [[Enter the group name.]]
local default = group and group.name
local name = utils.inputbox(msg, "Enter a Group Name", default)
if not name then return end
if filterGroups[name] and not (group and group.name == name) then
Note('[RossAsciiMap] - The group name: "'..name..'" is already used. Aborting.')
return
end
-- 2. Set the group color.
local default = group and group.color or DEFAULT_FILTER_GROUP.color
local color = PickColour(default)
if color == -1 then return end
-- 3. Set the group font family and size.
local defaultFamily = group and group.fontFamily or DEFAULT_FILTER_GROUP.fontFamily
local defaultSize = group and group.fontSize or DEFAULT_FILTER_GROUP.fontSize
local fontSpecs = utils.fontpicker(defaultFamily, defaultSize)
local fontFamily, fontSize = defaultFamily, defaultSize
if fontSpecs then
fontFamily, fontSize = fontSpecs.name, fontSpecs.size
end
-- 4. Set the group X-offset.
local default = group and group.ox or DEFAULT_FILTER_GROUP.ox
local title = "Choose X Offset"
local msg = "Enter the offset on the X-axis where the group score will be shown."
local ox = utils.inputbox(msg, title, default)
if not ox then return end
ox = tonumber(ox)
if not ox then Note("[RossAsciiMap] - Invalid offset. Must be a number. Aborting.") return end
-- 5. Set the group Y-offset.
local default = group and group.oy or DEFAULT_FILTER_GROUP.oy
local title = "Choose Y Offset"
local msg = "Enter the offset on the Y-axis where the group score will be shown."
local oy = utils.inputbox(msg, title, default)
if not oy then return end
oy = tonumber(oy)
if not oy then Note("[RossAsciiMap] - Invalid offset. Must be a number. Aborting.") return end
if group then
group.name = name
group.color = color
group.fontFamily = fontFamily
group.fontSize = fontSize
group.ox = ox
group.oy = oy
loadFilterGroupFont(winID, group)
else
addGroup(name, color, fontFamily, fontSize, ox, oy)
end
refresh()
end
local function chooseGroup(msg, title, isMulti)
local list = {}
for groupName,group in pairs(filterGroups) do
local str = '"%s", %s, %s, %s, %s, %s %s'
local isDisabled = disabledGroups[groupName] and "(disabled)" or ""
str = str:format(groupName, group.color, group.fontFamily, group.fontSize, group.ox, group.oy, isDisabled)
list[groupName] = str
end
local message = 'Shown as: "groupName", color, font, fontSize, offsetX, offsetY'
if msg then
message = message .. "\n\n" .. msg
end
local fn = isMulti and utils.multilistbox or utils.listbox
return fn(message, title, list), list -- Returns the group name.
end
local function modifyGroup()
local groupName = chooseGroup("Select a group to modify it.", "Choose a Group to Modify")
if groupName then
setGroup(nil, filterGroups[groupName])
end
end
local function deleteGroups()
local groupNames, list = chooseGroup("You may select multiple.", "Choose Groups to Delete", true)
if groupNames then
for groupName in pairs(groupNames) do
local idStr = list[groupName]
local msg = "Are you sure you want to delete the group:\n" .. idStr
local confirm = utils.msgbox(msg, "Really Delete?", "okcancel")
if confirm == "ok" then
filterGroups[groupName] = nil
end
end
refresh()
end
end
local function toggleGroups()
local msg = "You may select multiple."
msg = msg .. "\nAny filters associated with disabled groups will be skipped."
local groupNames, list = chooseGroup(msg, "Choose Groups to Toggle On/Off", true)
if groupNames then
for groupName in pairs(groupNames) do
if disabledGroups[groupName] then
disabledGroups[groupName] = nil -- Enable again, remove from dict.
else
disabledGroups[groupName] = true -- Disable.
end
end
refresh()
end
end
function aliasSetGroupsEnabled(name, line, captures)
local isDisable = captures[1] == "disable"
local groupList = captures[2]
groupList:gsub(",", "")
local groupNames = {}
for groupName in string.gmatch(groupList, "([^%s]+)") do
table.insert(groupNames, groupName)
end
local val = isDisable or nil
local msg = "[RossAsciiMap] - " .. (isDisable and "Disabling" or "Enabling") .. ' group: "'
for i,groupName in ipairs(groupNames) do
if not filterGroups[groupName] then
Note('[RossAsciiMap] - Group: "' .. groupName .. '" does not exist.')
else
disabledGroups[groupName] = val
Note(msg .. groupName .. '".')
end
end
refresh()
end
-- MDT Data Handling:
--------------------------------------------------
local function printLastMDTPacket()
if lastMDTPacket then print(lastMDTPacket) end
end
local function toggleMDTIndicators(i)
MDTIndicatorsEnabled = not MDTIndicatorsEnabled
window.checkMenuItem(winID, i, MDTIndicatorsEnabled)
refresh()
end
-- Loop through each room with things in it and give it a score property.
local function scoreMDTRooms(rooms)
for i,room in ipairs(rooms) do
local scoreList = {}
for i,entityStr in ipairs(room.entities) do
local _, _, capt = getCountRegex:match(entityStr)
local count = capt and capt[1] or 1
local wasCaughtByFilter = false
for i,filter in ipairs(entityFilters) do
if not disabledGroups[filter.group] then
if filter.regex:match(entityStr) then
wasCaughtByFilter = true
local groupName = filter.group or "default"
scoreList[groupName] = scoreList[groupName] or 0
scoreList[groupName] = scoreList[groupName] + count * filter.score
if not filter.keepEvaluating then break end
end
end
end
if not wasCaughtByFilter then
scoreList.default = scoreList.default or 0
scoreList.default = scoreList.default + count
end
end
room.scoreList = scoreList
end
end
-- Make a 2D array of room scores with relative coordinates from player.
local function makeMDTArraymap(data)
local map = {}
for i,room in ipairs(data) do
local x, y = room.dx, room.dy
map[y] = map[y] or {}
map[y][x] = room.scoreList
end
data.scoreMap = map
end
-- Right-Click Menu Handling:
--------------------------------------------------
local function pickFont()
local fnt = utils.fontpicker(fontFamily, fontSize)
if fnt then
fontFamily, fontSize = fnt.name, fnt.size
loadMapFont(winID, fontID, fontFamily, fontSize)
window.draw(winID)
end
end
local function pickColor(i, colorName)
local result = PickColour(COLORS[colorName])
if result ~= -1 then
COLORS[colorName] = result
refresh()
end
end
local function resetColorToDefault(i, colorName)
COLORS[colorName] = DEFAULT_COLORS[colorName]
refresh()
end
local function setSpacing(i, axis)
local curSpacing = axis == "X" and spacingX or spacingY
local msg = "Enter your desired " .. axis .. " spacing."
local title = "Set " .. axis .. " Spacing"
local newSpacing = utils.inputbox(msg, title, curSpacing)
if not newSpacing then return end
newSpacing = tonumber(newSpacing)
if not newSpacing then Note("[RossAsciiMap] - Invalid spacing. Must be a number. Aborting.") return end
if axis == "X" then
spacingX = newSpacing
window.setMenuItem(winID, 2, "Set X spacing...(cur: "..spacingX..")")
else
spacingY = newSpacing
window.setMenuItem(winID, 3, "Set Y spacing...(cur: "..spacingY..")")
end
refresh()
end
local menuItems = {} -- List of tables with button text and callback arguments.
local menuItemTextList = {} -- List of button texts for adding to window
local function menuItem(text, fn, ...)
table.insert(menuItems, {text, fn, ...})
end
local function setupMenuItems()
menuItems, menuItemTextList = {}, {}
menuItem("Set font...", pickFont)
menuItem("Set X spacing...(cur: "..spacingX..")", setSpacing, "X")
menuItem("Set Y spacing...(cur: "..spacingY..")", setSpacing, "Y")
local colorNames = {"background", "border", "Normal", "Yellow", "Red", "Green", "Cyan", "Blue", "Desert", "Magenta", "White"}
menuItem(">Colors:")
menuItem(">Defaults:")
for i,colorName in ipairs(colorNames) do
menuItem("Reset "..colorName.." to default", resetColorToDefault, colorName)
end
menuItem("<")
menuItem("-")
for i,colorName in ipairs(colorNames) do
menuItem("Set "..colorName.." color...", pickColor, colorName)
end
menuItem("<")
menuItem(">Living thing indicators:")
local checkChar = MDTIndicatorsEnabled and "+" or ""
menuItem(checkChar .. "Enabled", toggleMDTIndicators)
menuItem("Output last MDT packet (for debug)", printLastMDTPacket)
menuItem("Set player name prefix...(cur: "..MDTPlayerPrefix..")", setPlayerPrefix)
menuItem("-")
menuItem("Show filters...", modifyFilter)
menuItem("Add filter...", setFilter)
menuItem("Delete filters...", deleteFilters)
menuItem("Change filter order...", setFilterOrder)
menuItem("-")
menuItem("Show groups...", modifyGroup)
menuItem("Add group...", setGroup)
menuItem("Delete groups...", deleteGroups)
menuItem("Toggle groups...", toggleGroups)
menuItem("<")
for i,v in ipairs(menuItems) do table.insert(menuItemTextList, v[1]) end
end
local function menuItemClicked(winID, i, prefix, item)
local data = menuItems[i]
if data then
local fn = data[2]
fn(i, unpack(data, 3))
end
end
-- GMCP Parsing:
--------------------------------------------------
local function processColorCodes(startPos, line, lineIdx)
-- Strip out color codes and record at which character index the color changes.
local startI, endI, captures = mxpColorRegex:match(line, startPos)
if endI then
local colorName = captures[1]
if colorName == "Yellow" then -- This is the line the player is on.
playerX, playerY = startI, lineIdx
end
-- Convert color string into an integer RGB value.
local colorInt
if COLORS[colorName] then
colorInt = COLORS[colorName]
else
local _s, _e, hexCaptures = hexRegex:match(colorName)
if hexCaptures then
if hexCaptures[1] == "#ff8700" then
colorName = "Desert"
colorInt = COLORS[colorName]
else
colorInt = ColourNameToRGB(hexCaptures[1]) -- ColourNameToRGB will convert hex colors.
end
else
Note("[RossAsciiMap] - Unrecognized color code: '", string.sub(line, startI, endI), "'.")
colorInt = COLORS.Normal
end
end
colorChangesAt[lineIdx] = colorChangesAt[lineIdx] or {}
colorChangesAt[lineIdx][startI] = colorInt
-- Cut color code chunk out of map string.
local pre = string.sub(line, 1, startI - 1)
local post = string.sub(line, endI + 1)
line = pre .. post
end
-- We're removing everything from startI to endI, so our startI should
-- also be the start for the next match.
return startI, line
end
--[[
-- Output from MUD:
"\u001b[3z\u001b[4zMXP<RedMXP>+\u001b[3z \u001b[3z\u001b[4zMXP<RedMXP>+\u001b[3z \n\u001b[3z\u001b[4zMXP<GreenMXP>&\u001b[3z-\u001b[3z\u001b[4zMXP<CyanMXP>*\u001b[3z-\u001b[3z\u001b[4zMXP<YellowMXP>@\u001b[3z-\u001b[3z\u001b[4zMXP<GreenMXP>$\u001b[3z \n \u001b[3z\\\u001b[3z \n \u001b[3z\u001b[4zMXP<CyanMXP>*\u001b[3z-\u001b[3z\n"
-- Desired Result:
+ +
&-*-@-$
\
*-
--]]
-- example: regexSplit("a,b, c", ", ?") => {"a", "b", "c"}
function regexSplit(text, regularExpression)
local ret = {}
local reg = rex.new(regularExpression)
local matchStart, matchEnd = reg:match(text)
if matchStart == nil then
table.insert(ret, text)
else
local prevEnd = 0
while matchStart ~= nil do
table.insert(ret, string.sub(text, prevEnd + 1, matchStart - 1))
prevEnd = matchEnd
matchStart, matchEnd = reg:match(text, prevEnd + 1)
end
table.insert(ret, string.sub(text, prevEnd + 1, -1))
end
return ret
end
-- cb is passed the MDTData and should return true if the display should be updated
function editMDTData(cb)
if MDTIndicatorsEnabled and curMDTData ~= nil then
local changed = cb(curMDTData)
if changed then
scoreMDTRooms(curMDTData)
makeMDTArraymap(curMDTData)
window.draw(winID) -- writtenmap is sent second, so draw here.
end
end
end
local numStrToNum = { one = 1, two = 2, three = 3, four = 4, five = 5, six = 6, seven = 7, eight = 8, nine = 9, ten = 10, eleven = 11, twelve = 12, thirteen = 13, fourteen = 14 }
function parseNumNpc(str)
local count, name
local i = string.find(str, ' ')
if i then
local firstWord = string.sub(str, 1, i - 1)
name = string.sub(str, i + 1)
count = numStrToNum[string.lower(firstWord)]
if not count then
count = tonumber(firstWord)
if not count then
count = 1
name = firstWord .. ' ' .. name
end
end
else
count = 1
name = str
end
return count, name
end
function formatNumNpc(count, str)
if count == 1 then
return str
else
return count .. ' ' .. str
end
end
function invertKV(kv)
local ret = {}
for k, v in pairs(kv) do
ret[v] = k
end
return ret
end
local pluralizeCache = {
man = "men",
fisherman = "fishermen",
woman = "women",
mercenary = "mercenaries",
lady = "ladies",
sheep = "sheep",
grflx = "grflxen",
child = "children",
}
local singularizeCache = invertKV(pluralizeCache)
function autopluralize(str)
if str:match('[sxz]$') or str:match('[cs]h$') then
return str .. 'es'
elseif str:match('quy$') or str:match('[^aeiou]y$') then
return str .. 'ies'
else
return str .. 's'
end
end
function pluralizeWord(word)
if not pluralizeCache[word] then
local plural = autopluralize(word)
pluralizeCache[word] = plural
singularizeCache[plural] = word
end
return pluralizeCache[word]
end
function pluralize(str)
local words = regexSplit(str, ' ')
words[#words] = pluralizeWord(words[#words])
return table.concat(words, ' ')
end
function singularizeWord(word)
return singularizeCache[word] or string.sub(word, 1, -2)
end
-- only called when if there are 3 or more and all but 1 enter at once
function singularize(str)
local words = regexSplit(str, ' ')
words[#words] = singularizeWord(words[#words])
return table.concat(words, ' ')
end
local exitToMoves = { northeast = "1 ne", northwest = "1 nw", southeast = "1 se", southwest = "1 sw", north = "1 n", south = "1 s", east = "1 e", west = "1 w" }
function triggerNPCEntered(name, line, wildcards)
local allEntering = wildcards.npcs
local moves = exitToMoves[wildcards.exit]
editMDTData(function (data)
local changed = false
for roomI,room in ipairs(data) do
if room.entities ~= nil then
if #room.moves == 1 and room.moves[1] == moves then
for _, numNpc in ipairs(regexSplit(allEntering, "(, | and (?!yellow |white ))(an? )?")) do
local enteringCount, enteringName = parseNumNpc(numNpc)
local enteringPlural = enteringCount > 1
for distantI, distant in ipairs(room.entities) do
local distantCount, distantName = parseNumNpc(distant)
local distantPlural = distantCount > 1
local maybePluralEnteringName = enteringName
if distantPlural and not enteringPlural then
maybePluralEnteringName = pluralize(enteringName)
end
if maybePluralEnteringName == distantName then
local remaining = distantCount - enteringCount
if remaining < 1 then
table.remove(room.entities, distantI)
else
if remaining == 1 then
if enteringCount == 1 then
distantName = enteringName
else
distantName = singularize(enteringName)
end
end
room.entities[distantI] = formatNumNpc(remaining, distantName)
end
changed = true
break
end
end
end
end
end
end
return changed
end)
end
function onGMCPReceived(message, dataStr)
if message == "room.writtenmap" then
lastMDTPacket = dataStr
if MDTIndicatorsEnabled then
curMDTData = parseMDT(dataStr, nil, MDTPlayerPrefix) -- Gets a list of rooms with things in them.
scoreMDTRooms(curMDTData)
makeMDTArraymap(curMDTData)
end
window.draw(winID) -- writtenmap is sent second, so draw here.
elseif message == "room.map" then
-- Map is colored with MXP color codes. First strip out the extra junk, then split into lines and process the color codes.
lastMapPacket = dataStr
local map = dataStr
map = string.sub(map, 2, -2) -- remove surrounding quotes ("").
map = string.gsub(map, "\\u001b%[4z", "") -- Remove ANSI codes before MXP colors.
map = string.gsub(map, "MXP<[\\/]-send.-MXP>", "") -- remove MXP links, if any (not colors).
-- Replace ANSI reset code with a fake MXP color tag, which the next step will pick up.
map = string.gsub(map, "\\u001b%[3z", "MXP<NormalMXP>")
-- Un-escape actual map characters.
map = string.gsub(map, "\\\\", "\\") -- change \\ to \.
map = string.gsub(map, "\\\/", "\/") -- change \/ to /.
for i=#mapLines,1,-1 do -- Clear old lists.
mapLines[i] = nil
colorChangesAt[i] = nil
end
for line in string.gmatch(map, "(.-)\\n") do -- Split map up into lines.
table.insert(mapLines, line)
end
for lineIdx,line in ipairs(mapLines) do
local startPos = 1
while startPos do
startPos, line = processColorCodes(startPos, line, lineIdx)
end
mapLines[lineIdx] = line -- Update line after stripping out color codes.
end
end
end
-- Drawing:
--------------------------------------------------
local function drawWindow()
winRect.width = WindowInfo(winID, 3)
winRect.height = WindowInfo(winID, 4)
WindowRectOp(winID, 2, 0, 0, winRect.width, winRect.height, COLORS.background) -- Clear/Fill Background
WindowRectOp(winID, 1, 0, 0, winRect.width, winRect.height, COLORS.border) -- Draw Border
local gridX, gridY = fontCharWidth + spacingX, fontHeight + spacingY
local mapHalfWidth = (playerX - 0.5) * gridX
local mapHalfHeight = (playerY - 0.5) * gridY
local ox, oy = winRect.width/2 - mapHalfWidth, winRect.height/2 - mapHalfHeight
local baseCol = COLORS.Normal
local color = baseCol
for lineIdx,line in ipairs(mapLines) do
local x, y = ox, oy + (lineIdx - 1)*gridY
color = baseCol
for charIdx=1,string.len(line) do
if colorChangesAt[lineIdx] and colorChangesAt[lineIdx][charIdx] then
color = colorChangesAt[lineIdx][charIdx]
if type(color) ~= "number" then
Note("[RossAsciiMap] - Weird color value in colorChangesAt: ", colorChangesAt[lineIdx][charIdx])
color = COLORS.Normal
end
end
local char = string.sub(line, charIdx, charIdx)
if char ~= " " then
WindowText(winID, fontID, char, x, y, x+fontMaxCharWidth, y+gridY, color)
end
x = x + gridX
end
end
-- Draw room scores.
if MDTIndicatorsEnabled and curMDTData then
local ox, oy = winRect.width/2, winRect.height/2 -- Start from player pos, center of window.
for dy,row in pairs(curMDTData.scoreMap) do
for dx,scoreList in pairs(row) do
-- Double dx and dy because rooms are every-other line on the ASCII map.
local x, y = ox + dx*2*gridX, oy - dy*2*gridY -- Subtract dy because we draw in +y = down coordinates.
for groupName,score in pairs(scoreList) do
if not disabledGroups[groupName] then -- Only really affects the default group, other disabled groups are simply not scored.
local group = filterGroups[groupName]
if not group then
Note('[RossAsciiMap] - No filter group defined for name: "' .. groupName .. '"')
group = filterGroups.default or DEFAULT_FILTER_GROUP
end
local _x, _y = x + group.ox, y + group.oy
local width = group.fontMaxCharWidth * tostring(score):len()
WindowText(winID, group.fontID, score, _x, _y, _x+width, _y+gridY, group.color)
end
end
end
end
end
end
-- Tooltip - Show Things Under Cursor:
--------------------------------------------------
local floor = math.floor
local function round(x, incr)
if incr then
return floor(x/incr + 0.5)*incr
end
return floor(x + 0.5)
end
local function windowMousePosToRoomPos(mx, my)
local gridX, gridY = fontCharWidth + spacingX, fontHeight + spacingY
local x, y = mx - winRect.width/2, my - winRect.height/2
return round(x/gridX/2), -round(y/gridY/2)
end
local function getThingsInRoom(rx, ry)
if not curMDTData then return end
for i,room in ipairs(curMDTData) do
if room.dx == rx and room.dy == ry then
return room.entities
end
end
end
local tooltipRX, tooltipRY, tooltipText
local tooltipWinID = SELF_ID .. "tooltip"
local tooltipFont, tooltipFontSize = "FixedSys", 9
local tooltipFontHeight
local padding = 2
local tooltipBorderColor = RGBToInt(80)
local tooltipTextColor = RGBToInt(160)
local tooltipIsVisible = false
local tooltipW, tooltipH
local function drawTooltip()
WindowRectOp(tooltipWinID, 2, 0, 0, tooltipW, tooltipH, 0) -- Draw background
WindowRectOp(tooltipWinID, 1, 0, 0, tooltipW, tooltipH, tooltipBorderColor) -- Draw border
WindowText(tooltipWinID, "font", tooltipText, padding, padding, tooltipW, tooltipH, tooltipTextColor)
Redraw()
end
local function showTooltip(x, y, rx, ry, text)
tooltipIsVisible = true
tooltipRX, tooltipRY, tooltipText = rx, ry, text
tooltipW = WindowTextWidth(tooltipWinID, "font", text) + padding*2
tooltipH = tooltipFontHeight + padding*2
WindowResize(tooltipWinID, tooltipW, tooltipH, 0)
local gridX, gridY = fontCharWidth + spacingX, fontHeight + spacingY
local winX, winY = WindowInfo(winID, 1), WindowInfo(winID, 2)
local x = winX + winRect.width/2 + rx*2 * gridX
local y = winY + winRect.height/2 - ry*2 * gridY
WindowPosition(tooltipWinID, x, y - tooltipH*1.5, 5, 2)
WindowShow(tooltipWinID, true)
local mapWindowZ = WindowInfo(winID, 22)
WindowSetZOrder(tooltipWinID, mapWindowZ + 1) -- NOTE: winRect.z is only set when settings ore loaded or saved.
drawTooltip()
end