ifmapper 0.8.5 → 0.9

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.
data/HISTORY.txt CHANGED
@@ -1,3 +1,113 @@
1
+ v0.9 - What's new:
2
+
3
+ - Fixed a very idiotic bug in Cut algorithm. It was basically destroying
4
+ all cut connections right after they were cut.
5
+
6
+ - Editing rooms' names or objects now results in a much, much
7
+ faster redraw of the map, even if map is very complex.
8
+
9
+ - You can now display and edit a location's description. This
10
+ description is often used by the the automapper during transcript
11
+ mapping to determine the uniqueness of a room, albeit the main
12
+ use of editing a room description is to allow creation of
13
+ new games (currently, Inform).
14
+
15
+ - You can now optionally avoid displaying the task or description
16
+ section on rooms.
17
+ This is mainly to allow you to read maps that have walkthru tasks
18
+ in them without giving away the game.
19
+
20
+ - IFMapper now supports the concept of closed but unlocked doors.
21
+ These will be displayed as a white square between connections.
22
+ The main interest of placing these doors is for creation
23
+ of new Inform games, not so much for solving games.
24
+
25
+ - You can now select exit stubs. Useful for deleting them when
26
+ automapper detects them incorrectly.
27
+
28
+ - Saving and Loading a map will now verify the integrity of the map
29
+ is correct. This prevets autosave from saving a corrupt map if
30
+ there is a bug in the program.
31
+
32
+ - CTRL-Selection is a toggle selection. That is, if you click once
33
+ on something already selected, it is deselected.
34
+
35
+ - Automapper improvements
36
+ * Automapper will now deal with complex commands involving 'then'
37
+ a tad better.
38
+ * An additional fix to dark rooms. Looking in a dark room would
39
+ result in the dark room being created, even if the room had
40
+ already been tagged as dark. Fixed.
41
+ * When choosing an up/down/in/out/random move direction, a new
42
+ connection will not be generated if there is already a similar
43
+ n/s/e/w/etc. link between the two rooms. This helps to simplify
44
+ some maps.
45
+ * Automapper now will try to automatically remove stub exits that
46
+ are not really exits if the user tries to move in that direction
47
+ and fails.
48
+ * Automapping was incorrectly reporting a maze when a new exit
49
+ would point in the same direction as a visited up/down or in/out
50
+ exit.
51
+ * Room descriptions are now compared (and stored) without \n at the
52
+ end of each line. This helps with comparing descriptions with any
53
+ description the user typed manually.
54
+ * Automapper will no longer crash if a link is attempted between
55
+ two rooms in different sections, nor will it corrupt your map as
56
+ it did before.
57
+ * Automapper is now a tad smarter in not considering words such
58
+ as open/close in descriptions. This helps the automapper not
59
+ get confused if a door which is part of the room description
60
+ changes state.
61
+
62
+ - Inform support.
63
+
64
+ * You can now read an inform file and automap it, with
65
+ some limitations.
66
+ The main limitation deals with how doors are handled, as Inform
67
+ offers an almost infinite number of ways of dealing with doors,
68
+ including the use of at least one of 4 popular libraries. Some
69
+ code will actually resolve issues of where doors lead to within,
70
+ for example, the "Enter" or "Exit" command, making parsing .inf
71
+ files impossible without writing a full inform compiler.
72
+ A second limitation is that Inform allows doors that exist only
73
+ on one "side" of a room. These exits will be ignored.
74
+ The inform automapper should prove compatible with most door
75
+ libraries, but will probably choke on a lot of hand-coded code.
76
+
77
+ * You can now export a map as an Inform .inf (source code) file.
78
+ This can be helpful at the beginning of designing a game to place
79
+ all objects/characters, doors and room descriptions easily.
80
+ Obviously, these source code files will later get hacked heavily
81
+ manually to provide the real game functionality.
82
+ IFMapper just gives you a start, helping you design
83
+ the landscape a tad more easily.
84
+ Doors are created in Inform using Andrew MacKinnon's easydoors
85
+ Inform extension.
86
+
87
+ NOTE: A word of warning about reading Inform or TADS maps.
88
+ Since these file formats have no concept of 'sections', they
89
+ can lead to some **HUGE** maps. The original Adventure, for
90
+ example, results in a 100x100 map. These huge maps may run
91
+ into memory issues and currently make the path-finding algorithm
92
+ work extremely slow.
93
+
94
+ - TADS 3 support
95
+
96
+ * NOTE: TADS 2 is currently *NOT* supported.
97
+
98
+ * You can now read a TADS 3 file and automap it, with some
99
+ limitations.
100
+ Basically, TADS 3 templates are currently *NOT* supported.
101
+ Instead, the standard library templates are kind of hard-coded
102
+ in the reader.
103
+ TADS 3's standard lib is more sensible than Inform when it
104
+ comes to doors, so it should hopefully lead to less
105
+ problems there.
106
+
107
+ * You can now export a TADS 3 (.t) file. Same concept as Inform,
108
+ albeit doors are created using TADS3 standard door classes.
109
+
110
+
1
111
 
