ifmapper 0.9 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. data/HISTORY.txt +132 -5
  2. data/IFMapper.gemspec +2 -2
  3. data/TODO.txt +0 -5
  4. data/lib/IFMapper/AStar.rb +15 -8
  5. data/lib/IFMapper/Connection.rb +137 -25
  6. data/lib/IFMapper/FXConnection.rb +54 -40
  7. data/lib/IFMapper/FXConnectionDialogBox.rb +12 -4
  8. data/lib/IFMapper/FXMap.rb +161 -60
  9. data/lib/IFMapper/FXMapperWindow.rb +205 -70
  10. data/lib/IFMapper/FXRoom.rb +7 -2
  11. data/lib/IFMapper/FXRoomDialogBox.rb +2 -1
  12. data/lib/IFMapper/FXRoomList.rb +95 -0
  13. data/lib/IFMapper/PDFMapExporter.rb +10 -1
  14. data/lib/IFMapper/Room.rb +22 -0
  15. data/lib/IFMapper/TranscriptDialogBox.rb +117 -0
  16. data/lib/IFMapper/TranscriptReader.rb +320 -96
  17. data/maps/AllRoads.map +0 -0
  18. data/maps/Bureaucracy.map +0 -0
  19. data/maps/CityOfSecrets.map +0 -0
  20. data/maps/DDIV.map +0 -0
  21. data/maps/Heroine.map +0 -0
  22. data/maps/SavoirFare.map +0 -0
  23. data/maps/Tangle.map +0 -0
  24. data/maps/anchor.map +0 -0
  25. data/maps/ballerina.map +0 -0
  26. data/maps/bluechairs.map +0 -0
  27. data/maps/break_in.map +0 -0
  28. data/maps/christminster.map +0 -0
  29. data/maps/deadline.map +0 -0
  30. data/maps/delusions.map +0 -0
  31. data/maps/dreamhold.map +0 -0
  32. data/maps/eas.map +0 -0
  33. data/maps/eas2.map +0 -0
  34. data/maps/eas3.map +0 -0
  35. data/maps/inhumane.map +0 -0
  36. data/maps/lurkinghorror.map +0 -0
  37. data/maps/metamorphoses.map +0 -0
  38. data/maps/moonmist.map +0 -0
  39. data/maps/muldoon_legacy.map +0 -0
  40. data/maps/pawn.map +0 -0
  41. data/maps/pytho.map +0 -0
  42. data/maps/risorgimento.map +0 -0
  43. data/maps/sherbet.map +0 -0
  44. data/maps/slouch.map +0 -0
  45. data/maps/spring.map +0 -0
  46. data/maps/trinity.map +0 -0
  47. data/maps/worlds.map +0 -0
  48. data/maps/zdungeon.map +0 -0
  49. metadata +42 -8
@@ -11,6 +11,10 @@ class FXRoom < Room
11
11
 
12
12
  @@win = nil
13
13
 
14
+ def self.no_maps
15
+ @@win.hide if @@win
16
+ end
17
+
14
18
  def marshal_dump
15
19
  super + [ @selected ]
16
20
  end
@@ -60,8 +64,8 @@ class FXRoom < Room
60
64
  # Open a modal requester to change properties
61
65
  #
62
66
  def modal_properties(map)
63
- shown = @@win and @@win.shown?
64
- if shown
67
+ if @@win and @@win.shown?
68
+ shown = @@win
65
69
  @@win.hide
66
70
  end
67
71
  win = FXRoomDialogBox.new(map, self, nil, true)
@@ -142,6 +146,7 @@ class FXRoom < Room
142
146
  # Main draw function for room
143
147
  #
144
148
  def draw(dc, zoom, idx, opt, data)
149
+ dc.font = data['objfont']
145
150
  draw_box(dc, zoom, idx, opt)
146
151
  return if zoom < 0.5
147
152
  dc.font = data['font']
@@ -16,6 +16,7 @@ class FXRoomDialogBox < FXDialogBox
16
16
  @room.desc = @desc.text
17
17
 
18
18
  @map.draw_room(@room)
19
+ @map.update_roomlist
19
20
  end
20
21
 
21
22
 
@@ -142,7 +143,7 @@ class FXRoomDialogBox < FXDialogBox
142
143
  else
143
144
  @name.connect(SEL_CHANGED) { copy_to() }
144
145
  @objects.connect(SEL_CHANGED) { copy_to()}
145
- @tasks.connect(SEL_CHANGED) { copy_to() }
146
+ @tasks.connect(SEL_CHANGED) { @room.tasks = @tasks.text }
146
147
  @darkness.connect(SEL_COMMAND) { copy_to() }
147
148
  @desc.connect(SEL_CHANGED) { @room.desc = @desc.text }
148
149
  end
