ifmapper 0.9 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
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