2
112
  v0.8.5 - This is mainly a bug fix release.
3
113
 
data/IFMapper.gemspec CHANGED
@@ -2,7 +2,7 @@ require "rubygems"
2
2
 
3
3
  spec = Gem::Specification.new do |spec|
4
4
  spec.name = "ifmapper"
5
- spec.version = '0.8.5'
5
+ spec.version = '0.9'
6
6
  spec.author = "Gonzalo Garramuno"
7
7
  spec.email = 'ggarram@advance.dsl.com.ar'
8
8
  spec.homepage = 'http://www.rubyforge.org/projects/ifmapper/'
data/TODO.txt CHANGED
@@ -1,7 +1,11 @@
1
1
 
2
+ - Ability to remove stub exits if moving in a certain direction fails.
3
+ Need to trap 'fail' messages for moving such as:
4
+ You can't go that way.
5
+
6
+
2
7
  - Add SVG output
3
8
  - Do Printing
4
- - Cleanup code (lots of ugly and duplicated code
5
- between PDF/PS output)
9
+ - Cleanup code
6
10
  - Improve window scrolling (seems like an FXRuby bug)
7
11
  - Add Undo (at least for delete)
@@ -13,6 +13,7 @@ class Connection
13
13
  FREE = 0
14
14
  LOCKED_DOOR = 1
15
15
  SPECIAL = 2
16
+ CLOSED_DOOR = 3
16
17
 
17
18
  # Direction constants
18
19
  BOTH = 0
@@ -73,7 +74,7 @@ class Connection
73
74
  dirB = Room::DIRECTIONS[idx] if idx
74
75
  end
75
76
  end
76
- "#{a} #{dirA.upcase}<->#{b} #{dirB.upcase}"
77
+ "#{a} #{dirA}<->#{b} #{dirB}"
77
78
  end
78
79
  end
79
80
 
@@ -60,7 +60,7 @@ class FXConnection < Connection
60
60
  # window is open, copy the connection data to it.
61
61
  #
62
62
  def selected=(value)
63
- @@win.copy_from(self) if value and @@win
63
+ @@win.copy_from(self) if value and @@win and @@win.shown?
64
64
  @selected = value
65
65
  end
66
66
 
@@ -205,6 +205,37 @@ class FXConnection < Connection
205
205
  return dc.drawBSpline( p )
206
206
  end
207
207
 