@@ -0,0 +1,95 @@
1
+
2
+ #
3
+ # Class that lists all rooms in a map and allows you to jump to them
4
+ #
5
+ class FXRoomList < FXDialogBox
6
+
7
+ def pick(sender, sel, ptr)
8
+ item = @box.currentItem
9
+ idx, r = @box.getItemData(item)
10
+
11
+ @map.section = idx
12
+ @map.clear_selection
13
+ r.selected = true
14
+ @map.center_view_on_room(r)
15
+ @map.draw
16
+ end
17
+
18
+ def copy_from(map)
19
+ @map = map
20
+ sort
21
+ end
22
+
23
+ def sort
24
+ item = @box.currentItem
25
+ room = nil
26
+ if item >= 0
27
+ idx, room = @box.getItemData(item)
28
+ end
29
+
30
+ @box.clearItems
31
+
32
+ rooms = []
33
+ @map.sections.each_with_index { |s, idx|
34
+ s.rooms.each { |r|
35
+ rooms << [idx, r]
36
+ }
37
+ }
38
+
39
+ dir = @box.header.getArrowDir(0)
40
+ if dir != MAYBE
41
+ rooms = rooms.sort_by { |r| r[0] }
42
+ else
43
+ dir = @box.header.getArrowDir(1)
44
+ if dir != MAYBE
45
+ rooms = rooms.sort_by { |r| r[1].name }
46
+ end
47
+ end
48
+
49
+ if dir == Fox::TRUE
50
+ rooms.reverse!
51
+ end
52
+
53
+ rooms.each { |r|
54
+ item = "#{r[0]}\t#{r[1].name}"
55
+ @box.appendItem(item, nil, nil, r)
56
+ }
57
+
58
+ if room
59
+ rooms.each_with_index { |r, idx|
60
+ if r[1] == room
61
+ @box.currentItem = idx
62
+ break
63
+ end
64
+ }
65
+ end
66
+ end
67
+
68
+ def initialize(map)
69
+ super(map.window.parent, "Locations", DECOR_ALL, 40, 40, 300, 400)
70
+
71
+ @box = FXIconList.new(self, nil, 0,
72
+ ICONLIST_BROWSESELECT|
73
+ LAYOUT_FILL_X|LAYOUT_FILL_Y)
74
+ @box.appendHeader("Section", nil, 60)
75
+ @box.appendHeader("Name", nil, 200)
76
+ @box.header.connect(SEL_COMMAND) { |sender, sel, which|
77
+ if @box.header.arrowUp?(which)
78
+ dir = MAYBE
79
+ elsif @box.header.arrowDown?(which)
80
+ dir = TRUE
81
+ else
82
+ dir = FALSE
83
+ end
84
+ @box.header.setArrowDir(which, dir)
85
+ @box.header.setArrowDir(which ^ 1, MAYBE)
86
+ sort
87
+ }
88
+
89
+ @box.connect(SEL_COMMAND, method(:pick))
90
+
91
+ create
92
+ copy_from(map)
93
+ end
94
+
95
+ end
@@ -104,7 +104,16 @@ class FXConnection
104
104
 
105
105
  def pdf_draw_complex( pdf, opts )
106
106
  if opts['Paths as Curves']
107
- p = pdf_draw_complex_as_bspline( pdf, opts )
107
+ if @room[0] == @room[1]
108
+ dirA, dirB = dirs
109
+ if dirA == dirB
110
+ p = pdf_draw_complex_as_lines( pdf, opts )
111
+ else
112
+ p = pdf_draw_complex_as_bspline( pdf, opts )
113
+ end
114
+ else
115
+ p = pdf_draw_complex_as_bspline( pdf, opts )
116
+ end
108
117
  else
109
118
  p = pdf_draw_complex_as_lines( pdf, opts )
110
119
  end
data/lib/IFMapper/Room.rb CHANGED
@@ -44,6 +44,7 @@ class Room
44
44
  if not vars.empty? and vars[0].kind_of?(String)
45
45
  @desc = vars.shift
46
46
  @desc.sub!(/\n/, ' ')
47
+ @desc.strip!
47
48
  end
48
49
  end
49
50
 
@@ -59,6 +60,27 @@ class Room
59
60
  @exits[dir] = connection
60
61
  end
61
62
 
63
+ #
64
+ # Return the number of doors present in room
65
+ #
66
+ def num_doors
67
+ num = 0
68
+ @exits.each { |e|
69
+ next if not e
70
+ num += 1 if e.door?
71
+ }
72
+ return num
73
+ end
74
+
75
+ #
76
+ # Return the number of exits present in room
77
+ #
78
+ def num_exits
79
+ num = 0
80
+ @exits.each { |e| num += 1 if e }
81
+ return num
82
+ end
83
+
62
84
  #
63
85
  # Return a direction from the vector of the exit that would take
64
86
  # us to the 'b' room more cleanly.
