~eda-qa/dhlib/main

« back to all changes in this revision

Viewing changes to restricted/app-fortress/fortress/MapData.hx

  • Committer: edA-qa mort-ora-y
  • Date: 2010-02-16 05:36:32 UTC
  • Revision ID: eda-qa@disemia.com-20100216053632-60lt7fndfi3fgblw
first

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package fortress;
 
2
 
 
3
import mathx.Matrix;
 
4
import mathx.MatrixUtil;
 
5
import mathx.MatPoint;
 
6
import mathx.Point2;
 
7
import flashx.ObjectCollider;
 
8
 
 
9
/**
 
10
 * Contains the logical map, disconnecting from the
 
11
 * graphics display (MapBackground in this case)
 
12
 */
 
13
class MapData
 
14
{
 
15
        public var landMap(default,null) : Int24Matrix; //[read-only] for public
 
16
        public var modMap(default,null) : Int24Matrix;  //[read-only] for public
 
17
        public var costMap(default,null) : Int24Matrix;//[read-only] for public
 
18
        public var shoreDistMap(default,null) : Int24Matrix;    //[read-only] for all
 
19
        public var objMap(default,null) : Matrix<LiveObject>;
 
20
                
 
21
        private var gameDriver : GameDriver;    //data.map will never be used (since == this)
 
22
        
 
23
        //types
 
24
        static public var ctUnknown = 0;
 
25
        static public var ctWater = 1;
 
26
        static public var ctLand = 2;
 
27
        static public var ctWall = 3;   
 
28
        
 
29
        //modifiers
 
30
        static public var modNone = 0;
 
31
        static public var modFort = 0x1;        //belongs to a fortress 
 
32
        static public var modOccupied = 0x2;    //occupied by something
 
33
        
 
34
        var mapNdx : Int;
 
35
        
 
36
        public function new( amap : Int, gd : GameDriver )
 
37
        {
 
38
                mapNdx = amap;
 
39
                gameDriver = gd;
 
40
                
 
41
                //load static data, setup dynamic
 
42
                landMap = MapLoader.getLandMap( amap );
 
43
                shoreDistMap = MapLoader.getShoreDistMap( amap );
 
44
                costMap = Int24Matrix.create( landMap.size.x, landMap.size.y, -1 );
 
45
                modMap = Int24Matrix.create( landMap.size.x, landMap.size.x, modNone );
 
46
                objMap = Matrix.create( landMap.size.x, landMap.size.y, null );
 
47
                                        
 
48
                //setup live items
 
49
                liveObjects = new FastList<LiveObject>();
 
50
                wasStillActive = false;
 
51
                
 
52
                //add towers
 
53
                var los = new Array<LiveObject>();
 
54
                for( i in 0...MapLoader.getNumTowers( mapNdx ) )
 
55
                {
 
56
                        var to = new TowerObject( gameDriver );
 
57
                        to.player = true;
 
58
                        to.at = MapLoader.getTower( mapNdx, i ).promoteCtrF();
 
59
                        los.push( to );
 
60
                }
 
61
                addLiveObjects( los );
 
62
                
 
63
                calcAll();
 
64
        }
 
65
        
 
66
        /**
 
67
         * mapChanged is only called when the background (landMap, costMap) data
 
68
         * changes.  Changes in live objects will not trigger this.  It is tuned primarily
 
69
         * for its use in MapBackground -- NOTE: that class can not handle the original
 
70
         * land layout changing (water/land) but only owernship/mods/wall placements
 
71
         */
 
72
        private function mapChanged()
 
73
        {
 
74
                //a post here is fine since the map doesn't change often (except during build)
 
75
                gameDriver.postEvent( new GameEvent( GameEvent.MAP_CHANGED ) );
 
76
        }
 
77
        
 
78
        /**
 
79
         * Convert the logical index into a grid index
 
80
         */
 
81
        public function index( mp : Point2 ) : MatPoint
 
82
        {
 
83
                return MatPoint.at( Math.floor( mp.x ),  Math.floor( mp.y ) );
 
84
        }
 
85
        
 
86
        /**
 
87
         * Gets the logical position of the Live Object in terms of an
 
88
         * iterator over the covered area.
 
89
         *
 
90
         * The covered area is considered to be 
 
91
         */
 
92
        public function indexLive( lo : LiveObject ) : Iterator<MatPoint>
 
93
        {
 
94
                //assuming it is centered (TODO: proper hooks, like BuildObject)
 
95
                var ul = lo.at.sub( lo.size.div( 2 ) );
 
96
                var br = ul.add( lo.size );
 
97
                
 
98
                //round to grid
 
99
                var ulp = ul.floorDemote();
 
100
                var brp = br.ceilDemote();
 
101
                
 
102
                return landMap.subIter( ulp,  brp.sub( ulp ) );
 
103
        }
 
104
        
 
105
        //TODO: clean this up, along with canPlace/build, objects themselves should somehow do this
 
106
        public var cannonAllowance : CannonAllowance;
 
107
        
 
108
        /**
 
109
         * Places a build object on the map
 
110
         *
 
111
         * @param p [in] where to put it (globalPoint that will be gridAdjusted)
 
112
         * @param bo    [in] which object
 
113
         * @return      [out] true if successful, false if failed / cannot be done
 
114
         */
 
115
        public function placeBuildObj   ( x : Int, y : Int, bo : BuildObject ) : Bool
 
116
        {
 
117
                if( !canPlaceBuildObj( x, y, bo ) )
 
118
                        return false;
 
119
                        
 
120
                if( Std.is( bo, WallObject ) )
 
121
                {       
 
122
                        var wo = cast( bo, WallObject );
 
123
                        for( np in wo.wl.xOrderIter() )
 
124
                                if( wo.wl.get( np.x,np.y) == 1)
 
125
                                        landMap.set( x+np.x, y+np.y, ctWall );
 
126
                        
 
127
                        SoundManager.playSound( "BuildWall", 0 );
 
128
                        
 
129
                        //update area surrounding wall as well, since costs may have changed (open/fort aspects)
 
130
                        calcUpdate( MatPoint.at( x-1, y-1 ), MatPoint.at( wo.wl.size.x+2, wo.wl.size.y+2 ) );
 
131
                        
 
132
                        mapChanged();
 
133
                        return true;
 
134
                }
 
135
                
 
136
                if( Std.is( bo, SelectObject ) )
 
137
                {
 
138
                        SoundManager.playSound( "Select", 0 );
 
139
                        
 
140
                        selectHomeTower( x, y );
 
141
                        return true;
 
142
                }
 
143
                
 
144
                if( Std.is( bo, CannonBuildObject ) )
 
145
                {
 
146
                        SoundManager.playSound( "BuildCannon", 0 );
 
147
                        
 
148
                        var can = new CannonObject( gameDriver );
 
149
                        can.player = true;
 
150
                        //this aligns the LiveObject to its placement in the grid
 
151
                        can.at.x = (x + 0.5) + (bo.dragGridTransform().x * bo.getBuildMap().size.x);
 
152
                        can.at.y = (y + 0.5) + (bo.dragGridTransform().y * bo.getBuildMap().size.y);
 
153
                        addLiveObject( can );
 
154
                        
 
155
                        if( cannonAllowance != null )
 
156
                        {
 
157
                                cannonAllowance.decAllowanceAt( x, y );
 
158
                                mapChanged();   //part of modmap, so trigger change
 
159
                        }
 
160
                        
 
161
                        return true;
 
162
                }
 
163
 
 
164
                return false;
 
165
        }
 
166
        
 
167
        /**
 
168
         * Forces a recalculation of the map.  Recalcuation is normally only
 
169
         * done during build and related operations, it is not done during
 
170
         * stepping of objects.
 
171
         */
 
172
        public function recalcMap()
 
173
        {
 
174
                calcAll();
 
175
                mapChanged();
 
176
        }
 
177
        
 
178
        /**
 
179
         * Here we want to determine what parts reside inside walls,
 
180
         * and what ones are outside.  Basically outside areas can reach
 
181
         * the outer borders freely, inside not.  Rather than do a complete
 
182
         * costing we can simply do a floodfill (with many seed points)
 
183
         * to find the outside parts, then we can use the same type of fill
 
184
         * to find the inside parts.
 
185
         */
 
186
         
 
187
        //TODO: Make private and update landmap so that these don't need to be public (other than for debugging)
 
188
        static public var costOpen = 1;
 
189
        static public var costBlock = 2;
 
190
        static public var costArea1 = 3;
 
191
        static public var costArea2 = 4;
 
192
        static public var costArea3 = 5;
 
193
        static public var costFort = 6;
 
194
        
 
195
        static public var costUnknown = 0;      //must be < all other costs for some operations!
 
196
        
 
197
        private function calcCostMap( ul : MatPoint, size : MatPoint, update : Bool )
 
198
        {
 
199
                var open = new FastList<MatPoint>();
 
200
                
 
201
                //assign initial colors
 
202
                //for( mp in landMap.subIter( ul, size, true ) ) //NOTE: This requires a different algorithm to determine Fort areas...
 
203
                //for( mp in landMap.xOrderIter() )     //Too slow, use direct
 
204
                for( mpy in 0...landMap.size.y )
 
205
                for( mpx in 0...landMap.size.x )
 
206
                {
 
207
                        var ct = landMap.get(mpx,mpy);
 
208
                        var cost;
 
209
                        if( ct == ctWater )
 
210
                                cost = costBlock;
 
211
                        else if( ct == ctWall )
 
212
                                cost = costBlock;
 
213
                        else if( mpx == 0 || mpy == 0 || mpx == (landMap.size.x-1) || mpy == (landMap.size.y-1) )
 
214
                                cost = costOpen;        //edges as well, since they aren't actually on water. (do first, since faster than next)
 
215
                        else if( ct == ctLand && shoreDistMap.get( mpx, mpy ) == -1 )
 
216
                                cost = costOpen;        //anything on the shore is open
 
217
                        else
 
218
                                cost = costUnknown;
 
219
                                
 
220
                        costMap.set(mpx,mpy, cost);
 
221
                        if( cost == costOpen )
 
222
                                open.push( MatPoint.at( mpx, mpy ) );
 
223
                }
 
224
                
 
225
                MatrixUtil.seedFill8( costMap, open, costUnknown, costOpen );
 
226
                
 
227
                var nextFill = costFort;
 
228
                
 
229
                //find closed blocks
 
230
                //for( mp in landMap.xOrderIter() )
 
231
                for( mpy in 0...landMap.size.y )
 
232
                for( mpx in 0...landMap.size.x )
 
233
                {
 
234
                        var ct = costMap.get(mpx,mpy);
 
235
                        if( ct != costUnknown )
 
236
                                continue;
 
237
                                
 
238
                        var seeds = new FastList<MatPoint>();
 
239
                        seeds.push( MatPoint.at( mpx, mpy ) );
 
240
                        MatrixUtil.seedFill8( costMap, seeds, costUnknown, nextFill );
 
241
                        nextFill++;
 
242
                }
 
243
                
 
244
        }
 
245
        
 
246
        //determines the modifiers for the map
 
247
        //calcCostMap needs to be called first, since this will
 
248
        //actually use the costs to determine the modifiers
 
249
        private function calcModMap()
 
250
        {
 
251
                //for( mp in landMap.xOrderIter() )
 
252
                for( mpy in 0...landMap.size.y )
 
253
                for( mpx in 0...landMap.size.x )
 
254
                {
 
255
                        var m = modNone;
 
256
                        var lm = landMap.get( mpx, mpy );
 
257
                        if( lm == ctWall )
 
258
                        {
 
259
                                m |= modOccupied;       //space is taken
 
260
                                
 
261
                                var wallOffsets = 
 
262
                                        [ 
 
263
                                                [ -1, 0 ], [ 1, 0 ], [ 0, -1], [0,1] ,  //standard four offets
 
264
                                                [ 0, 0 ],       //special case to include single-cell forts
 
265
                                                [ -1, -1], [ -1, 1], [ 1, -1], [1,1]    //extra cases to have "solider" walls
 
266
                                        ];
 
267
                                for( off in 0...wallOffsets.length )
 
268
                                {
 
269
                                        var nx = mpx + wallOffsets[off][0];
 
270
                                        var ny = mpy + wallOffsets[off][1];
 
271
                                        
 
272
                                        //index may be invalid, treat as unknown
 
273
                                        if( costMap.def_get(nx,ny,costUnknown) >= costFort )
 
274
                                        {
 
275
                                                m |= modFort;   //borders on a city
 
276
                                                break;  //no need for further checking of same condition
 
277
                                        }
 
278
                                }
 
279
                        }
 
280
                        else if( lm == ctLand )
 
281
                        {
 
282
                                if( costMap.get( mpx, mpy ) >= costFort )
 
283
                                        m |= modFort;   //land is only directly in forts...
 
284
                        }
 
285
                        
 
286
                        modMap.set(mpx,mpy, m);
 
287
                        objMap.set( mpx, mpy, null );
 
288
                } //end mp
 
289
                
 
290
                //iterate over LiveObjects
 
291
                for( lo in liveObjects )
 
292
                {
 
293
                        if( lo.isVisible() && lo.doesOccupy() )
 
294
                        {
 
295
                                for( mp in indexLive( lo ) )
 
296
                                {
 
297
                                        modMap.set( mp.x, mp.y, modMap.get( mp.x, mp.y ) | modOccupied );
 
298
                                        //TODO: Vlad actually hit a case where this is triggered!!!!!
 
299
                                        //NOTE: cannot handle multiple objects...?
 
300
                                        //Assert.isNull( objMap.get( mp.x, mp.y ) );
 
301
                                        objMap.set( mp.x, mp.y, lo );   
 
302
                                }
 
303
                        }
 
304
                }
 
305
        }
 
306
        
 
307
        /**
 
308
         * This should usually be called if something changed. The calcUpdate
 
309
         * can be called when a specific part of the map has changed, though not
 
310
         * all calculations benefit from knowing.
 
311
         */
 
312
        private function calcAll()
 
313
        {
 
314
                calcUpdate( MatPoint.at( 0, 0 ), MatPoint.at( landMap.size.x, landMap.size.y ), false );
 
315
        }
 
316
        
 
317
        private function calcUpdate( ul : MatPoint, size : MatPoint, ?update : Null<Bool> )
 
318
        {
 
319
                var start = flash.Lib.getTimer();
 
320
                if( update == null )
 
321
                        update = true;
 
322
                        
 
323
                calcCostMap( ul, size, update );
 
324
                var cost = flash.Lib.getTimer();
 
325
                calcModMap();
 
326
                
 
327
                //trace( "CA:" + (cost-start) + "/" + (flash.Lib.getTimer() - cost) );
 
328
        }
 
329
        
 
330
        /**
 
331
         * Determines whether a build object can be placed here
 
332
         */
 
333
        public function canPlaceBuildObj( x : Int, y : Int, bo : BuildObject ) : Bool
 
334
        {
 
335
                var bm =  bo.getBuildMap();
 
336
                Assert.notNull( bm );
 
337
                for( n in bm.xOrderIter() )
 
338
                {
 
339
                        if( bm.get( n.x, n.y ) == 1 )
 
340
                        {
 
341
                                if( !landMap.validIndex( x+n.x, y+n.y ) )
 
342
                                        return false;
 
343
                                        
 
344
                                if( !bo.canBuildAt( 
 
345
                                                n, 
 
346
                                                landMap.get( x + n.x, y + n.y ), 
 
347
                                                modMap.get( x + n.x, y + n.y ),
 
348
                                                objMap.get( x + n.x, y + n.y )  ) )
 
349
                                        return false;
 
350
                        }
 
351
                }
 
352
                
 
353
                if( Std.is( bo, CannonBuildObject ) )
 
354
                {
 
355
                        if( cannonAllowance != null )
 
356
                                return cannonAllowance.isAllowedAt( x, y );
 
357
                }
 
358
                
 
359
                return true;
 
360
        }
 
361
        
 
362
        /**
 
363
         * Makes a particular tower the first tower and surrounds it with
 
364
         * a wall.  IT does this by shading the possible area, on land, and
 
365
         * then using the fill algorithm to select the real area, and then
 
366
         * it traces the border
 
367
         *
 
368
         * Assumptions:
 
369
         *              A wall can actually be created to enclose the tower!
 
370
         *              --all of the spaces directly beside the tower must be free
 
371
         *              --the open spaces must be connected
 
372
         */
 
373
        private function selectHomeTower( x : Int, y : Int )
 
374
        {
 
375
                calcAll();
 
376
                
 
377
                var txr = GConst.towerSelectSizeX;
 
378
                var tyr = GConst.towerSelectSizeY;
 
379
                
 
380
                var left = x - txr;
 
381
                var right = x + txr;
 
382
                var top = y - tyr;
 
383
                var bottom = y + tyr;
 
384
                
 
385
                //now cover the area around our tower
 
386
                for( ny in top...bottom+1 )
 
387
                {
 
388
                        for( nx in left...right+1 )
 
389
                        {
 
390
                                if( landMap.def_get(nx,ny,ctUnknown) == ctLand ) //we'll overwrite the tower cost at this phase
 
391
                                        costMap.set(nx,ny, costArea1);
 
392
                        }
 
393
                }
 
394
                
 
395
                //create the seeds (maybe this works even if seeds aren't connected?
 
396
                var seeds = new FastList<MatPoint>();
 
397
                var offsets = [ [ -1, 0 ], [ 1, 0 ], [ 0, -1], [0,1] ];
 
398
                for( off in 0...offsets.length )
 
399
                {
 
400
                        var nx = x + offsets[off][0];
 
401
                        var ny = y + offsets[off][1];
 
402
                        if( costMap.get(nx,ny) == costArea1 )
 
403
                                seeds.push( MatPoint.at( nx, ny ) );
 
404
                }
 
405
                Assert.isTrue( seeds.length > 0 );
 
406
                MatrixUtil.seedFill8( costMap, seeds, costArea1, costArea2 );
 
407
                
 
408
                //trace the edges from the tower
 
409
                MatrixUtil.traceEdges8( costMap, landMap, costArea2, ctWall );
 
410
                
 
411
                //since our algorithm may block walls we need to clean those away
 
412
                //cleanLostWalls will also recalc costs
 
413
                cleanLostWalls();
 
414
                mapChanged();
 
415
                
 
416
                //automatically position the starting cannons
 
417
                //TODO: better logic here (actually do this somewhere else...?)
 
418
                var le = new Array<LiveObject>();
 
419
                
 
420
                var can = new CannonObject( gameDriver );
 
421
                can.player = true;
 
422
                can.at.x = x - can.size.x/2;
 
423
                can.at.y = y;
 
424
                le.push( can );
 
425
 
 
426
                can = new CannonObject( gameDriver );
 
427
                can.player = true;
 
428
                can.at.x = x  + can.size.x;
 
429
                can.at.y = y;
 
430
                le.push( can );
 
431
                
 
432
                addLiveObjects( le );
 
433
        }
 
434
        
 
435
        /**
 
436
         * Cleans out the tiles not matching a specific criteria
 
437
         */
 
438
        private function cleanMapInv( ct : Int, mod : Int, replaceCt : Int )
 
439
        {
 
440
                for( mp in landMap.xOrderIter() )
 
441
                        if( landMap.get( mp.x, mp.y ) == ct 
 
442
                                && modMap.get( mp.x, mp.y ) & mod != mod )
 
443
                                        landMap.set( mp.x, mp.y, replaceCt );
 
444
        }
 
445
        
 
446
        public function cleanLostWalls( )
 
447
        {
 
448
                calcAll();
 
449
                cleanMapInv( ctWall, modFort, ctLand );
 
450
                calcAll();
 
451
        }
 
452
        
 
453
        public function updateDormantCannons()
 
454
        {
 
455
                for( lo in liveObjects )
 
456
                {
 
457
                        if( Std.is( lo, CannonObject ) )
 
458
                        {
 
459
                                var co = cast( lo, CannonObject );
 
460
                                var ul = indexLive( co ).next();        //assuming in map...
 
461
                                co.setDormant( modMap.get( ul.x, ul.y ) & modFort != modFort );
 
462
                        }
 
463
                }
 
464
        }
 
465
        
 
466
        /**
 
467
         * Finds the closest owned tower to the given location.  This is
 
468
         * usually used as a targetting method -- find the closest place
 
469
         * to attack
 
470
         */
 
471
        public function getClosestOwnedTower( mp : Point2 )
 
472
        {
 
473
                var close = null;
 
474
                var dist = 0.0;
 
475
                
 
476
                for( lo in liveObjects )
 
477
                {
 
478
                        if( !Std.is( lo, TowerObject ) )
 
479
                                continue;
 
480
                                
 
481
                        //any part not in the fort makes this unowned (our towers are however only 1x1 in size now)
 
482
                        var own = true;
 
483
                        for( mp in indexLive( lo ) )
 
484
                                own = own && (modMap.get( mp.x, mp.y ) & modFort == modFort);
 
485
                                
 
486
                        if( !own )
 
487
                                continue;
 
488
                                
 
489
                        var ndist = lo.at.distanceToSqr( mp );
 
490
                        if( close == null || ndist < dist )
 
491
                        {
 
492
                                close = lo;
 
493
                                dist = ndist;
 
494
                        }
 
495
                }
 
496
                
 
497
                if( close == null )
 
498
                        return null;
 
499
                return close.at;
 
500
        }
 
501
        
 
502
        public function getNumOwnedTowers() : Int
 
503
        {
 
504
                var count = 0;
 
505
                
 
506
                for( lo in liveObjects )
 
507
                {
 
508
                        if( !Std.is( lo, TowerObject ) )
 
509
                                continue;
 
510
                                
 
511
                        //any part not in the fort makes this unowned (our towers are however only 1x1 in size now)
 
512
                        var own = true;
 
513
                        for( mp in indexLive( lo ) )
 
514
                                own = own && (modMap.get( mp.x, mp.y ) & modFort == modFort);
 
515
                                
 
516
                        if( own )
 
517
                                count++;
 
518
                }
 
519
                
 
520
                return count;
 
521
        }
 
522
        
 
523
        public function getAllTowers( ) : Array<TowerObject>
 
524
        {
 
525
                var ret = new Array<TowerObject>();
 
526
                for( lo in liveObjects )
 
527
                {
 
528
                        if( !Std.is( lo, TowerObject ) )
 
529
                                continue;
 
530
                                
 
531
                        ret.push( cast( lo, TowerObject ) );
 
532
                }
 
533
                
 
534
                return ret;
 
535
        }
 
536
 
 
537
        public function getClosestCannon( mp : Point2 ) : Point2
 
538
        {
 
539
                //finds the closest cannon
 
540
                var close = null;
 
541
                var dist = 0.0;
 
542
                for( lo in liveObjects )
 
543
                {
 
544
                        if( !Std.is( lo, CannonObject ) )
 
545
                                continue;
 
546
                        var ndist = lo.at.distanceToSqr( mp );
 
547
                        if( close == null || ndist < dist )
 
548
                        {
 
549
                                close = lo;
 
550
                                dist = ndist;
 
551
                        }
 
552
                }
 
553
                
 
554
                if( close == null )
 
555
                        return null;
 
556
                return close.at;
 
557
        }
 
558
        
 
559
        /**
 
560
         * Returns the wall closest to the given point.  This is one of the
 
561
         * search strategies of the boats.
 
562
         *
 
563
         * @param       mp [in] wher eto start searching from
 
564
         * @param       limit   [in] maximum number of cells to check
 
565
         */
 
566
        public function getWallClosestTo( mp : Point2, limit : Int ) : Point2
 
567
        {
 
568
                for( at in landMap.boxOrderIter( index(mp) ) )
 
569
                {
 
570
                        if( landMap.get( at.x, at.y ) == ctWall )
 
571
                                return at.promoteCtrF();
 
572
                                
 
573
                        limit--;
 
574
                        if( limit < 0 )
 
575
                                break;
 
576
                }
 
577
                
 
578
                return null;
 
579
        }
 
580
        
 
581
        /**
 
582
         * Does damage to the indicated grid location
 
583
         *
 
584
         * @return      [out] was something actually damaged
 
585
         */
 
586
        public function damageGrid( mpi : Iterator<MatPoint>, damage : Float ) : Bool
 
587
        {
 
588
                var hit  = false;
 
589
                
 
590
                for( mp in mpi )
 
591
                {
 
592
                        var ct = landMap.def_get(mp.x,mp.y,ctUnknown);
 
593
                        if( ct == ctWall )
 
594
                        {
 
595
                                landMap.set( mp.x, mp.y, ctLand );      //simply destroy it
 
596
                                hit = true;
 
597
                        }
 
598
                }
 
599
                
 
600
                if( hit )
 
601
                        mapChanged();
 
602
                        
 
603
                if( hit )
 
604
                        SoundManager.playSound( "WallHit", 1 );
 
605
                        
 
606
                return hit;
 
607
        }
 
608
        
 
609
        /////////////////////////////////////////////////////////////////////////
 
610
        // The active elements, should they be in a different
 
611
        // class?
 
612
        private var liveObjects : FastList<LiveObject>;
 
613
        
 
614
        public function allLiveObjects() : Iterator<LiveObject>
 
615
        {
 
616
                return liveObjects.iterator();
 
617
        }
 
618
        
 
619
        /**
 
620
         * Gets available cannon (can fire now) closest to given location.
 
621
         *
 
622
         * @return      [out] cannon, or null if none available
 
623
         */
 
624
        public function getClosestAvailCannon( x : Float, y : Float ) : CannonObject
 
625
        {
 
626
                var close : CannonObject = null;
 
627
                var dist : Float = 0;   //first value not used
 
628
                for( lo in liveObjects )
 
629
                {
 
630
                        if( Std.is( lo, CannonObject ) )
 
631
                        {
 
632
                                var co = cast( lo, CannonObject );
 
633
                                if( co.canFire() )
 
634
                                {
 
635
                                        var ndist = lo.at.distanceToSqr( Point2.at(x,y) );
 
636
                                        if( close == null || ndist < dist )
 
637
                                        {
 
638
                                                close = co;
 
639
                                                dist = ndist;
 
640
                                        }
 
641
                                        
 
642
                                }
 
643
                        }
 
644
                        //end Cannon
 
645
                        
 
646
                } //end liveObjects
 
647
                
 
648
                return close;   //may be null
 
649
        }
 
650
        
 
651
        /**
 
652
         * Determines which objects are touched to this one
 
653
         *
 
654
         * I'll use the flash test for this rather than our own, it 
 
655
         * will likely be faster (compiled code), though we can't
 
656
         * write unit tests then...
 
657
         * ...not using the flash one since it is based only on
 
658
         * bounding box, but new one is still build on Flash library.
 
659
         */
 
660
        public function hitObjects( lo : LiveObject ) : Array<LiveObject>
 
661
        {
 
662
                //var start = flash.Lib.getTimer();
 
663
                
 
664
                var co = new ObjectCollider( lo.stage );        //use stage of this target
 
665
                
 
666
                var ret = new Array<LiveObject>();
 
667
                for( tlo in liveObjects )
 
668
                {
 
669
                        if( tlo == lo || !tlo.isVisible() )
 
670
                                continue;       //don't touch ourselves or invisibles
 
671
                                
 
672
                        if( co.doCollide( lo, tlo ) )
 
673
                                ret.push( tlo );
 
674
                }
 
675
                
 
676
                //works fast! (0-5ms with over 100 objects to test, albeit all small)
 
677
                //trace( "hitObjects: " + (flash.Lib.getTimer() - start) );
 
678
 
 
679
                return ret;
 
680
        }
 
681
        
 
682
        
 
683
        /**
 
684
         * Adds a single live object -- a common use of addLiveObjects
 
685
         */
 
686
        public function addLiveObject( lo : LiveObject, ?placeEntry : Null<Bool>  )
 
687
        {
 
688
                var los = new Array<LiveObject>();
 
689
                los.push( lo );
 
690
                addLiveObjects( los, placeEntry );
 
691
        }
 
692
        
 
693
        /**
 
694
         * Places objects on the map.  This uses the placeEntry option
 
695
         * of LiveObjects to do automatic placement.  It is however assumed,
 
696
         * for spacing reasons, that if any one has placeEntry that all
 
697
         * have placeEntry (strictly for spacing purposes, those without will
 
698
         * still not be placed).
 
699
         */
 
700
        public function addLiveObjects( los : Array<LiveObject>, ?placeEntry : Null<Bool> )
 
701
        {
 
702
                if( placeEntry == null )
 
703
                        placeEntry = false;
 
704
                        
 
705
                var le = new LiveEvent( LiveEvent.OBJECT_PLACED );
 
706
                
 
707
                //break available entry places into even sections then place
 
708
                //randomly in each section
 
709
                var w = MapLoader.getNumWaterEntry( mapNdx ) /  los.length;
 
710
                var arr = null;
 
711
                if( placeEntry )
 
712
                        arr = ArrayUtil.shuffle( ArrayUtil.incList( 0, los.length ) );
 
713
                
 
714
                //trace( "alo: " + los.length );
 
715
                for( i in 0...los.length )
 
716
                {
 
717
                        var lo = los[i];
 
718
                        if( placeEntry )
 
719
                        {
 
720
                                var ei = Math.floor( w * arr[i] + Math.random() * w );
 
721
                                lo.at = MapLoader.getWaterEntry( mapNdx, ei ).promoteF();
 
722
                        }
 
723
                        liveObjects.push( lo );
 
724
                        le.liveObjs.push( lo );
 
725
                        
 
726
                        lo.addedTo( gameDriver );
 
727
                }
 
728
                
 
729
                gameDriver.postEvent( le );
 
730
        }
 
731
        
 
732
        /**
 
733
         * Steps the map data (game model) by the given amount
 
734
         * of time.
 
735
         */
 
736
        public function step( elapsed : Float )
 
737
        {
 
738
                stepLiveObjects( elapsed );
 
739
        }
 
740
        
 
741
        private var wasStillActive : Bool;
 
742
        public function stepLiveObjects( elapsed : Float )
 
743
        {
 
744
                //trace( "X: " + liveObjects.length );
 
745
                var lr = new LiveEvent( LiveEvent.OBJECT_REMOVED );
 
746
                
 
747
                var stillActive = false;
 
748
                
 
749
                var isAction = gameDriver.mode == GameMode.Action;
 
750
                for( lo in liveObjects )
 
751
                {
 
752
                        if( !isAction && lo.isOnlyAction() )
 
753
                                continue;
 
754
                                
 
755
                        stillActive = stillActive || lo.isKeepAction();
 
756
                        
 
757
                        var sr = lo.step( elapsed );
 
758
                        switch( sr )
 
759
                        {
 
760
                                case Changed:
 
761
                                        //harks back to old dispathc system, but let objects handle themselves now (if needed)
 
762
                                        lo.reflectChange();
 
763
                                case Remove:
 
764
                                        lr.liveObjs.push( lo );
 
765
                                case Nothing:   
 
766
                                        //do nothing
 
767
                        }
 
768
                                
 
769
                }
 
770
                        
 
771
                //get rid of those we don't want anymore
 
772
                for( r in lr.liveObjs )
 
773
                        liveObjects.remove( r );
 
774
                        
 
775
                if( lr.liveObjs.length > 0)
 
776
                        gameDriver.postEvent(lr );
 
777
                        
 
778
                //dispatch events about transitions
 
779
                if( stillActive != wasStillActive )
 
780
                {
 
781
                        wasStillActive = stillActive;
 
782
                        if( stillActive )
 
783
                                gameDriver.postEvent( new GameEvent( GameEvent.BECAME_ACTIVE) );
 
784
                        else
 
785
                                gameDriver.postEvent( new GameEvent( GameEvent.BECAME_INACTIVE) );
 
786
                }
 
787
                
 
788
        }
 
789
}