4
import mathx.MatrixUtil;
7
import flashx.ObjectCollider;
10
* Contains the logical map, disconnecting from the
11
* graphics display (MapBackground in this case)
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>;
21
private var gameDriver : GameDriver; //data.map will never be used (since == this)
24
static public var ctUnknown = 0;
25
static public var ctWater = 1;
26
static public var ctLand = 2;
27
static public var ctWall = 3;
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
36
public function new( amap : Int, gd : GameDriver )
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 );
49
liveObjects = new FastList<LiveObject>();
50
wasStillActive = false;
53
var los = new Array<LiveObject>();
54
for( i in 0...MapLoader.getNumTowers( mapNdx ) )
56
var to = new TowerObject( gameDriver );
58
to.at = MapLoader.getTower( mapNdx, i ).promoteCtrF();
61
addLiveObjects( los );
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
72
private function mapChanged()
74
//a post here is fine since the map doesn't change often (except during build)
75
gameDriver.postEvent( new GameEvent( GameEvent.MAP_CHANGED ) );
79
* Convert the logical index into a grid index
81
public function index( mp : Point2 ) : MatPoint
83
return MatPoint.at( Math.floor( mp.x ), Math.floor( mp.y ) );
87
* Gets the logical position of the Live Object in terms of an
88
* iterator over the covered area.
90
* The covered area is considered to be
92
public function indexLive( lo : LiveObject ) : Iterator<MatPoint>
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 );
99
var ulp = ul.floorDemote();
100
var brp = br.ceilDemote();
102
return landMap.subIter( ulp, brp.sub( ulp ) );
105
//TODO: clean this up, along with canPlace/build, objects themselves should somehow do this
106
public var cannonAllowance : CannonAllowance;
109
* Places a build object on the map
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
115
public function placeBuildObj ( x : Int, y : Int, bo : BuildObject ) : Bool
117
if( !canPlaceBuildObj( x, y, bo ) )
120
if( Std.is( bo, WallObject ) )
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 );
127
SoundManager.playSound( "BuildWall", 0 );
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 ) );
136
if( Std.is( bo, SelectObject ) )
138
SoundManager.playSound( "Select", 0 );
140
selectHomeTower( x, y );
144
if( Std.is( bo, CannonBuildObject ) )
146
SoundManager.playSound( "BuildCannon", 0 );
148
var can = new CannonObject( gameDriver );
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 );
155
if( cannonAllowance != null )
157
cannonAllowance.decAllowanceAt( x, y );
158
mapChanged(); //part of modmap, so trigger change
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.
172
public function recalcMap()
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.
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;
195
static public var costUnknown = 0; //must be < all other costs for some operations!
197
private function calcCostMap( ul : MatPoint, size : MatPoint, update : Bool )
199
var open = new FastList<MatPoint>();
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 )
207
var ct = landMap.get(mpx,mpy);
211
else if( ct == ctWall )
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
220
costMap.set(mpx,mpy, cost);
221
if( cost == costOpen )
222
open.push( MatPoint.at( mpx, mpy ) );
225
MatrixUtil.seedFill8( costMap, open, costUnknown, costOpen );
227
var nextFill = costFort;
230
//for( mp in landMap.xOrderIter() )
231
for( mpy in 0...landMap.size.y )
232
for( mpx in 0...landMap.size.x )
234
var ct = costMap.get(mpx,mpy);
235
if( ct != costUnknown )
238
var seeds = new FastList<MatPoint>();
239
seeds.push( MatPoint.at( mpx, mpy ) );
240
MatrixUtil.seedFill8( costMap, seeds, costUnknown, nextFill );
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()
251
//for( mp in landMap.xOrderIter() )
252
for( mpy in 0...landMap.size.y )
253
for( mpx in 0...landMap.size.x )
256
var lm = landMap.get( mpx, mpy );
259
m |= modOccupied; //space is taken
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
267
for( off in 0...wallOffsets.length )
269
var nx = mpx + wallOffsets[off][0];
270
var ny = mpy + wallOffsets[off][1];
272
//index may be invalid, treat as unknown
273
if( costMap.def_get(nx,ny,costUnknown) >= costFort )
275
m |= modFort; //borders on a city
276
break; //no need for further checking of same condition
280
else if( lm == ctLand )
282
if( costMap.get( mpx, mpy ) >= costFort )
283
m |= modFort; //land is only directly in forts...
286
modMap.set(mpx,mpy, m);
287
objMap.set( mpx, mpy, null );
290
//iterate over LiveObjects
291
for( lo in liveObjects )
293
if( lo.isVisible() && lo.doesOccupy() )
295
for( mp in indexLive( lo ) )
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 );
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.
312
private function calcAll()
314
calcUpdate( MatPoint.at( 0, 0 ), MatPoint.at( landMap.size.x, landMap.size.y ), false );
317
private function calcUpdate( ul : MatPoint, size : MatPoint, ?update : Null<Bool> )
319
var start = flash.Lib.getTimer();
323
calcCostMap( ul, size, update );
324
var cost = flash.Lib.getTimer();
327
//trace( "CA:" + (cost-start) + "/" + (flash.Lib.getTimer() - cost) );
331
* Determines whether a build object can be placed here
333
public function canPlaceBuildObj( x : Int, y : Int, bo : BuildObject ) : Bool
335
var bm = bo.getBuildMap();
336
Assert.notNull( bm );
337
for( n in bm.xOrderIter() )
339
if( bm.get( n.x, n.y ) == 1 )
341
if( !landMap.validIndex( x+n.x, y+n.y ) )
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 ) ) )
353
if( Std.is( bo, CannonBuildObject ) )
355
if( cannonAllowance != null )
356
return cannonAllowance.isAllowedAt( x, y );
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
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
373
private function selectHomeTower( x : Int, y : Int )
377
var txr = GConst.towerSelectSizeX;
378
var tyr = GConst.towerSelectSizeY;
383
var bottom = y + tyr;
385
//now cover the area around our tower
386
for( ny in top...bottom+1 )
388
for( nx in left...right+1 )
390
if( landMap.def_get(nx,ny,ctUnknown) == ctLand ) //we'll overwrite the tower cost at this phase
391
costMap.set(nx,ny, costArea1);
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 )
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 ) );
405
Assert.isTrue( seeds.length > 0 );
406
MatrixUtil.seedFill8( costMap, seeds, costArea1, costArea2 );
408
//trace the edges from the tower
409
MatrixUtil.traceEdges8( costMap, landMap, costArea2, ctWall );
411
//since our algorithm may block walls we need to clean those away
412
//cleanLostWalls will also recalc costs
416
//automatically position the starting cannons
417
//TODO: better logic here (actually do this somewhere else...?)
418
var le = new Array<LiveObject>();
420
var can = new CannonObject( gameDriver );
422
can.at.x = x - can.size.x/2;
426
can = new CannonObject( gameDriver );
428
can.at.x = x + can.size.x;
432
addLiveObjects( le );
436
* Cleans out the tiles not matching a specific criteria
438
private function cleanMapInv( ct : Int, mod : Int, replaceCt : Int )
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 );
446
public function cleanLostWalls( )
449
cleanMapInv( ctWall, modFort, ctLand );
453
public function updateDormantCannons()
455
for( lo in liveObjects )
457
if( Std.is( lo, CannonObject ) )
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 );
467
* Finds the closest owned tower to the given location. This is
468
* usually used as a targetting method -- find the closest place
471
public function getClosestOwnedTower( mp : Point2 )
476
for( lo in liveObjects )
478
if( !Std.is( lo, TowerObject ) )
481
//any part not in the fort makes this unowned (our towers are however only 1x1 in size now)
483
for( mp in indexLive( lo ) )
484
own = own && (modMap.get( mp.x, mp.y ) & modFort == modFort);
489
var ndist = lo.at.distanceToSqr( mp );
490
if( close == null || ndist < dist )
502
public function getNumOwnedTowers() : Int
506
for( lo in liveObjects )
508
if( !Std.is( lo, TowerObject ) )
511
//any part not in the fort makes this unowned (our towers are however only 1x1 in size now)
513
for( mp in indexLive( lo ) )
514
own = own && (modMap.get( mp.x, mp.y ) & modFort == modFort);
523
public function getAllTowers( ) : Array<TowerObject>
525
var ret = new Array<TowerObject>();
526
for( lo in liveObjects )
528
if( !Std.is( lo, TowerObject ) )
531
ret.push( cast( lo, TowerObject ) );
537
public function getClosestCannon( mp : Point2 ) : Point2
539
//finds the closest cannon
542
for( lo in liveObjects )
544
if( !Std.is( lo, CannonObject ) )
546
var ndist = lo.at.distanceToSqr( mp );
547
if( close == null || ndist < dist )
560
* Returns the wall closest to the given point. This is one of the
561
* search strategies of the boats.
563
* @param mp [in] wher eto start searching from
564
* @param limit [in] maximum number of cells to check
566
public function getWallClosestTo( mp : Point2, limit : Int ) : Point2
568
for( at in landMap.boxOrderIter( index(mp) ) )
570
if( landMap.get( at.x, at.y ) == ctWall )
571
return at.promoteCtrF();
582
* Does damage to the indicated grid location
584
* @return [out] was something actually damaged
586
public function damageGrid( mpi : Iterator<MatPoint>, damage : Float ) : Bool
592
var ct = landMap.def_get(mp.x,mp.y,ctUnknown);
595
landMap.set( mp.x, mp.y, ctLand ); //simply destroy it
604
SoundManager.playSound( "WallHit", 1 );
609
/////////////////////////////////////////////////////////////////////////
610
// The active elements, should they be in a different
612
private var liveObjects : FastList<LiveObject>;
614
public function allLiveObjects() : Iterator<LiveObject>
616
return liveObjects.iterator();
620
* Gets available cannon (can fire now) closest to given location.
622
* @return [out] cannon, or null if none available
624
public function getClosestAvailCannon( x : Float, y : Float ) : CannonObject
626
var close : CannonObject = null;
627
var dist : Float = 0; //first value not used
628
for( lo in liveObjects )
630
if( Std.is( lo, CannonObject ) )
632
var co = cast( lo, CannonObject );
635
var ndist = lo.at.distanceToSqr( Point2.at(x,y) );
636
if( close == null || ndist < dist )
648
return close; //may be null
652
* Determines which objects are touched to this one
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.
660
public function hitObjects( lo : LiveObject ) : Array<LiveObject>
662
//var start = flash.Lib.getTimer();
664
var co = new ObjectCollider( lo.stage ); //use stage of this target
666
var ret = new Array<LiveObject>();
667
for( tlo in liveObjects )
669
if( tlo == lo || !tlo.isVisible() )
670
continue; //don't touch ourselves or invisibles
672
if( co.doCollide( lo, tlo ) )
676
//works fast! (0-5ms with over 100 objects to test, albeit all small)
677
//trace( "hitObjects: " + (flash.Lib.getTimer() - start) );
684
* Adds a single live object -- a common use of addLiveObjects
686
public function addLiveObject( lo : LiveObject, ?placeEntry : Null<Bool> )
688
var los = new Array<LiveObject>();
690
addLiveObjects( los, placeEntry );
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).
700
public function addLiveObjects( los : Array<LiveObject>, ?placeEntry : Null<Bool> )
702
if( placeEntry == null )
705
var le = new LiveEvent( LiveEvent.OBJECT_PLACED );
707
//break available entry places into even sections then place
708
//randomly in each section
709
var w = MapLoader.getNumWaterEntry( mapNdx ) / los.length;
712
arr = ArrayUtil.shuffle( ArrayUtil.incList( 0, los.length ) );
714
//trace( "alo: " + los.length );
715
for( i in 0...los.length )
720
var ei = Math.floor( w * arr[i] + Math.random() * w );
721
lo.at = MapLoader.getWaterEntry( mapNdx, ei ).promoteF();
723
liveObjects.push( lo );
724
le.liveObjs.push( lo );
726
lo.addedTo( gameDriver );
729
gameDriver.postEvent( le );
733
* Steps the map data (game model) by the given amount
736
public function step( elapsed : Float )
738
stepLiveObjects( elapsed );
741
private var wasStillActive : Bool;
742
public function stepLiveObjects( elapsed : Float )
744
//trace( "X: " + liveObjects.length );
745
var lr = new LiveEvent( LiveEvent.OBJECT_REMOVED );
747
var stillActive = false;
749
var isAction = gameDriver.mode == GameMode.Action;
750
for( lo in liveObjects )
752
if( !isAction && lo.isOnlyAction() )
755
stillActive = stillActive || lo.isKeepAction();
757
var sr = lo.step( elapsed );
761
//harks back to old dispathc system, but let objects handle themselves now (if needed)
764
lr.liveObjs.push( lo );
771
//get rid of those we don't want anymore
772
for( r in lr.liveObjs )
773
liveObjects.remove( r );
775
if( lr.liveObjs.length > 0)
776
gameDriver.postEvent(lr );
778
//dispatch events about transitions
779
if( stillActive != wasStillActive )
781
wasStillActive = stillActive;
783
gameDriver.postEvent( new GameEvent( GameEvent.BECAME_ACTIVE) );
785
gameDriver.postEvent( new GameEvent( GameEvent.BECAME_INACTIVE) );