208
+ #
209
+ # Draw a door
210
+ #
211
+ def draw_door(dc, zoom, x1, y1, x2, y2)
212
+ v = [ (x2-x1), (y2-y1) ]
213
+ t = 10 * zoom / Math.sqrt(v[0]*v[0]+v[1]*v[1])
214
+ v = [ (v[0]*t).to_i, (v[1]*t).to_i ]
215
+ m = [ (x2+x1)/2, (y2+y1)/2 ]
216
+ x1, y1 = [m[0] + v[1], m[1] - v[0]]
217
+ x2, y2 = [m[0] - v[1], m[1] + v[0]]
218
+ if @type == LOCKED_DOOR
219
+ # Locked door
220
+ dc.drawLine(x1, y1, x2, y2)
221
+ else
222
+ # open door
223
+ v = [ v[0] / 3, v[1] / 3]
224
+ x1, y1 = x1.to_i, y1.to_i
225
+ x2, y2 = x2.to_i, y2.to_i
226
+ pts = []
227
+ pts << FXPoint.new(x1 - v[0], y1 - v[1])
228
+ pts << FXPoint.new(x1 + v[0], y1 + v[1])
229
+ pts << FXPoint.new(x2 + v[0], y2 + v[1])
230
+ pts << FXPoint.new(x2 - v[0], y2 - v[1])
231
+ pts << pts[0]
232
+ width = dc.lineWidth
233
+ dc.lineWidth = 0
234
+ dc.drawLines(pts)
235
+ dc.lineWidth = width
236
+ end
237
+ end
238
+
208
239
  #
209
240
  # Draw a complex connection
210
241
  #
@@ -219,18 +250,11 @@ class FXConnection < Connection
219
250
  x2, y2 = [p[-1].x, p[-1].y]
220
251
  draw_arrow(dc, zoom, x1, y1, x2, y2)
221
252
 
222
- if @type == LOCKED_DOOR
253
+ if @type == LOCKED_DOOR or @type == CLOSED_DOOR
223
254
  t = p.size / 2
224
255
  x1, y1 = [ p[t].x, p[t].y ]
225
256
  x2, y2 = [ p[t-2].x, p[t-2].y ]
226
-
227
- v = [ (x2-x1), (y2-y1) ]
228
- t = 10 * zoom / Math.sqrt(v[0]*v[0]+v[1]*v[1])
229
- v = [ (v[0]*t).to_i, (v[1]*t).to_i ]
230
- m = [ (x2+x1)/2, (y2+y1)/2 ]
231
- x1, y1 = [m[0] + v[1], m[1] - v[0]]
232
- x2, y2 = [m[0] - v[1], m[1] + v[0]]
233
- dc.drawLine(x1, y1, x2, y2)
257
+ draw_door(dc, zoom, x1, y1, x2, y2)
234
258
  end
235
259
  end
236
260
 
@@ -245,14 +269,8 @@ class FXConnection < Connection
245
269
  x2, y2 = @roomB.corner(self, zoom)
246
270
  dc.drawLine( x1, y1, x2, y2 )
247
271
  draw_arrow(dc, zoom, x1, y1, x2, y2)
248
- if @type == LOCKED_DOOR
249
- v = [ (x2-x1)*0.25, (y2-y1)*0.25 ]
250
- t = 10 * zoom / Math.sqrt(v[0]*v[0]+v[1]*v[1])
251
- v = [ (v[0]*t).to_i, (v[1]*t).to_i ]
252
- m = [ (x2+x1)/2, (y2+y1)/2 ]
253
- x1, y1 = [m[0] + v[1], m[1] - v[0]]
254
- x2, y2 = [m[0] - v[1], m[1] + v[0]]
255
- dc.drawLine(x1, y1, x2, y2)
272
+ if @type == LOCKED_DOOR or @type == CLOSED_DOOR
273
+ draw_door(dc, zoom, x1, y1, x2, y2)
256
274
  end
257
275
  else
258
276
  # Complex connection in progress or "stub" exit
@@ -6,10 +6,18 @@ class FXConnectionDialogBox < FXDialogBox
6
6
 
7
7
  TYPE_TEXT = [
8
8
  'Free',
9
+ 'Door',
9
10
  'Locked',
10
11
  'Special',
11
12
  ]
12
13
 