@@ -0,0 +1,117 @@
1
+
2
+ class TranscriptDialogBox < FXDialogBox
3
+
4
+ LOCATION_TEXT = [
5
+ "
6
+ West of House
7
+ You are standing in an open field west of a white house, with a boarded
8
+ front door.
9
+ ",
10
+ "
11
+ (You are now in the driveway.)
12
+ (You're sitting in the sports car.)
13
+ You are by the front gate of the Tresyllian Castle. You can hear the ocean
14
+ beating urgently against the rocks far below.
15
+ ",
16
+ "
17
+ You are now in the driveway entrance.
18
+ You are standing at the foot of the driveway, the entrance to the Linder
19
+ property. The entire lot is screened from the street and the neighbors by
20
+ a wooden fence, except on the east side, which fronts on dense
21
+ bamboo woods.
22
+ ",
23
+ ]
24
+
25
+ LOCATION2_TEXT = [
26
+ "
27
+ West of House
28
+ ",
29
+ "
30
+ (You are in the driveway.)
31
+ ",
32
+ "
33
+ (driveway entrance)
34
+ ",
35
+ ]
36
+
37
+ IDENTIFY_TYPE = [
38
+ 'By Descriptions',
39
+ 'By Short Name',
40
+ ]
41
+
42
+ SHORTNAME_TYPE = [
43
+ 'Classic',
44
+ 'Moonmist Style',
45
+ 'Witness Style',
46
+ ]
47
+
48
+ def copy_from(transcript)
49
+ @transcript = transcript
50
+ @type.currentNo = transcript.identify
51
+ @short.currentNo = transcript.shortName
52
+ @show.text = LOCATION_TEXT[@short.currentNo]
53
+ @show2.text = LOCATION2_TEXT[@short.currentNo]
54
+ parent = transcript.map.window
55
+ end
56
+
57
+ def copy_to()
58
+ @transcript.identify = @type.currentNo
59
+ @transcript.shortName = @short.currentNo
60
+ @show.text = LOCATION_TEXT[@short.currentNo]
61
+ @show2.text = LOCATION2_TEXT[@short.currentNo]
62
+ end
63
+
64
+ def initialize(transcript)
65
+ map = transcript.map
66
+ pos = [40, 40]
67
+ maxW = map.window.width - 390
68
+ maxH = map.window.height - 300
69
+ pos[0] = maxW if pos[0] > maxW
70
+ pos[1] = maxH if pos[1] > maxH
71
+
72
+ decor = DECOR_TITLE|DECOR_BORDER|DECOR_CLOSE
73
+ super( map.window, 'Transcript Options', decor, pos[0], pos[1], 0, 0 )
74
+
75
+ mainFrame = FXVerticalFrame.new(self,
76
+ FRAME_SUNKEN|FRAME_THICK|
77
+ LAYOUT_FILL_X|LAYOUT_FILL_Y)
78
+
79
+ frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
80
+ FXLabel.new(frame, "Identify Locations: ", nil, 0, LAYOUT_FILL_X)
81
+ pane = FXPopup.new(self)
82
+ IDENTIFY_TYPE.each { |t|
83
+ FXOption.new(pane, t, nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
84
+ }
85
+ @type = FXOptionMenu.new(frame, pane, FRAME_RAISED|FRAME_THICK|
86
+ JUSTIFY_HZ_APART|ICON_AFTER_TEXT|
87
+ LAYOUT_CENTER_X|LAYOUT_CENTER_Y)
88
+
89
+
90
+ frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
91
+ FXLabel.new(frame, "Short Name Type: ", nil, 0, LAYOUT_FILL_X)
92
+ pane = FXPopup.new(self)
93
+ SHORTNAME_TYPE.each { |t|
94
+ FXOption.new(pane, t, nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
95
+ }
96
+ @short = FXOptionMenu.new(frame, pane, FRAME_RAISED|FRAME_THICK|
97
+ JUSTIFY_HZ_APART|ICON_AFTER_TEXT|
98
+ LAYOUT_CENTER_X|LAYOUT_CENTER_Y)
99
+
100
+ frame = FXVerticalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
101
+ FXLabel.new(frame, "Sample Verbose Mode: ", nil, 0, LAYOUT_FILL_X)
102
+ @show = FXText.new(frame, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
103
+ @show.visibleRows = 7
104
+ @show.visibleColumns = 60
105
+ @show.disable
106
+ FXLabel.new(frame, "Sample Brief Mode: ", nil, 0, LAYOUT_FILL_X)
107
+ @show2 = FXText.new(frame, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y)
108
+ @show2.visibleRows = 3
109
+ @show2.visibleColumns = 60
110
+ @show2.disable
111
+
112
+ @type.connect(SEL_COMMAND) { copy_to }
113
+ @short.connect(SEL_COMMAND) { copy_to }
114
+
115
+ copy_from(transcript)
116
+ end
117
+ end
@@ -12,6 +12,8 @@
12
12
  #
13
13
  class TranscriptReader
14
14
 
15
+ TRANSCRIPT = /^(?:Start of a transcript of|Here begins a transcript of interation with (.*)|Here begins a transcript of interation with)/
16
+
15
17
  PROMPT = /^>\s*/
16
18
  LOOK = /^l(ook)?/i
17
19
  UNDO = /^undo$/i
@@ -24,7 +26,7 @@ class TranscriptReader
24
26
  TAKE = /^(take|get)\s+(a\s+|the\s+)?(.*)/i
25
27
  DROP = /^(drop|leave)\s+()/i
26
28
  STARTUP = /(^[A-Z]+$|Copyright|\([cC]\)\s*\d|Trademark|Release|Version|[Ss]erial [Nn]umber|Written by)/
27
- DARKNESS = /^dark(ness)?$|In the dark|It is pitch black/i
29
+ DARKNESS = /^dark(ness)?$|^in the dark$|^It is pitch black|^It is too dark to see anything|^You stumble around in the dark/i
28
30
  DEAD = /(You die|You have died|You are dead)/i
29
31
  YES = /^y(es)/i
30
32
 
@@ -42,16 +44,21 @@ class TranscriptReader
42
44
  "in" => 3, "out" => 4, 'enter' => 3, 'exit' => 4 }
43
45
 
44
46
  DIR_REX = '(' + DIRMAP.keys.join('|') + '|' + ODIRMAP.keys.join('|') + ')'
45
- GO = /^((walk|run|go)\s+)?#{DIR_REX}[.,\s]*\b/i
47
+ GO = /^((walk|run|go|drive|jump)\s+)?#{DIR_REX}[.,\s]*\b/i
46
48
 
47
49
  # Direction list in order of positioning preference.
48
50
  DIRLIST = [ 0, 4, 2, 6, 1, 3, 5, 7 ]
49
51
 
50
52
  # No exit replies
51
53
  NO_EXIT = [
52
- /you\s+can't\s+go\s+that\s+way/i,
53
- /doorways\s+lead\s+/i,
54
- /that's\s+a\s+wall/i,
54
+ /\byou\scan't\sgo\sthat\sway\b/i,
55
+ /\bdoorways\slead\s/i,
56
+ /\bthat's\sa\swall\b/i,
57
+ /\bno\sexit\b/i,
58
+ /\bthere's\s+nothing\sin\sthat\sdirection\b/i,
59
+ /\bblock\sthe\sway/i,
60
+ /\byou\scan\sonly\sgo\s/i,
61
+ /\byou\scan\sgo\sonly\s/i,
55
62
  ]
56
63
 
57
64
  # Closed exit replies
@@ -59,11 +66,12 @@ class TranscriptReader
59
66
  /door\s+is\s+closed/i,
60
67
  ]
61
68
 
62
- NAME_REMOVE = /(\s+\(.+\)|,\s+[io]n\s+.+)/ # remove things like (on the bed)
63
- NAME_INVALID = /[:;\[\]!\.\?\000]/
64
- SALUTATIONS = /\b(Mr|Mr?s|Miss|Jr|Sr)\./
69
+ # remove things like (on the bed)
70
+ NAME_REMOVE = /(\s+\(.+\)|,\s+[io]n\s+[^\,\.]+)/
71
+ NAME_INVALID = /[:;\*\[\]\|\+\=!\.\?\000]/
72
+ SALUTATIONS = /\b(Mr|Mr?s|Miss|Jr|Sr|St)\./
65
73
  NAME_MAXWORDS = 20
66
- NAME_MAXUNCAP = 4 # so that lowercase room/end will be accepted
74
+ NAME_MAXUNCAP = 4 # so that lowercase "room" "end" "of" will be accepted
67
75
 
68
76
  # Default room description recognition parameters.
69
77
  DESC_MINWORDS = 10
@@ -73,10 +81,33 @@ class TranscriptReader
73
81
  DESC_IGNORE = /(?:an|a|open(ed)?|closed?)/i
74
82
 
75
83
 
76
- RESTORE_OK = /\b(Ok|completed?)\b/
84
+ RESTORE_OK = /\b(Ok|completed?|Okay)\b/
85
+
86
+ ##
87
+ THE = '(?:the|a|your|some of the|some)\s+'
88
+
89
+ ## Regex to eliminate articles from get/take replies.
90
+ ARTICLE = /^#{THE}/i
91
+
92
+ ## Possible nessages indicating get/take succeeded
93
+ TAKE_OBJ = '([\w\d\-\'\s]+)'
94
+ TAKE_FROM = '(?:\s+(up|from|out)\s+.*)?'
95
+ TAKE_ALIAS = '(?:grab|pick\s+up|pick|pilfer|take(?:\s+up)?)'
96
+ TAKE_OK = [
97
+ /\btaken\b/i,
98
+ /you\s+have\s+now\s+(?:got\s+)?(?:#{THE})?#{TAKE_OBJ}#{TAKE_FROM}/i,
99
+ /you\s+#{TAKE_ALIAS}\s+(?:#{THE})?#{TAKE_OBJ}#{TAKE_FROM}/i,
100
+ /you\s+now\s+have\s+(?:got\s+)?(?:#{THE})?#{TAKE_OBJ}/i,
101
+ /you\s+pick\s+up\s+(?:#{THE})?#{TAKE_OBJ}/i,
102
+ /you\s+are\s+now\s+holding\s+(?:#{THE})?#{TAKE_OBJ}/i,
103
+ ]
104
+
105
+ IT = /^(it|them)$/i
106
+
107
+ @@win = nil
77
108
 
78
- ## Change this to non-dil to print out debugging info
79
- @@debug = 1
109
+ ## Change this to non-nil to print out debugging info
110
+ @@debug = nil
80
111
 
81
112
  def debug(*msg)
82
113
  if @@debug
@@ -84,20 +115,15 @@ class TranscriptReader
84
115
  end
85
116
  end
86
117
 
87
- ##
88
- THE = '\b(the|a|your|some)\b'
118
+ IDENTIFY_BY_DESCRIPTION = 0
119
+ IDENTIFY_BY_SHORTNAME = 1
89
120
 
90
- ARTICLE = /^#{THE}\s+/i
121
+ SHORTNAME_CLASSIC = 0
122
+ SHORTNAME_MOONMIST = 1
123
+ SHORTNAME_WITNESS = 2
91
124
 
92
- ## Possible nessages indicating get/take succeeded
93
- TAKE_OK = [
94
- /taken/i,
95
- /you\s+pick\s+up\s+the\s+?/i,
96
- /you\s+now\s+have\s+(got\s+)?/i,
97
- /you\s+(grab|pick|take)\s+#{THE}\s+\w+/i
98
- ]
125
+ attr_accessor :identify, :shortName, :map
99
126
 
100
- IT = /^(it|them)$/i
101
127
 
102
128
 
103
129
  #
@@ -105,26 +131,47 @@ class TranscriptReader
105
131
  #
106
132
  def take(move, objs)
107
133
  return unless @here
108
- endl = ''
109
- endl = "\n" if @here.objects != '' and @here.objects[-1,1] != "\n"
134
+ if @here.objects != '' and @here.objects[-1,1] != "\n"
135
+ @here.objects << "\n"
136
+ end
137
+
110
138
  objs.each { |cmd, dummy, obj|
111
- next if not obj
139
+ next if not obj or not move[:reply]
112
140
 
113
141
  objlist = obj.split(',')
114
142
  o = objlist[0]
115
143
  if objlist.size == 1 and o != 'all'
116
144
  # ignore 'get up'
117
- next if cmd == 'get' and o == 'up'
145
+ next if cmd == 'get' and (o == 'up' or o == 'off')
118
146
  o = @last_obj if o =~ IT
119
147
  next if not o
148
+ if move[:reply][0] =~ /^\((.*)\)$/
149
+ nobj = $1
150
+ words = nobj.split(/\s+/)
151
+ if words.size < 7
152
+ # Acclaration, like:
153
+ # > get mask
154
+ # (the brown mask)
155
+ # Taken.
156
+ o = nobj
157
+ else
158
+ # too big of an aclaration... probably something like
159
+ # (putting the xxx in your bag to make room)
160
+ end
161
+ end
120
162
  o.sub!(ARTICLE, '')
121
163
  status = move[:reply].to_s
122
164
  TAKE_OK.each { |re|
123
- if status =~ re and not @objects.has_key?(o) and
124
- not @here.objects =~ /\b#{o}\b/
125
- @here.objects << endl << o << "\n"
126
- @objects[o] = 1
127
- @last_obj = o
165
+ if status =~ re then
166
+ obj = $1 || o
167
+ obj = o if obj =~ /\b(it|them)\b/
168
+ @last_obj = obj
169
+ # Take succeeded, change object o
170
+ if not @objects.has_key?(obj) and
171
+ not @here.objects =~ /\b#{obj}\b/
172
+ @here.objects << obj << "\n"
173
+ @objects[obj] = 1
174
+ end
128
175
  break
129
176
  end
130
177
  }
@@ -133,17 +180,23 @@ class TranscriptReader
133
180
  move[:reply].each { |reply|
134
181
  o, status = reply.split(':')
135
182
  next if not status
136
- o = @last_obj if o =~ IT
183
+ next if o.split(' ').size > 6 # ignore if too many words
137
184
  o.sub!(ARTICLE, '')
138
- if o and not @objects.has_key?(o) and
139
- not @here.objects =~ /\b#{o}\b/
140
- @here.objects << endl << o << "\n"
141
- @objects[o] = 1
142
- @last_obj = o
143
- end
185
+ TAKE_OK.each { |re|
186
+ if status =~ re then
187
+ if o and not @objects.has_key?(o) and
188
+ not @here.objects =~ /\b#{o}\b/
189
+ @here.objects << o << "\n"
190
+ @objects[o] = 1
191
+ end
192
+ @last_obj = o
193
+ break
194
+ end
195
+ }
144
196
  }
145
197
  end
146
198
  }
199
+ @here.objects.squeeze("\n")
147
200
  end
148
201
 
149
202
  #
@@ -161,39 +214,29 @@ class TranscriptReader
161
214
  end
162
215
 
163
216
 
164
- def parse
165
- # Find first command prompt
166
- while line = @f.gets
167
- break if PROMPT =~ line
168
- end
169
- parse_line(line)
170
- end
171
-
172
217
  #
173
218
  # Given a room description, parse it to try to find out all exits
174
219
  # from room.
175
220
  #
176
221
 
177
222
  DIRS = {
178
- 'north' => 0, 'northeast' => 1, 'east' => 2, 'southeast' => 3, 'south' => 4,
179
- 'southwest' => 5, 'west' => 6, 'northwest' => 7
223
+ 'north' => 0, 'northeast' => 1, 'east' => 2, 'southeast' => 3,
224
+ 'south' => 4, 'southwest' => 5, 'west' => 6, 'northwest' => 7
180
225
  }
181
226
 
182
227
  DIR = '(' + DIRS.keys.join('|') + ')'
183
- OR = '(and|or)'
228
+ OR = '(?:and|or)'
184
229
 
185
230
  EXITS_REGEX =
186
231
  [
187
- # (You can) go south or east
188
- /\s+go\s+#{DIR}\s+#{OR}\s+#{DIR}\b/i,
189
- # You can go south
190
- /you\s+can\s+go\s+#{DIR}\b/i,
232
+ # paths lead west and north
233
+ /(run|bears|lead|wander|winds|go)\s+#{DIR}\s+#{OR}\s+#{DIR}\b/i,
191
234
  # to the east or west
192
235
  /to\s+the\s+#{DIR}\s+#{OR}\s+(to\s+the\s+)?#{DIR}\b/i,
236
+ # You can go south
237
+ /you\s+can\s+go\s+#{DIR}\b/i,
193
238
  # to the east
194
239
  /to\s+the\s+#{DIR}\b/i,
195
- # paths lead west and north
196
- /(run|bears|lead|wander|winds)\s+#{DIR}\s+#{OR}\s+#{DIR}\b/i,
197
240
  # east-west corridor
198
241
  /[\.\s+]#{DIR}[\/-]#{DIR}\b/i,
199
242
  # East is the postoffice
@@ -248,15 +291,34 @@ class TranscriptReader
248
291
  # Add a 'stub' for the new connection
249
292
  exits.each { |exit|
250
293
  next if r[exit]
251
- c = @map.new_connection( r, exit, nil )
252
- c.dir = Connection::AtoB
253
- debug "\tADDED STUB #{c}"
294
+ begin
295
+ c = @map.new_connection( r, exit, nil )
296
+ c.dir = Connection::AtoB
297
+ debug "\tADDED STUB #{c}"
298
+ rescue ConnectionError
299
+ end
254
300
  }
255
301
  end
256
302
 
303
+
257
304
  def parse_line(line)
258
305
  return unless line
306
+ @map.mutex.synchronize do
307
+ _parse_line(line)
308
+ end
309
+
310
+ if @map.kind_of?(FXMap)
311
+ @map.clear_selection
312
+ @here.selected = true if @here
313
+ @map.create_pathmap
314
+ @map.zoom = @map.zoom
315
+ puts 'transcript zoom done'
316
+ @map.center_view_on_room(@here) if @here
317
+ @map.draw
318
+ end
319
+ end
259
320
 
321
+ def _parse_line(line)
260
322
  @moves.clear
261
323
 
262
324
  #
@@ -286,7 +348,10 @@ class TranscriptReader
286
348
 
287
349
  # Replace all 'THEN' in command for periods
288
350
  cmd.sub!(THEN, '.')
289
- cmd.sub!(/\s*\.+\s*/, '.') # and multiple periods for just one
351
+ # and all ANDs for commas
352
+ cmd.sub!(/\band\b/i,',')
353
+ # and multiple periods for just one
354
+ cmd.sub!(/\s*\.+\s*/, '.')
290
355
 
291
356
  move[:cmd] = cmd
292
357
  move[:reply] = reply
@@ -336,7 +401,6 @@ class TranscriptReader
336
401
  startup = false
337
402
  rooms = []
338
403
  move[:reply].each { |r|
339
- puts "LINE: #{r}"
340
404
  tele = 1 if r =~ DEAD
341
405
 
342
406
  if r =~ STARTUP
@@ -357,11 +421,11 @@ class TranscriptReader
357
421
  next if startup and r !~ BLANK
358
422
  startup = false
359
423
 
360
- if not roomflag and r !~ BLANK and room_name(r)
361
- debug "set roomflag with #{r}"
424
+ if not roomflag and r !~ BLANK and n = room_name(r)
425
+ debug "Found room #{n}"
362
426
  roomflag = true
363
427
  desc_gap = false
364
- name = r
428
+ name = n
365
429
  desc = ''
366
430
  next
367
431
  end
@@ -373,13 +437,11 @@ class TranscriptReader
373
437
 
374
438
 
375
439
  if roomflag and r !~ BLANK
376
- puts "ADD TO DESC: #{r}"
377
440
  desc << r << "\n"
378
441
  next
379
442
  end
380
443
 
381
444
  if r =~ BLANK and roomflag
382
- puts "DONE DESC"
383
445
  if desc.count("\n") == 1 and desc =~ /\?$/
384
446
  # A "What next?" type of prompt, not a room description
385
447
  desc = ''
@@ -388,9 +450,9 @@ class TranscriptReader
388
450
  desc = nil
389
451
  else
390
452
  desc.gsub!(/\n/, ' ')
453
+ desc.strip!
391
454
  end
392
455
 
393
- puts "DESC: #{desc}"
394
456
  rooms << {
395
457
  :name => name,
396
458
  :desc => desc,
@@ -403,6 +465,17 @@ class TranscriptReader
403
465
  tele = nil
404
466
  end
405
467
  }
468
+
469
+ # Oops, there was no newline between room description and prompt and
470
+ # we missed the last room.
471
+ # This happens for example with Magnetic Scrolls transcripts.
472
+ if name
473
+ rooms << {
474
+ :name => name,
475
+ :desc => desc,
476
+ :tele => tele
477
+ }
478
+ end
406
479
 
407
480
  if not rooms.empty?
408
481
  move[:rooms] = rooms
@@ -439,7 +512,7 @@ class TranscriptReader
439
512
  end
440
513
 
441
514
  # Check if there is a closed door
442
- if @here[dir].type == Connection::FREE
515
+ if @here[dir] and @here[dir].type == Connection::FREE
443
516
  CLOSED_EXIT.each { |re|
444
517
  if move[:reply][0] =~ re
445
518
  # If so, flag it
@@ -512,6 +585,8 @@ class TranscriptReader
512
585
  break
513
586
  end
514
587
 
588
+ sect = @map.section
589
+
515
590
  # Otherwise, assume we moved in some way. Try to find the new room.
516
591
  if name =~ DARKNESS
517
592
  idx = DIRMAP[dir] || ODIRMAP[dir]
@@ -556,7 +631,8 @@ class TranscriptReader
556
631
  dir = choose_dir(@here, there, go)
557
632
  else
558
633
  # special move --- teleport/death/etc.
559
- dir = choose_dir(@here, there, 0)
634
+ go = 0
635
+ dir = choose_dir(@here, there, go)
560
636
  end
561
637
  debug "MOVED IN DIR INDEX: #{dir}"
562
638
 
@@ -566,7 +642,9 @@ class TranscriptReader
566
642
  @here = new_room(move, name, desc, dir, @here, go)
567
643
  else
568
644
  # Visited before -- new link
569
- new_link(move, @here, there, dir, go)
645
+ if sect == @map.section # don't conn
646
+ new_link(move, @here, there, dir, go)
647
+ end
570
648
  @here = there
571
649
  end
572
650
  }
@@ -575,17 +653,80 @@ class TranscriptReader
575
653
  @moves.clear
576
654
  @map.fit
577
655
  if @map.kind_of?(FXMap)
578
- @map.clear_selection
579
- @here.selected = true if @here
580
- @map.create_pathmap
581
- @map.zoom = @map.zoom
582
- @map.center_view_on_room(@here) if @here
583
- @map.verify_integrity
584
- @map.draw
585
656
  end
586
657
  end
587
658
 
588
659
  def find_room(name, desc)
660
+ case @identify
661
+ when IDENTIFY_BY_DESCRIPTION
662
+ return find_room_by_desc(name, desc)
663
+ when IDENTIFY_BY_SHORTNAME
664
+ return find_room_by_name(name, desc)
665
+ end
666
+ end
667
+
668
+ def find_room_by_name(name, desc)
669
+ bestscore = 0
670
+ best = nil
671
+
672
+ @map.sections.each_with_index { |sect, idx|
673
+ sect.rooms.each { |room|
674
+ score = 0
675
+ score += 1 if room.name == name
676
+
677
+ if score == 1 and desc and room.desc
678
+ # We have a description...
679
+ # Try exact description match first
680
+ score += 10 if room.desc == desc
681
+
682
+ # Try substring match
683
+ score += 5 if room.desc.index(desc)
684
+
685
+ # If we have a room where both name and desc match,
686
+ # we get a better score than just description only.
687
+ # This is to help, for example, Trinity, where two rooms have
688
+ # the exact description but different name.
689
+ score += 1 if room.name == name and score > 0
690
+
691
+ # If still no luck, try first N words
692
+ if score == 1
693
+ dwords = room.desc.split(' ')
694
+ words = desc.split(' ')
695
+ match = true
696
+ 0.upto(DESC_MINWORDS) { |i|
697
+ # Ignore some words (like open/close, which may just mean
698
+ # some doors changed state)
699
+ next if words[i] =~ DESC_IGNORE
700
+
701
+ if words[i] != dwords[i]
702
+ match = false
703
+ break
704
+ end
705
+ }
706
+
707
+ score += 1 if match
708
+ end
709
+ end
710
+ next if score <= bestscore
711
+ bestscore = score
712
+ best = [room, idx]
713
+ }
714
+ }
715
+
716
+ return nil if not best
717
+
718
+ # Make sure we are in the right section
719
+ if best[1] != @map.section
720
+ @map.fit
721
+ @map.section = best[1]
722
+ end
723
+ if desc and (not best[0].desc or best[0].desc == '')
724
+ best[0].desc = desc
725
+ end
726
+ return best[0]
727
+ end
728
+
729
+ def find_room_by_desc(name, desc)
589
730
  bestscore = 0
590
731
  best = nil
591
732
 
@@ -642,32 +783,77 @@ class TranscriptReader
642
783
  @map.fit
643
784
  @map.section = best[1]
644
785
  end
645
- puts "DESC: #{desc} RDESC:#{best[0].desc}"
646
786
  if desc and (not best[0].desc or best[0].desc == '')
647
787
  best[0].desc = desc
648
- puts "CHANGED: #{best[0].desc}"
649
788
  end
650
789
  return best[0]
651
790
  end
791
+
792
+ def room_name(line)
793
+ case @shortName
794
+ when SHORTNAME_CLASSIC
795
+ return room_name_classic(line)
796
+ when SHORTNAME_MOONMIST
797
+ return room_name_moonmist(line)
798
+ when SHORTNAME_WITNESS
799
+ return room_name_witness(line)
800
+ end
801
+ end
802
+
803
+ def capitalize_room(line)
804
+ words = line.split(' ')
805
+ words.each { |w|
806
+ if w =~ /^(?:of|on|in|the|a)$/i
807
+ w.downcase!
808
+ else
809
+ w.capitalize!
810
+ end
811
+ }
812
+ return words.join(' ')
813
+ end
814
+
815
+ def room_name_witness(line)
816
+ if line =~ /^You\sare\s(?:now\s)?(?:[io]n\s)?(?:#{THE})?([\w'\d\s\-_]+)\.$/
817
+ return false if $1 =~ /own feet/
818
+ return $1
819
+ elsif line =~ /^\(([\w\d\s_]+)\)$/
820
+ return $1
821
+ else
822
+ return false
823
+ end
824
+ end
825
+
826
+ def room_name_moonmist(line)
827
+ return false if line =~ /^\(You are not holding it.\)$/
828
+ if line =~ /^\(You\sare\s(?:now\s)?(?:[io]n\s)?(?:#{THE})?([\w'\d\s\-_]+)\.\)$/
829
+ return capitalize_room( $1 )
830
+ else
831
+ return false
832
+ end
833
+ end
652
834
 
653
835
  #
654
836
  # Determine if line corresponds to a room name
655
837
  #
656
- def room_name(line)
838
+ def room_name_classic(line)
657
839
  # Check if user/game has created a room with that name already
658
- return true if find_room(line, nil)
840
+ return line if find_room(line, nil)
841
+
842
+ # We have a room if we match darkness
843
+ return line if line =~ DARKNESS
659
844
 
660
845
  # Remove unwanted stuff line (on the bed)
661
846
  line.sub!(NAME_REMOVE, '')
662
847
 
663
848
  # Check if user/game has created a room with that name already
664
- return true if find_room(line, nil)
849
+ return line if find_room(line, nil)
665
850
 
666
851
  # Remove periods from salutations
667
852
  line.sub!(SALUTATIONS, '\1')
668
853
 
669
854
  # quick check for invalid format
670
855
  return false if line =~ NAME_INVALID
856
+
671
857
  # Qucik check for word characters
672
858
  return false unless line =~ /\w/
673
859
 
@@ -676,8 +862,12 @@ class TranscriptReader
676
862
  return false if words.size > NAME_MAXWORDS
677
863
 
678
864
  # Check if we start line with uncapitalized words or symbols
679
- return false if line =~ /^[ a-z\/\\\-\(\)'#]/
865
+ return false if line =~ /^[ a-z\/\\\-\(\)']/
680
866
 
867
+ # Check if line holds only capitalized words or several spaces together
868
+ # or a quote or a 1) line. If so, not a room.
869
+ return false if line =~ /^[A-Z\d,\.\/\-"'\s]+$/ or line =~ /\s\s/ or
870
+ line =~ /^".*"$/ or line =~ /^"[^"]+$/ or line =~ /^\d+\)/
681
871
 
682
872
  # If not, check all words of 4 chars or more are capitalized
683
873
  # and that there are no 3 or more short letter words together
@@ -694,7 +884,7 @@ class TranscriptReader
694
884
  }
695
885
 
696
886
  # Okay, it is a room.
697
- return true
887
+ return line
698
888
  end
699
889
 
700
890
  #
@@ -752,6 +942,7 @@ class TranscriptReader
752
942
  debug "\tUPDATE #{c}"
753
943
  odir = (dir + 4) % 8
754
944
  c.exitAtext = go if go
945
+ c.type = Connection::SPECIAL if go == 0
755
946
  c.roomB = r
756
947
  r[odir] = c
757
948
  debug "\tNOW IT IS #{c}"
@@ -766,7 +957,8 @@ class TranscriptReader
766
957
  begin
767
958
  c = @map.new_connection( from, dir, r )
768
959
  c.exitAtext = go if go
769
- c.dir = Connection::AtoB
960
+ c.type = Connection::SPECIAL if go == 0
961
+ c.dir = Connection::AtoB
770
962
  rescue Section::ConnectionError
771
963
  end
772
964
  end
@@ -820,6 +1012,7 @@ class TranscriptReader
820
1012
  # Stub connection, fill it
821
1013
  c.roomB = to
822
1014
  c.exitAtext = go if go
1015
+ c.type = Connection::SPECIAL if go == 0
823
1016
  # we still need to check the destination to[odir]...
824
1017
  if not to[odir]
825
1018
  # nothing to do
@@ -830,7 +1023,7 @@ class TranscriptReader
830
1023
  # this end cannot be deleted. we need to shift odir
831
1024
  debug "\tCHOOSE NEW DIR for #{odir}"
832
1025
  rgo = nil
833
- if go
1026
+ if go and go > 0
834
1027
  rgo = rgo % 2 == 0? go - 1 : go + 1
835
1028
  end
836
1029
  odir = choose_dir(to, from, rgo, dir)
@@ -849,7 +1042,7 @@ class TranscriptReader
849
1042
  debug "\tVERIFIED EXIT BOTH WAYS"
850
1043
  else
851
1044
  debug "\tOTHER"
852
- if c.roomA == from and c.dir != Connection::BtoA
1045
+ if c.roomA == from
853
1046
  b = c.roomB
854
1047
  if b.name =~ DARKNESS
855
1048
  debug "*** REPLACING DARK ROOM ***"
@@ -859,15 +1052,17 @@ class TranscriptReader
859
1052
  c = nil
860
1053
  else
861
1054
  if c.exitAtext != 0
1055
+ # if it was an up/down/in/out dir, we shift it
862
1056
  shift_link(from, dir)
863
1057
  c = nil
864
1058
  else
1059
+ # else, we really have a problem in the map
865
1060
  debug "*** CANNOT AUTOMAP --- MAZE ***"
866
1061
  dir = Room::DIRECTIONS[dir]
867
1062
  @map.cannot_automap "Maze detected.\n'#{from}' #{dir} leads to '#{c.roomB}',\nnot to this '#{to}'."
1063
+ # self.stop
1064
+ return nil
868
1065
  end
869
- # self.stop
870
- return nil
871
1066
  end
872
1067
  else
873
1068
  debug "SHIFT LINK #{from} #{dir}"
@@ -888,12 +1083,14 @@ class TranscriptReader
888
1083
  from[dir] = c
889
1084
  c.dir = Connection::BtoA
890
1085
  c.exitBtext = go if go
1086
+ c.type = Connection::SPECIAL if go == 0
891
1087
  elsif c.roomB == from
892
1088
  c.exitBtext = go if go
1089
+ c.type = Connection::SPECIAL if go == 0
893
1090
  else
894
1091
  # We need to change odir to something else
895
1092
  rgo = nil
896
- if go
1093
+ if go and go > 0
897
1094
  rgo = go % 2 == 0? go - 1 : go + 1
898
1095
  end
899
1096
  odir = choose_dir(to, from, rgo, dir)
@@ -918,6 +1115,7 @@ class TranscriptReader
918
1115
  from[dir] = c
919
1116
  c.dir = Connection::BOTH
920
1117
  c.exitAtext = go if go
1118
+ c.type = Connection::SPECIAL if go == 0
921
1119
  end
922
1120
  end
923
1121
 
@@ -1018,7 +1216,23 @@ class TranscriptReader
1018
1216
  GC.start
1019
1217
  end
1020
1218
 
1219
+
1220
+ def properties(modal = false)
1221
+ require 'IFMapper/TranscriptDialogBox'
1222
+ if not @@win
1223
+ @@win = TranscriptDialogBox.new(self)
1224
+ else
1225
+ @@win.copy_from(self)
1226
+ end
1227
+ if modal
1228
+ @@win.execute
1229
+ end
1230
+ end
1231
+
1021
1232
  def initialize(map, file)
1233
+ @shortName = 0
1234
+ @identify = 0
1235
+
1022
1236
  @file = file
1023
1237
  @map = map
1024
1238
  @objects = {}
@@ -1026,12 +1240,6 @@ class TranscriptReader
1026
1240
  @here = nil
1027
1241
  @section = -1
1028
1242
  @last_obj = nil
1029
-
1030
- @f = File.open(@file, 'r')
1031
- while line = @f.gets
1032
- break if PROMPT =~ line
1033
- end
1034
- parse
1035
1243
  end
1036
1244
 
1037
1245
  # Step one user command at a time
@@ -1045,6 +1253,22 @@ class TranscriptReader
1045
1253
  end
1046
1254
 
1047
1255
  def start
1256
+ if not @f
1257
+ @f = File.open(@file, 'r')
1258
+ while line = @f.gets
1259
+ if @map.name =~ /^Empty Map/ and line =~ TRANSCRIPT
1260
+ if $1
1261
+ @map.name = $1
1262
+ else
1263
+ @map.name = @f.gets.strip
1264
+ end
1265
+ @map.name = capitalize_room(@map.name)
1266
+ end
1267
+ break if PROMPT =~ line
1268
+ end
1269
+ parse_line(line)
1270
+ end
1271
+
1048
1272
  @t = Thread.new {
1049
1273
  loop do
1050
1274
  self.step