14
+ TYPE_NUM = [
15
+ Connection::FREE, # free
16
+ Connection::CLOSED_DOOR, # door
17
+ Connection::LOCKED_DOOR, # locked
18
+ Connection::SPECIAL, # special
19
+ ]
20
+
13
21
  DIR_TEXT = [
14
22
  'Both',
15
23
  'A to B',
@@ -28,7 +36,7 @@ class FXConnectionDialogBox < FXDialogBox
28
36
 
29
37
  def copy_to()
30
38
  @conn.dir = @dir.currentNo
31
- @conn.type = @type.currentNo
39
+ @conn.type = TYPE_NUM[ @type.currentNo ]
32
40
  @conn.exitAtext = @exitA.currentNo
33
41
  @conn.exitBtext = @exitB.currentNo
34
42
 
@@ -41,7 +49,7 @@ class FXConnectionDialogBox < FXDialogBox
41
49
  self.title = title
42
50
 
43
51
  @dir.currentNo = conn.dir
44
- @type.currentNo = conn.type || 0
52
+ @type.currentNo = TYPE_NUM.index(conn.type) || 0
45
53
  @exitA.currentNo = conn.exitAtext
46
54
  @exitB.currentNo = conn.exitBtext
47
55
  @conn = conn
@@ -53,13 +53,6 @@ class FXMap < Map
53
53
  end
54
54
  end
55
55
 
56
- #
57
- # Fit all rooms in map, adjusting map size if needed
58
- #
59
- def fit
60
- super
61
- create_pathmap
62
- end
63
56
 
64
57
  #
65
58
  # Jump to a certain section #
@@ -241,10 +234,10 @@ class FXMap < Map
241
234
  b = c.roomB
242
235
 
243
236
  dirA = a.exits.index(c)
244
- raise "connection not found #{c} at #{a}" unless dirA
237
+ raise "A connection not found #{c} at #{a}" unless dirA
245
238
 
246
239
  dirB = b.exits.rindex(c)
247
- raise "connection not found #{c} at #{b}" unless dirB
240
+ raise "B connection not found #{c} at #{b}" unless dirB
248
241
 
249
242
  vA = FXRoom::DIR_TO_VECTOR[dirA]
250
243
  vB = FXRoom::DIR_TO_VECTOR[dirB]
@@ -284,6 +277,12 @@ class FXMap < Map
284
277
  return false
285
278
  end
286
279
 
280
+ if (pA[0] - pB[0]).abs > 30 or
281
+ (pA[1] - pB[1]).abs > 30
282
+ c.failed = true
283
+ return
284
+ end
285
+
287
286
  # No, okay, we need to do true A* path finding
288
287
  c.failed = false
289
288
  aStar = AStar.new
@@ -433,7 +432,7 @@ class FXMap < Map
433
432
  @sections[@section].connections.each { |c|
434
433
  a = c.roomA
435
434
  b = c.roomB
436
- next if not b # Complex connection in progress
435
+ next if not b and @complexConnection
437
436
 
438
437
  if c.gpts.size > 0
439
438
  2.times { |t|
@@ -467,7 +466,15 @@ class FXMap < Map
467
466
  }
468
467
  else
469
468
  x1, y1 = a.corner(c, 1, a.exits.index(c))
470
- x2, y2 = b.corner(c, 1, b.exits.rindex(c))
469
+ if b
470
+ x2, y2 = b.corner(c, 1, b.exits.rindex(c))
471
+ else
472
+ dir = a.exits.index(c)
473
+ v = FXRoom::DIR_TO_VECTOR[dir]
474
+ x2 = x1 + v[0] * WS
475
+ y2 = y1 + v[1] * HS
476
+ end
477
+
471
478
  x1, x2 = x2, x1 if x2 < x1
472
479
  y1, y2 = y2, y1 if y2 < y1
473
480
  if x1 == x2
@@ -530,6 +537,7 @@ class FXMap < Map
530
537
  # Then, create an off-screen image with that same size for double
531
538
  # buffering
532
539
  @image.destroy
540
+ GC.start
533
541
  @image = FXBMPImage.new(@window.getApp, nil, IMAGE_SHMI|IMAGE_SHMP,
534
542
  width, height)
535
543
  @image.create
@@ -731,7 +739,7 @@ class FXMap < Map
731
739
 
732
740
  # Self-explanatory.
733
741
  def zoom_out
734
- if @zoom > 0.25
742
+ if @zoom > 0.1
735
743
  self.zoom -= 0.1
736
744
  end
737
745
  end
@@ -1136,13 +1144,16 @@ class FXMap < Map
1136
1144
  draw
1137
1145
  return
1138
1146
  else
1139
- if event.state & SHIFTMASK == 0 and
1140
- event.state & CONTROLMASK == 0
1147
+ if event.state & SHIFTMASK == 0
1141
1148
  clear_selection
1142
1149
  end
1143
1150
  # Select the stuff
1144
1151
  selection.update_properties(self)
1145
- selection.selected = true
1152
+ if event.state & CONTROLMASK == 0
1153
+ selection.selected ^= true # toggle selection
1154
+ else
1155
+ selection.selected = true
1156
+ end
1146
1157
  end
1147
1158
  end
1148
1159
  draw(sender, sel, event)
@@ -1315,11 +1326,10 @@ class FXMap < Map
1315
1326
  self.zoom = 0.8
1316
1327
  end
1317
1328
 
1318
-
1319
1329
  #
1320
- # Handle deleting selected rooms and/or connections
1330
+ # Handle 'cutting' any selected rooms and/or connections
1321
1331
  #
1322
- def delete_selected
1332
+ def _cut_selected
1323
1333
  rooms = @sections[@section].rooms.find_all { |r| r.selected }
1324
1334
  conns = @sections[@section].connections.find_all { |c| c.selected }
1325
1335
  ############################
@@ -1343,6 +1353,22 @@ class FXMap < Map
1343
1353
  conns.each { |c| clean_path(c) }
1344
1354
  # Remove connections from current section in map
1345
1355
  @sections[@section].connections -= conns
1356
+
1357
+ return conns
1358
+ end
1359
+
1360
+ def cut_selected
1361
+ _cut_selected
1362
+ modified = true
1363
+ draw
1364
+ end
1365
+
1366
+ #
1367
+ # Handle deleting selected rooms and/or connections
1368
+ #
1369
+ def delete_selected
1370
+ conns = _cut_selected
1371
+
1346
1372
  # Remove room exits pointing to any removed connection
1347
1373
  conns.each { |c|
1348
1374
  a = c.roomA
@@ -1354,10 +1380,8 @@ class FXMap < Map
1354
1380
  end
1355
1381
  }
1356
1382
 
1357
- # Recreate pathmap. We need to recreate pathmap for all paths
1358
- # as the removal of one path may help shorten the path of another.
1359
- create_pathmap
1360
- draw()
1383
+ modified = true
1384
+ draw
1361
1385
  end
1362
1386
 
1363
1387
  #
@@ -1705,12 +1729,7 @@ class FXMap < Map
1705
1729
  #
1706
1730
  def draw_background(dc, event = nil)
1707
1731
  dc.foreground = @options['BG Color']
1708
-
1709
- if event
1710
- dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
1711
- else
1712
- dc.fillRectangle(0,0, @canvas.width, @canvas.height)
1713
- end
1732
+ dc.fillRectangle(0,0, @canvas.width, @canvas.height)
1714
1733
  end
1715
1734
 
1716
1735
  #
@@ -1794,6 +1813,22 @@ class FXMap < Map
1794
1813
  draw
1795
1814
  end
1796
1815
 
1816
+ #
1817
+ # Draw a single room (callback used when editing room dialog box)
1818
+ #
1819
+ def draw_room(room)
1820
+ idx = @sections[@section].rooms.index(room)
1821
+ return unless idx
1822
+
1823
+ dc = FXDCWindow.new(@canvas)
1824
+ dc.font = @font
1825
+ data = { }
1826
+ data['font'] = @font
1827
+ data['objfont'] = @objfont
1828
+ room.draw(dc, @zoom, idx, @options, data)
1829
+ dc.end
1830
+ end
1831
+
1797
1832
  #
1798
1833
  # Draw map
1799
1834
  #
@@ -1846,6 +1881,11 @@ class FXMap < Map
1846
1881
  end
1847
1882
 
1848
1883
  status "Saving '#{@filename}'..."
1884
+
1885
+ # Make sure we save a valid map. This is mainly a fail-safe
1886
+ # in case of an autosave due to a bug.
1887
+ verify_integrity
1888
+
1849
1889
  @version = FILE_FORMAT_VERSION
1850
1890
  begin
1851
1891
  f = File.open(@filename, "wb")