· 6 years ago · Aug 18, 2019, 01:28 PM
1#include "map.h"
2#include "HashTable.h"
3#include "monument.h"
4#include "arcReport.h"
5
6#include "CoordinateTimeTracking.h"
7
8
9// cell pixel dimension on client
10#define CELL_D 128
11
12#include "minorGems/util/random/JenkinsRandomSource.h"
13#include "minorGems/util/random/CustomRandomSource.h"
14
15#include "minorGems/util/stringUtils.h"
16#include "minorGems/util/SimpleVector.h"
17#include "minorGems/util/SettingsManager.h"
18
19#include "minorGems/util/log/AppLog.h"
20
21#include "minorGems/system/Time.h"
22
23#include "minorGems/formats/encodingUtils.h"
24
25#include "kissdb.h"
26//#include "stackdb.h"
27//#include "lineardb.h"
28#include "lineardb3.h"
29
30#include "minorGems/util/crc32.h"
31
32
33/*
34#define DB KISSDB
35#define DB_open KISSDB_open
36#define DB_close KISSDB_close
37#define DB_get KISSDB_get
38#define DB_put KISSDB_put
39// no distinction between insert and replace in KISSS
40#define DB_put_new KISSDB_put
41#define DB_Iterator KISSDB_Iterator
42#define DB_Iterator_init KISSDB_Iterator_init
43#define DB_Iterator_next KISSDB_Iterator_next
44#define DB_maxStack (int)( db.num_hash_tables )
45// no support for shrinking
46#define DB_getShrinkSize( dbP, n ) dbP->hashTableSize
47#define DB_getCurrentSize( dbP ) dbP->hashTableSize
48// no support for counting records
49#define DB_getNumRecords( dbP ) 0
50*/
51
52
53/*
54#define DB STACKDB
55#define DB_open STACKDB_open
56#define DB_close STACKDB_close
57#define DB_get STACKDB_get
58#define DB_put STACKDB_put
59// stack DB has faster insert
60#define DB_put_new STACKDB_put_new
61#define DB_Iterator STACKDB_Iterator
62#define DB_Iterator_init STACKDB_Iterator_init
63#define DB_Iterator_next STACKDB_Iterator_next
64#define DB_maxStack db.maxStackDepth
65// no support for shrinking
66#define DB_getShrinkSize( dbP, n ) dbP->hashTableSize
67#define DB_getCurrentSize( dbP ) dbP->hashTableSize
68// no support for counting records
69#define DB_getNumRecords( dbP ) 0
70*/
71
72/*
73#define DB LINEARDB
74#define DB_open LINEARDB_open
75#define DB_close LINEARDB_close
76#define DB_get LINEARDB_get
77#define DB_put LINEARDB_put
78// no distinction between put and put_new in lineardb
79#define DB_put_new LINEARDB_put
80#define DB_Iterator LINEARDB_Iterator
81#define DB_Iterator_init LINEARDB_Iterator_init
82#define DB_Iterator_next LINEARDB_Iterator_next
83#define DB_maxStack db.maxProbeDepth
84#define DB_getShrinkSize LINEARDB_getShrinkSize
85#define DB_getCurrentSize LINEARDB_getCurrentSize
86#define DB_getNumRecords LINEARDB_getNumRecords
87*/
88
89
90#define DB LINEARDB3
91#define DB_open LINEARDB3_open
92#define DB_close LINEARDB3_close
93#define DB_get LINEARDB3_get
94#define DB_put LINEARDB3_put
95// no distinction between put and put_new in lineardb3
96#define DB_put_new LINEARDB3_put
97#define DB_Iterator LINEARDB3_Iterator
98#define DB_Iterator_init LINEARDB3_Iterator_init
99#define DB_Iterator_next LINEARDB3_Iterator_next
100#define DB_maxStack db.maxOverflowDepth
101#define DB_getShrinkSize LINEARDB3_getShrinkSize
102#define DB_getCurrentSize LINEARDB3_getCurrentSize
103#define DB_getNumRecords LINEARDB3_getNumRecords
104
105
106
107
108
109
110
111
112
113#include "dbCommon.h"
114
115
116#include <stdarg.h>
117#include <math.h>
118#include <values.h>
119#include <stdint.h>
120
121
122#include "../gameSource/transitionBank.h"
123#include "../gameSource/objectBank.h"
124#include "../gameSource/GridPos.h"
125
126#include "../gameSource/GridPos.h"
127#include "../gameSource/objectMetadata.h"
128
129
130
131timeSec_t startFrozenTime = -1;
132
133timeSec_t frozenTime() {
134 if( startFrozenTime == -1 ) {
135 startFrozenTime = Time::timeSec();
136 }
137 return startFrozenTime;
138 }
139
140
141timeSec_t startFastTime = -1;
142
143timeSec_t fastTime() {
144 if( startFastTime == -1 ) {
145 startFastTime = Time::timeSec();
146 }
147 return 1000 * ( Time::timeSec() - startFastTime ) + startFastTime;
148 }
149
150
151
152timeSec_t slowTime() {
153 if( startFastTime == -1 ) {
154 startFastTime = Time::timeSec();
155 }
156 return ( Time::timeSec() - startFastTime ) / 4 + startFastTime;
157 }
158
159
160// can replace with frozenTime to freeze time
161// or slowTime to slow it down
162#define MAP_TIMESEC Time::timeSec()
163//#define MAP_TIMESEC frozenTime()
164//#define MAP_TIMESEC fastTime()
165//#define MAP_TIMESEC slowTime()
166
167
168extern GridPos getClosestPlayerPos( int inX, int inY );
169
170
171
172// track recent placements to determine camp where
173// we'll stick next Eve
174#define NUM_RECENT_PLACEMENTS 100
175
176typedef struct RecentPlacement {
177 GridPos pos;
178 // depth of object in tech tree
179 int depth;
180 } RecentPlacement;
181
182
183static RecentPlacement recentPlacements[ NUM_RECENT_PLACEMENTS ];
184
185// ring buffer
186static int nextPlacementIndex = 0;
187
188static int eveRadiusStart = 2;
189static int eveRadius = eveRadiusStart;
190
191
192
193GridPos eveLocation = { 0,0 };
194static int eveLocationUsage = 0;
195static int maxEveLocationUsage = 3;
196
197// eves are placed along an Archimedean spiral
198// we track the angle of the last Eve to compute the position on
199// the spiral of the next Eve
200static double eveAngle = 2 * M_PI;
201
202static char eveStartSpiralPosSet = false;
203static GridPos eveStartSpiralPos = { 0, 0 };
204
205
206
207static int evePrimaryLocSpacing = 0;
208static int evePrimaryLocObjectID = -1;
209static SimpleVector<int> eveSecondaryLocObjectIDs;
210
211static GridPos lastEvePrimaryLocation = {0,0};
212
213static SimpleVector<GridPos> recentlyUsedPrimaryEvePositions;
214static SimpleVector<int> recentlyUsedPrimaryEvePositionPlayerIDs;
215// when they were place, so they can time out
216static SimpleVector<double> recentlyUsedPrimaryEvePositionTimes;
217// one hour
218static double recentlyUsedPrimaryEvePositionTimeout = 3600;
219
220static int eveHomeMarkerObjectID = -1;
221
222
223
224
225// what human-placed stuff, together, counts as a camp
226static int campRadius = 20;
227
228static float minEveCampRespawnAge = 120.0;
229
230
231static int barrierRadius = 250;
232
233static int barrierOn = 1;
234
235static int longTermCullEnabled = 1;
236
237
238static unsigned int biomeRandSeed = 723;
239
240
241static SimpleVector<int> barrierItemList;
242
243
244static FILE *mapChangeLogFile = NULL;
245
246static double mapChangeLogTimeStart = -1;
247
248
249
250extern int apocalypsePossible;
251extern char apocalypseTriggered;
252extern GridPos apocalypseLocation;
253
254
255
256
257
258
259// what object is placed on edge of map
260static int edgeObjectID = 0;
261
262
263
264static int currentResponsiblePlayer = -1;
265
266
267void setResponsiblePlayer( int inPlayerID ) {
268 currentResponsiblePlayer = inPlayerID;
269 }
270
271
272
273static double gapIntScale = 1000000.0;
274
275
276
277
278// object ids that occur naturally on map at random, per biome
279static int numBiomes;
280static int *biomes;
281static float *biomeWeights;
282static float *biomeCumuWeights;
283static float biomeTotalWeight;
284static int regularBiomeLimit;
285
286static int numSpecialBiomes;
287static int *specialBiomes;
288static float *specialBiomeCumuWeights;
289static float specialBiomeTotalWeight;
290
291
292
293// one vector per biome
294static SimpleVector<int> *naturalMapIDs;
295static SimpleVector<float> *naturalMapChances;
296
297typedef struct MapGridPlacement {
298 int id;
299 int spacing;
300 int phase;
301 int wiggleScale;
302 SimpleVector<int> permittedBiomes;
303 } MapGridPlacement;
304
305static SimpleVector<MapGridPlacement> gridPlacements;
306
307
308
309static SimpleVector<int> allNaturalMapIDs;
310
311static float *totalChanceWeight;
312
313
314static int getBiomeIndex( int inBiome ) {
315 for( int i=0; i<numBiomes; i++ ) {
316 if( biomes[i] == inBiome ) {
317 return i;
318 }
319 }
320 return -1;
321 }
322
323
324
325// tracking when a given map cell was last seen
326static DB lookTimeDB;
327static char lookTimeDBOpen = false;
328
329
330
331static DB db;
332static char dbOpen = false;
333
334
335static DB timeDB;
336static char timeDBOpen = false;
337
338
339static DB biomeDB;
340static char biomeDBOpen = false;
341
342
343static DB floorDB;
344static char floorDBOpen = false;
345
346static DB floorTimeDB;
347static char floorTimeDBOpen = false;
348
349
350static DB graveDB;
351static char graveDBOpen = false;
352
353
354// per-player memory of where they should spawn as eve
355static DB eveDB;
356static char eveDBOpen = false;
357
358
359static DB metaDB;
360static char metaDBOpen = false;
361
362
363
364static int randSeed = 124567;
365//static JenkinsRandomSource randSource( randSeed );
366static CustomRandomSource randSource( randSeed );
367
368
369
370#define DECAY_SLOT 1
371#define NUM_CONT_SLOT 2
372#define FIRST_CONT_SLOT 3
373
374#define NO_DECAY_SLOT -1
375
376
377// decay slots for contained items start after container slots
378
379
380// 15 minutes
381static int maxSecondsForActiveDecayTracking = 900;
382
383// 15 seconds (before no-look regions are purged from live tracking)
384static int maxSecondsNoLookDecayTracking = 15;
385
386// live players look at their surrounding map region every 5 seconds
387// we count a region as stale after no one looks at it for 10 seconds
388// (we actually purge the live tracking of that region after 15 seconds).
389// This gives us some wiggle room with the timing, so we always make
390// sure to re-look at a region (when walking back into it) that is >10
391// seconds old, because it may (or may not) have fallen out of our live
392// tracking (if our re-look time was 15 seconds to match the time stuff actually
393// is dropped from live tracking, we might miss some stuff, depending
394// on how the check calls are interleaved time-wise).
395static int noLookCountAsStaleSeconds = 10;
396
397
398
399typedef struct LiveDecayRecord {
400 int x, y;
401
402 // 0 means main object decay
403 // 1 - NUM_CONT_SLOT means contained object decay
404 int slot;
405
406 timeSec_t etaTimeSeconds;
407
408 // 0 means main object
409 // >0 indexs sub containers of object
410 int subCont;
411
412 // the transition that will apply when this decay happens
413 // this allows us to avoid marking certain types of move decays
414 // as stale when not looked at in a while (all other types of decays
415 // go stale)
416 // Can be NULL if we don't care about the transition
417 // associated with this decay (for contained item decay, for example)
418 TransRecord *applicableTrans;
419
420 } LiveDecayRecord;
421
422
423
424#include "minorGems/util/MinPriorityQueue.h"
425
426static MinPriorityQueue<LiveDecayRecord> liveDecayQueue;
427
428
429// for quick lookup of existing records in liveDecayQueue
430// store the eta time here
431// before storing a new record in the queue, we can check this hash
432// table to see whether it already exists
433static HashTable<timeSec_t> liveDecayRecordPresentHashTable( 1024 );
434
435// times in seconds that a tracked live decay map cell or slot
436// was last looked at
437static HashTable<timeSec_t> liveDecayRecordLastLookTimeHashTable( 1024 );
438
439
440typedef struct ContRecord {
441 int maxSlots;
442 int maxSubSlots;
443 } ContRecord;
444
445static ContRecord defaultContRecord = { 0, 0 };
446
447
448// track max tracked contained for each x,y
449// this allows us to update last look times without getting contained count
450// from map
451// indexed as x, y, 0, 0
452static HashTable<ContRecord>
453liveDecayRecordLastLookTimeMaxContainedHashTable( 1024, defaultContRecord );
454
455
456
457static CoordinateTimeTracking lookTimeTracking;
458
459
460
461// track currently in-process movements so that we can be queried
462// about whether arrival has happened or not
463typedef struct MovementRecord {
464 int x, y;
465 double etaTime;
466 } MovementRecord;
467
468
469// clock time in fractional seconds of destination ETA
470// indexed as x, y, 0
471static HashTable<double> liveMovementEtaTimes( 1024, 0 );
472
473static MinPriorityQueue<MovementRecord> liveMovements;
474
475
476
477
478
479// track all map changes that happened since the last
480// call to stepMap
481static SimpleVector<ChangePosition> mapChangePosSinceLastStep;
482
483
484static char anyBiomesInDB = false;
485static int maxBiomeXLoc = -2000000000;
486static int maxBiomeYLoc = -2000000000;
487static int minBiomeXLoc = 2000000000;
488static int minBiomeYLoc = 2000000000;
489
490
491
492
493// if true, rest of natural map is blank
494static char useTestMap = false;
495
496// read from testMap.txt
497// unless testMapStale.txt is present
498
499// each line contains data in this order:
500// x y biome floor id_and_contained
501// id and contained are in CONTAINER OBJECT FORMAT described in protocol.txt
502// biome = -1 means use naturally-occurring biome
503typedef struct TestMapRecord {
504 int x, y;
505 int biome;
506 int floor;
507 int id;
508 SimpleVector<int> contained;
509 SimpleVector< SimpleVector<int> > subContained;
510 } TestMapRecord;
511
512
513
514
515
516
517#include "../commonSource/fractalNoise.h"
518
519
520
521
522
523// four ints to a 16-byte key
524void intQuadToKey( int inX, int inY, int inSlot, int inB,
525 unsigned char *outKey ) {
526 for( int i=0; i<4; i++ ) {
527 int offset = i * 8;
528 outKey[i] = ( inX >> offset ) & 0xFF;
529 outKey[i+4] = ( inY >> offset ) & 0xFF;
530 outKey[i+8] = ( inSlot >> offset ) & 0xFF;
531 outKey[i+12] = ( inB >> offset ) & 0xFF;
532 }
533 }
534
535
536// two ints to an 8-byte key
537void intPairToKey( int inX, int inY, unsigned char *outKey ) {
538 for( int i=0; i<4; i++ ) {
539 int offset = i * 8;
540 outKey[i] = ( inX >> offset ) & 0xFF;
541 outKey[i+4] = ( inY >> offset ) & 0xFF;
542 }
543 }
544
545
546
547
548
549
550
551// one timeSec_t to an 8-byte double value
552void timeToValue( timeSec_t inT, unsigned char *outValue ) {
553
554
555 // pack double time into 8 bytes in whatever endian order the
556 // double is stored on this platform
557
558 union{ timeSec_t doubleTime; uint64_t intTime; };
559
560 doubleTime = inT;
561
562 for( int i=0; i<8; i++ ) {
563 outValue[i] = ( intTime >> (i * 8) ) & 0xFF;
564 }
565 }
566
567
568timeSec_t valueToTime( unsigned char *inValue ) {
569
570 union{ timeSec_t doubleTime; uint64_t intTime; };
571
572 // get bytes back out in same order they were put in
573 intTime =
574 (uint64_t)inValue[7] << 56 | (uint64_t)inValue[6] << 48 |
575 (uint64_t)inValue[5] << 40 | (uint64_t)inValue[4] << 32 |
576 (uint64_t)inValue[3] << 24 | (uint64_t)inValue[2] << 16 |
577 (uint64_t)inValue[1] << 8 | (uint64_t)inValue[0];
578
579 // caste back to timeSec_t
580 return doubleTime;
581 }
582
583
584
585
586timeSec_t dbLookTimeGet( int inX, int inY );
587void dbLookTimePut( int inX, int inY, timeSec_t inTime );
588
589
590
591
592// returns -1 if not found
593static int biomeDBGet( int inX, int inY,
594 int *outSecondPlaceBiome = NULL,
595 double *outSecondPlaceGap = NULL ) {
596 unsigned char key[8];
597 unsigned char value[12];
598
599 // look for changes to default in database
600 intPairToKey( inX, inY, key );
601
602 int result = DB_get( &biomeDB, key, value );
603
604 if( result == 0 ) {
605 // found
606 int biome = valueToInt( &( value[0] ) );
607
608 if( outSecondPlaceBiome != NULL ) {
609 *outSecondPlaceBiome = valueToInt( &( value[4] ) );
610 }
611
612 if( outSecondPlaceGap != NULL ) {
613 *outSecondPlaceGap = valueToInt( &( value[8] ) ) / gapIntScale;
614 }
615
616 return biome;
617 }
618 else {
619 return -1;
620 }
621 }
622
623
624
625static void biomeDBPut( int inX, int inY, int inValue, int inSecondPlace,
626 double inSecondPlaceGap ) {
627 unsigned char key[8];
628 unsigned char value[12];
629
630
631 intPairToKey( inX, inY, key );
632 intToValue( inValue, &( value[0] ) );
633 intToValue( inSecondPlace, &( value[4] ) );
634 intToValue( lrint( inSecondPlaceGap * gapIntScale ),
635 &( value[8] ) );
636
637
638 anyBiomesInDB = true;
639
640 if( inX > maxBiomeXLoc ) {
641 maxBiomeXLoc = inX;
642 }
643 if( inX < minBiomeXLoc ) {
644 minBiomeXLoc = inX;
645 }
646 if( inY > maxBiomeYLoc ) {
647 maxBiomeYLoc = inY;
648 }
649 if( inY < minBiomeYLoc ) {
650 minBiomeYLoc = inY;
651 }
652
653
654 DB_put( &biomeDB, key, value );
655 }
656
657
658
659
660// returns -1 on failure, 1 on success
661static int eveDBGet( const char *inEmail, int *outX, int *outY,
662 int *outRadius ) {
663 unsigned char key[50];
664
665 unsigned char value[12];
666
667
668 emailToKey( inEmail, key );
669
670 int result = DB_get( &eveDB, key, value );
671
672 if( result == 0 ) {
673 // found
674 *outX = valueToInt( &( value[0] ) );
675 *outY = valueToInt( &( value[4] ) );
676 *outRadius = valueToInt( &( value[8] ) );
677
678 return 1;
679 }
680 else {
681 return -1;
682 }
683 }
684
685
686
687static void eveDBPut( const char *inEmail, int inX, int inY, int inRadius ) {
688 unsigned char key[50];
689 unsigned char value[12];
690
691
692 emailToKey( inEmail, key );
693
694 intToValue( inX, &( value[0] ) );
695 intToValue( inY, &( value[4] ) );
696 intToValue( inRadius, &( value[8] ) );
697
698
699 DB_put( &eveDB, key, value );
700 }
701
702
703
704static void dbFloorPut( int inX, int inY, int inValue );
705
706
707
708
709// inKnee in 0..inf, smaller values make harder knees
710// intput in 0..1
711// output in 0..1
712
713// from Simplest AI trick in the book:
714// Normalized Tunable SIgmoid Function
715// Dino Dini, GDC 2013
716double sigmoid( double inInput, double inKnee ) {
717
718 // in -1,-1
719 double shiftedInput = inInput * 2 - 1;
720
721
722 double sign = 1;
723 if( shiftedInput < 0 ) {
724 sign = -1;
725 }
726
727
728 double k = -1 - inKnee;
729
730 double absInput = fabs( shiftedInput );
731
732 // out in -1..1
733 double out = sign * absInput * k / ( 1 + k - absInput );
734
735 return ( out + 1 ) * 0.5;
736 }
737
738
739
740
741
742
743// optimization:
744// cache biomeIndex results in RAM
745
746// 3.1 MB of RAM for this.
747#define BIOME_CACHE_SIZE 131072
748
749typedef struct BiomeCacheRecord {
750 int x, y;
751 int biome, secondPlace;
752 double secondPlaceGap;
753 } BiomeCacheRecord;
754
755static BiomeCacheRecord biomeCache[ BIOME_CACHE_SIZE ];
756
757
758#define CACHE_PRIME_A 776509273
759#define CACHE_PRIME_B 904124281
760#define CACHE_PRIME_C 528383237
761#define CACHE_PRIME_D 148497157
762
763static int computeXYCacheHash( int inKeyA, int inKeyB ) {
764
765 int hashKey = ( inKeyA * CACHE_PRIME_A +
766 inKeyB * CACHE_PRIME_B ) % BIOME_CACHE_SIZE;
767 if( hashKey < 0 ) {
768 hashKey += BIOME_CACHE_SIZE;
769 }
770 return hashKey;
771 }
772
773
774
775static void initBiomeCache() {
776 BiomeCacheRecord blankRecord = { 0, 0, -2, 0, 0 };
777 for( int i=0; i<BIOME_CACHE_SIZE; i++ ) {
778 biomeCache[i] = blankRecord;
779 }
780 }
781
782
783
784
785// returns -2 on miss
786static int biomeGetCached( int inX, int inY,
787 int *outSecondPlaceIndex,
788 double *outSecondPlaceGap ) {
789 BiomeCacheRecord r =
790 biomeCache[ computeXYCacheHash( inX, inY ) ];
791
792 if( r.x == inX && r.y == inY ) {
793 *outSecondPlaceIndex = r.secondPlace;
794 *outSecondPlaceGap = r.secondPlaceGap;
795
796 return r.biome;
797 }
798 else {
799 return -2;
800 }
801 }
802
803
804
805static void biomePutCached( int inX, int inY, int inBiome, int inSecondPlace,
806 double inSecondPlaceGap ) {
807 BiomeCacheRecord r = { inX, inY, inBiome, inSecondPlace, inSecondPlaceGap };
808
809 biomeCache[ computeXYCacheHash( inX, inY ) ] = r;
810 }
811
812
813
814
815
816
817
818// new code, topographic rings
819static int computeMapBiomeIndex( int inX, int inY,
820 int *outSecondPlaceIndex = NULL,
821 double *outSecondPlaceGap = NULL ) {
822
823 int secondPlace = -1;
824
825 double secondPlaceGap = 0;
826
827
828 int pickedBiome = biomeGetCached( inX, inY, &secondPlace, &secondPlaceGap );
829
830 if( pickedBiome != -2 ) {
831 // hit cached
832
833 if( outSecondPlaceIndex != NULL ) {
834 *outSecondPlaceIndex = secondPlace;
835 }
836 if( outSecondPlaceGap != NULL ) {
837 *outSecondPlaceGap = secondPlaceGap;
838 }
839
840 return pickedBiome;
841 }
842
843 // else cache miss
844 pickedBiome = -1;
845
846
847 // try topographical altitude mapping
848
849 setXYRandomSeed( biomeRandSeed );
850
851 double randVal =
852 ( getXYFractal( inX, inY,
853 0.55,
854 0.83332 + 0.08333 * numBiomes ) );
855
856 // push into range 0..1, based on sampled min/max values
857 randVal -= 0.099668;
858 randVal *= 1.268963;
859
860
861 // flatten middle
862 //randVal = ( pow( 2*(randVal - 0.5 ), 3 ) + 1 ) / 2;
863
864
865 // push into range 0..1 with manually tweaked values
866 // these values make it pretty even in terms of distribution:
867 //randVal -= 0.319;
868 //randVal *= 3;
869
870
871
872 // these values are more intuitve to make a map that looks good
873 //randVal -= 0.23;
874 //randVal *= 1.9;
875
876
877
878
879
880 // apply gamma correction
881 //randVal = pow( randVal, 1.5 );
882 /*
883 randVal += 0.4* sin( inX / 40.0 );
884 randVal += 0.4 *sin( inY / 40.0 );
885
886 randVal += 0.8;
887 randVal /= 2.6;
888 */
889
890 // slow arc n to s:
891
892 // pow version has flat area in middle
893 //randVal += 0.7 * pow( ( inY / 354.0 ), 3 ) ;
894
895 // sin version
896 //randVal += 0.3 * sin( 0.5 * M_PI * inY / 354.0 );
897
898
899 /*
900 ( sin( M_PI * inY / 708 ) +
901 (1/3.0) * sin( 3 * M_PI * inY / 708 ) );
902 */
903 //randVal += 0.5;
904 //randVal /= 2.0;
905
906
907
908 float i = randVal * biomeTotalWeight;
909
910 pickedBiome = 0;
911 while( pickedBiome < numBiomes &&
912 i > biomeCumuWeights[pickedBiome] ) {
913 pickedBiome++;
914 }
915 if( pickedBiome >= numBiomes ) {
916 pickedBiome = numBiomes - 1;
917 }
918
919
920
921 if( pickedBiome >= regularBiomeLimit && numSpecialBiomes > 0 ) {
922 // special case: on a peak, place a special biome here
923
924 // use patches mode for these
925 pickedBiome = -1;
926
927
928 double maxValue = -10;
929 double secondMaxVal = -10;
930
931 for( int i=regularBiomeLimit; i<numBiomes; i++ ) {
932 int biome = biomes[i];
933
934 setXYRandomSeed( biome * 263 + biomeRandSeed + 38475 );
935
936 double randVal = getXYFractal( inX,
937 inY,
938 0.55,
939 2.4999 +
940 0.2499 * numSpecialBiomes );
941
942 if( randVal > maxValue ) {
943 if( maxValue != -10 ) {
944 secondMaxVal = maxValue;
945 }
946 maxValue = randVal;
947 pickedBiome = i;
948 }
949 }
950
951 if( maxValue - secondMaxVal < 0.03 ) {
952 // close! that means we're on a boundary between special biomes
953
954 // stick last regular biome on this boundary, so special
955 // biomes never touch
956 secondPlace = pickedBiome;
957 secondPlaceGap = 0.1;
958 pickedBiome = regularBiomeLimit - 1;
959 }
960 else {
961 secondPlace = regularBiomeLimit - 1;
962 secondPlaceGap = 0.1;
963 }
964 }
965 else {
966 // second place for regular biome rings
967
968 secondPlace = pickedBiome - 1;
969 if( secondPlace < 0 ) {
970 secondPlace = pickedBiome + 1;
971 }
972 secondPlaceGap = 0.1;
973 }
974
975
976
977 biomePutCached( inX, inY, pickedBiome, secondPlace, secondPlaceGap );
978
979
980 if( outSecondPlaceIndex != NULL ) {
981 *outSecondPlaceIndex = secondPlace;
982 }
983 if( outSecondPlaceGap != NULL ) {
984 *outSecondPlaceGap = secondPlaceGap;
985 }
986
987 return pickedBiome;
988 }
989
990
991
992
993// old code, separate height fields per biome that compete
994// and create a patchwork layout
995static int computeMapBiomeIndexOld( int inX, int inY,
996 int *outSecondPlaceIndex = NULL,
997 double *outSecondPlaceGap = NULL ) {
998
999 int secondPlace = -1;
1000
1001 double secondPlaceGap = 0;
1002
1003
1004 int pickedBiome = biomeGetCached( inX, inY, &secondPlace, &secondPlaceGap );
1005
1006 if( pickedBiome != -2 ) {
1007 // hit cached
1008
1009 if( outSecondPlaceIndex != NULL ) {
1010 *outSecondPlaceIndex = secondPlace;
1011 }
1012 if( outSecondPlaceGap != NULL ) {
1013 *outSecondPlaceGap = secondPlaceGap;
1014 }
1015
1016 return pickedBiome;
1017 }
1018
1019 // else cache miss
1020 pickedBiome = -1;
1021
1022
1023 double maxValue = -DBL_MAX;
1024
1025
1026 for( int i=0; i<numBiomes; i++ ) {
1027 int biome = biomes[i];
1028 printf("biome: %d\n", biome);
1029 setXYRandomSeed( biome * 263 + biomeRandSeed );
1030
1031 double randVal = getXYFractal( inX,
1032 inY,
1033 0.55,
1034 0.83332 + 0.08333 * numBiomes );
1035
1036 if( randVal > maxValue ) {
1037 // a new first place
1038
1039 // old first moves into second
1040 secondPlace = pickedBiome;
1041 secondPlaceGap = randVal - maxValue;
1042
1043
1044 maxValue = randVal;
1045 pickedBiome = i;
1046 }
1047 else if( randVal > maxValue - secondPlaceGap ) {
1048 // a better second place
1049 secondPlace = i;
1050 secondPlaceGap = maxValue - randVal;
1051 }
1052 }
1053
1054 biomePutCached( inX, inY, pickedBiome, secondPlace, secondPlaceGap );
1055
1056
1057 if( outSecondPlaceIndex != NULL ) {
1058 *outSecondPlaceIndex = secondPlace;
1059 }
1060 if( outSecondPlaceGap != NULL ) {
1061 *outSecondPlaceGap = secondPlaceGap;
1062 }
1063
1064 return pickedBiome;
1065 }
1066
1067
1068
1069
1070static int getMapBiomeIndex( int inX, int inY,
1071 int *outSecondPlaceIndex = NULL,
1072 double *outSecondPlaceGap = NULL ) {
1073
1074 int secondPlaceBiome = -1;
1075
1076 int dbBiome = -1;
1077
1078 if( anyBiomesInDB &&
1079 inX >= minBiomeXLoc && inX <= maxBiomeXLoc &&
1080 inY >= minBiomeYLoc && inY <= maxBiomeYLoc ) {
1081 // don't bother with this call unless biome DB has
1082 // something in it, and this inX,inY is in the region where biomes
1083 // exist in the database (tutorial loading, or test maps)
1084 dbBiome = biomeDBGet( inX, inY,
1085 &secondPlaceBiome,
1086 outSecondPlaceGap );
1087 }
1088
1089
1090 if( dbBiome != -1 ) {
1091
1092 int index = getBiomeIndex( dbBiome );
1093
1094 if( index != -1 ) {
1095 // biome still exists!
1096
1097 char secondPlaceFailed = false;
1098
1099 if( outSecondPlaceIndex != NULL ) {
1100 int secondIndex = getBiomeIndex( secondPlaceBiome );
1101
1102 if( secondIndex != -1 ) {
1103
1104 *outSecondPlaceIndex = secondIndex;
1105 }
1106 else {
1107 secondPlaceFailed = true;
1108 }
1109 }
1110
1111 if( ! secondPlaceFailed ) {
1112 return index;
1113 }
1114 }
1115 else {
1116 dbBiome = -1;
1117 }
1118
1119 // else a biome or second place in biome.db that isn't in game anymore
1120 // ignore it
1121 }
1122
1123
1124 int secondPlace = -1;
1125
1126 double secondPlaceGap = 0;
1127
1128
1129 int pickedBiome = computeMapBiomeIndex( inX, inY,
1130 &secondPlace, &secondPlaceGap );
1131
1132
1133 if( outSecondPlaceIndex != NULL ) {
1134 *outSecondPlaceIndex = secondPlace;
1135 }
1136 if( outSecondPlaceGap != NULL ) {
1137 *outSecondPlaceGap = secondPlaceGap;
1138 }
1139
1140
1141 if( dbBiome == -1 || secondPlaceBiome == -1 ) {
1142 // not stored, OR some part of stored stale, re-store it
1143
1144 secondPlaceBiome = 0;
1145 if( secondPlace != -1 ) {
1146 secondPlaceBiome = biomes[ secondPlace ];
1147 }
1148
1149 // skip saving proc-genned biomes for now
1150 // huge RAM impact as players explore distant areas of map
1151
1152 // we still check the biomeDB above for loading test maps
1153 /*
1154 biomeDBPut( inX, inY, biomes[pickedBiome],
1155 secondPlaceBiome, secondPlaceGap );
1156 */
1157 }
1158
1159
1160 return pickedBiome;
1161 }
1162
1163
1164
1165
1166// gets procedurally-generated base map at a given spot
1167// player modifications are overlayed on top of this
1168
1169// SIDE EFFECT:
1170// if biome at x,y needed to be determined in order to compute map
1171// at this spot, it is saved into lastCheckedBiome
1172
1173static int lastCheckedBiome = -1;
1174static int lastCheckedBiomeX = 0;
1175static int lastCheckedBiomeY = 0;
1176
1177
1178// 1671 shy of int max
1179static int xLimit = 2147481977;
1180static int yLimit = 2147481977;
1181
1182
1183
1184
1185
1186typedef struct BaseMapCacheRecord {
1187 int x, y;
1188 int id;
1189 char gridPlacement;
1190 } BaseMapCacheRecord;
1191
1192
1193// should be a power of 2
1194// cache will contain squared number of records
1195#define BASE_MAP_CACHE_SIZE 256
1196
1197// if BASE_MAP_CACHE_SIZE is a power of 2, then this is the bit mask
1198// of solid 1's that can limit an integer to that range
1199static int mapCacheBitMask = BASE_MAP_CACHE_SIZE - 1;
1200
1201BaseMapCacheRecord baseMapCache[ BASE_MAP_CACHE_SIZE ][ BASE_MAP_CACHE_SIZE ];
1202
1203static void mapCacheClear() {
1204 for( int y=0; y<BASE_MAP_CACHE_SIZE; y++ ) {
1205 for( int x=0; x<BASE_MAP_CACHE_SIZE; x++ ) {
1206 baseMapCache[y][x].x = 0;
1207 baseMapCache[y][x].y = 0;
1208 baseMapCache[y][x].id = -1;
1209 }
1210 }
1211 }
1212
1213
1214
1215static BaseMapCacheRecord *mapCacheRecordLookup( int inX, int inY ) {
1216 // apply bitmask to x and y
1217 return &( baseMapCache[ inY & mapCacheBitMask ][ inX & mapCacheBitMask ] );
1218 }
1219
1220
1221
1222// returns -1 if not in cache
1223static int mapCacheLookup( int inX, int inY, char *outGridPlacement = NULL ) {
1224 BaseMapCacheRecord *r = mapCacheRecordLookup( inX, inY );
1225
1226 if( r->x == inX && r->y == inY ) {
1227 if( outGridPlacement != NULL ) {
1228 *outGridPlacement = r->gridPlacement;
1229 }
1230 return r->id;
1231 }
1232
1233 return -1;
1234 }
1235
1236
1237
1238static void mapCacheInsert( int inX, int inY, int inID,
1239 char inGridPlacement = false ) {
1240 BaseMapCacheRecord *r = mapCacheRecordLookup( inX, inY );
1241
1242 r->x = inX;
1243 r->y = inY;
1244 r->id = inID;
1245 r->gridPlacement = inGridPlacement;
1246 }
1247
1248
1249
1250static int getBaseMapCallCount = 0;
1251
1252
1253static int getBaseMap( int inX, int inY, char *outGridPlacement = NULL ) {
1254
1255 if( inX > xLimit || inX < -xLimit ||
1256 inY > yLimit || inY < -yLimit ) {
1257
1258 return edgeObjectID;
1259 }
1260
1261 int cachedID = mapCacheLookup( inX, inY, outGridPlacement );
1262
1263 if( cachedID != -1 ) {
1264 return cachedID;
1265 }
1266
1267 getBaseMapCallCount ++;
1268
1269
1270 if( outGridPlacement != NULL ) {
1271 *outGridPlacement = false;
1272 }
1273
1274
1275 // see if any of our grids apply
1276 setXYRandomSeed( 9753 );
1277
1278 for( int g=0; g < gridPlacements.size(); g++ ) {
1279 MapGridPlacement *gp = gridPlacements.getElement( g );
1280
1281
1282 /*
1283 double gridWiggleX = getXYFractal( inX / gp->spacing,
1284 inY / gp->spacing,
1285 0.1, 0.25 );
1286
1287 double gridWiggleY = getXYFractal( inX / gp->spacing,
1288 inY / gp->spacing + 392387,
1289 0.1, 0.25 );
1290 */
1291 // turn wiggle off for now
1292 double gridWiggleX = 0;
1293 double gridWiggleY = 0;
1294
1295 if( ( inX + gp->phase + lrint( gridWiggleX * gp->wiggleScale ) )
1296 % gp->spacing == 0 &&
1297 ( inY + gp->phase + lrint( gridWiggleY * gp->wiggleScale ) )
1298 % gp->spacing == 0 ) {
1299
1300 // hits this grid
1301
1302 // make sure this biome is on the list for this object
1303 int secondPlace;
1304 double secondPlaceGap;
1305
1306 int pickedBiome = getMapBiomeIndex( inX, inY, &secondPlace,
1307 &secondPlaceGap );
1308
1309 if( pickedBiome == -1 ) {
1310 mapCacheInsert( inX, inY, 0 );
1311 return 0;
1312 }
1313
1314 if( gp->permittedBiomes.getElementIndex( pickedBiome ) != -1 ) {
1315 mapCacheInsert( inX, inY, gp->id, true );
1316
1317 if( outGridPlacement != NULL ) {
1318 *outGridPlacement = true;
1319 }
1320 return gp->id;
1321 }
1322 }
1323 }
1324
1325
1326
1327 setXYRandomSeed( 5379 );
1328
1329 // first step: save rest of work if density tells us that
1330 // nothing is here anyway
1331 double density = getXYFractal( inX, inY, 0.1, 0.25 );
1332
1333 // correction
1334 density = sigmoid( density, 0.1 );
1335
1336 // scale
1337 density *= .4;
1338 // good for zoom in to map for teaser
1339 //density = .70;
1340
1341 setXYRandomSeed( 9877 );
1342
1343 if( getXYRandom( inX, inY ) < density ) {
1344
1345
1346
1347
1348 // next step, pick top two biomes
1349 int secondPlace;
1350 double secondPlaceGap;
1351
1352 int pickedBiome = getMapBiomeIndex( inX, inY, &secondPlace,
1353 &secondPlaceGap );
1354
1355 if( pickedBiome == -1 ) {
1356 mapCacheInsert( inX, inY, 0 );
1357 return 0;
1358 }
1359
1360 // only override if it's not already set
1361 // if it's already set, then we're calling getBaseMap for neighboring
1362 // map cells (wide, tall, moving objects, etc.)
1363 // getBaseMap is always called for our cell in question first
1364 // before examining neighboring cells if needed
1365 if( lastCheckedBiome == -1 ) {
1366 lastCheckedBiome = biomes[pickedBiome];
1367 lastCheckedBiomeX = inX;
1368 lastCheckedBiomeY = inY;
1369 }
1370
1371
1372
1373 // randomly let objects from second place biome peek through
1374
1375 // if gap is 0, this should happen 50 percent of the time
1376
1377 // if gap is 1.0, it should never happen
1378
1379 // larger values make second place less likely
1380 double secondPlaceReduction = 10.0;
1381
1382 //printf( "Second place gap = %f, random(%d,%d)=%f\n", secondPlaceGap,
1383 // inX, inY, getXYRandom( 2087 + inX, 793 + inY ) );
1384
1385 setXYRandomSeed( 348763 );
1386
1387 if( getXYRandom( inX, inY ) >
1388 .5 + secondPlaceReduction * secondPlaceGap ) {
1389
1390 // note that lastCheckedBiome is NOT changed, so ground
1391 // shows the true, first-place biome, but object placement
1392 // follows the second place biome
1393 pickedBiome = secondPlace;
1394 }
1395
1396
1397 int numObjects = naturalMapIDs[pickedBiome].size();
1398
1399 if( numObjects == 0 ) {
1400 mapCacheInsert( inX, inY, 0 );
1401 return 0;
1402 }
1403
1404
1405
1406 // something present here
1407
1408
1409 // special object in this region is 10x more common than it
1410 // would be otherwise
1411
1412
1413 int specialObjectIndex = -1;
1414 double maxValue = -DBL_MAX;
1415
1416
1417 for( int i=0; i<numObjects; i++ ) {
1418 if (pickedBiome == 7)
1419 {
1420 int returnID = naturalMapIDs[pickedBiome].getElementDirect( i );
1421 mapCacheInsert(inX,inY,returnID);
1422 return returnID;
1423 }
1424 setXYRandomSeed( 793 * i + 123 );
1425
1426 double randVal = getXYFractal( inX,
1427 inY,
1428 0.3,
1429 0.15 + 0.016666 * numObjects );
1430
1431 if( randVal > maxValue ) {
1432 maxValue = randVal;
1433 specialObjectIndex = i;
1434 }
1435
1436 }
1437
1438
1439 float oldSpecialChance =
1440 naturalMapChances[pickedBiome].getElementDirect(
1441 specialObjectIndex );
1442
1443 float newSpecialChance = oldSpecialChance * 10;
1444
1445 *( naturalMapChances[pickedBiome].getElement( specialObjectIndex ) )
1446 = newSpecialChance;
1447
1448 float oldTotalChanceWeight = totalChanceWeight[pickedBiome];
1449
1450 totalChanceWeight[pickedBiome] -= oldSpecialChance;
1451 totalChanceWeight[pickedBiome] += newSpecialChance;
1452
1453 if (pickedBiome == 7){
1454
1455 totalChanceWeight[pickedBiome] = 10000;
1456
1457 }
1458
1459 // pick one of our natural objects at random
1460
1461 // pick value between 0 and total weight
1462
1463 setXYRandomSeed( 4593873 );
1464
1465 double randValue =
1466 totalChanceWeight[pickedBiome] * getXYRandom( inX, inY );
1467
1468 // walk through objects, summing weights, until one crosses threshold
1469 int i = 0;
1470 float weightSum = 0;
1471
1472 while( weightSum < randValue && i < numObjects ) {
1473 weightSum += naturalMapChances[pickedBiome].getElementDirect( i );
1474 i++;
1475 }
1476
1477 i--;
1478
1479
1480 // restore chance of special object
1481 *( naturalMapChances[pickedBiome].getElement( specialObjectIndex ) )
1482 = oldSpecialChance;
1483
1484 totalChanceWeight[pickedBiome] = oldTotalChanceWeight;
1485
1486 if( i >= 0 ) {
1487 int returnID = naturalMapIDs[pickedBiome].getElementDirect( i );
1488
1489 if( pickedBiome == secondPlace ) {
1490 // object peeking through from second place biome
1491
1492 // make sure it's not a moving object (animal)
1493 // those are locked to their target biome only
1494 TransRecord *t = getPTrans( -1, returnID );
1495 if( t != NULL && t->move != 0 ) {
1496 // put empty tile there instead
1497 returnID = 0;
1498 }
1499 }
1500
1501 mapCacheInsert( inX, inY, returnID );
1502 return returnID;
1503 }
1504 else {
1505 mapCacheInsert( inX, inY, 0 );
1506 return 0;
1507 }
1508 }
1509 else {
1510 mapCacheInsert( inX, inY, 0 );
1511 return 0;
1512 }
1513
1514 }
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526#include "minorGems/graphics/Image.h"
1527#include "minorGems/graphics/converters/TGAImageConverter.h"
1528#include "minorGems/io/file/File.h"
1529#include "minorGems/system/Time.h"
1530
1531void outputMapImage() {
1532
1533 // output a chunk of the map as an image
1534
1535 int w = 708;
1536 int h = 708;
1537
1538 Image objIm( w, h, 3, true );
1539 Image biomeIm( w, h, 3, true );
1540
1541 SimpleVector<Color> objColors;
1542 SimpleVector<Color> biomeColors;
1543 SimpleVector<int> objCounts;
1544
1545 int totalCounts = 0;
1546
1547 for( int i=0; i<allNaturalMapIDs.size(); i++ ) {
1548 Color *c = Color::makeColorFromHSV( (float)i / allNaturalMapIDs.size(),
1549 1, 1 );
1550 objColors.push_back( *c );
1551
1552 objCounts.push_back( 0 );
1553
1554 delete c;
1555 }
1556
1557 SimpleVector<int> biomeCounts;
1558 int totalBiomeCount = 0;
1559
1560 for( int j=0; j<numBiomes; j++ ) {
1561 biomeCounts.push_back( 0 );
1562
1563 Color *c;
1564
1565 int biomeNumber = biomes[j];
1566
1567 switch( biomeNumber ) {
1568 case 0:
1569 c = new Color( 0, 0.8, .1 );
1570 break;
1571 case 1:
1572 c = new Color( 0.4, 0.2, 0.7 );
1573 break;
1574 case 2:
1575 c = new Color( 1, .8, 0 );
1576 break;
1577 case 3:
1578 c = new Color( 0.6, 0.6, 0.6 );
1579 break;
1580 case 4:
1581 c = new Color( 1, 1, 1 );
1582 break;
1583 case 5:
1584 c = new Color( 0.7, 0.6, 0.0 );
1585 break;
1586 case 6:
1587 c = new Color( 0.0, 0.5, 0.0 );
1588 break;
1589 default:
1590 c = Color::makeColorFromHSV( (float)j / numBiomes, 1, 1 );
1591 }
1592
1593 biomeColors.push_back( *c );
1594 delete c;
1595 }
1596
1597 /*
1598 double startTime = Time::getCurrentTime();
1599 for( int y = 0; y<h; y++ ) {
1600
1601 for( int x = 0; x<w; x++ ) {
1602 // discard id output
1603 // just invoking this to time it
1604 getBaseMap( x, y );
1605 }
1606 }
1607
1608 printf( "Generating %d map spots took %f sec\n",
1609 w * h, Time::getCurrentTime() - startTime );
1610 //exit(0);
1611
1612 */
1613
1614
1615 for( int y = 0; y<h; y++ ) {
1616
1617 for( int x = 0; x<w; x++ ) {
1618
1619 /*
1620 // raw rand output and correlation test
1621 uint32_t xHit = xxTweakedHash2D( x, y );
1622 uint32_t yHit = xxTweakedHash2D( x+1, y+1 );
1623 //uint32_t xHit = getXYRandom_test( x, y );
1624 //uint32_t yHit = getXYRandom_test( x+1, y );
1625
1626 xHit = xHit % w;
1627 yHit = yHit % h;
1628
1629 double val = xxTweakedHash2D( x, y ) * oneOverIntMax;
1630
1631 Color c = im.getColor( yHit * w + xHit );
1632 c.r += 0.1;
1633 c.g += 0.1;
1634 c.b += 0.1;
1635
1636 im.setColor( yHit * w + xHit, c );
1637
1638 c.r = val;
1639 c.g = val;
1640 c.b = val;
1641
1642 //im.setColor( y * w + x, c );
1643 */
1644
1645
1646 int id = getBaseMap( x - h/2, - ( y - h/2 ) );
1647
1648 int biomeInd = getMapBiomeIndex( x - h/2, -( y - h/2 ) );
1649
1650 if( id > 0 ) {
1651 for( int i=0; i<allNaturalMapIDs.size(); i++ ) {
1652 if( allNaturalMapIDs.getElementDirect(i) == id ) {
1653 objIm.setColor( y * w + x,
1654 objColors.getElementDirect( i ) );
1655
1656 (* objCounts.getElement( i ) )++;
1657 totalCounts++;
1658 break;
1659 }
1660 }
1661 }
1662
1663 biomeIm.setColor( y * w + x,
1664 biomeColors.getElementDirect( biomeInd ) );
1665 ( *( biomeCounts.getElement( biomeInd ) ) ) ++;
1666 totalBiomeCount++;
1667 }
1668 }
1669
1670
1671 const char *biomeNames[] = { "Grass ",
1672 "Swamp ",
1673 "Yellow",
1674 "Gray ",
1675 "Snow ",
1676 "Desert",
1677 "Jungle",
1678 "Ocean"};
1679
1680 for( int j=0; j<numBiomes; j++ ) {
1681 const char *name = "unknwn";
1682
1683 if( biomes[j] < 7 ) {
1684 name = biomeNames[ biomes[j] ];
1685 }
1686 int c = biomeCounts.getElementDirect( j );
1687
1688 printf( "Biome %d (%s) \tcount = %d\t%.1f%%\n",
1689 biomes[j], name, c, 100 * (float)c / totalBiomeCount );
1690 }
1691
1692
1693
1694 for( int i=0; i<allNaturalMapIDs.size(); i++ ) {
1695 ObjectRecord *obj = getObject( allNaturalMapIDs.getElementDirect( i ) );
1696
1697 int count = objCounts.getElementDirect( i );
1698
1699 printf(
1700 "%d\t%-30s (actual=%f, expected=%f\n",
1701 objCounts.getElementDirect( i ),
1702 obj->description,
1703 count / (double)totalCounts,
1704 obj->mapChance / allNaturalMapIDs.size() );
1705 }
1706
1707 // rough legend in corner
1708 for( int i=0; i<allNaturalMapIDs.size(); i++ ) {
1709 if( i < h ) {
1710 objIm.setColor( i * w + 0, objColors.getElementDirect( i ) );
1711 }
1712 }
1713
1714
1715 File tgaFile( NULL, "mapOut.tga" );
1716 FileOutputStream tgaStream( &tgaFile );
1717
1718 TGAImageConverter converter;
1719
1720 converter.formatImage( &objIm, &tgaStream );
1721
1722
1723 File tgaBiomeFile( NULL, "mapBiomeOut.tga" );
1724 FileOutputStream tgaBiomeStream( &tgaBiomeFile );
1725
1726 converter.formatImage( &biomeIm, &tgaBiomeStream );
1727
1728 exit(0);
1729 }
1730
1731
1732
1733
1734void outputBiomeFractals() {
1735 for( int scale = 1; scale <=4; scale *= 2 ) {
1736
1737 for( int b=0; b<numBiomes; b++ ) {
1738 int biome = biomes[ b ];
1739
1740 setXYRandomSeed( biome * 263 + 723 );
1741
1742 int r = 100 * scale;
1743
1744 Image outIm( r * 2, r * 2, 4 );
1745
1746 for( int y=-r; y<r; y++ ) {
1747 for( int x=-r; x<r; x++ ) {
1748
1749 double v = getXYFractal( x,
1750 y,
1751 0.55,
1752 scale );
1753 Color c( v, v, v, 1 );
1754
1755 int imX = x + r;
1756 int imY = y + r;
1757
1758 outIm.setColor( imY * 2 * r + imX, c );
1759 }
1760 }
1761
1762 char *name = autoSprintf( "fractal_b%d_s%d.tga",
1763 biome, scale );
1764
1765 File tgaFile( NULL, name );
1766
1767 FileOutputStream tgaStream( &tgaFile );
1768
1769 TGAImageConverter converter;
1770
1771 converter.formatImage( &outIm, &tgaStream );
1772 printf( "Outputting file %s\n", name );
1773
1774 delete [] name;
1775 }
1776 }
1777 }
1778
1779
1780
1781
1782
1783
1784int *getContainedRaw( int inX, int inY, int *outNumContained,
1785 int inSubCont = 0 );
1786
1787void setMapObjectRaw( int inX, int inY, int inID );
1788
1789static void dbPut( int inX, int inY, int inSlot, int inValue,
1790 int inSubCont = 0 );
1791
1792
1793
1794
1795void writeRecentPlacements() {
1796 FILE *placeFile = fopen( "recentPlacements.txt", "w" );
1797 if( placeFile != NULL ) {
1798 for( int i=0; i<NUM_RECENT_PLACEMENTS; i++ ) {
1799 fprintf( placeFile, "%d,%d %d\n", recentPlacements[i].pos.x,
1800 recentPlacements[i].pos.y,
1801 recentPlacements[i].depth );
1802 }
1803 fprintf( placeFile, "nextPlacementIndex=%d\n", nextPlacementIndex );
1804
1805 fclose( placeFile );
1806 }
1807 }
1808
1809
1810
1811static void writeEveRadius() {
1812 FILE *eveRadFile = fopen( "eveRadius.txt", "w" );
1813 if( eveRadFile != NULL ) {
1814
1815 fprintf( eveRadFile, "%d", eveRadius );
1816
1817 fclose( eveRadFile );
1818 }
1819 }
1820
1821
1822
1823void doubleEveRadius() {
1824 if( eveRadius < 1024 ) {
1825 eveRadius *= 2;
1826 writeEveRadius();
1827 }
1828 }
1829
1830
1831
1832void resetEveRadius() {
1833 eveRadius = eveRadiusStart;
1834 writeEveRadius();
1835 }
1836
1837
1838
1839void clearRecentPlacements() {
1840 for( int i=0; i<NUM_RECENT_PLACEMENTS; i++ ) {
1841 recentPlacements[i].pos.x = 0;
1842 recentPlacements[i].pos.y = 0;
1843 recentPlacements[i].depth = 0;
1844 }
1845
1846 writeRecentPlacements();
1847 }
1848
1849
1850
1851
1852void printBiomeSamples() {
1853 int *biomeSamples = new int[ numBiomes ];
1854
1855 for( int i=0; i<numBiomes; i++ ) {
1856 biomeSamples[i] = 0;
1857 }
1858
1859 JenkinsRandomSource sampleRandSource;
1860
1861 int numSamples = 10000;
1862
1863 int range = 2000;
1864
1865 for( int i=0; i<numSamples; i++ ) {
1866 int x = sampleRandSource.getRandomBoundedInt( -range, range );
1867 int y = sampleRandSource.getRandomBoundedInt( -range, range );
1868
1869 biomeSamples[ computeMapBiomeIndex( x, y ) ] ++;
1870 }
1871
1872 for( int i=0; i<numBiomes; i++ ) {
1873 printf( "Biome %d: %d (%.2f)\n",
1874 biomes[ i ], biomeSamples[i],
1875 biomeSamples[i] / (double)numSamples );
1876 }
1877 }
1878
1879
1880
1881void printObjectSamples() {
1882 int objectToCount = 2285;
1883
1884 JenkinsRandomSource sampleRandSource;
1885
1886 int numSamples = 0;
1887
1888 int range = 500;
1889
1890 int count = 0;
1891
1892 for( int y=-range; y<range; y++ ) {
1893 for( int x=-range; x<range; x++ ) {
1894 int obj = getMapObjectRaw( x, y );
1895
1896
1897 if( obj == objectToCount ) {
1898 count++;
1899 }
1900 numSamples++;
1901 }
1902 }
1903
1904
1905 int rangeSize = (range + range ) * ( range + range );
1906
1907 float sampleFraction =
1908 numSamples /
1909 ( float ) rangeSize;
1910
1911 printf( "Counted %d objects in %d/%d samples, expect %d total\n",
1912 count, numSamples, rangeSize, (int)( count / sampleFraction ) );
1913 }
1914
1915
1916
1917
1918
1919// optimization:
1920// cache dbGet results in RAM
1921
1922// 2.6 MB of RAM for this.
1923#define DB_CACHE_SIZE 131072
1924
1925typedef struct DBCacheRecord {
1926 int x, y, slot, subCont;
1927 int value;
1928 } DBCacheRecord;
1929
1930static DBCacheRecord dbCache[ DB_CACHE_SIZE ];
1931
1932
1933
1934static int computeDBCacheHash( int inKeyA, int inKeyB,
1935 int inKeyC, int inKeyD ) {
1936
1937 int hashKey = ( inKeyA * CACHE_PRIME_A +
1938 inKeyB * CACHE_PRIME_B +
1939 inKeyC * CACHE_PRIME_C +
1940 inKeyD * CACHE_PRIME_D ) % DB_CACHE_SIZE;
1941 if( hashKey < 0 ) {
1942 hashKey += DB_CACHE_SIZE;
1943 }
1944 return hashKey;
1945 }
1946
1947
1948
1949
1950typedef struct DBTimeCacheRecord {
1951 int x, y, slot, subCont;
1952 timeSec_t timeVal;
1953 } DBTimeCacheRecord;
1954
1955static DBTimeCacheRecord dbTimeCache[ DB_CACHE_SIZE ];
1956
1957
1958
1959typedef struct BlockingCacheRecord {
1960 int x, y;
1961 // -1 if not present
1962 char blocking;
1963 } BlockingCacheRecord;
1964
1965static BlockingCacheRecord blockingCache[ DB_CACHE_SIZE ];
1966
1967
1968
1969
1970
1971static void initDBCaches() {
1972 DBCacheRecord blankRecord = { 0, 0, 0, 0, -2 };
1973 for( int i=0; i<DB_CACHE_SIZE; i++ ) {
1974 dbCache[i] = blankRecord;
1975 }
1976 // 1 for empty (because 0 is a valid value)
1977 DBTimeCacheRecord blankTimeRecord = { 0, 0, 0, 0, 1 };
1978 for( int i=0; i<DB_CACHE_SIZE; i++ ) {
1979 dbTimeCache[i] = blankTimeRecord;
1980 }
1981 // -1 for empty
1982 BlockingCacheRecord blankBlockingRecord = { 0, 0, -1 };
1983 for( int i=0; i<DB_CACHE_SIZE; i++ ) {
1984 blockingCache[i] = blankBlockingRecord;
1985 }
1986 }
1987
1988
1989
1990
1991// returns -2 on miss
1992static int dbGetCached( int inX, int inY, int inSlot, int inSubCont ) {
1993 DBCacheRecord r =
1994 dbCache[ computeDBCacheHash( inX, inY, inSlot, inSubCont ) ];
1995
1996 if( r.x == inX && r.y == inY &&
1997 r.slot == inSlot && r.subCont == inSubCont &&
1998 r.value != -2 ) {
1999 return r.value;
2000 }
2001 else {
2002 return -2;
2003 }
2004 }
2005
2006
2007
2008static void dbPutCached( int inX, int inY, int inSlot, int inSubCont,
2009 int inValue ) {
2010 DBCacheRecord r = { inX, inY, inSlot, inSubCont, inValue };
2011
2012 dbCache[ computeDBCacheHash( inX, inY, inSlot, inSubCont ) ] = r;
2013 }
2014
2015
2016
2017
2018
2019// returns 1 on miss
2020static int dbTimeGetCached( int inX, int inY, int inSlot, int inSubCont ) {
2021 DBTimeCacheRecord r =
2022 dbTimeCache[ computeDBCacheHash( inX, inY, inSlot, inSubCont ) ];
2023
2024 if( r.x == inX && r.y == inY &&
2025 r.slot == inSlot && r.subCont == inSubCont &&
2026 r.timeVal != 1 ) {
2027 return r.timeVal;
2028 }
2029 else {
2030 return 1;
2031 }
2032 }
2033
2034
2035
2036static void dbTimePutCached( int inX, int inY, int inSlot, int inSubCont,
2037 timeSec_t inValue ) {
2038 DBTimeCacheRecord r = { inX, inY, inSlot, inSubCont, inValue };
2039
2040 dbTimeCache[ computeDBCacheHash( inX, inY, inSlot, inSubCont ) ] = r;
2041 }
2042
2043
2044
2045
2046
2047// returns -1 on miss
2048static char blockingGetCached( int inX, int inY ) {
2049 BlockingCacheRecord r =
2050 blockingCache[ computeXYCacheHash( inX, inY ) ];
2051
2052 if( r.x == inX && r.y == inY &&
2053 r.blocking != -1 ) {
2054 return r.blocking;
2055 }
2056 else {
2057 return -1;
2058 }
2059 }
2060
2061
2062
2063static void blockingPutCached( int inX, int inY, char inBlocking ) {
2064 BlockingCacheRecord r = { inX, inY, inBlocking };
2065
2066 blockingCache[ computeXYCacheHash( inX, inY ) ] = r;
2067 }
2068
2069
2070static void blockingClearCached( int inX, int inY ) {
2071
2072 BlockingCacheRecord *r =
2073 &( blockingCache[ computeXYCacheHash( inX, inY ) ] );
2074
2075 if( r->x == inX && r->y == inY ) {
2076 r->blocking = -1;
2077 }
2078 }
2079
2080
2081
2082
2083
2084char lookTimeDBEmpty = false;
2085char skipLookTimeCleanup = 0;
2086char skipRemovedObjectCleanup = 0;
2087
2088// if lookTimeDBEmpty, then we init all map cell look times to NOW
2089int cellsLookedAtToInit = 0;
2090
2091
2092
2093// version of open call that checks whether look time exists in lookTimeDB
2094// for each record in opened DB, and clears any entries that are not
2095// rebuilding file storage for DB in the process
2096// lookTimeDB MUST be open before calling this
2097//
2098// If lookTimeDBEmpty, this call just opens the target DB normally without
2099// shrinking it.
2100//
2101// Can handle max key and value size of 16 and 12 bytes
2102// Assumes that first 8 bytes of key are xy as 32-bit ints
2103int DB_open_timeShrunk(
2104 DB *db,
2105 const char *path,
2106 int mode,
2107 unsigned long hash_table_size,
2108 unsigned long key_size,
2109 unsigned long value_size) {
2110
2111 File dbFile( NULL, path );
2112
2113 if( ! dbFile.exists() || lookTimeDBEmpty || skipLookTimeCleanup ) {
2114
2115 if( lookTimeDBEmpty ) {
2116 AppLog::infoF( "No lookTimes present, not cleaning %s", path );
2117 }
2118
2119 int error = DB_open( db,
2120 path,
2121 mode,
2122 hash_table_size,
2123 key_size,
2124 value_size );
2125
2126 if( ! error && ! skipLookTimeCleanup ) {
2127 // add look time for cells in this DB to present
2128 // essentially resetting all look times to NOW
2129
2130 DB_Iterator dbi;
2131
2132
2133 DB_Iterator_init( db, &dbi );
2134
2135 // key and value size that are big enough to handle all of our DB
2136 unsigned char key[16];
2137
2138 unsigned char value[12];
2139
2140 while( DB_Iterator_next( &dbi, key, value ) > 0 ) {
2141 int x = valueToInt( key );
2142 int y = valueToInt( &( key[4] ) );
2143
2144 cellsLookedAtToInit++;
2145
2146 dbLookTimePut( x, y, MAP_TIMESEC );
2147 }
2148 }
2149 return error;
2150 }
2151
2152 char *dbTempName = autoSprintf( "%s.temp", path );
2153 File dbTempFile( NULL, dbTempName );
2154
2155 if( dbTempFile.exists() ) {
2156 dbTempFile.remove();
2157 }
2158
2159 if( dbTempFile.exists() ) {
2160 AppLog::errorF( "Failed to remove temp DB file %s", dbTempName );
2161
2162 delete [] dbTempName;
2163
2164 return DB_open( db,
2165 path,
2166 mode,
2167 hash_table_size,
2168 key_size,
2169 value_size );
2170 }
2171
2172 DB oldDB;
2173
2174 int error = DB_open( &oldDB,
2175 path,
2176 mode,
2177 hash_table_size,
2178 key_size,
2179 value_size );
2180 if( error ) {
2181 AppLog::errorF( "Failed to open DB file %s in DB_open_timeShrunk",
2182 path );
2183 delete [] dbTempName;
2184
2185 return error;
2186 }
2187
2188
2189
2190
2191
2192
2193 DB_Iterator dbi;
2194
2195
2196 DB_Iterator_init( &oldDB, &dbi );
2197
2198 // key and value size that are big enough to handle all of our DB
2199 unsigned char key[16];
2200
2201 unsigned char value[12];
2202
2203 int total = 0;
2204 int stale = 0;
2205 int nonStale = 0;
2206
2207 // first, just count
2208 while( DB_Iterator_next( &dbi, key, value ) > 0 ) {
2209 total++;
2210
2211 int x = valueToInt( key );
2212 int y = valueToInt( &( key[4] ) );
2213
2214 if( dbLookTimeGet( x, y ) > 0 ) {
2215 // keep
2216 nonStale++;
2217 }
2218 else {
2219 // stale
2220 // ignore
2221 stale++;
2222 }
2223 }
2224
2225
2226
2227 // optimial size for DB of remaining elements
2228 unsigned int newSize = DB_getShrinkSize( &oldDB, nonStale );
2229
2230 AppLog::infoF( "Shrinking hash table in %s from %d down to %d",
2231 path,
2232 DB_getCurrentSize( &oldDB ),
2233 newSize );
2234
2235
2236 DB tempDB;
2237
2238 error = DB_open( &tempDB,
2239 dbTempName,
2240 mode,
2241 newSize,
2242 key_size,
2243 value_size );
2244 if( error ) {
2245 AppLog::errorF( "Failed to open DB file %s in DB_open_timeShrunk",
2246 dbTempName );
2247 delete [] dbTempName;
2248 DB_close( &oldDB );
2249 return error;
2250 }
2251
2252
2253 // now that we have new temp db properly sized,
2254 // iterate again and insert, but don't count
2255 DB_Iterator_init( &oldDB, &dbi );
2256
2257 while( DB_Iterator_next( &dbi, key, value ) > 0 ) {
2258 int x = valueToInt( key );
2259 int y = valueToInt( &( key[4] ) );
2260
2261 if( dbLookTimeGet( x, y ) > 0 ) {
2262 // keep
2263 // insert it in temp
2264 DB_put_new( &tempDB, key, value );
2265 }
2266 else {
2267 // stale
2268 // ignore
2269 }
2270 }
2271
2272
2273
2274 AppLog::infoF( "Cleaned %d / %d stale map cells from %s", stale, total,
2275 path );
2276
2277 printf( "\n" );
2278
2279
2280 DB_close( &tempDB );
2281 DB_close( &oldDB );
2282
2283 dbTempFile.copy( &dbFile );
2284 dbTempFile.remove();
2285
2286 delete [] dbTempName;
2287
2288 // now open new, shrunk file
2289 return DB_open( db,
2290 path,
2291 mode,
2292 hash_table_size,
2293 key_size,
2294 value_size );
2295 }
2296
2297
2298
2299int countNewlines( char *inString ) {
2300 int len = strlen( inString );
2301 int num = 0;
2302 for( int i=0; i<len; i++ ) {
2303 if( inString[i] == '\n' ) {
2304 num++;
2305 }
2306 }
2307 return num;
2308 }
2309
2310
2311
2312
2313#include "../gameSource/categoryBank.h"
2314
2315
2316// true if ID is a non-pattern category
2317char getIsCategory( int inID ) {
2318 CategoryRecord *r = getCategory( inID );
2319
2320 if( r == NULL ) {
2321 return false;
2322 }
2323 if( r->isPattern ) {
2324 return false;
2325 }
2326 return true;
2327 }
2328
2329
2330
2331// for large inserts, like tutorial map loads, we don't want to
2332// track individual map changes.
2333static char skipTrackingMapChanges = false;
2334
2335
2336
2337// returns num set after
2338int cleanMap() {
2339 AppLog::info( "\nCleaning map of objects that have been removed..." );
2340
2341 skipTrackingMapChanges = true;
2342
2343 DB_Iterator dbi;
2344
2345
2346 DB_Iterator_init( &db, &dbi );
2347
2348 unsigned char key[16];
2349
2350 unsigned char value[4];
2351
2352
2353 // keep list of x,y coordinates in map that need clearing
2354 SimpleVector<int> xToClear;
2355 SimpleVector<int> yToClear;
2356
2357 // container slots that need clearing
2358 SimpleVector<int> xContToCheck;
2359 SimpleVector<int> yContToCheck;
2360
2361 int totalDBRecordCount = 0;
2362
2363 int totalSetCount = 0;
2364 int numClearedCount = 0;
2365 int totalNumContained = 0;
2366 int numContainedCleared = 0;
2367
2368 while( DB_Iterator_next( &dbi, key, value ) > 0 ) {
2369 totalDBRecordCount++;
2370
2371 int s = valueToInt( &( key[8] ) );
2372 int b = valueToInt( &( key[12] ) );
2373
2374 if( s == 0 ) {
2375 int id = valueToInt( value );
2376
2377 if( id > 0 ) {
2378 totalSetCount++;
2379
2380 ObjectRecord *o = getObject( id );
2381
2382 if( o == NULL || getIsCategory( id )
2383 || o->description[0] == '@'
2384 || o->isOwned ) {
2385 // id doesn't exist anymore
2386
2387 // OR it's a non-pattern category
2388 // those should never exist in map
2389 // may be left over from a non-clean shutdown
2390
2391 // OR object is flagged with @
2392 // this may be a pattern category that is actually
2393 // a place-holder
2394
2395 // OR it's owned (no owned objects should be left
2396 // on map after server restarts... server must have
2397 // crashed and not shut down properly)
2398
2399 numClearedCount++;
2400
2401 int x = valueToInt( key );
2402 int y = valueToInt( &( key[4] ) );
2403
2404 xToClear.push_back( x );
2405 yToClear.push_back( y );
2406 }
2407 }
2408 }
2409 if( s == 2 && b == 0 ) {
2410 int numSlots = valueToInt( value );
2411 if( numSlots > 0 ) {
2412 totalNumContained += numSlots;
2413
2414 int x = valueToInt( key );
2415 int y = valueToInt( &( key[4] ) );
2416 xContToCheck.push_back( x );
2417 yContToCheck.push_back( y );
2418 }
2419 }
2420 }
2421
2422
2423 for( int i=0; i<xToClear.size(); i++ ) {
2424 int x = xToClear.getElementDirect( i );
2425 int y = yToClear.getElementDirect( i );
2426
2427 clearAllContained( x, y );
2428 setMapObject( x, y, 0 );
2429 }
2430
2431 for( int i=0; i<xContToCheck.size(); i++ ) {
2432 int x = xContToCheck.getElementDirect( i );
2433 int y = yContToCheck.getElementDirect( i );
2434
2435 if( getMapObjectRaw( x, y ) != 0 ) {
2436 int numCont;
2437 int *cont = getContainedRaw( x, y, &numCont );
2438 timeSec_t *decay = getContainedEtaDecay( x, y, &numCont );
2439
2440 SimpleVector<int> newCont;
2441 SimpleVector<timeSec_t> newDecay;
2442
2443 SimpleVector< SimpleVector<int> > newSubCont;
2444 SimpleVector< SimpleVector<timeSec_t> > newSubContDecay;
2445
2446 char anyRemoved = false;
2447
2448
2449 for( int c=0; c<numCont; c++ ) {
2450
2451 SimpleVector<int> subCont;
2452 SimpleVector<timeSec_t> subContDecay;
2453
2454
2455 char thisKept = false;
2456
2457 if( cont[c] < 0 ) {
2458
2459 ObjectRecord *o = getObject( - cont[c] );
2460
2461 if( o != NULL && ! getIsCategory( - cont[c] ) ) {
2462
2463 thisKept = true;
2464
2465 newCont.push_back( cont[c] );
2466 newDecay.push_back( decay[c] );
2467
2468 int numSub;
2469
2470 int *contSub =
2471 getContainedRaw( x, y, &numSub, c + 1 );
2472 timeSec_t *decaySub =
2473 getContainedEtaDecay( x, y, &numSub, c + 1 );
2474
2475 for( int s=0; s<numSub; s++ ) {
2476
2477 if( getObject( contSub[s] ) != NULL &&
2478 ! getIsCategory( contSub[s] ) ) {
2479
2480 subCont.push_back( contSub[s] );
2481 subContDecay.push_back( decaySub[s] );
2482 }
2483 else {
2484 anyRemoved = true;
2485 }
2486 }
2487
2488 if( contSub != NULL ) {
2489 delete [] contSub;
2490 }
2491 if( decaySub != NULL ) {
2492 delete [] decaySub;
2493 }
2494 numContainedCleared += numSub - subCont.size();
2495 }
2496 }
2497 else {
2498 ObjectRecord *o = getObject( cont[c] );
2499 if( o != NULL && ! getIsCategory( cont[c] ) ) {
2500
2501 thisKept = true;
2502 newCont.push_back( cont[c] );
2503 newDecay.push_back( decay[c] );
2504 }
2505 else {
2506 anyRemoved = true;
2507 }
2508 }
2509
2510 if( thisKept ) {
2511 newSubCont.push_back( subCont );
2512 newSubContDecay.push_back( subContDecay );
2513 }
2514 else {
2515 anyRemoved = true;
2516 }
2517 }
2518
2519
2520
2521 delete [] cont;
2522 delete [] decay;
2523
2524 if( anyRemoved ) {
2525
2526 numContainedCleared +=
2527 ( numCont - newCont.size() );
2528
2529 int *newContArray = newCont.getElementArray();
2530 timeSec_t *newDecayArray = newDecay.getElementArray();
2531
2532 setContained( x, y, newCont.size(), newContArray );
2533 setContainedEtaDecay( x, y, newDecay.size(), newDecayArray );
2534
2535 for( int c=0; c<newCont.size(); c++ ) {
2536 int numSub =
2537 newSubCont.getElementDirect( c ).size();
2538
2539 if( numSub > 0 ) {
2540 int *newSubArray =
2541 newSubCont.getElementDirect( c ).getElementArray();
2542 timeSec_t *newSubDecayArray =
2543 newSubContDecay.
2544 getElementDirect( c ).getElementArray();
2545
2546 setContained( x, y, numSub, newSubArray, c + 1 );
2547
2548 setContainedEtaDecay( x, y, numSub, newSubDecayArray,
2549 c + 1 );
2550
2551 delete [] newSubArray;
2552 delete [] newSubDecayArray;
2553 }
2554 else {
2555 clearAllContained( x, y, c + 1 );
2556 }
2557 }
2558
2559 delete [] newContArray;
2560 delete [] newDecayArray;
2561 }
2562 }
2563 }
2564
2565
2566 AppLog::infoF( "...%d map cells were set, and %d needed to be cleared.",
2567 totalSetCount, numClearedCount );
2568 AppLog::infoF(
2569 "...%d contained objects present, and %d needed to be cleared.",
2570 totalNumContained, numContainedCleared );
2571 AppLog::infoF( "...%d database records total (%d max hash bin depth).",
2572 totalDBRecordCount, DB_maxStack );
2573
2574 printf( "\n" );
2575
2576 skipTrackingMapChanges = false;
2577
2578 return totalSetCount;
2579 }
2580
2581
2582
2583
2584
2585
2586
2587
2588// reads lines from inFile until EOF reached or inTimeLimitSec passes
2589// leaves file pos at end of last line read, ready to read more lines
2590// on future calls
2591// returns true if there's more file to read, or false if end of file reached
2592static char loadIntoMapFromFile( FILE *inFile,
2593 int inOffsetX = 0,
2594 int inOffsetY = 0,
2595 double inTimeLimitSec = 0 ) {
2596
2597 skipTrackingMapChanges = true;
2598
2599
2600 double startTime = Time::getCurrentTime();
2601
2602 char moreFileLeft = true;
2603
2604 // break out when read fails
2605 // or if time limit passed
2606 while( inTimeLimitSec == 0 ||
2607 Time::getCurrentTime() < startTime + inTimeLimitSec ) {
2608
2609 TestMapRecord r;
2610
2611 char stringBuff[1000];
2612
2613 int numRead = fscanf( inFile, "%d %d %d %d %999s",
2614 &(r.x), &(r.y), &(r.biome),
2615 &(r.floor),
2616 stringBuff );
2617
2618 if( numRead != 5 ) {
2619 moreFileLeft = false;
2620 break;
2621 }
2622 r.x += inOffsetX;
2623 r.y += inOffsetY;
2624
2625 int numSlots;
2626
2627 char **slots = split( stringBuff, ",", &numSlots );
2628
2629 for( int i=0; i<numSlots; i++ ) {
2630
2631 if( i == 0 ) {
2632 r.id = atoi( slots[0] );
2633 }
2634 else {
2635
2636 int numSub;
2637 char **subSlots = split( slots[i], ":", &numSub );
2638
2639 for( int j=0; j<numSub; j++ ) {
2640 if( j == 0 ) {
2641 int contID = atoi( subSlots[0] );
2642
2643 if( numSub > 1 ) {
2644 contID *= -1;
2645 }
2646
2647 r.contained.push_back( contID );
2648 SimpleVector<int> subVec;
2649
2650 r.subContained.push_back( subVec );
2651 }
2652 else {
2653 SimpleVector<int> *subVec =
2654 r.subContained.getElement( i - 1 );
2655 subVec->push_back( atoi( subSlots[j] ) );
2656 }
2657 delete [] subSlots[j];
2658 }
2659 delete [] subSlots;
2660 }
2661
2662 delete [] slots[i];
2663 }
2664 delete [] slots;
2665
2666
2667
2668 // set all test map directly in database
2669 biomeDBPut( r.x, r.y, r.biome, r.biome, 0.5 );
2670
2671 dbFloorPut( r.x, r.y, r.floor );
2672
2673 setMapObject( r.x, r.y, r.id );
2674
2675 int *contArray = r.contained.getElementArray();
2676
2677 setContained( r.x, r.y, r.contained.size(), contArray );
2678 delete [] contArray;
2679
2680 for( int c=0; c<r.contained.size(); c++ ) {
2681
2682 int *subContArray =
2683 r.subContained.getElement( c )->getElementArray();
2684
2685 setContained( r.x, r.y,
2686 r.subContained.getElement(c)->size(),
2687 subContArray, c + 1 );
2688
2689 delete [] subContArray;
2690 }
2691 }
2692
2693 skipTrackingMapChanges = false;
2694
2695 return moreFileLeft;
2696 }
2697
2698
2699
2700static inline void changeContained( int inX, int inY, int inSlot,
2701 int inSubCont, int inID ) {
2702 dbPut( inX, inY, FIRST_CONT_SLOT + inSlot, inID, inSubCont );
2703 }
2704
2705
2706
2707typedef struct GlobalTriggerState {
2708 SimpleVector<GridPos> triggerOnLocations;
2709
2710 // receivers for this trigger that are waiting to be turned on
2711 SimpleVector<GridPos> receiverLocations;
2712
2713 SimpleVector<GridPos> triggeredLocations;
2714 SimpleVector<int> triggeredIDs;
2715 // what we revert to when global trigger turns off (back to receiver)
2716 SimpleVector<int> triggeredRevertIDs;
2717 } GlobalTriggerState;
2718
2719
2720
2721static SimpleVector<GlobalTriggerState> globalTriggerStates;
2722
2723
2724static int numSpeechPipes = 0;
2725
2726static SimpleVector<GridPos> *speechPipesIn = NULL;
2727
2728static SimpleVector<GridPos> *speechPipesOut = NULL;
2729
2730
2731
2732static SimpleVector<GridPos> flightLandingPos;
2733
2734
2735
2736static char isAdjacent( GridPos inPos, int inX, int inY ) {
2737 if( inX <= inPos.x + 1 &&
2738 inX >= inPos.x - 1 &&
2739 inY <= inPos.y + 1 &&
2740 inY >= inPos.y - 1 ) {
2741 return true;
2742 }
2743 return false;
2744 }
2745
2746
2747
2748void getSpeechPipesIn( int inX, int inY, SimpleVector<int> *outIndicies ) {
2749 for( int i=0; i<numSpeechPipes; i++ ) {
2750
2751 for( int p=0; p<speechPipesIn[ i ].size(); p++ ) {
2752
2753 GridPos inPos = speechPipesIn[i].getElementDirect( p );
2754 if( isAdjacent( inPos, inX, inY ) ) {
2755
2756 // make sure pipe-in is still here
2757 int id = getMapObjectRaw( inPos.x, inPos.y );
2758
2759 char stillHere = false;
2760
2761 if( id > 0 ) {
2762 ObjectRecord *oIn = getObject( id );
2763
2764 if( oIn->speechPipeIn &&
2765 oIn->speechPipeIndex == i ) {
2766 stillHere = true;
2767 }
2768 }
2769 if( ! stillHere ) {
2770 speechPipesIn[ i ].deleteElement( p );
2771 p--;
2772 }
2773 else {
2774 outIndicies->push_back( i );
2775 break;
2776 }
2777 }
2778 }
2779 }
2780 }
2781
2782
2783
2784SimpleVector<GridPos> *getSpeechPipesOut( int inIndex ) {
2785 // first, filter them to make sure they are all still here
2786 for( int p=0; p<speechPipesOut[ inIndex ].size(); p++ ) {
2787
2788 GridPos outPos = speechPipesOut[ inIndex ].getElementDirect( p );
2789 // make sure pipe-out is still here
2790 int id = getMapObjectRaw( outPos.x, outPos.y );
2791
2792 char stillHere = false;
2793
2794 if( id > 0 ) {
2795 ObjectRecord *oOut = getObject( id );
2796
2797 if( oOut->speechPipeOut &&
2798 oOut->speechPipeIndex == inIndex ) {
2799 stillHere = true;
2800 }
2801 }
2802 if( ! stillHere ) {
2803 speechPipesOut[ inIndex ].deleteElement( p );
2804 p--;
2805 }
2806 }
2807
2808 return &( speechPipesOut[ inIndex ] );
2809 }
2810
2811
2812
2813static void deleteFileByName( const char *inFileName ) {
2814 File f( NULL, inFileName );
2815
2816 if( f.exists() ) {
2817 f.remove();
2818 }
2819 }
2820
2821
2822
2823static void setupMapChangeLogFile() {
2824 File logFolder( NULL, "mapChangeLogs" );
2825
2826 if( ! logFolder.exists() ) {
2827 logFolder.makeDirectory();
2828 }
2829
2830
2831 if( mapChangeLogFile != NULL ) {
2832 fclose( mapChangeLogFile );
2833 mapChangeLogFile = NULL;
2834 }
2835
2836
2837 if( logFolder.isDirectory() ) {
2838
2839 char *biomeSeedString = autoSprintf( "%d", biomeRandSeed );
2840
2841 // does log file already exist?
2842
2843 int numFiles;
2844 File **childFiles = logFolder.getChildFiles( &numFiles );
2845
2846 for( int i=0; i<numFiles; i++ ) {
2847 File *f = childFiles[i];
2848
2849 char *name = f->getFileName();
2850
2851 if( strstr( name, biomeSeedString ) != NULL ) {
2852 // found!
2853 char *fullFileName = f->getFullFileName();
2854 mapChangeLogFile = fopen( fullFileName, "a" );
2855 delete [] fullFileName;
2856 }
2857 delete [] name;
2858 if( mapChangeLogFile != NULL ) {
2859 break;
2860 }
2861 }
2862 for( int i=0; i<numFiles; i++ ) {
2863 delete childFiles[i];
2864 }
2865 delete [] childFiles;
2866
2867 delete [] biomeSeedString;
2868
2869
2870 if( mapChangeLogFile == NULL ) {
2871
2872 // file does not exist
2873 char *newFileName = autoSprintf( "%.ftime_%useed_mapLog.txt",
2874 Time::getCurrentTime(),
2875 biomeRandSeed );
2876
2877 File *f = logFolder.getChildFile( newFileName );
2878
2879 char *fullName = f->getFullFileName();
2880
2881 delete f;
2882
2883 mapChangeLogFile = fopen( fullName, "a" );
2884 delete [] fullName;
2885 }
2886 }
2887
2888 mapChangeLogTimeStart = Time::getCurrentTime();
2889 fprintf( mapChangeLogFile, "startTime: %.2f\n", mapChangeLogTimeStart );
2890 }
2891
2892
2893
2894
2895void reseedMap( char inForceFresh ) {
2896
2897 FILE *seedFile = NULL;
2898
2899 if( ! inForceFresh ) {
2900 seedFile = fopen( "biomeRandSeed.txt", "r" );
2901 }
2902
2903 if( seedFile != NULL ) {
2904 fscanf( seedFile, "%d", &biomeRandSeed );
2905 fclose( seedFile );
2906 AppLog::infoF( "Reading map rand seed from file: %u\n", biomeRandSeed );
2907 }
2908 else {
2909 // no seed set, or ignoring it, make a new one
2910
2911 if( !inForceFresh ) {
2912 // not forced (not internal apocalypse)
2913 // seed file wiped externally, so it's like a manual apocalypse
2914 // report a fresh arc starting
2915 reportArcEnd();
2916 }
2917
2918 char *secret =
2919 SettingsManager::getStringSetting( "statsServerSharedSecret",
2920 "secret" );
2921
2922 unsigned int seedBase =
2923 crc32( (unsigned char*)secret, strlen( secret ) );
2924
2925 unsigned int modTimeSeed =
2926 (unsigned int)fmod( Time::getCurrentTime() + seedBase,
2927 4294967295U );
2928
2929 JenkinsRandomSource tempRandSource( modTimeSeed );
2930
2931 biomeRandSeed = tempRandSource.getRandomInt();
2932
2933 AppLog::infoF( "Generating fresh map rand seed and saving to file: "
2934 "%u\n", biomeRandSeed );
2935
2936 // and save it
2937 seedFile = fopen( "biomeRandSeed.txt", "w" );
2938 if( seedFile != NULL ) {
2939
2940 fprintf( seedFile, "%d", biomeRandSeed );
2941 fclose( seedFile );
2942 }
2943 }
2944 }
2945
2946
2947
2948
2949char initMap() {
2950
2951 reseedMap( false );
2952
2953
2954 numSpeechPipes = getMaxSpeechPipeIndex() + 1;
2955
2956 speechPipesIn = new SimpleVector<GridPos>[ numSpeechPipes ];
2957 speechPipesOut = new SimpleVector<GridPos>[ numSpeechPipes ];
2958
2959
2960 eveSecondaryLocObjectIDs.deleteAll();
2961 recentlyUsedPrimaryEvePositionTimes.deleteAll();
2962 recentlyUsedPrimaryEvePositions.deleteAll();
2963 recentlyUsedPrimaryEvePositionPlayerIDs.deleteAll();
2964
2965
2966 initDBCaches();
2967 initBiomeCache();
2968
2969 mapCacheClear();
2970
2971 edgeObjectID = SettingsManager::getIntSetting( "edgeObject", 0 );
2972
2973 minEveCampRespawnAge =
2974 SettingsManager::getFloatSetting( "minEveCampRespawnAge", 120.0f );
2975
2976
2977 barrierRadius = SettingsManager::getIntSetting( "barrierRadius", 250 );
2978 barrierOn = SettingsManager::getIntSetting( "barrierOn", 1 );
2979
2980 longTermCullEnabled =
2981 SettingsManager::getIntSetting( "longTermNoLookCullEnabled", 1 );
2982
2983
2984 SimpleVector<int> *list =
2985 SettingsManager::getIntSettingMulti( "barrierObjects" );
2986
2987 barrierItemList.deleteAll();
2988 barrierItemList.push_back_other( list );
2989 delete list;
2990
2991
2992
2993 for( int i=0; i<NUM_RECENT_PLACEMENTS; i++ ) {
2994 recentPlacements[i].pos.x = 0;
2995 recentPlacements[i].pos.y = 0;
2996 recentPlacements[i].depth = 0;
2997 }
2998
2999
3000 nextPlacementIndex = 0;
3001
3002 FILE *placeFile = fopen( "recentPlacements.txt", "r" );
3003 if( placeFile != NULL ) {
3004 for( int i=0; i<NUM_RECENT_PLACEMENTS; i++ ) {
3005 fscanf( placeFile, "%d,%d %d",
3006 &( recentPlacements[i].pos.x ),
3007 &( recentPlacements[i].pos.y ),
3008 &( recentPlacements[i].depth ) );
3009 }
3010 fscanf( placeFile, "\nnextPlacementIndex=%d", &nextPlacementIndex );
3011
3012 fclose( placeFile );
3013 }
3014
3015
3016 FILE *eveRadFile = fopen( "eveRadius.txt", "r" );
3017 if( eveRadFile != NULL ) {
3018
3019 fscanf( eveRadFile, "%d", &eveRadius );
3020
3021 fclose( eveRadFile );
3022 }
3023
3024 FILE *eveLocFile = fopen( "lastEveLocation.txt", "r" );
3025 if( eveLocFile != NULL ) {
3026
3027 fscanf( eveLocFile, "%d,%d", &( eveLocation.x ), &( eveLocation.y ) );
3028
3029 fclose( eveLocFile );
3030
3031 printf( "Loading lastEveLocation %d,%d\n",
3032 eveLocation.x, eveLocation.y );
3033 }
3034
3035 // override if shutdownLongLineagePos exists
3036 FILE *lineagePosFile = fopen( "shutdownLongLineagePos.txt", "r" );
3037 if( lineagePosFile != NULL ) {
3038
3039 fscanf( lineagePosFile, "%d,%d",
3040 &( eveLocation.x ), &( eveLocation.y ) );
3041
3042 fclose( lineagePosFile );
3043
3044 printf( "Overriding eveLocation with shutdownLongLineagePos %d,%d\n",
3045 eveLocation.x, eveLocation.y );
3046 }
3047 else {
3048 printf( "No shutdownLongLineagePos.txt file exists\n" );
3049
3050 // look for longest monument log file
3051 // that has been touched in last 24 hours
3052 // (ignore spots that may have been culled)
3053 File f( NULL, "monumentLogs" );
3054 if( f.exists() && f.isDirectory() ) {
3055 int numChildFiles;
3056 File **childFiles = f.getChildFiles( &numChildFiles );
3057
3058 timeSec_t longTime = 0;
3059 int longLen = 0;
3060 int longX = 0;
3061 int longY = 0;
3062
3063 timeSec_t curTime = Time::timeSec();
3064
3065 int secInDay = 3600 * 24;
3066
3067 for( int i=0; i<numChildFiles; i++ ) {
3068 timeSec_t modTime = childFiles[i]->getModificationTime();
3069
3070 if( curTime - modTime < secInDay ) {
3071 char *cont = childFiles[i]->readFileContents();
3072
3073 int numNewlines = countNewlines( cont );
3074
3075 delete [] cont;
3076
3077 if( numNewlines > longLen ||
3078 ( numNewlines == longLen && modTime > longTime ) ) {
3079
3080 char *name = childFiles[i]->getFileName();
3081
3082 int x, y;
3083 int numRead = sscanf( name, "%d_%d_",
3084 &x, &y );
3085
3086 delete [] name;
3087
3088 if( numRead == 2 ) {
3089 longTime = modTime;
3090 longLen = numNewlines;
3091 longX = x;
3092 longY = y;
3093 }
3094 }
3095 }
3096 delete childFiles[i];
3097 }
3098 delete [] childFiles;
3099
3100 if( longLen > 0 ) {
3101 eveLocation.x = longX;
3102 eveLocation.y = longY;
3103
3104 printf( "Overriding eveLocation with "
3105 "tallest recent monument location %d,%d\n",
3106 eveLocation.x, eveLocation.y );
3107 }
3108 }
3109 }
3110
3111
3112
3113
3114
3115
3116 const char *lookTimeDBName = "lookTime.db";
3117
3118 char lookTimeDBExists = false;
3119
3120 File lookTimeDBFile( NULL, lookTimeDBName );
3121
3122
3123
3124 if( lookTimeDBFile.exists() &&
3125 SettingsManager::getIntSetting( "flushLookTimes", 0 ) ) {
3126
3127 AppLog::info( "flushLookTimes.ini set, deleting lookTime.db" );
3128
3129 lookTimeDBFile.remove();
3130 }
3131
3132
3133
3134 lookTimeDBExists = lookTimeDBFile.exists();
3135
3136 if( ! lookTimeDBExists ) {
3137 lookTimeDBEmpty = true;
3138 }
3139
3140
3141 skipLookTimeCleanup =
3142 SettingsManager::getIntSetting( "skipLookTimeCleanup", 0 );
3143
3144
3145 if( skipLookTimeCleanup ) {
3146 AppLog::info( "skipLookTimeCleanup.ini flag set, "
3147 "not cleaning databases based on stale look times." );
3148 }
3149
3150 LINEARDB3_setMaxLoad( 0.80 );
3151
3152 if( ! skipLookTimeCleanup ) {
3153 DB lookTimeDB_old;
3154
3155 int error = DB_open( &lookTimeDB_old,
3156 lookTimeDBName,
3157 KISSDB_OPEN_MODE_RWCREAT,
3158 80000,
3159 8, // two 32-bit ints, xy
3160 8 // one 64-bit double, representing an ETA time
3161 // in whatever binary format and byte order
3162 // "double" on the server platform uses
3163 );
3164
3165 if( error ) {
3166 AppLog::errorF( "Error %d opening look time KissDB", error );
3167 return false;
3168 }
3169
3170
3171 int staleSec =
3172 SettingsManager::getIntSetting( "mapCellForgottenSeconds", 0 );
3173
3174 if( lookTimeDBExists && staleSec > 0 ) {
3175 AppLog::info( "\nCleaning stale look times from map..." );
3176
3177
3178 static DB lookTimeDB_temp;
3179
3180 const char *lookTimeDBName_temp = "lookTime_temp.db";
3181
3182 File tempDBFile( NULL, lookTimeDBName_temp );
3183
3184 if( tempDBFile.exists() ) {
3185 tempDBFile.remove();
3186 }
3187
3188
3189
3190 DB_Iterator dbi;
3191
3192
3193 DB_Iterator_init( &lookTimeDB_old, &dbi );
3194
3195
3196 timeSec_t curTime = MAP_TIMESEC;
3197
3198 unsigned char key[8];
3199 unsigned char value[8];
3200
3201 int total = 0;
3202 int stale = 0;
3203 int nonStale = 0;
3204
3205 // first, just count them
3206 while( DB_Iterator_next( &dbi, key, value ) > 0 ) {
3207 total++;
3208
3209 timeSec_t t = valueToTime( value );
3210
3211 if( curTime - t >= staleSec ) {
3212 // stale cell
3213 // ignore
3214 stale++;
3215 }
3216 else {
3217 // non-stale
3218 nonStale++;
3219 }
3220 }
3221
3222 // optimial size for DB of remaining elements
3223 unsigned int newSize = DB_getShrinkSize( &lookTimeDB_old,
3224 nonStale );
3225
3226 AppLog::infoF( "Shrinking hash table for lookTimes from "
3227 "%d down to %d",
3228 DB_getCurrentSize( &lookTimeDB_old ),
3229 newSize );
3230
3231
3232 error = DB_open( &lookTimeDB_temp,
3233 lookTimeDBName_temp,
3234 KISSDB_OPEN_MODE_RWCREAT,
3235 newSize,
3236 8, // two 32-bit ints, xy
3237 8 // one 64-bit double, representing an ETA time
3238 // in whatever binary format and byte order
3239 // "double" on the server platform uses
3240 );
3241
3242 if( error ) {
3243 AppLog::errorF(
3244 "Error %d opening look time temp KissDB", error );
3245 return false;
3246 }
3247
3248
3249 // now that we have new temp db properly sized,
3250 // iterate again and insert
3251 DB_Iterator_init( &lookTimeDB_old, &dbi );
3252
3253 while( DB_Iterator_next( &dbi, key, value ) > 0 ) {
3254 timeSec_t t = valueToTime( value );
3255
3256 if( curTime - t >= staleSec ) {
3257 // stale cell
3258 // ignore
3259 }
3260 else {
3261 // non-stale
3262 // insert it in temp
3263 DB_put_new( &lookTimeDB_temp, key, value );
3264 }
3265 }
3266
3267
3268 AppLog::infoF( "Cleaned %d / %d stale look times", stale, total );
3269
3270 printf( "\n" );
3271
3272 if( total == 0 ) {
3273 lookTimeDBEmpty = true;
3274 }
3275
3276 DB_close( &lookTimeDB_temp );
3277 DB_close( &lookTimeDB_old );
3278
3279 tempDBFile.copy( &lookTimeDBFile );
3280 tempDBFile.remove();
3281 }
3282 else {
3283 DB_close( &lookTimeDB_old );
3284 }
3285 }
3286
3287
3288 int error = DB_open( &lookTimeDB,
3289 lookTimeDBName,
3290 KISSDB_OPEN_MODE_RWCREAT,
3291 80000,
3292 8, // two 32-bit ints, xy
3293 8 // one 64-bit double, representing an ETA time
3294 // in whatever binary format and byte order
3295 // "double" on the server platform uses
3296 );
3297
3298 if( error ) {
3299 AppLog::errorF( "Error %d opening look time KissDB", error );
3300 return false;
3301 }
3302
3303 lookTimeDBOpen = true;
3304
3305
3306
3307
3308 // note that the various decay ETA slots in map.db
3309 // are define but unused, because we store times separately
3310 // in mapTime.db
3311 error = DB_open_timeShrunk( &db,
3312 "map.db",
3313 KISSDB_OPEN_MODE_RWCREAT,
3314 80000,
3315 16, // four 32-bit ints, xysb
3316 // s is the slot number
3317 // s=0 for base object
3318 // s=1 decay ETA seconds (wall clock time)
3319 // s=2 for count of contained objects
3320 // s=3 first contained object
3321 // s=4 second contained object
3322 // s=... remaining contained objects
3323 // Then decay ETA for each slot, in order,
3324 // after that.
3325 // s = -1
3326 // is a special flag slot set to 0 if NONE
3327 // of the contained items have ETA decay
3328 // or 1 if some of the contained items might
3329 // have ETA decay.
3330 // (this saves us from having to check each
3331 // one)
3332 // If a contained object id is negative,
3333 // that indicates that it sub-contains
3334 // other objects in its corresponding b slot
3335 //
3336 // b is for indexing sub-container slots
3337 // b=0 is the main object
3338 // b=1 is the first sub-slot, etc.
3339 4 // one int, object ID at x,y in slot (s-3)
3340 // OR contained count if s=2
3341 );
3342
3343 if( error ) {
3344 AppLog::errorF( "Error %d opening map KissDB", error );
3345 return false;
3346 }
3347
3348 dbOpen = true;
3349
3350
3351
3352 // this DB uses the same slot numbers as the map.db
3353 // however, only times are stored here, because they require 8 bytes
3354 // so, slot 0 and 2 are never used, for example
3355 error = DB_open_timeShrunk( &timeDB,
3356 "mapTime.db",
3357 KISSDB_OPEN_MODE_RWCREAT,
3358 80000,
3359 16, // four 32-bit ints, xysb
3360 // s is the slot number
3361 // s=0 for base object
3362 // s=1 decay ETA seconds (wall clock time)
3363 // s=2 for count of contained objects
3364 // s=3 first contained object
3365 // s=4 second contained object
3366 // s=... remaining contained objects
3367 // Then decay ETA for each slot, in order,
3368 // after that.
3369 // If a contained object id is negative,
3370 // that indicates that it sub-contains
3371 // other objects in its corresponding b slot
3372 //
3373 // b is for indexing sub-container slots
3374 // b=0 is the main object
3375 // b=1 is the first sub-slot, etc.
3376 8 // one 64-bit double, representing an ETA time
3377 // in whatever binary format and byte order
3378 // "double" on the server platform uses
3379 );
3380
3381 if( error ) {
3382 AppLog::errorF( "Error %d opening map time KissDB", error );
3383 return false;
3384 }
3385
3386 timeDBOpen = true;
3387
3388
3389
3390
3391
3392
3393 error = DB_open_timeShrunk( &biomeDB,
3394 "biome.db",
3395 KISSDB_OPEN_MODE_RWCREAT,
3396 80000,
3397 8, // two 32-bit ints, xy
3398 12 // three ints,
3399 // 1: biome number at x,y
3400 // 2: second place biome number at x,y
3401 // 3: second place biome gap as int (float gap
3402 // multiplied by 1,000,000)
3403 );
3404
3405 if( error ) {
3406 AppLog::errorF( "Error %d opening biome KissDB", error );
3407 return false;
3408 }
3409
3410 biomeDBOpen = true;
3411
3412
3413
3414 // see if any biomes are listed in DB
3415 // if not, we don't even need to check it when generating map
3416 DB_Iterator biomeDBi;
3417 DB_Iterator_init( &biomeDB, &biomeDBi );
3418
3419 unsigned char biomeKey[8];
3420 unsigned char biomeValue[12];
3421
3422
3423 while( DB_Iterator_next( &biomeDBi, biomeKey, biomeValue ) > 0 ) {
3424 int x = valueToInt( biomeKey );
3425 int y = valueToInt( &( biomeKey[4] ) );
3426
3427 anyBiomesInDB = true;
3428
3429 if( x > maxBiomeXLoc ) {
3430 maxBiomeXLoc = x;
3431 }
3432 if( x < minBiomeXLoc ) {
3433 minBiomeXLoc = x;
3434 }
3435 if( y > maxBiomeYLoc ) {
3436 maxBiomeYLoc = y;
3437 }
3438 if( y < minBiomeYLoc ) {
3439 minBiomeYLoc = y;
3440 }
3441 }
3442
3443 printf( "Min (x,y) of biome in db = (%d,%d), "
3444 "Max (x,y) of biome in db = (%d,%d)\n",
3445 minBiomeXLoc, minBiomeYLoc,
3446 maxBiomeXLoc, maxBiomeYLoc );
3447
3448
3449
3450
3451 error = DB_open_timeShrunk( &floorDB,
3452 "floor.db",
3453 KISSDB_OPEN_MODE_RWCREAT,
3454 80000,
3455 8, // two 32-bit ints, xy
3456 4 // one int, the floor object ID at x,y
3457 );
3458
3459 if( error ) {
3460 AppLog::errorF( "Error %d opening floor KissDB", error );
3461 return false;
3462 }
3463
3464 floorDBOpen = true;
3465
3466
3467
3468 error = DB_open_timeShrunk( &floorTimeDB,
3469 "floorTime.db",
3470 KISSDB_OPEN_MODE_RWCREAT,
3471 80000,
3472 8, // two 32-bit ints, xy
3473 8 // one 64-bit double, representing an ETA time
3474 // in whatever binary format and byte order
3475 // "double" on the server platform uses
3476 );
3477
3478 if( error ) {
3479 AppLog::errorF( "Error %d opening floor time KissDB", error );
3480 return false;
3481 }
3482
3483 floorTimeDBOpen = true;
3484
3485
3486
3487 // ALWAYS delete old grave DB at each server startup
3488 // grave info is only player ID, and server only remembers players
3489 // live, in RAM, while it is still running
3490 deleteFileByName( "grave.db" );
3491
3492
3493 error = DB_open( &graveDB,
3494 "grave.db",
3495 KISSDB_OPEN_MODE_RWCREAT,
3496 80000,
3497 8, // two 32-bit ints, xy
3498 4 // one int, the grave player ID at x,y
3499 );
3500
3501 if( error ) {
3502 AppLog::errorF( "Error %d opening grave KissDB", error );
3503 return false;
3504 }
3505
3506 graveDBOpen = true;
3507
3508
3509
3510
3511
3512 error = DB_open( &eveDB,
3513 "eve.db",
3514 KISSDB_OPEN_MODE_RWCREAT,
3515 // this can be a lot smaller than other DBs
3516 // it's not performance-critical, and the keys are
3517 // much longer, so stackdb will waste disk space
3518 5000,
3519 50, // first 50 characters of email address
3520 // append spaces to the end if needed
3521 12 // three ints, x_center, y_center, radius
3522 );
3523
3524 if( error ) {
3525 AppLog::errorF( "Error %d opening eve KissDB", error );
3526 return false;
3527 }
3528
3529 eveDBOpen = true;
3530
3531
3532
3533
3534 error = DB_open( &metaDB,
3535 "meta.db",
3536 KISSDB_OPEN_MODE_RWCREAT,
3537 // starting size doesn't matter here
3538 500,
3539 4, // one 32-bit int as key
3540 // data
3541 MAP_METADATA_LENGTH
3542 );
3543
3544 if( error ) {
3545 AppLog::errorF( "Error %d opening meta KissDB", error );
3546 return false;
3547 }
3548
3549 metaDBOpen = true;
3550
3551 DB_Iterator metaIterator;
3552
3553 DB_Iterator_init( &metaDB, &metaIterator );
3554
3555 unsigned char metaKey[4];
3556
3557 unsigned char metaValue[MAP_METADATA_LENGTH];
3558
3559 int maxMetaID = 0;
3560 int numMetaRecords = 0;
3561
3562 while( DB_Iterator_next( &metaIterator, metaKey, metaValue ) > 0 ) {
3563 numMetaRecords++;
3564
3565 int metaID = valueToInt( metaKey );
3566
3567 if( metaID > maxMetaID ) {
3568 maxMetaID = metaID;
3569 }
3570 }
3571
3572 AppLog::infoF(
3573 "MetadataDB: Found %d records with max MetadataID of %d",
3574 numMetaRecords, maxMetaID );
3575
3576 setLastMetadataID( maxMetaID );
3577
3578
3579
3580
3581
3582
3583 if( lookTimeDBEmpty && cellsLookedAtToInit > 0 ) {
3584 printf( "Since lookTime db was empty, we initialized look times "
3585 "for %d cells to now.\n\n", cellsLookedAtToInit );
3586 }
3587
3588
3589
3590 int numObjects;
3591 ObjectRecord **allObjects = getAllObjects( &numObjects );
3592
3593
3594 // first, find all biomes
3595 SimpleVector<int> biomeList;
3596
3597
3598 for( int i=0; i<numObjects; i++ ) {
3599 ObjectRecord *o = allObjects[i];
3600
3601 if( o->mapChance > 0 ) {
3602
3603 for( int j=0; j< o->numBiomes; j++ ) {
3604 int b = o->biomes[j];
3605
3606 if( biomeList.getElementIndex(b) == -1 ) {
3607 biomeList.push_back( b );
3608 }
3609 }
3610 }
3611
3612 }
3613
3614
3615 // manually controll order
3616 SimpleVector<int> *biomeOrderList =
3617 SettingsManager::getIntSettingMulti( "biomeOrder" );
3618
3619 SimpleVector<float> *biomeWeightList =
3620 SettingsManager::getFloatSettingMulti( "biomeWeights" );
3621
3622 for( int i=0; i<biomeOrderList->size(); i++ ) {
3623 int b = biomeOrderList->getElementDirect( i );
3624
3625 if( biomeList.getElementIndex( b ) == -1 ) {
3626 biomeOrderList->deleteElement( i );
3627 biomeWeightList->deleteElement( i );
3628 i--;
3629 }
3630 }
3631
3632 // now add any discovered biomes to end of list
3633 for( int i=0; i<biomeList.size(); i++ ) {
3634 int b = biomeList.getElementDirect( i );
3635 if( biomeOrderList->getElementIndex( b ) == -1 ) {
3636 biomeOrderList->push_back( b );
3637 // default weight
3638 biomeWeightList->push_back( 0.1 );
3639 }
3640 }
3641
3642 numBiomes = biomeOrderList->size();
3643 biomes = biomeOrderList->getElementArray();
3644 biomeWeights = biomeWeightList->getElementArray();
3645 biomeCumuWeights = new float[ numBiomes ];
3646
3647 biomeTotalWeight = 0;
3648 for( int i=0; i<numBiomes; i++ ) {
3649 biomeTotalWeight += biomeWeights[i];
3650 biomeCumuWeights[i] = biomeTotalWeight;
3651 }
3652
3653 delete biomeOrderList;
3654 delete biomeWeightList;
3655
3656
3657 SimpleVector<int> *specialBiomeList =
3658 SettingsManager::getIntSettingMulti( "specialBiomes" );
3659
3660 numSpecialBiomes = specialBiomeList->size();
3661 specialBiomes = specialBiomeList->getElementArray();
3662
3663 regularBiomeLimit = numBiomes - numSpecialBiomes;
3664
3665 delete specialBiomeList;
3666
3667 specialBiomeCumuWeights = new float[ numSpecialBiomes ];
3668
3669 specialBiomeTotalWeight = 0;
3670 for( int i=regularBiomeLimit; i<numBiomes; i++ ) {
3671 specialBiomeTotalWeight += biomeWeights[i];
3672 specialBiomeCumuWeights[i-regularBiomeLimit] = specialBiomeTotalWeight;
3673 }
3674
3675
3676
3677
3678 naturalMapIDs = new SimpleVector<int>[ numBiomes ];
3679 naturalMapChances = new SimpleVector<float>[ numBiomes ];
3680 totalChanceWeight = new float[ numBiomes ];
3681
3682 for( int j=0; j<numBiomes; j++ ) {
3683 totalChanceWeight[j] = 0;
3684 }
3685
3686
3687 CustomRandomSource phaseRandSource( randSeed );
3688
3689
3690 for( int i=0; i<numObjects; i++ ) {
3691 ObjectRecord *o = allObjects[i];
3692
3693 if( strstr( o->description, "eveSecondaryLoc" ) != NULL ) {
3694 eveSecondaryLocObjectIDs.push_back( o->id );
3695 }
3696 if( strstr( o->description, "eveHomeMarker" ) != NULL ) {
3697 eveHomeMarkerObjectID = o->id;
3698 }
3699
3700
3701
3702 float p = o->mapChance;
3703 if( p > 0 ) {
3704 int id = o->id;
3705
3706 allNaturalMapIDs.push_back( id );
3707
3708 char *gridPlacementLoc =
3709 strstr( o->description, "gridPlacement" );
3710
3711 if( gridPlacementLoc != NULL ) {
3712 // special grid placement
3713
3714 int spacing = 10;
3715 sscanf( gridPlacementLoc, "gridPlacement%d", &spacing );
3716
3717 if( strstr( o->description, "evePrimaryLoc" ) != NULL ) {
3718 evePrimaryLocObjectID = id;
3719 evePrimaryLocSpacing = spacing;
3720 }
3721
3722 SimpleVector<int> permittedBiomes;
3723 for( int b=0; b<o->numBiomes; b++ ) {
3724 permittedBiomes.push_back(
3725 getBiomeIndex( o->biomes[ b ] ) );
3726 }
3727
3728 int wiggleScale = 4;
3729
3730 if( spacing > 12 ) {
3731 wiggleScale = spacing / 3;
3732 }
3733
3734 MapGridPlacement gp =
3735 { id, spacing,
3736 0,
3737 //phaseRandSource.getRandomBoundedInt( 0,
3738 // spacing - 1 ),
3739 wiggleScale,
3740 permittedBiomes };
3741
3742 gridPlacements.push_back( gp );
3743 }
3744 else {
3745 // regular fractal placement
3746
3747 for( int j=0; j< o->numBiomes; j++ ) {
3748 int b = o->biomes[j];
3749
3750 int bIndex = getBiomeIndex( b );
3751 naturalMapIDs[bIndex].push_back( id );
3752 naturalMapChances[bIndex].push_back( p );
3753
3754 totalChanceWeight[bIndex] += p;
3755 }
3756 }
3757 }
3758 }
3759
3760
3761 for( int j=0; j<numBiomes; j++ ) {
3762 AppLog::infoF(
3763 "Biome %d: Found %d natural objects with total weight %f",
3764 biomes[j], naturalMapIDs[j].size(), totalChanceWeight[j] );
3765 }
3766
3767 delete [] allObjects;
3768
3769
3770 skipRemovedObjectCleanup =
3771 SettingsManager::getIntSetting( "skipRemovedObjectCleanup", 0 );
3772
3773
3774
3775
3776
3777 FILE *dummyFile = fopen( "mapDummyRecall.txt", "r" );
3778
3779 if( dummyFile != NULL ) {
3780 AppLog::info( "Found mapDummyRecall.txt file, restoring dummy objects "
3781 "on map" );
3782
3783 skipTrackingMapChanges = true;
3784
3785 int numRead = 5;
3786
3787 int numSet = 0;
3788
3789 int numStale = 0;
3790
3791 while( numRead == 5 || numRead == 7 ) {
3792
3793 int x, y, parentID, dummyIndex, slot, b;
3794
3795 char marker;
3796
3797 slot = -1;
3798 b = 0;
3799
3800 numRead = fscanf( dummyFile, "(%d,%d) %c %d %d [%d %d]\n",
3801 &x, &y, &marker, &parentID, &dummyIndex,
3802 &slot, &b );
3803 if( numRead == 5 || numRead == 7 ) {
3804
3805 if( dbLookTimeGet( x, y ) <= 0 ) {
3806 // stale area of map
3807 numStale++;
3808 continue;
3809 }
3810
3811 ObjectRecord *parent = getObject( parentID );
3812
3813 int dummyID = -1;
3814
3815 if( parent != NULL ) {
3816
3817 if( marker == 'u' && parent->numUses-1 > dummyIndex ) {
3818 dummyID = parent->useDummyIDs[ dummyIndex ];
3819 }
3820 else if( marker == 'v' &&
3821 parent->numVariableDummyIDs > dummyIndex ) {
3822 dummyID = parent->variableDummyIDs[ dummyIndex ];
3823 }
3824 }
3825 if( dummyID > 0 ) {
3826 if( numRead == 5 ) {
3827 setMapObjectRaw( x, y, dummyID );
3828 }
3829 else {
3830 changeContained( x, y, slot, b, dummyID );
3831 }
3832 numSet++;
3833 }
3834 }
3835 }
3836 skipTrackingMapChanges = false;
3837
3838 fclose( dummyFile );
3839
3840
3841 AppLog::infoF( "Restored %d dummy objects to map "
3842 "(%d skipped as stale)", numSet, numStale );
3843
3844 remove( "mapDummyRecall.txt" );
3845
3846 printf( "\n" );
3847 }
3848
3849
3850
3851 // clean map after restoring dummy objects
3852 int totalSetCount = 1;
3853
3854 if( ! skipRemovedObjectCleanup ) {
3855 totalSetCount = cleanMap();
3856 }
3857 else {
3858 AppLog::info( "Skipping cleaning map of removed objects" );
3859 }
3860
3861
3862
3863
3864 if( totalSetCount == 0 ) {
3865 // map has been cleared
3866
3867 // ignore old value for placements
3868 clearRecentPlacements();
3869 }
3870
3871
3872
3873 globalTriggerStates.deleteAll();
3874
3875 int numTriggers = getNumGlobalTriggers();
3876 for( int i=0; i<numTriggers; i++ ) {
3877 GlobalTriggerState s;
3878 globalTriggerStates.push_back( s );
3879 }
3880
3881
3882
3883 useTestMap = SettingsManager::getIntSetting( "useTestMap", 0 );
3884
3885
3886 if( useTestMap ) {
3887
3888 FILE *testMapFile = fopen( "testMap.txt", "r" );
3889 FILE *testMapStaleFile = fopen( "testMapStale.txt", "r" );
3890
3891 if( testMapFile != NULL && testMapStaleFile == NULL ) {
3892
3893 testMapStaleFile = fopen( "testMapStale.txt", "w" );
3894
3895 if( testMapStaleFile != NULL ) {
3896 fprintf( testMapStaleFile, "1" );
3897 fclose( testMapStaleFile );
3898 testMapStaleFile = NULL;
3899 }
3900
3901 printf( "Loading testMap.txt\n" );
3902
3903 loadIntoMapFromFile( testMapFile );
3904
3905 fclose( testMapFile );
3906 testMapFile = NULL;
3907 }
3908
3909 if( testMapFile != NULL ) {
3910 fclose( testMapFile );
3911 }
3912 if( testMapStaleFile != NULL ) {
3913 fclose( testMapStaleFile );
3914 }
3915 }
3916
3917
3918 SimpleVector<char*> *specialPlacements =
3919 SettingsManager::getSetting( "specialMapPlacements" );
3920
3921 if( specialPlacements != NULL ) {
3922
3923 for( int i=0; i<specialPlacements->size(); i++ ) {
3924 char *line = specialPlacements->getElementDirect( i );
3925
3926 int x, y, id;
3927 id = -1;
3928 int numRead = sscanf( line, "%d_%d_%d", &x, &y, &id );
3929
3930 if( numRead == 3 && id > -1 ) {
3931
3932 }
3933 setMapObject( x, y, id );
3934 }
3935
3936
3937 specialPlacements->deallocateStringElements();
3938 delete specialPlacements;
3939 }
3940
3941
3942
3943
3944
3945
3946 // for debugging the map
3947 // printObjectSamples();
3948 // printBiomeSamples();
3949 //outputMapImage();
3950
3951 //outputBiomeFractals();
3952
3953
3954 setupMapChangeLogFile();
3955
3956 return true;
3957 }
3958
3959
3960
3961
3962
3963
3964
3965void freeAndNullString( char **inStringPointer ) {
3966 if( *inStringPointer != NULL ) {
3967 delete [] *inStringPointer;
3968 *inStringPointer = NULL;
3969 }
3970 }
3971
3972
3973
3974static void rememberDummy( FILE *inFile, int inX, int inY,
3975 ObjectRecord *inDummyO,
3976 int inSlot = -1, int inB = 0 ) {
3977
3978 if( inFile == NULL ) {
3979 return;
3980 }
3981
3982 int parent = -1;
3983 int dummyIndex = -1;
3984
3985 char marker = 'x';
3986
3987 if( inDummyO->isUseDummy ) {
3988 marker = 'u';
3989
3990 parent = inDummyO->useDummyParent;
3991 ObjectRecord *parentO = getObject( parent );
3992
3993 if( parentO != NULL ) {
3994 for( int i=0; i<parentO->numUses - 1; i++ ) {
3995 if( parentO->useDummyIDs[i] == inDummyO->id ) {
3996 dummyIndex = i;
3997 break;
3998 }
3999 }
4000 }
4001 }
4002 else if( inDummyO->isVariableDummy ) {
4003 marker = 'v';
4004
4005 parent = inDummyO->variableDummyParent;
4006 ObjectRecord *parentO = getObject( parent );
4007
4008 if( parentO != NULL ) {
4009 for( int i=0; i<parentO->numVariableDummyIDs; i++ ) {
4010 if( parentO->variableDummyIDs[i] == inDummyO->id ) {
4011 dummyIndex = i;
4012 break;
4013 }
4014 }
4015 }
4016 }
4017
4018 if( parent > 0 && dummyIndex >= 0 ) {
4019 if( inSlot == -1 && inB == 0 ) {
4020 fprintf( inFile, "(%d,%d) %c %d %d\n",
4021 inX, inY,
4022 marker, parent, dummyIndex );
4023 }
4024 else {
4025 fprintf( inFile, "(%d,%d) %c %d %d [%d %d]\n",
4026 inX, inY,
4027 marker, parent, dummyIndex, inSlot, inB );
4028 }
4029 }
4030 }
4031
4032
4033
4034void freeMap( char inSkipCleanup ) {
4035 if( mapChangeLogFile != NULL ) {
4036 fclose( mapChangeLogFile );
4037 mapChangeLogFile = NULL;
4038 }
4039
4040 printf( "%d calls to getBaseMap\n", getBaseMapCallCount );
4041
4042 skipTrackingMapChanges = true;
4043
4044 if( lookTimeDBOpen ) {
4045 DB_close( &lookTimeDB );
4046 lookTimeDBOpen = false;
4047 }
4048
4049
4050 if( dbOpen && ! inSkipCleanup ) {
4051
4052 AppLog::infoF( "Cleaning up map database on server shutdown." );
4053
4054 // iterate through DB and look for useDummy objects
4055 // replace them with unused version object
4056 // useDummy objects aren't real objects in objectBank,
4057 // and their IDs may change in the future, so they're
4058 // not safe to store in the map between server runs.
4059
4060 DB_Iterator dbi;
4061
4062
4063 DB_Iterator_init( &db, &dbi );
4064
4065 unsigned char key[16];
4066
4067 unsigned char value[4];
4068
4069
4070 // keep list of x,y coordinates in map that need replacing
4071 SimpleVector<int> xToPlace;
4072 SimpleVector<int> yToPlace;
4073
4074 SimpleVector<int> idToPlace;
4075
4076
4077 // container slots that need replacing
4078 SimpleVector<int> xContToCheck;
4079 SimpleVector<int> yContToCheck;
4080 SimpleVector<int> bContToCheck;
4081
4082
4083 int skipUseDummyCleanup =
4084 SettingsManager::getIntSetting( "skipUseDummyCleanup", 0 );
4085
4086
4087
4088
4089 if( !skipUseDummyCleanup ) {
4090
4091 FILE *dummyFile = fopen( "mapDummyRecall.txt", "w" );
4092
4093 while( DB_Iterator_next( &dbi, key, value ) > 0 ) {
4094
4095 int s = valueToInt( &( key[8] ) );
4096 int b = valueToInt( &( key[12] ) );
4097
4098 if( s == 0 ) {
4099 int id = valueToInt( value );
4100
4101 if( id > 0 ) {
4102
4103 ObjectRecord *mapO = getObject( id );
4104
4105
4106 if( mapO != NULL ) {
4107 if( mapO->isUseDummy ) {
4108 int x = valueToInt( key );
4109 int y = valueToInt( &( key[4] ) );
4110
4111 xToPlace.push_back( x );
4112 yToPlace.push_back( y );
4113 idToPlace.push_back( mapO->useDummyParent );
4114
4115 rememberDummy( dummyFile, x, y, mapO );
4116 }
4117 else if( mapO->isVariableDummy ) {
4118 int x = valueToInt( key );
4119 int y = valueToInt( &( key[4] ) );
4120
4121 xToPlace.push_back( x );
4122 yToPlace.push_back( y );
4123 idToPlace.push_back(
4124 mapO->variableDummyParent );
4125
4126 rememberDummy( dummyFile, x, y, mapO );
4127 }
4128 }
4129 }
4130 }
4131 else if( s == 2 ) {
4132 int numSlots = valueToInt( value );
4133 if( numSlots > 0 ) {
4134 int x = valueToInt( key );
4135 int y = valueToInt( &( key[4] ) );
4136 xContToCheck.push_back( x );
4137 yContToCheck.push_back( y );
4138 bContToCheck.push_back( b );
4139 }
4140 }
4141 }
4142
4143
4144 for( int i=0; i<xToPlace.size(); i++ ) {
4145 int x = xToPlace.getElementDirect( i );
4146 int y = yToPlace.getElementDirect( i );
4147
4148 setMapObjectRaw( x, y, idToPlace.getElementDirect( i ) );
4149 }
4150
4151
4152 int numContChanged = 0;
4153
4154 for( int i=0; i<xContToCheck.size(); i++ ) {
4155 int x = xContToCheck.getElementDirect( i );
4156 int y = yContToCheck.getElementDirect( i );
4157 int b = bContToCheck.getElementDirect( i );
4158
4159 if( getMapObjectRaw( x, y ) != 0 ) {
4160
4161 int numCont;
4162 int *cont = getContainedRaw( x, y, &numCont, b );
4163
4164 char anyChanged = false;
4165
4166 for( int c=0; c<numCont; c++ ) {
4167
4168 char subCont = false;
4169
4170 if( cont[c] < 0 ) {
4171 cont[c] *= -1;
4172 subCont = true;
4173 }
4174
4175 ObjectRecord *contObj = getObject( cont[c] );
4176
4177 if( contObj != NULL ) {
4178 if( contObj->isUseDummy ) {
4179 cont[c] = contObj->useDummyParent;
4180 rememberDummy( dummyFile, x, y, contObj, c, b );
4181
4182 anyChanged = true;
4183 numContChanged ++;
4184 }
4185 else if( contObj->isVariableDummy ) {
4186 cont[c] = contObj->variableDummyParent;
4187 rememberDummy( dummyFile, x, y, contObj, c, b );
4188
4189 anyChanged = true;
4190 numContChanged ++;
4191 }
4192 }
4193
4194 if( subCont ) {
4195 cont[c] *= -1;
4196 }
4197 }
4198
4199 if( anyChanged ) {
4200 setContained( x, y, numCont, cont, b );
4201 }
4202
4203 delete [] cont;
4204 }
4205 }
4206
4207 if( dummyFile != NULL ) {
4208 fclose( dummyFile );
4209 }
4210
4211 AppLog::infoF(
4212 "...%d useDummy/variable objects present that were changed "
4213 "back into their unused parent.",
4214 xToPlace.size() );
4215 AppLog::infoF(
4216 "...%d contained useDummy/variable objects present and changed "
4217 "back to usused parent.",
4218 numContChanged );
4219 }
4220 else {
4221 AppLog::info( "Skipping use dummy cleanup." );
4222 }
4223
4224 printf( "\n" );
4225
4226 if( ! skipRemovedObjectCleanup ) {
4227 AppLog::info( "Now running normal map clean..." );
4228 cleanMap();
4229 }
4230 else {
4231 AppLog::info( "Skipping running normal map clean." );
4232 }
4233
4234
4235 DB_close( &db );
4236 dbOpen = false;
4237 }
4238 else if( dbOpen ) {
4239 // just close with no cleanup
4240 DB_close( &db );
4241 dbOpen = false;
4242 }
4243
4244 if( timeDBOpen ) {
4245 DB_close( &timeDB );
4246 timeDBOpen = false;
4247 }
4248
4249 if( biomeDBOpen ) {
4250 DB_close( &biomeDB );
4251 biomeDBOpen = false;
4252 }
4253
4254
4255 if( floorDBOpen ) {
4256 DB_close( &floorDB );
4257 floorDBOpen = false;
4258 }
4259
4260 if( floorTimeDBOpen ) {
4261 DB_close( &floorTimeDB );
4262 floorTimeDBOpen = false;
4263 }
4264
4265
4266 if( graveDBOpen ) {
4267 DB_close( &graveDB );
4268 graveDBOpen = false;
4269 }
4270
4271
4272 if( eveDBOpen ) {
4273 DB_close( &eveDB );
4274 eveDBOpen = false;
4275 }
4276
4277 if( metaDBOpen ) {
4278 DB_close( &metaDB );
4279 metaDBOpen = false;
4280 }
4281
4282
4283 writeEveRadius();
4284 writeRecentPlacements();
4285
4286 delete [] biomes;
4287 delete [] biomeWeights;
4288 delete [] biomeCumuWeights;
4289 delete [] specialBiomes;
4290 delete [] specialBiomeCumuWeights;
4291
4292 delete [] naturalMapIDs;
4293 delete [] naturalMapChances;
4294 delete [] totalChanceWeight;
4295
4296
4297 allNaturalMapIDs.deleteAll();
4298
4299 liveDecayQueue.clear();
4300 liveDecayRecordPresentHashTable.clear();
4301 liveDecayRecordLastLookTimeHashTable.clear();
4302 liveMovementEtaTimes.clear();
4303
4304 liveMovements.clear();
4305
4306 mapChangePosSinceLastStep.deleteAll();
4307
4308 skipTrackingMapChanges = false;
4309
4310
4311 delete [] speechPipesIn;
4312 delete [] speechPipesOut;
4313
4314 speechPipesIn = NULL;
4315 speechPipesOut = NULL;
4316
4317 flightLandingPos.deleteAll();
4318 }
4319
4320
4321
4322
4323
4324
4325
4326void wipeMapFiles() {
4327 deleteFileByName( "biome.db" );
4328 deleteFileByName( "eve.db" );
4329 deleteFileByName( "floor.db" );
4330 deleteFileByName( "floorTime.db" );
4331 deleteFileByName( "grave.db" );
4332 deleteFileByName( "lookTime.db" );
4333 deleteFileByName( "map.db" );
4334 deleteFileByName( "mapTime.db" );
4335 deleteFileByName( "playerStats.db" );
4336 deleteFileByName( "meta.db" );
4337 }
4338
4339
4340
4341
4342
4343
4344
4345// returns -1 if not found
4346static int dbGet( int inX, int inY, int inSlot, int inSubCont = 0 ) {
4347
4348 int cachedVal = dbGetCached( inX, inY, inSlot, inSubCont );
4349 if( cachedVal != -2 ) {
4350
4351 return cachedVal;
4352 }
4353
4354
4355 unsigned char key[16];
4356 unsigned char value[4];
4357
4358 // look for changes to default in database
4359 intQuadToKey( inX, inY, inSlot, inSubCont, key );
4360
4361 int result = DB_get( &db, key, value );
4362
4363
4364
4365 int returnVal;
4366
4367 if( result == 0 ) {
4368 // found
4369 returnVal = valueToInt( value );
4370 }
4371 else {
4372 returnVal = -1;
4373 }
4374
4375 dbPutCached( inX, inY, inSlot, inSubCont, returnVal );
4376
4377 return returnVal;
4378 }
4379
4380
4381
4382
4383// returns 0 if not found
4384static timeSec_t dbTimeGet( int inX, int inY, int inSlot, int inSubCont = 0 ) {
4385
4386 timeSec_t cachedVal = dbTimeGetCached( inX, inY, inSlot, inSubCont );
4387 if( cachedVal != 1 ) {
4388
4389 return cachedVal;
4390 }
4391
4392
4393 unsigned char key[16];
4394 unsigned char value[8];
4395
4396 // look for changes to default in database
4397 intQuadToKey( inX, inY, inSlot, inSubCont, key );
4398
4399 int result = DB_get( &timeDB, key, value );
4400
4401 timeSec_t timeVal;
4402
4403 if( result == 0 ) {
4404 // found
4405 timeVal = valueToTime( value );
4406 }
4407 else {
4408 timeVal = 0;
4409 }
4410
4411 dbTimePutCached( inX, inY, inSlot, inSubCont, timeVal );
4412
4413 return timeVal;
4414 }
4415
4416
4417
4418static int dbFloorGet( int inX, int inY ) {
4419 unsigned char key[9];
4420 unsigned char value[4];
4421
4422 // look for changes to default in database
4423 intPairToKey( inX, inY, key );
4424
4425 int result = DB_get( &floorDB, key, value );
4426
4427 if( result == 0 ) {
4428 // found
4429 int returnVal = valueToInt( value );
4430
4431 return returnVal;
4432 }
4433 else {
4434 return -1;
4435 }
4436 }
4437
4438
4439
4440// returns 0 if not found
4441static timeSec_t dbFloorTimeGet( int inX, int inY ) {
4442 unsigned char key[8];
4443 unsigned char value[8];
4444
4445 intPairToKey( inX, inY, key );
4446
4447 int result = DB_get( &floorTimeDB, key, value );
4448
4449 if( result == 0 ) {
4450 // found
4451 return valueToTime( value );
4452 }
4453 else {
4454 return 0;
4455 }
4456 }
4457
4458
4459
4460// returns 0 if not found
4461timeSec_t dbLookTimeGet( int inX, int inY ) {
4462 unsigned char key[8];
4463 unsigned char value[8];
4464
4465 intPairToKey( inX/100, inY/100, key );
4466
4467 int result = DB_get( &lookTimeDB, key, value );
4468
4469 if( result == 0 ) {
4470 // found
4471 return valueToTime( value );
4472 }
4473 else {
4474 return 0;
4475 }
4476 }
4477
4478
4479
4480
4481static void dbPut( int inX, int inY, int inSlot, int inValue,
4482 int inSubCont ) {
4483
4484 if( inSlot == 0 && inSubCont == 0 ) {
4485 // object has changed
4486 // clear blocking cache
4487 blockingClearCached( inX, inY );
4488 }
4489
4490
4491 if( ! skipTrackingMapChanges ) {
4492
4493 // count all slot changes as changes, because we're storing
4494 // time in a separate database now (so we don't need to worry
4495 // about time changes being reported as map changes)
4496
4497 char found = false;
4498 for( int i=0; i<mapChangePosSinceLastStep.size(); i++ ) {
4499
4500 ChangePosition *p = mapChangePosSinceLastStep.getElement( i );
4501
4502 if( p->x == inX && p->y == inY ) {
4503 found = true;
4504
4505 // update it
4506 p->responsiblePlayerID = currentResponsiblePlayer;
4507 break;
4508 }
4509 }
4510
4511 if( ! found ) {
4512 ChangePosition p = { inX, inY, false, currentResponsiblePlayer,
4513 0, 0, 0.0 };
4514 mapChangePosSinceLastStep.push_back( p );
4515 }
4516 }
4517
4518
4519
4520 if( apocalypsePossible && inValue > 0 && inSlot == 0 && inSubCont == 0 ) {
4521 // a primary tile put
4522 // check if this triggers the apocalypse
4523 if( isApocalypseTrigger( inValue ) ) {
4524 apocalypseTriggered = true;
4525 apocalypseLocation.x = inX;
4526 apocalypseLocation.y = inY;
4527 }
4528 }
4529 if( inValue > 0 && inSlot == 0 && inSubCont == 0 ) {
4530
4531 int status = getMonumentStatus( inValue );
4532
4533 if( status > 0 ) {
4534 int player = currentResponsiblePlayer;
4535 if( player < 0 ) {
4536 player = -player;
4537 }
4538 monumentAction( inX, inY, inValue, player,
4539 status );
4540 }
4541 }
4542
4543
4544
4545 unsigned char key[16];
4546 unsigned char value[4];
4547
4548
4549 intQuadToKey( inX, inY, inSlot, inSubCont, key );
4550 intToValue( inValue, value );
4551
4552
4553 DB_put( &db, key, value );
4554
4555 dbPutCached( inX, inY, inSlot, inSubCont, inValue );
4556 }
4557
4558
4559
4560
4561
4562static void dbTimePut( int inX, int inY, int inSlot, timeSec_t inTime,
4563 int inSubCont = 0 ) {
4564 // ETA decay changes don't get reported as map changes
4565
4566 unsigned char key[16];
4567 unsigned char value[8];
4568
4569
4570 intQuadToKey( inX, inY, inSlot, inSubCont, key );
4571 timeToValue( inTime, value );
4572
4573
4574 DB_put( &timeDB, key, value );
4575
4576 dbTimePutCached( inX, inY, inSlot, inSubCont, inTime );
4577 }
4578
4579
4580
4581
4582static void dbFloorPut( int inX, int inY, int inValue ) {
4583
4584
4585 if( ! skipTrackingMapChanges ) {
4586
4587 char found = false;
4588 for( int i=0; i<mapChangePosSinceLastStep.size(); i++ ) {
4589
4590 ChangePosition *p = mapChangePosSinceLastStep.getElement( i );
4591
4592 if( p->x == inX && p->y == inY ) {
4593 found = true;
4594
4595 // update it
4596 p->responsiblePlayerID = currentResponsiblePlayer;
4597 break;
4598 }
4599 }
4600
4601 if( ! found ) {
4602 ChangePosition p = { inX, inY, false, currentResponsiblePlayer,
4603 0, 0, 0.0 };
4604 mapChangePosSinceLastStep.push_back( p );
4605 }
4606 }
4607
4608
4609 unsigned char key[8];
4610 unsigned char value[4];
4611
4612
4613 intPairToKey( inX, inY, key );
4614 intToValue( inValue, value );
4615
4616
4617 DB_put( &floorDB, key, value );
4618 }
4619
4620
4621
4622static void dbFloorTimePut( int inX, int inY, timeSec_t inTime ) {
4623 // ETA decay changes don't get reported as map changes
4624
4625 unsigned char key[8];
4626 unsigned char value[8];
4627
4628
4629 intPairToKey( inX, inY, key );
4630 timeToValue( inTime, value );
4631
4632
4633 DB_put( &floorTimeDB, key, value );
4634 }
4635
4636
4637
4638void dbLookTimePut( int inX, int inY, timeSec_t inTime ) {
4639 if( !lookTimeDBOpen ) return;
4640
4641 unsigned char key[8];
4642 unsigned char value[8];
4643
4644
4645 intPairToKey( inX/100, inY/100, key );
4646 timeToValue( inTime, value );
4647
4648
4649 DB_put( &lookTimeDB, key, value );
4650 }
4651
4652
4653
4654// certain types of movement transitions should always be live
4655// tracked, even when out of view (NSEW moves, for human-made traveling objects
4656// for example)
4657static char isDecayTransAlwaysLiveTracked( TransRecord *inTrans ) {
4658 if( inTrans != NULL &&
4659 inTrans->move >=4 && inTrans->move <= 7 ) {
4660
4661 return true;
4662 }
4663
4664 return false;
4665 }
4666
4667
4668
4669// slot is 0 for main map cell, or higher for container slots
4670static void trackETA( int inX, int inY, int inSlot, timeSec_t inETA,
4671 int inSubCont = 0,
4672 TransRecord *inApplicableTrans = NULL ) {
4673
4674 timeSec_t timeLeft = inETA - MAP_TIMESEC;
4675
4676 if( timeLeft < maxSecondsForActiveDecayTracking ) {
4677 // track it live
4678
4679 // duplicates okay
4680 // we'll deal with them when they ripen
4681 // (we check the true ETA stored in map before acting
4682 // on one stored in this queue)
4683 LiveDecayRecord r = { inX, inY, inSlot, inETA, inSubCont,
4684 inApplicableTrans };
4685
4686 char exists;
4687 timeSec_t existingETA =
4688 liveDecayRecordPresentHashTable.lookup( inX, inY, inSlot,
4689 inSubCont,
4690 &exists );
4691
4692 if( !exists || existingETA != inETA ) {
4693
4694 liveDecayQueue.insert( r, inETA );
4695
4696 liveDecayRecordPresentHashTable.insert( inX, inY, inSlot,
4697 inSubCont, inETA );
4698
4699 char exists;
4700
4701 liveDecayRecordLastLookTimeHashTable.lookup( inX, inY, inSlot,
4702 inSubCont,
4703 &exists );
4704
4705 if( !exists ) {
4706 // don't overwrite old one
4707 liveDecayRecordLastLookTimeHashTable.insert( inX, inY, inSlot,
4708 inSubCont,
4709 MAP_TIMESEC );
4710 if( inSlot > 0 || inSubCont > 0 ) {
4711
4712 ContRecord *oldCount =
4713 liveDecayRecordLastLookTimeMaxContainedHashTable.
4714 lookupPointer( inX, inY, 0, 0 );
4715
4716 if( oldCount != NULL ) {
4717 // update if needed
4718 if( oldCount->maxSlots < inSlot ) {
4719 oldCount->maxSlots = inSlot;
4720 }
4721 if( oldCount->maxSubSlots < inSubCont ) {
4722 oldCount->maxSubSlots = inSubCont;
4723 }
4724 }
4725 else {
4726 // insert new
4727 ContRecord r = { inSlot, inSubCont };
4728
4729 liveDecayRecordLastLookTimeMaxContainedHashTable.
4730 insert( inX, inY, 0, 0, r );
4731 }
4732 }
4733 }
4734 }
4735 }
4736 }
4737
4738
4739
4740
4741
4742float getMapContainerTimeStretch( int inX, int inY, int inSubCont=0 ) {
4743
4744 float stretch = 1.0f;
4745
4746 int containerID;
4747
4748 if( inSubCont == 0 ) {
4749 containerID = getMapObjectRaw( inX, inY );
4750 }
4751 else {
4752 containerID = getContained( inX, inY, inSubCont - 1 );
4753 }
4754
4755 if( containerID < 0 ) {
4756 containerID *= -1;
4757 }
4758
4759 if( containerID != 0 ) {
4760 stretch = getObject( containerID )->slotTimeStretch;
4761 }
4762 return stretch;
4763 }
4764
4765
4766
4767void checkDecayContained( int inX, int inY, int inSubCont = 0 );
4768
4769
4770
4771
4772
4773
4774
4775int *getContainedRaw( int inX, int inY, int *outNumContained,
4776 int inSubCont ) {
4777 int num = getNumContained( inX, inY, inSubCont );
4778
4779 *outNumContained = num;
4780
4781 if( num == 0 ) {
4782 return NULL;
4783 }
4784
4785 int *contained = new int[ num ];
4786
4787 int trueNum = 0;
4788
4789 for( int i=0; i<num; i++ ) {
4790 int result = dbGet( inX, inY, FIRST_CONT_SLOT + i, inSubCont );
4791 if( result == -1 ) {
4792 result = 0;
4793 }
4794 if( result != 0 ) {
4795 contained[trueNum] = result;
4796 trueNum++;
4797 }
4798 }
4799
4800 *outNumContained = trueNum;
4801
4802 if( trueNum < num ) {
4803 // fix filled count permanently in DB
4804 dbPut( inX, inY, NUM_CONT_SLOT, trueNum, inSubCont );
4805 }
4806
4807 if( trueNum > 0 ) {
4808 return contained;
4809 }
4810 else {
4811 delete [] contained;
4812 return NULL;
4813 }
4814 }
4815
4816
4817
4818// returns true if no contained items will decay
4819char getSlotItemsNoDecay( int inX, int inY, int inSubCont ) {
4820 int result = dbGet( inX, inY, NO_DECAY_SLOT, inSubCont );
4821
4822 if( result != -1 ) {
4823 // found
4824 return ( result == 0 );
4825 }
4826 else {
4827 // default, some may decay
4828 return false;
4829 }
4830 }
4831
4832
4833void setSlotItemsNoDecay( int inX, int inY, int inSubCont, char inNoDecay ) {
4834 int val = 1;
4835 if( inNoDecay ) {
4836 val = 0;
4837 }
4838 dbPut( inX, inY, NO_DECAY_SLOT, val, inSubCont );
4839 }
4840
4841
4842
4843
4844int *getContained( int inX, int inY, int *outNumContained, int inSubCont ) {
4845 if( ! getSlotItemsNoDecay( inX, inY, inSubCont ) ) {
4846 checkDecayContained( inX, inY, inSubCont );
4847 }
4848 int *result = getContainedRaw( inX, inY, outNumContained, inSubCont );
4849
4850 // look at these slots if they are subject to live decay
4851 timeSec_t currentTime = MAP_TIMESEC;
4852
4853 for( int i=0; i<*outNumContained; i++ ) {
4854 timeSec_t *oldLookTime =
4855 liveDecayRecordLastLookTimeHashTable.lookupPointer( inX, inY,
4856 i + 1,
4857 inSubCont );
4858 if( oldLookTime != NULL ) {
4859 // look at it now
4860 *oldLookTime = currentTime;
4861 }
4862 }
4863
4864 return result;
4865 }
4866
4867
4868int *getContainedNoLook( int inX, int inY, int *outNumContained,
4869 int inSubCont = 0 ) {
4870 if( ! getSlotItemsNoDecay( inX, inY, inSubCont ) ) {
4871 checkDecayContained( inX, inY, inSubCont );
4872 }
4873 return getContainedRaw( inX, inY, outNumContained, inSubCont );
4874 }
4875
4876
4877
4878// gets DB slot number where a given container slot's decay time is stored
4879// if inNumContained is -1, it will be looked up in database
4880static int getContainerDecaySlot( int inX, int inY, int inSlot,
4881 int inSubCont = 0,
4882 int inNumContained = -1 ) {
4883 if( inNumContained == -1 ) {
4884 inNumContained = getNumContained( inX, inY, inSubCont );
4885 }
4886
4887 return FIRST_CONT_SLOT + inNumContained + inSlot;
4888 }
4889
4890
4891
4892
4893
4894timeSec_t *getContainedEtaDecay( int inX, int inY, int *outNumContained,
4895 int inSubCont ) {
4896 int num = getNumContained( inX, inY, inSubCont );
4897
4898 *outNumContained = num;
4899
4900 if( num == 0 ) {
4901 return NULL;
4902 }
4903
4904 timeSec_t *containedEta = new timeSec_t[ num ];
4905
4906 for( int i=0; i<num; i++ ) {
4907 // can be 0 if not found, which is okay
4908 containedEta[i] = dbTimeGet( inX, inY,
4909 getContainerDecaySlot( inX, inY, i,
4910 inSubCont, num ),
4911 inSubCont );
4912 }
4913 return containedEta;
4914 }
4915
4916
4917
4918int checkDecayObject( int inX, int inY, int inID ) {
4919 if( inID == 0 ) {
4920 return inID;
4921 }
4922
4923 TransRecord *t = getPTrans( -1, inID );
4924
4925 if( t == NULL ) {
4926 // no auto-decay for this object
4927 return inID;
4928 }
4929
4930
4931 // else decay exists for this object
4932
4933 int newID = inID;
4934 int movingObjID = newID;
4935 int leftBehindID = 0;
4936
4937 // in case of movement
4938 int newX = inX;
4939 int newY = inY;
4940
4941
4942 // is eta stored in map?
4943 timeSec_t mapETA = getEtaDecay( inX, inY );
4944
4945 if( mapETA != 0 ) {
4946
4947 if( (int)mapETA <= MAP_TIMESEC ) {
4948
4949 // object in map has decayed (eta expired)
4950
4951 // apply the transition
4952 newID = t->newTarget;
4953 movingObjID = newID;
4954
4955 int oldSlots = getNumContainerSlots( inID );
4956
4957 int newSlots = getNumContainerSlots( newID );
4958
4959 if( newSlots < oldSlots ) {
4960 shrinkContainer( inX, inY, newSlots );
4961 }
4962 if( newSlots > 0 ) {
4963 restretchMapContainedDecays( inX, inY, inID, newID );
4964 }
4965
4966
4967
4968 if( t->move != 0 ) {
4969 // moving
4970 doublePair dir = { 0, 0 };
4971
4972 TransRecord *destTrans = NULL;
4973
4974 int desiredMoveDist = t->desiredMoveDist;
4975
4976 char stayInBiome = false;
4977
4978
4979 if( t->move < 3 ) {
4980
4981 GridPos p = getClosestPlayerPos( inX, inY );
4982
4983 double dX = (double)p.x - (double)inX;
4984 double dY = (double)p.y - (double)inY;
4985
4986 double dist = sqrt( dX * dX + dY * dY );
4987
4988 if( dist <= 7 &&
4989 ( p.x != 0 || p.y != 0 ) ) {
4990
4991 if( t->move == 1 && dist <= desiredMoveDist ) {
4992 // chase. Try to land exactly on them
4993 // if they're close enough to do it in one move
4994 desiredMoveDist = lrint( dist );
4995 }
4996
4997 dir.x = dX;
4998 dir.y = dY;
4999 dir = normalize( dir );
5000
5001 // round to one of 8 cardinal directions
5002
5003 double a = angle( dir );
5004
5005 a = 2 * M_PI *
5006 lrint( ( a / ( 2 * M_PI ) ) * 8 ) /
5007 8.0;
5008
5009 dir.x = 1;
5010 dir.y = 0;
5011 dir = rotate( dir, a );
5012 }
5013 if( t->move == 2 ) {
5014 // flee
5015 stayInBiome = true;
5016 dir = mult( dir, -1 );
5017 }
5018 }
5019 else if( t->move > 3 ) {
5020 // NSEW
5021
5022 switch( t->move ) {
5023 case 4:
5024 dir.y = 1;
5025 break;
5026 case 5:
5027 dir.y = -1;
5028 break;
5029 case 6:
5030 dir.x = 1;
5031 break;
5032 case 7:
5033 dir.x = -1;
5034 break;
5035 }
5036 }
5037
5038 // round to 1000ths to avoid rounding errors
5039 // that can separate our values from zero
5040
5041 dir.x = lrint( dir.x * 1000 ) / 1000.0;
5042 dir.y = lrint( dir.y * 1000 ) / 1000.0;
5043
5044
5045 if( dir.x == 0 && dir.y == 0 ) {
5046 // random instead
5047
5048 stayInBiome = true;
5049
5050 dir.x = 1;
5051 dir.y = 0;
5052
5053 // 8 cardinal directions
5054 dir = rotate(
5055 dir,
5056 2 * M_PI *
5057 randSource.getRandomBoundedInt( 0, 7 ) / 8.0 );
5058 }
5059
5060 if( dir.x != 0 && dir.y != 0 ) {
5061 // diag
5062
5063 // push both up to full step
5064
5065 if( dir.x < 0 ) {
5066 dir.x = -1;
5067 }
5068 else if( dir.x > 0 ) {
5069 dir.x = 1;
5070 }
5071
5072 if( dir.y < 0 ) {
5073 dir.y = -1;
5074 }
5075 else if( dir.y > 0 ) {
5076 dir.y = 1;
5077 }
5078 }
5079
5080 // now we have the dir we want to go in
5081
5082
5083 int tryDist = desiredMoveDist;
5084
5085 if( tryDist < 1 ) {
5086 tryDist = 1;
5087 }
5088
5089 int tryRadius = 4;
5090
5091 if( t->move > 3 && tryDist == 1 ) {
5092 // single-step NSEW moves never go beyond
5093 // their intended distance
5094 tryRadius = 0;
5095 }
5096
5097 // try again and again with smaller distances until we
5098 // find an empty spot
5099 while( newX == inX && newY == inY && tryDist > 0 ) {
5100
5101 // walk up to 4 steps past our dist in that direction,
5102 // looking for non-blocking objects or an empty spot
5103
5104 for( int i=1; i<=tryDist + tryRadius; i++ ) {
5105 int testX = lrint( inX + dir.x * i );
5106 int testY = lrint( inY + dir.y * i );
5107
5108 int oID = getMapObjectRaw( testX, testY );
5109
5110 // does trans exist for this object used on destination
5111 // obj?
5112 TransRecord *trans = NULL;
5113
5114 if( oID > 0 ) {
5115 trans = getPTrans( inID, oID );
5116
5117 if( trans == NULL ) {
5118 // does trans exist for newID applied to
5119 // destination?
5120 trans = getPTrans( newID, oID );
5121 }
5122 }
5123 else if( oID == 0 ) {
5124 // check for bare ground trans
5125 trans = getPTrans( inID, -1 );
5126
5127 if( trans == NULL ) {
5128 // does trans exist for newID applied to
5129 // bare ground
5130 trans = getPTrans( newID, -1 );
5131 }
5132 }
5133
5134
5135
5136 if( i >= tryDist && oID == 0 ) {
5137 // found a bare ground spot for it to move
5138 newX = testX;
5139 newY = testY;
5140 // keep any bare ground transition (or NULL)
5141 destTrans = trans;
5142 break;
5143 }
5144 else if( i >= tryDist && trans != NULL ) {
5145 newX = testX;
5146 newY = testY;
5147 destTrans = trans;
5148 break;
5149 }
5150 else if( oID > 0 && getObject( oID ) != NULL &&
5151 getObject( oID )->blocksWalking ) {
5152 // blocked, stop now
5153 break;
5154 }
5155 // else walk through it
5156 }
5157
5158 tryDist--;
5159
5160 if( tryRadius != 0 ) {
5161 // 4 on first try, but then 1 on remaining tries to
5162 // avoid overlap with previous tries
5163 tryRadius = 1;
5164 }
5165 }
5166
5167
5168 int curBiome = -1;
5169 if( stayInBiome ) {
5170 curBiome = getMapBiome( inX, inY );
5171
5172 if( newX != inX || newY != inY ) {
5173 int newBiome = getMapBiome( newX, newY );
5174
5175 if( newBiome != curBiome ) {
5176 // block move
5177 newX = inX;
5178 newY = inY;
5179
5180 // forget about trans that we found above
5181 // it crosses biome boundary
5182 // (and this fixes the infamous sliding
5183 // penguin ice-hole bug)
5184 destTrans = NULL;
5185 }
5186 }
5187 }
5188
5189
5190
5191 if( newX == inX && newY == inY &&
5192 t->move <= 3 ) {
5193 // can't move where we want to go in flee/chase/random
5194
5195 // pick some random spot to go instead
5196
5197 int possibleX[8];
5198 int possibleY[8];
5199 int numPossibleDirs = 0;
5200
5201 for( int d=0; d<8; d++ ) {
5202
5203 dir.x = 1;
5204 dir.y = 0;
5205
5206 // 8 cardinal directions
5207 dir = rotate(
5208 dir,
5209 2 * M_PI * d / 8.0 );
5210
5211
5212 tryDist = t->desiredMoveDist;
5213
5214 if( tryDist < 1 ) {
5215 tryDist = 1;
5216 }
5217
5218 tryRadius = 4;
5219
5220 // try again and again with smaller distances until we
5221 // find an empty spot
5222 char stopCheckingDir = false;
5223
5224 while( !stopCheckingDir && tryDist > 0 ) {
5225
5226 // walk up to 4 steps in that direction, looking
5227 // for non-blocking objects or an empty spot
5228
5229 for( int i=0; i<tryDist + tryRadius; i++ ) {
5230 int testX = lrint( inX + dir.x * i );
5231 int testY = lrint( inY + dir.y * i );
5232
5233 int oID = getMapObjectRaw( testX, testY );
5234
5235
5236 if( i >= tryDist && oID == 0 ) {
5237 // found a spot for it to move
5238
5239 if( stayInBiome &&
5240 curBiome !=
5241 getMapBiome( testX, testY ) ) {
5242
5243 continue;
5244 }
5245
5246 possibleX[ numPossibleDirs ] = testX;
5247 possibleY[ numPossibleDirs ] = testY;
5248 numPossibleDirs++;
5249 stopCheckingDir = true;
5250 break;
5251 }
5252 else if( oID > 0 && getObject( oID ) != NULL &&
5253 getObject( oID )->blocksWalking ) {
5254 // blocked, stop now
5255 break;
5256 }
5257 // else walk through it
5258 }
5259
5260 tryDist --;
5261 // 1 on remaining tries to avoid overlap
5262 tryRadius = 1;
5263 }
5264 }
5265
5266 if( numPossibleDirs > 0 ) {
5267 int pick =
5268 randSource.getRandomBoundedInt(
5269 0, numPossibleDirs - 1 );
5270
5271 newX = possibleX[ pick ];
5272 newY = possibleY[ pick ];
5273 }
5274 }
5275
5276
5277
5278 if( newX != inX || newY != inY ) {
5279 // a reall move!
5280
5281 printf( "Object moving from (%d,%d) to (%d,%d)\n",
5282 inX, inY, newX, newY );
5283
5284 // move object
5285
5286 if( destTrans != NULL ) {
5287 newID = destTrans->newTarget;
5288 }
5289
5290 dbPut( newX, newY, 0, newID );
5291
5292
5293 // update old spot
5294 // do this second, so that it is reported to client
5295 // after move is reported
5296
5297 if( destTrans == NULL || destTrans->newActor == 0 ) {
5298 // try bare ground trans
5299 destTrans = getPTrans( inID, -1 );
5300
5301 if( destTrans == NULL ) {
5302 // another attempt at bare ground transition
5303 destTrans = getPTrans( movingObjID, -1 );
5304 }
5305
5306 if( destTrans != NULL &&
5307 destTrans->newTarget != newID &&
5308 destTrans->newTarget != movingObjID ) {
5309 // for bare ground, make sure newTarget
5310 // matches newTarget of our orginal move transition
5311 destTrans = NULL;
5312 }
5313 }
5314
5315
5316
5317 if( destTrans != NULL ) {
5318 // leave new actor behind
5319
5320 leftBehindID = destTrans->newActor;
5321
5322 dbPut( inX, inY, 0, leftBehindID );
5323
5324
5325
5326 TransRecord *leftDecayT =
5327 getMetaTrans( -1, leftBehindID );
5328
5329 double leftMapETA = 0;
5330
5331 if( leftDecayT != NULL ) {
5332
5333 // add some random variation to avoid lock-step
5334 // especially after a server restart
5335 int tweakedSeconds =
5336 randSource.getRandomBoundedInt(
5337 lrint(
5338 leftDecayT->autoDecaySeconds * 0.9 ),
5339 leftDecayT->autoDecaySeconds );
5340
5341 if( tweakedSeconds < 1 ) {
5342 tweakedSeconds = 1;
5343 }
5344 leftMapETA = MAP_TIMESEC + tweakedSeconds;
5345 }
5346 else {
5347 // no further decay
5348 leftMapETA = 0;
5349 }
5350 setEtaDecay( inX, inY, leftMapETA );
5351 }
5352 else {
5353 // leave empty spot behind
5354 dbPut( inX, inY, 0, 0 );
5355 leftBehindID = 0;
5356 }
5357
5358
5359 // move contained
5360 int numCont;
5361 int *cont = getContained( inX, inY, &numCont );
5362 timeSec_t *contEta =
5363 getContainedEtaDecay( inX, inY, &numCont );
5364
5365 if( numCont > 0 ) {
5366 setContained( newX, newY, numCont, cont );
5367 setContainedEtaDecay( newX, newY, numCont, contEta );
5368
5369 for( int c=0; c<numCont; c++ ) {
5370 if( cont[c] < 0 ) {
5371 // sub cont
5372 int numSub;
5373 int *subCont = getContained( inX, inY,
5374 &numSub,
5375 c + 1 );
5376 timeSec_t *subContEta = getContainedEtaDecay(
5377 inX, inY, &numSub, c + 1 );
5378
5379 if( numSub > 0 ) {
5380 setContained( newX, newY, numSub,
5381 subCont, c + 1 );
5382 setContainedEtaDecay(
5383 newX, newY, numSub, subContEta, c + 1 );
5384 }
5385 delete [] subCont;
5386 delete [] subContEta;
5387 }
5388 }
5389
5390
5391 clearAllContained( inX, inY );
5392
5393 delete [] cont;
5394 delete [] contEta;
5395 }
5396
5397 double moveDist = sqrt( (newX - inX) * (newX - inX) +
5398 (newY - inY) * (newY - inY) );
5399
5400 double speed = 4.0f;
5401
5402
5403 if( newID > 0 ) {
5404 ObjectRecord *newObj = getObject( newID );
5405
5406 if( newObj != NULL ) {
5407 speed *= newObj->speedMult;
5408 }
5409 }
5410
5411 double moveTime = moveDist / speed;
5412
5413 double etaTime = Time::getCurrentTime() + moveTime;
5414
5415 MovementRecord moveRec = { newX, newY, etaTime };
5416
5417 liveMovementEtaTimes.insert( newX, newY, 0, 0, etaTime );
5418
5419 liveMovements.insert( moveRec, etaTime );
5420
5421
5422 // now patch up change record marking this as a move
5423
5424 for( int i=0; i<mapChangePosSinceLastStep.size(); i++ ) {
5425
5426 ChangePosition *p =
5427 mapChangePosSinceLastStep.getElement( i );
5428
5429 if( p->x == newX && p->y == newY ) {
5430
5431 // update it
5432 p->oldX = inX;
5433 p->oldY = inY;
5434 p->speed = (float)speed;
5435
5436 break;
5437 }
5438 }
5439 }
5440 else {
5441 // failed to find a spot to move
5442
5443 // default to applying bare-ground transition, if any
5444 TransRecord *trans = getPTrans( inID, -1 );
5445
5446 if( trans == NULL ) {
5447 // does trans exist for newID applied to
5448 // bare ground
5449 trans = getPTrans( newID, -1 );
5450 }
5451 if( trans != NULL ) {
5452 newID = trans->newTarget;
5453
5454 // what was SUPPOSED to be left behind on ground
5455 // that object moved away from?
5456 if( trans->newActor > 0 ) {
5457
5458 // see if there's anything defined for when
5459 // the new object moves ONTO this thing
5460
5461 // (object is standing still in same spot,
5462 // effectively on top of what it was supposed
5463 // to leave behind)
5464
5465 TransRecord *inPlaceTrans =
5466 getPTrans( newID, trans->newActor );
5467
5468 if( inPlaceTrans != NULL &&
5469 inPlaceTrans->newTarget > 0 ) {
5470
5471 newID = inPlaceTrans->newTarget;
5472 }
5473 }
5474 }
5475 }
5476 }
5477
5478
5479 if( newX == inX && newY == inY ) {
5480 // no move happened
5481
5482 // just set change in DB
5483 dbPut( inX, inY, 0, newID );
5484 }
5485
5486
5487 TransRecord *newDecayT = getMetaTrans( -1, newID );
5488
5489 if( newDecayT != NULL ) {
5490
5491 // add some random variation to avoid lock-step
5492 // especially after a server restart
5493 int tweakedSeconds =
5494 randSource.getRandomBoundedInt(
5495 lrint( newDecayT->autoDecaySeconds * 0.9 ),
5496 newDecayT->autoDecaySeconds );
5497
5498 if( tweakedSeconds < 1 ) {
5499 tweakedSeconds = 1;
5500 }
5501 mapETA = MAP_TIMESEC + tweakedSeconds;
5502 }
5503 else {
5504 // no further decay
5505 mapETA = 0;
5506 }
5507
5508 if( mapETA != 0 &&
5509 ( newX != inX ||
5510 newY != inY ) ) {
5511
5512 // copy old last look time from where we came from
5513 char foundInOldSpot;
5514
5515 timeSec_t lastLookTime =
5516 liveDecayRecordLastLookTimeHashTable.lookup(
5517 inX, inY, 0, 0, &foundInOldSpot );
5518
5519 if( foundInOldSpot ) {
5520
5521 char foundInNewSpot;
5522 liveDecayRecordLastLookTimeHashTable.
5523 lookup( newX, newY, 0, 0, &foundInNewSpot );
5524
5525 if( ! foundInNewSpot ) {
5526 // we're not tracking decay for this new cell yet
5527 // but leave a look time here to affect
5528 // the tracking that we're about to setup
5529
5530 liveDecayRecordLastLookTimeHashTable.
5531 insert( newX, newY, 0, 0, lastLookTime );
5532 }
5533 }
5534 }
5535
5536 setEtaDecay( newX, newY, mapETA, newDecayT );
5537 }
5538
5539 }
5540 else {
5541 // an object on the map that has never been seen by anyone before
5542 // not decaying yet
5543
5544 // update map with decay for the applicable transition
5545
5546 // randomize it so that every same object on map
5547 // doesn't cycle at same time
5548 int decayTime =
5549 randSource.getRandomBoundedInt( t->autoDecaySeconds / 2 ,
5550 t->autoDecaySeconds );
5551 if( decayTime < 1 ) {
5552 decayTime = 1;
5553 }
5554
5555 mapETA = MAP_TIMESEC + decayTime;
5556
5557 setEtaDecay( inX, inY, mapETA );
5558 }
5559
5560
5561 if( newX != inX ||
5562 newY != inY ) {
5563 // object moved and is gone
5564 return leftBehindID;
5565 }
5566 else {
5567 return newID;
5568 }
5569 }
5570
5571
5572
5573void checkDecayContained( int inX, int inY, int inSubCont ) {
5574
5575 if( getNumContained( inX, inY, inSubCont ) == 0 ) {
5576 return;
5577 }
5578
5579 int numContained;
5580 int *contained = getContainedRaw( inX, inY, &numContained, inSubCont );
5581
5582 SimpleVector<int> newContained;
5583 SimpleVector<timeSec_t> newDecayEta;
5584
5585 SimpleVector< SimpleVector<int> > newSubCont;
5586 SimpleVector< SimpleVector<timeSec_t> > newSubContDecay;
5587
5588
5589 char change = false;
5590
5591 // track last ID we saw with no decay, so we don't have to keep
5592 // looking it up over and over.
5593 int lastIDWithNoDecay = 0;
5594
5595
5596 for( int i=0; i<numContained; i++ ) {
5597 int oldID = contained[i];
5598
5599 if( oldID == lastIDWithNoDecay ) {
5600 // same ID we've already seen before
5601 newContained.push_back( oldID );
5602 newDecayEta.push_back( 0 );
5603 continue;
5604 }
5605
5606
5607 char isSubCont = false;
5608
5609 if( oldID < 0 ) {
5610 // negative ID means this is a sub-container
5611 isSubCont = true;
5612 oldID *= -1;
5613 }
5614
5615 TransRecord *t = getPTrans( -1, oldID );
5616
5617 if( t == NULL ) {
5618 // no auto-decay for this object
5619 if( isSubCont ) {
5620 oldID *= -1;
5621 }
5622 lastIDWithNoDecay = oldID;
5623
5624 newContained.push_back( oldID );
5625 newDecayEta.push_back( 0 );
5626 continue;
5627 }
5628
5629
5630 // else decay exists for this object
5631
5632 int newID = oldID;
5633
5634 // is eta stored in map?
5635 timeSec_t mapETA = getSlotEtaDecay( inX, inY, i, inSubCont );
5636
5637 if( mapETA != 0 ) {
5638
5639 if( (int)mapETA <= MAP_TIMESEC ) {
5640
5641 // object in container slot has decayed (eta expired)
5642
5643 // apply the transition
5644 newID = t->newTarget;
5645
5646 if( newID != oldID ) {
5647 change = true;
5648 }
5649
5650 if( newID != 0 ) {
5651
5652 TransRecord *newDecayT = getMetaTrans( -1, newID );
5653
5654 if( newDecayT != NULL ) {
5655
5656 // add some random variation to avoid lock-step
5657 // especially after a server restart
5658 int tweakedSeconds =
5659 randSource.getRandomBoundedInt(
5660 lrint( newDecayT->autoDecaySeconds * 0.9 ),
5661 newDecayT->autoDecaySeconds );
5662
5663 if( tweakedSeconds < 1 ) {
5664 tweakedSeconds = 1;
5665 }
5666
5667 mapETA =
5668 MAP_TIMESEC +
5669 tweakedSeconds /
5670 getMapContainerTimeStretch( inX, inY, inSubCont );
5671 }
5672 else {
5673 // no further decay
5674 mapETA = 0;
5675 }
5676 }
5677 }
5678 }
5679
5680 if( newID != 0 ) {
5681 if( isSubCont ) {
5682
5683 int oldSlots = getNumContainerSlots( oldID );
5684
5685 int newSlots = getNumContainerSlots( newID );
5686
5687 if( newSlots < oldSlots ) {
5688 shrinkContainer( inX, inY, newSlots, i + 1 );
5689 }
5690 if( newSlots > 0 ) {
5691 restretchMapContainedDecays( inX, inY,
5692 oldID, newID, i + 1 );
5693
5694 // negative IDs indicate sub-containment
5695 newID *= -1;
5696 }
5697 }
5698 newContained.push_back( newID );
5699 newDecayEta.push_back( mapETA );
5700 }
5701 }
5702
5703
5704 if( change ) {
5705 int *containedArray = newContained.getElementArray();
5706 int numContained = newContained.size();
5707
5708 setContained( inX, inY, newContained.size(), containedArray,
5709 inSubCont );
5710 delete [] containedArray;
5711
5712 for( int i=0; i<numContained; i++ ) {
5713 timeSec_t mapETA = newDecayEta.getElementDirect( i );
5714
5715 if( mapETA != 0 ) {
5716 trackETA( inX, inY, 1 + i, mapETA, inSubCont );
5717 }
5718
5719 setSlotEtaDecay( inX, inY, i, mapETA, inSubCont );
5720 }
5721 }
5722
5723 if( contained != NULL ) {
5724 delete [] contained;
5725 }
5726 }
5727
5728
5729
5730
5731int getTweakedBaseMap( int inX, int inY ) {
5732
5733 // nothing in map
5734 char wasGridPlacement = false;
5735
5736 int result = getBaseMap( inX, inY, &wasGridPlacement );
5737
5738 if( result > 0 ) {
5739 ObjectRecord *o = getObject( result );
5740
5741 if( o->wide ) {
5742 // make sure there's not possibly another wide object too close
5743 int maxR = getMaxWideRadius();
5744
5745 for( int dx = -( o->leftBlockingRadius + maxR );
5746 dx <= ( o->rightBlockingRadius + maxR ); dx++ ) {
5747
5748 if( dx != 0 ) {
5749 int nID = getBaseMap( inX + dx, inY );
5750
5751 if( nID > 0 ) {
5752 ObjectRecord *nO = getObject( nID );
5753
5754 if( nO->wide ) {
5755
5756 int minDist;
5757 int dist;
5758
5759 if( dx < 0 ) {
5760 minDist = nO->rightBlockingRadius +
5761 o->leftBlockingRadius;
5762 dist = -dx;
5763 }
5764 else {
5765 minDist = nO->leftBlockingRadius +
5766 o->rightBlockingRadius;
5767 dist = dx;
5768 }
5769
5770 if( dist <= minDist ) {
5771 // collision
5772 // don't allow this wide object here
5773 return 0;
5774 }
5775 }
5776 }
5777 }
5778 }
5779 }
5780 else if( !wasGridPlacement && getObjectHeight( result ) < CELL_D ) {
5781 // a short object should be here
5782 // and it wasn't forced by a grid placement
5783
5784 // make sure there's not any semi-short objects below already
5785
5786 // this avoids vertical stacking of short objects
5787 // and ensures that the map is sparse with short object
5788 // clusters, even in very dense map areas
5789 // (we don't want the floor tiled with berry bushes)
5790
5791 // this used to be an unintentional bug, but it was in place
5792 // for a year, and we got used to it.
5793
5794 // when the bug was fixed, the map became way too dense
5795 // in short-object areas
5796
5797 // actually, fully replicate the bug for now
5798 // only block short objects with objects to the south
5799 // that extend above the tile midline
5800
5801 // So we can have clusters of very short objects, like stones
5802 // but not less-short ones like berry bushes, rabbit holes, etc.
5803
5804 // use the old buggy "2 pixels" and "3 pixels" above the
5805 // midline measure just to keep the map the same
5806
5807 // south
5808 int sID = getBaseMap( inX, inY - 1 );
5809
5810 if( sID > 0 && getObjectHeight( sID ) >= 2 ) {
5811 return 0;
5812 }
5813
5814 int s2ID = getBaseMap( inX, inY - 2 );
5815
5816 if( s2ID > 0 && getObjectHeight( s2ID ) >= 3 ) {
5817 return 0;
5818 }
5819 }
5820 }
5821 return result;
5822 }
5823
5824
5825
5826static int getPossibleBarrier( int inX, int inY ) {
5827 if( barrierOn )
5828 if( inX == barrierRadius ||
5829 inX == - barrierRadius ||
5830 inY == barrierRadius ||
5831 inY == - barrierRadius ) {
5832
5833 // along barrier line
5834
5835 // now make sure that we don't stick out beyond square
5836
5837 if( inX <= barrierRadius &&
5838 inX >= -barrierRadius &&
5839 inY <= barrierRadius &&
5840 inY >= -barrierRadius ) {
5841
5842
5843 setXYRandomSeed( 9238597 );
5844
5845 int numOptions = barrierItemList.size();
5846
5847 if( numOptions > 0 ) {
5848
5849 // random doesn't always look good
5850 int pick =
5851 floor( numOptions * getXYRandom( inX * 10, inY * 10 ) );
5852
5853 if( pick >= numOptions ) {
5854 pick = numOptions - 1;
5855 }
5856
5857 int barrierID = barrierItemList.getElementDirect( pick );
5858
5859 if( getObject( barrierID ) != NULL ) {
5860 // actually exists
5861 return barrierID;
5862 }
5863 }
5864 }
5865 }
5866
5867 return 0;
5868 }
5869
5870
5871
5872int getMapObjectRaw( int inX, int inY ) {
5873
5874 int barrier = getPossibleBarrier( inX, inY );
5875
5876 if( barrier != 0 ) {
5877 return barrier;
5878 }
5879
5880 int result = dbGet( inX, inY, 0 );
5881
5882 if( result == -1 ) {
5883 result = getTweakedBaseMap( inX, inY );
5884 }
5885
5886 return result;
5887 }
5888
5889
5890
5891
5892void lookAtRegion( int inXStart, int inYStart, int inXEnd, int inYEnd ) {
5893 timeSec_t currentTime = MAP_TIMESEC;
5894
5895 for( int y=inYStart; y<=inYEnd; y++ ) {
5896 for( int x=inXStart; x<=inXEnd; x++ ) {
5897
5898
5899 if( ! lookTimeTracking.checkExists( x, y, currentTime ) ) {
5900
5901 // we haven't looked at this spot in a while
5902
5903 // see if any decays apply
5904 // if so, get that part of the tile once to re-trigger
5905 // live tracking
5906
5907 timeSec_t floorEtaDecay = getFloorEtaDecay( x, y );
5908
5909 if( floorEtaDecay != 0 &&
5910 floorEtaDecay <
5911 currentTime + maxSecondsForActiveDecayTracking ) {
5912
5913 getMapFloor( x, y );
5914 }
5915
5916
5917
5918 timeSec_t etaDecay = getEtaDecay( x, y );
5919
5920 int objID = 0;
5921
5922 if( etaDecay != 0 &&
5923 etaDecay <
5924 currentTime + maxSecondsForActiveDecayTracking ) {
5925
5926 objID = getMapObject( x, y );
5927 }
5928
5929 // also check all contained items to trigger
5930 // live tracking of their decays too
5931 if( objID != 0 ) {
5932
5933 int numCont = getNumContained( x, y );
5934
5935 for( int c=0; c<numCont; c++ ) {
5936 int contID = getContained( x, y, c );
5937
5938 if( contID < 0 ) {
5939 // sub cont
5940 int numSubCont = getNumContained( x, y, c + 1 );
5941
5942 for( int s=0; s<numSubCont; s++ ) {
5943 getContained( x, y, c, s + 1 );
5944 }
5945 }
5946 }
5947 }
5948 }
5949
5950
5951 timeSec_t *oldLookTime =
5952 liveDecayRecordLastLookTimeHashTable.lookupPointer( x, y,
5953 0, 0 );
5954
5955 if( oldLookTime != NULL ) {
5956 // we're tracking decay for this cell
5957 *oldLookTime = currentTime;
5958 }
5959
5960 ContRecord *contRec =
5961 liveDecayRecordLastLookTimeMaxContainedHashTable.
5962 lookupPointer( x, y, 0, 0 );
5963
5964 if( contRec != NULL ) {
5965
5966 for( int c=1; c<= contRec->maxSlots; c++ ) {
5967
5968 oldLookTime =
5969 liveDecayRecordLastLookTimeHashTable.lookupPointer(
5970 x, y, c, 0 );
5971 if( oldLookTime != NULL ) {
5972 // look at it now
5973 *oldLookTime = currentTime;
5974 }
5975
5976 for( int s=1; s<= contRec->maxSubSlots; s++ ) {
5977
5978 oldLookTime =
5979 liveDecayRecordLastLookTimeHashTable.lookupPointer(
5980 x, y, c, s );
5981 if( oldLookTime != NULL ) {
5982 // look at it now
5983 *oldLookTime = currentTime;
5984 }
5985 }
5986 }
5987 }
5988 }
5989 }
5990 }
5991
5992
5993
5994int getMapObject( int inX, int inY ) {
5995
5996 // look at this map cell
5997 timeSec_t *oldLookTime =
5998 liveDecayRecordLastLookTimeHashTable.lookupPointer( inX, inY, 0, 0 );
5999
6000 timeSec_t curTime = MAP_TIMESEC;
6001
6002 if( oldLookTime != NULL ) {
6003 // we're tracking decay for this cell
6004 *oldLookTime = curTime;
6005 }
6006
6007 // apply any decay that should have happened by now
6008 return checkDecayObject( inX, inY, getMapObjectRaw( inX, inY ) );
6009 }
6010
6011
6012int getMapObjectNoLook( int inX, int inY ) {
6013
6014 // apply any decay that should have happened by now
6015 return checkDecayObject( inX, inY, getMapObjectRaw( inX, inY ) );
6016 }
6017
6018
6019
6020char isMapObjectInTransit( int inX, int inY ) {
6021 char found;
6022
6023 double etaTime =
6024 liveMovementEtaTimes.lookup( inX, inY, 0, 0, &found );
6025
6026 if( found ) {
6027 if( etaTime > Time::getCurrentTime() ) {
6028 return true;
6029 }
6030 }
6031
6032 return false;
6033 }
6034
6035
6036
6037int getMapBiome( int inX, int inY ) {
6038 return biomes[getMapBiomeIndex( inX, inY )];
6039 }
6040
6041
6042
6043
6044// returns properly formatted chunk message for chunk centered
6045// around x,y
6046unsigned char *getChunkMessage( int inStartX, int inStartY,
6047 int inWidth, int inHeight,
6048 GridPos inRelativeToPos,
6049 int *outMessageLength ) {
6050
6051 int chunkCells = inWidth * inHeight;
6052
6053 int *chunk = new int[chunkCells];
6054
6055 int *chunkBiomes = new int[chunkCells];
6056 int *chunkFloors = new int[chunkCells];
6057
6058 int *containedStackSizes = new int[ chunkCells ];
6059 int **containedStacks = new int*[ chunkCells ];
6060
6061 int **subContainedStackSizes = new int*[chunkCells];
6062 int ***subContainedStacks = new int**[chunkCells];
6063
6064
6065 int endY = inStartY + inHeight;
6066 int endX = inStartX + inWidth;
6067
6068 timeSec_t curTime = MAP_TIMESEC;
6069
6070 // look at four corners of chunk whenever we fetch one
6071 dbLookTimePut( inStartX, inStartY, curTime );
6072 dbLookTimePut( inStartX, endY, curTime );
6073 dbLookTimePut( endX, inStartY, curTime );
6074 dbLookTimePut( endX, endY, curTime );
6075
6076 for( int y=inStartY; y<endY; y++ ) {
6077 int chunkY = y - inStartY;
6078
6079
6080 for( int x=inStartX; x<endX; x++ ) {
6081 int chunkX = x - inStartX;
6082
6083 int cI = chunkY * inWidth + chunkX;
6084
6085 lastCheckedBiome = -1;
6086
6087 chunk[cI] = getMapObject( x, y );
6088
6089 if( lastCheckedBiome == -1 ||
6090 lastCheckedBiomeX != x ||
6091 lastCheckedBiomeY != y ) {
6092 // biome wasn't checked in order to compute
6093 // getMapObject
6094
6095 // get it ourselves
6096
6097 lastCheckedBiome = biomes[getMapBiomeIndex( x, y )];
6098 }
6099 chunkBiomes[ cI ] = lastCheckedBiome;
6100
6101 chunkFloors[cI] = getMapFloor( x, y );
6102
6103
6104 int numContained;
6105 int *contained = NULL;
6106
6107 if( chunk[cI] > 0 && getObject( chunk[cI] )->numSlots > 0 ) {
6108 contained = getContained( x, y, &numContained );
6109 }
6110
6111 if( contained != NULL ) {
6112 containedStackSizes[cI] = numContained;
6113 containedStacks[cI] = contained;
6114
6115 subContainedStackSizes[cI] = new int[numContained];
6116 subContainedStacks[cI] = new int*[numContained];
6117
6118 for( int i=0; i<numContained; i++ ) {
6119 subContainedStackSizes[cI][i] = 0;
6120 subContainedStacks[cI][i] = NULL;
6121
6122 if( containedStacks[cI][i] < 0 ) {
6123 // a sub container
6124 containedStacks[cI][i] *= -1;
6125
6126 int numSubContained;
6127 int *subContained = getContained( x, y,
6128 &numSubContained,
6129 i + 1 );
6130 if( subContained != NULL ) {
6131 subContainedStackSizes[cI][i] = numSubContained;
6132 subContainedStacks[cI][i] = subContained;
6133 }
6134 }
6135 }
6136 }
6137 else {
6138 containedStackSizes[cI] = 0;
6139 containedStacks[cI] = NULL;
6140 subContainedStackSizes[cI] = NULL;
6141 subContainedStacks[cI] = NULL;
6142 }
6143 }
6144
6145 }
6146
6147
6148
6149 SimpleVector<unsigned char> chunkDataBuffer;
6150
6151 for( int i=0; i<chunkCells; i++ ) {
6152
6153 if( i > 0 ) {
6154 chunkDataBuffer.appendArray( (unsigned char*)" ", 1 );
6155 }
6156
6157
6158 char *cell = autoSprintf( "%d:%d:%d", chunkBiomes[i],
6159 hideIDForClient( chunkFloors[i] ),
6160 hideIDForClient( chunk[i] ) );
6161
6162 chunkDataBuffer.appendArray( (unsigned char*)cell, strlen(cell) );
6163 delete [] cell;
6164
6165 if( containedStacks[i] != NULL ) {
6166 for( int c=0; c<containedStackSizes[i]; c++ ) {
6167 char *containedString =
6168 autoSprintf( ",%d",
6169 hideIDForClient( containedStacks[i][c] ) );
6170
6171 chunkDataBuffer.appendArray( (unsigned char*)containedString,
6172 strlen( containedString ) );
6173 delete [] containedString;
6174
6175 if( subContainedStacks[i][c] != NULL ) {
6176
6177 for( int s=0; s<subContainedStackSizes[i][c]; s++ ) {
6178
6179 char *subContainedString =
6180 autoSprintf( ":%d",
6181 hideIDForClient(
6182 subContainedStacks[i][c][s] ) );
6183
6184 chunkDataBuffer.appendArray(
6185 (unsigned char*)subContainedString,
6186 strlen( subContainedString ) );
6187 delete [] subContainedString;
6188 }
6189 delete [] subContainedStacks[i][c];
6190 }
6191 }
6192
6193 delete [] subContainedStackSizes[i];
6194 delete [] subContainedStacks[i];
6195
6196 delete [] containedStacks[i];
6197 }
6198 }
6199
6200 delete [] chunk;
6201 delete [] chunkBiomes;
6202 delete [] chunkFloors;
6203
6204 delete [] containedStackSizes;
6205 delete [] containedStacks;
6206
6207 delete [] subContainedStackSizes;
6208 delete [] subContainedStacks;
6209
6210
6211
6212 unsigned char *chunkData = chunkDataBuffer.getElementArray();
6213
6214 int compressedSize;
6215 unsigned char *compressedChunkData =
6216 zipCompress( chunkData, chunkDataBuffer.size(),
6217 &compressedSize );
6218
6219
6220
6221 char *header = autoSprintf( "MC\n%d %d %d %d\n%d %d\n#",
6222 inWidth, inHeight,
6223 inStartX - inRelativeToPos.x,
6224 inStartY - inRelativeToPos.y,
6225 chunkDataBuffer.size(),
6226 compressedSize );
6227
6228 SimpleVector<unsigned char> buffer;
6229 buffer.appendArray( (unsigned char*)header, strlen( header ) );
6230 delete [] header;
6231
6232
6233 buffer.appendArray( compressedChunkData, compressedSize );
6234
6235 delete [] chunkData;
6236 delete [] compressedChunkData;
6237
6238
6239
6240 *outMessageLength = buffer.size();
6241 return buffer.getElementArray();
6242 }
6243
6244
6245
6246
6247
6248
6249
6250
6251
6252
6253char isMapSpotBlocking( int inX, int inY ) {
6254
6255 char cachedVal = blockingGetCached( inX, inY );
6256 if( cachedVal != -1 ) {
6257
6258 return cachedVal;
6259 }
6260
6261
6262 int target = getMapObject( inX, inY );
6263
6264 if( target != 0 ) {
6265 ObjectRecord *obj = getObject( target );
6266
6267 if( obj->blocksWalking ) {
6268 // only cache direct hits
6269 // wide objects that block are difficult to clear from cache
6270 // when map cell changes
6271 blockingPutCached( inX, inY, 1 );
6272 return true;
6273 }
6274 }
6275
6276 // not directly blocked
6277 // need to check for wide objects to left and right
6278 int maxR = getMaxWideRadius();
6279
6280 for( int dx = -maxR; dx <= maxR; dx++ ) {
6281
6282 if( dx != 0 ) {
6283
6284 int nX = inX + dx;
6285
6286 int nID = getMapObject( nX, inY );
6287
6288 if( nID != 0 ) {
6289 ObjectRecord *nO = getObject( nID );
6290
6291 if( nO->wide ) {
6292
6293 int dist;
6294 int minDist;
6295
6296 if( dx < 0 ) {
6297 dist = -dx;
6298 minDist = nO->rightBlockingRadius;
6299 }
6300 else {
6301 dist = dx;
6302 minDist = nO->leftBlockingRadius;
6303 }
6304
6305 if( dist <= minDist ) {
6306 // don't cache results from wide objects
6307 return true;
6308 }
6309 }
6310 }
6311 }
6312 }
6313
6314 // cache non-blocking results
6315 blockingPutCached( inX, inY, 0 );
6316 return false;
6317 }
6318
6319
6320
6321
6322
6323static char equal( GridPos inA, GridPos inB ) {
6324 if( inA.x == inB.x && inA.y == inB.y ) {
6325 return true;
6326 }
6327 return false;
6328 }
6329
6330
6331
6332static char tooClose( GridPos inA, GridPos inB, int inMinComponentDistance ) {
6333 double xDist = (double)inA.x - (double)inB.x;
6334 if( xDist < 0 ) {
6335 xDist = -xDist;
6336 }
6337 double yDist = (double)inA.y - (double)inB.y;
6338 if( yDist < 0 ) {
6339 yDist = -yDist;
6340 }
6341
6342 if( xDist < inMinComponentDistance &&
6343 yDist < inMinComponentDistance ) {
6344 return true;
6345 }
6346 return false;
6347 }
6348
6349
6350
6351
6352static int findGridPos( SimpleVector<GridPos> *inList, GridPos inP ) {
6353 for( int i=0; i<inList->size(); i++ ) {
6354 GridPos q = inList->getElementDirect( i );
6355 if( equal( inP, q ) ) {
6356 return i;
6357 }
6358 }
6359 return -1;
6360 }
6361
6362
6363
6364void setMapObjectRaw( int inX, int inY, int inID ) {
6365 dbPut( inX, inY, 0, inID );
6366
6367
6368 // global trigger and speech pipe stuff
6369
6370 if( inID <= 0 ) {
6371 return;
6372 }
6373
6374 ObjectRecord *o = getObject( inID );
6375
6376 if( o == NULL ) {
6377 return;
6378 }
6379
6380
6381
6382 if( o->isFlightLanding ) {
6383 GridPos p = { inX, inY };
6384
6385 char found = false;
6386
6387 for( int i=0; i<flightLandingPos.size(); i++ ) {
6388 GridPos otherP = flightLandingPos.getElementDirect( i );
6389
6390 if( equal( p, otherP ) ) {
6391
6392 // make sure this other strip really still exists
6393 int oID = getMapObject( otherP.x, otherP.y );
6394
6395 if( oID <=0 ||
6396 ! getObject( oID )->isFlightLanding ) {
6397
6398 // not even a valid landing pos anymore
6399 flightLandingPos.deleteElement( i );
6400 i--;
6401 }
6402 else {
6403 found = true;
6404 break;
6405 }
6406 }
6407 }
6408
6409 if( !found ) {
6410 flightLandingPos.push_back( p );
6411 }
6412 }
6413
6414
6415
6416 if( o->speechPipeIn ) {
6417 GridPos p = { inX, inY };
6418
6419 int foundIndex =
6420 findGridPos( &( speechPipesIn[ o->speechPipeIndex ] ), p );
6421
6422 if( foundIndex == -1 ) {
6423 speechPipesIn[ o->speechPipeIndex ].push_back( p );
6424 }
6425 }
6426 else if( o->speechPipeOut ) {
6427 GridPos p = { inX, inY };
6428
6429 int foundIndex =
6430 findGridPos( &( speechPipesOut[ o->speechPipeIndex ] ), p );
6431
6432 if( foundIndex == -1 ) {
6433 speechPipesOut[ o->speechPipeIndex ].push_back( p );
6434 }
6435 }
6436 else if( o->isGlobalTriggerOn ) {
6437 GlobalTriggerState *s = globalTriggerStates.getElement(
6438 o->globalTriggerIndex );
6439
6440 GridPos p = { inX, inY };
6441
6442 int foundIndex = findGridPos( &( s->triggerOnLocations ), p );
6443
6444 if( foundIndex == -1 ) {
6445 s->triggerOnLocations.push_back( p );
6446
6447 if( s->triggerOnLocations.size() == 1 ) {
6448 // just turned on globally
6449
6450 /// process all receivers
6451 for( int i=0; i<s->receiverLocations.size(); i++ ) {
6452 GridPos q = s->receiverLocations.getElementDirect( i );
6453
6454 int id = getMapObjectRaw( q.x, q.y );
6455
6456 if( id <= 0 ) {
6457 // receiver no longer here
6458 s->receiverLocations.deleteElement( i );
6459 i--;
6460 continue;
6461 }
6462
6463 ObjectRecord *oR = getObject( id );
6464
6465 if( oR->isGlobalReceiver &&
6466 oR->globalTriggerIndex == o->globalTriggerIndex ) {
6467 // match
6468
6469 int metaID = getMetaTriggerObject(
6470 o->globalTriggerIndex );
6471
6472 if( metaID > 0 ) {
6473 TransRecord *tR = getPTrans( metaID, id );
6474
6475 if( tR != NULL ) {
6476
6477 dbPut( q.x, q.y, 0, tR->newTarget );
6478
6479 // save this to our "triggered" list
6480 int foundIndex = findGridPos(
6481 &( s->triggeredLocations ), q );
6482
6483 if( foundIndex != -1 ) {
6484 // already exists
6485 // replace
6486 *( s->triggeredIDs.getElement(
6487 foundIndex ) ) =
6488 tR->newTarget;
6489 *( s->triggeredRevertIDs.getElement(
6490 foundIndex ) ) =
6491 tR->target;
6492 }
6493 else {
6494 s->triggeredLocations.push_back( q );
6495 s->triggeredIDs.push_back( tR->newTarget );
6496 s->triggeredRevertIDs.push_back(
6497 tR->target );
6498 }
6499 }
6500 }
6501 }
6502 // receiver no longer here
6503 // (either wasn't here anymore for other reasons,
6504 // or we just changed it into its triggered state)
6505 // remove it
6506 s->receiverLocations.deleteElement( i );
6507 i--;
6508 }
6509 }
6510 }
6511 }
6512 else if( o->isGlobalTriggerOff ) {
6513 GlobalTriggerState *s = globalTriggerStates.getElement(
6514 o->globalTriggerIndex );
6515
6516 GridPos p = { inX, inY };
6517
6518 int foundIndex = findGridPos( &( s->triggerOnLocations ), p );
6519
6520 if( foundIndex != -1 ) {
6521 s->triggerOnLocations.deleteElement( foundIndex );
6522
6523 if( s->triggerOnLocations.size() == 0 ) {
6524 // just turned off globally, no on triggers left on map
6525
6526 /// process all triggered elements back to off
6527
6528 for( int i=0; i<s->triggeredLocations.size(); i++ ) {
6529 GridPos q = s->triggeredLocations.getElementDirect( i );
6530
6531 int curID = getMapObjectRaw( q.x, q.y );
6532
6533 int triggeredID = s->triggeredIDs.getElementDirect( i );
6534
6535 if( curID == triggeredID ) {
6536 // cell still in triggered state
6537
6538 // revert it
6539 int revertID =
6540 s->triggeredRevertIDs.getElementDirect( i );
6541
6542 dbPut( q.x, q.y, 0, revertID );
6543
6544 // no longer triggered, remove it
6545 s->triggeredLocations.deleteElement( i );
6546 s->triggeredIDs.deleteElement( i );
6547 s->triggeredRevertIDs.deleteElement( i );
6548 i--;
6549
6550 // remember it as a reciever (it has gone back
6551 // to being a receiver again)
6552 s->receiverLocations.push_back( q );
6553 }
6554 }
6555 }
6556 }
6557 }
6558 else if( o->isGlobalReceiver ) {
6559 GlobalTriggerState *s = globalTriggerStates.getElement(
6560 o->globalTriggerIndex );
6561
6562 GridPos p = { inX, inY };
6563
6564 int foundIndex = findGridPos( &( s->receiverLocations ), p );
6565
6566 if( foundIndex == -1 ) {
6567 s->receiverLocations.push_back( p );
6568 }
6569
6570 if( s->triggerOnLocations.size() > 0 ) {
6571 // this receiver is currently triggered
6572
6573 // trigger it now, right away, as soon as it is placed on map
6574
6575 int metaID = getMetaTriggerObject( o->globalTriggerIndex );
6576
6577 if( metaID > 0 ) {
6578 TransRecord *tR = getPTrans( metaID, inID );
6579
6580 if( tR != NULL ) {
6581
6582 dbPut( inX, inY, 0, tR->newTarget );
6583
6584 GridPos q = { inX, inY };
6585
6586
6587 // save this to our "triggered" list
6588 int foundIndex = findGridPos(
6589 &( s->triggeredLocations ), q );
6590
6591 if( foundIndex != -1 ) {
6592 // already exists
6593 // replace
6594 *( s->triggeredIDs.getElement( foundIndex ) ) =
6595 tR->newTarget;
6596 *( s->triggeredRevertIDs.getElement(
6597 foundIndex ) ) =
6598 tR->target;
6599 }
6600 else {
6601 s->triggeredLocations.push_back( q );
6602 s->triggeredIDs.push_back( tR->newTarget );
6603 s->triggeredRevertIDs.push_back( tR->target );
6604 }
6605 }
6606 }
6607 }
6608 }
6609 }
6610
6611
6612
6613static void logMapChange( int inX, int inY, int inID ) {
6614 // log it?
6615 if( mapChangeLogFile != NULL ) {
6616
6617 ObjectRecord *o = getObject( inID );
6618
6619 const char *extraFlag = "";
6620
6621 if( o != NULL && o->floor ) {
6622 extraFlag = "f";
6623 }
6624
6625 if( o != NULL && o->isUseDummy ) {
6626 fprintf( mapChangeLogFile,
6627 "%.2f %d %d %s%du%d\n",
6628 Time::getCurrentTime() - mapChangeLogTimeStart,
6629 inX, inY,
6630 extraFlag,
6631 o->useDummyParent,
6632 o->thisUseDummyIndex );
6633 }
6634 else if( o != NULL && o->isVariableDummy ) {
6635 fprintf( mapChangeLogFile,
6636 "%.2f %d %d %s%dv%d\n",
6637 Time::getCurrentTime() - mapChangeLogTimeStart,
6638 inX, inY,
6639 extraFlag,
6640 o->variableDummyParent,
6641 o->thisVariableDummyIndex );
6642 }
6643 else {
6644 fprintf( mapChangeLogFile,
6645 "%.2f %d %d %s%d\n",
6646 Time::getCurrentTime() - mapChangeLogTimeStart,
6647 inX, inY,
6648 extraFlag,
6649 inID );
6650 }
6651 }
6652 }
6653
6654
6655
6656void setMapObject( int inX, int inY, int inID ) {
6657
6658 logMapChange( inX, inY, inID );
6659
6660 setMapObjectRaw( inX, inY, inID );
6661
6662
6663 // actually need to set decay here
6664 // otherwise, if we wait until getObject, it will assume that
6665 // this is a never-before-seen object and randomize the decay.
6666 TransRecord *newDecayT = getMetaTrans( -1, inID );
6667
6668 timeSec_t mapETA = 0;
6669
6670 if( newDecayT != NULL && newDecayT->autoDecaySeconds > 0 ) {
6671
6672 mapETA = MAP_TIMESEC + newDecayT->autoDecaySeconds;
6673 }
6674
6675 setEtaDecay( inX, inY, mapETA );
6676
6677
6678 // note that we also potentially set up decay for objects on get
6679 // if they have no decay set already
6680 // We do this because there are loads
6681 // of gets that have no set (for example, getting a map chunk)
6682 // Those decay times get randomized to avoid lock-step in newly-seen
6683 // objects
6684
6685 if( inID > 0 ) {
6686
6687 char found = false;
6688
6689 for( int i=0; i<NUM_RECENT_PLACEMENTS; i++ ) {
6690
6691 if( inX == recentPlacements[i].pos.x
6692 &&
6693 inY == recentPlacements[i].pos.y ) {
6694
6695 found = true;
6696 // update depth
6697 int newDepth = getObjectDepth( inID );
6698
6699 if( newDepth != UNREACHABLE ) {
6700 recentPlacements[i].depth = getObjectDepth( inID );
6701 }
6702 break;
6703 }
6704 }
6705
6706
6707 if( !found ) {
6708
6709 int newDepth = getObjectDepth( inID );
6710 if( newDepth != UNREACHABLE ) {
6711
6712 recentPlacements[nextPlacementIndex].pos.x = inX;
6713 recentPlacements[nextPlacementIndex].pos.y = inY;
6714 recentPlacements[nextPlacementIndex].depth = newDepth;
6715
6716 nextPlacementIndex++;
6717
6718 if( nextPlacementIndex >= NUM_RECENT_PLACEMENTS ) {
6719 nextPlacementIndex = 0;
6720
6721 // write again every time we have a fresh 100
6722 writeRecentPlacements();
6723 }
6724 }
6725 }
6726
6727 }
6728
6729 }
6730
6731
6732
6733
6734void setEtaDecay( int inX, int inY, timeSec_t inAbsoluteTimeInSeconds,
6735 TransRecord *inApplicableTrans ) {
6736 dbTimePut( inX, inY, DECAY_SLOT, inAbsoluteTimeInSeconds );
6737 if( inAbsoluteTimeInSeconds != 0 ) {
6738 trackETA( inX, inY, 0, inAbsoluteTimeInSeconds, 0, inApplicableTrans );
6739 }
6740 }
6741
6742
6743
6744
6745timeSec_t getEtaDecay( int inX, int inY ) {
6746 // 0 if not found
6747 return dbTimeGet( inX, inY, DECAY_SLOT );
6748 }
6749
6750
6751
6752
6753
6754
6755
6756void setSlotEtaDecay( int inX, int inY, int inSlot,
6757 timeSec_t inAbsoluteTimeInSeconds, int inSubCont ) {
6758 dbTimePut( inX, inY, getContainerDecaySlot( inX, inY, inSlot, inSubCont ),
6759 inAbsoluteTimeInSeconds, inSubCont );
6760 if( inAbsoluteTimeInSeconds != 0 ) {
6761 setSlotItemsNoDecay( inX, inY, inSubCont, false );
6762
6763 trackETA( inX, inY, inSlot + 1, inAbsoluteTimeInSeconds,
6764 inSubCont );
6765 }
6766 }
6767
6768
6769timeSec_t getSlotEtaDecay( int inX, int inY, int inSlot, int inSubCont ) {
6770 // 0 if not found
6771 return dbTimeGet( inX, inY, getContainerDecaySlot( inX, inY, inSlot,
6772 inSubCont ),
6773 inSubCont );
6774 }
6775
6776
6777
6778
6779
6780
6781void addContained( int inX, int inY, int inContainedID,
6782 timeSec_t inEtaDecay, int inSubCont ) {
6783 int oldNum;
6784
6785
6786 timeSec_t curTime = MAP_TIMESEC;
6787
6788 if( inEtaDecay != 0 ) {
6789 timeSec_t etaOffset = inEtaDecay - curTime;
6790
6791 inEtaDecay = curTime +
6792 etaOffset / getMapContainerTimeStretch( inX, inY, inSubCont );
6793 }
6794
6795 int *oldContained = getContained( inX, inY, &oldNum, inSubCont );
6796
6797 timeSec_t *oldContainedETA = getContainedEtaDecay( inX, inY, &oldNum,
6798 inSubCont );
6799
6800 int *newContained = new int[ oldNum + 1 ];
6801
6802 if( oldNum != 0 ) {
6803 memcpy( newContained, oldContained, oldNum * sizeof( int ) );
6804 }
6805
6806 newContained[ oldNum ] = inContainedID;
6807
6808 if( oldContained != NULL ) {
6809 delete [] oldContained;
6810 }
6811
6812 timeSec_t *newContainedETA = new timeSec_t[ oldNum + 1 ];
6813
6814 if( oldNum != 0 ) {
6815 memcpy( newContainedETA,
6816 oldContainedETA, oldNum * sizeof( timeSec_t ) );
6817 }
6818
6819 newContainedETA[ oldNum ] = inEtaDecay;
6820
6821 if( oldContainedETA != NULL ) {
6822 delete [] oldContainedETA;
6823 }
6824
6825 int newNum = oldNum + 1;
6826
6827 setContained( inX, inY, newNum, newContained, inSubCont );
6828 setContainedEtaDecay( inX, inY, newNum, newContainedETA, inSubCont );
6829
6830 delete [] newContained;
6831 delete [] newContainedETA;
6832 }
6833
6834
6835int getNumContained( int inX, int inY, int inSubCont ) {
6836 int result = dbGet( inX, inY, NUM_CONT_SLOT, inSubCont );
6837
6838 if( result != -1 ) {
6839 // found
6840 return result;
6841 }
6842 else {
6843 // default, empty container
6844 return 0;
6845 }
6846 }
6847
6848
6849
6850
6851
6852
6853void setContained( int inX, int inY, int inNumContained, int *inContained,
6854 int inSubCont ) {
6855 dbPut( inX, inY, NUM_CONT_SLOT, inNumContained, inSubCont );
6856 for( int i=0; i<inNumContained; i++ ) {
6857 changeContained( inX, inY, i, inSubCont, inContained[i] );
6858 }
6859 }
6860
6861
6862void setContainedEtaDecay( int inX, int inY, int inNumContained,
6863 timeSec_t *inContainedEtaDecay, int inSubCont ) {
6864 char someDecay = false;
6865 for( int i=0; i<inNumContained; i++ ) {
6866 dbTimePut( inX, inY,
6867 getContainerDecaySlot( inX, inY, i, inSubCont,
6868 inNumContained ),
6869 inContainedEtaDecay[i], inSubCont );
6870
6871 if( inContainedEtaDecay[i] != 0 ) {
6872 someDecay = true;
6873 trackETA( inX, inY, i + 1, inContainedEtaDecay[i], inSubCont );
6874 }
6875 }
6876 setSlotItemsNoDecay( inX, inY, inSubCont, !someDecay );
6877 }
6878
6879
6880
6881
6882
6883int getContained( int inX, int inY, int inSlot, int inSubCont ) {
6884 int num = getNumContained( inX, inY, inSubCont );
6885
6886 if( num == 0 ) {
6887 return 0;
6888 }
6889
6890 if( inSlot == -1 || inSlot > num - 1 ) {
6891 inSlot = num - 1;
6892 }
6893
6894
6895 int result = dbGet( inX, inY, FIRST_CONT_SLOT + inSlot, inSubCont );
6896
6897 if( result != -1 ) {
6898 return result;
6899 }
6900 else {
6901 // nothing in that slot
6902 return 0;
6903 }
6904 }
6905
6906
6907
6908
6909// removes from top of stack
6910int removeContained( int inX, int inY, int inSlot, timeSec_t *outEtaDecay,
6911 int inSubCont ) {
6912 int num = getNumContained( inX, inY, inSubCont );
6913
6914 if( num == 0 ) {
6915 return 0;
6916 }
6917
6918 if( inSlot == -1 || inSlot > num - 1 ) {
6919 inSlot = num - 1;
6920 }
6921
6922
6923 int result = dbGet( inX, inY, FIRST_CONT_SLOT + inSlot, inSubCont );
6924
6925 timeSec_t curTime = MAP_TIMESEC;
6926
6927 timeSec_t resultEta = dbTimeGet( inX, inY,
6928 getContainerDecaySlot(
6929 inX, inY, inSlot, inSubCont, num ),
6930 inSubCont );
6931
6932 if( resultEta != 0 ) {
6933 timeSec_t etaOffset = resultEta - curTime;
6934
6935 etaOffset = etaOffset * getMapContainerTimeStretch( inX, inY );
6936
6937 resultEta = curTime + etaOffset;
6938 }
6939
6940 *outEtaDecay = resultEta;
6941
6942 int oldNum;
6943 int *oldContained = getContained( inX, inY, &oldNum, inSubCont );
6944
6945 timeSec_t *oldContainedETA = getContainedEtaDecay( inX, inY, &oldNum,
6946 inSubCont );
6947
6948 SimpleVector<int> newContainedList;
6949 SimpleVector<timeSec_t> newContainedETAList;
6950
6951 SimpleVector<int> newSubContainedNumList;
6952 SimpleVector<int*> newSubContainedList;
6953 SimpleVector<timeSec_t*> newSubContainedEtaList;
6954
6955
6956 for( int i=0; i<oldNum; i++ ) {
6957 if( i != inSlot ) {
6958 newContainedList.push_back( oldContained[i] );
6959 newContainedETAList.push_back( oldContainedETA[i] );
6960
6961 if( inSubCont == 0 ) {
6962 int num;
6963
6964 newSubContainedList.push_back(
6965 getContained( inX, inY, &num, i + 1 ) );
6966 newSubContainedNumList.push_back( num );
6967
6968 newSubContainedEtaList.push_back(
6969 getContainedEtaDecay( inX, inY, &num, i + 1 ) );
6970 }
6971 }
6972 }
6973 clearAllContained( inX, inY );
6974
6975 int *newContained = newContainedList.getElementArray();
6976 timeSec_t *newContainedETA = newContainedETAList.getElementArray();
6977
6978 int newNum = oldNum - 1;
6979
6980 setContained( inX, inY, newNum, newContained, inSubCont );
6981 setContainedEtaDecay( inX, inY, newNum, newContainedETA, inSubCont );
6982
6983 if( inSubCont == 0 ) {
6984 for( int i=0; i<newNum; i++ ) {
6985 int *idList = newSubContainedList.getElementDirect( i );
6986 timeSec_t *etaList = newSubContainedEtaList.getElementDirect( i );
6987
6988 if( idList != NULL ) {
6989 int num = newSubContainedNumList.getElementDirect( i );
6990
6991 setContained( inX, inY, num, idList, i + 1 );
6992 setContainedEtaDecay( inX, inY, num, etaList, i + 1 );
6993
6994 delete [] idList;
6995 delete [] etaList;
6996 }
6997 }
6998 }
6999
7000
7001
7002 delete [] oldContained;
7003 delete [] oldContainedETA;
7004 delete [] newContained;
7005 delete [] newContainedETA;
7006
7007 if( result != -1 ) {
7008 return result;
7009 }
7010 else {
7011 // nothing in that slot
7012 return 0;
7013 }
7014 }
7015
7016
7017
7018void clearAllContained( int inX, int inY, int inSubCont ) {
7019 int oldNum = getNumContained( inX, inY, inSubCont );
7020
7021 if( inSubCont == 0 ) {
7022 // clear sub container slots too, if any
7023
7024 for( int i=0; i<oldNum; i++ ) {
7025 if( getNumContained( inX, inY, i + 1 ) > 0 ) {
7026 dbPut( inX, inY, NUM_CONT_SLOT, 0, i + 1 );
7027 }
7028 }
7029 }
7030
7031 if( oldNum != 0 ) {
7032 dbPut( inX, inY, NUM_CONT_SLOT, 0, inSubCont );
7033 }
7034 }
7035
7036
7037
7038
7039#include "spiral.h"
7040
7041
7042void shrinkContainer( int inX, int inY, int inNumNewSlots, int inSubCont ) {
7043 int oldNum = getNumContained( inX, inY, inSubCont );
7044
7045 if( oldNum > inNumNewSlots ) {
7046
7047 // first, scatter extra contents into empty nearby spots.
7048 int nextSprialIndex = 1;
7049
7050 for( int i=inNumNewSlots; i<oldNum; i++ ) {
7051
7052 int contID = getContained( inX, inY, i, inSubCont );
7053
7054 char subCont = false;
7055
7056 if( contID < 0 ) {
7057 contID *= -1;
7058 subCont = true;
7059 }
7060
7061 int emptyX, emptyY;
7062 char foundEmpty = false;
7063
7064 GridPos center = { inX, inY };
7065
7066 while( !foundEmpty ) {
7067 GridPos sprialPoint = getSpriralPoint( center,
7068 nextSprialIndex );
7069 if( getMapObjectRaw( sprialPoint.x, sprialPoint.y ) == 0 ) {
7070 emptyX = sprialPoint.x;
7071 emptyY = sprialPoint.y;
7072 foundEmpty = true;
7073 }
7074 nextSprialIndex ++;
7075 }
7076
7077 if( foundEmpty ) {
7078 setMapObject( emptyX, emptyY, contID );
7079
7080 if( subCont ) {
7081 int numSub = getNumContained( inX, inY, i + 1 );
7082
7083 for( int s=0; s<numSub; s++ ) {
7084 addContained( emptyX, emptyY,
7085 getContained( inX, inY, s, i + 1 ),
7086 getSlotEtaDecay( inX, inY,
7087 s, i + 1 ) );
7088 }
7089 }
7090 }
7091 }
7092
7093
7094 // now clear old extra contents from original spot
7095 dbPut( inX, inY, NUM_CONT_SLOT, inNumNewSlots, inSubCont );
7096
7097 if( inSubCont == 0 ) {
7098 // clear sub cont slots too
7099 for( int i=inNumNewSlots; i<oldNum; i++ ) {
7100 dbPut( inX, inY, NUM_CONT_SLOT, 0, i + 1 );
7101 }
7102 }
7103
7104 }
7105 }
7106
7107
7108
7109
7110MapChangeRecord getMapChangeRecord( ChangePosition inPos ) {
7111
7112 MapChangeRecord r;
7113 r.absoluteX = inPos.x;
7114 r.absoluteY = inPos.y;
7115 r.oldCoordsUsed = false;
7116
7117 // compose format string
7118 SimpleVector<char> buffer;
7119
7120
7121 char *header = autoSprintf( "%%d %%d %d ",
7122 hideIDForClient(
7123 getMapFloor( inPos.x, inPos.y ) ) );
7124
7125 buffer.appendElementString( header );
7126
7127 delete [] header;
7128
7129
7130 char *idString = autoSprintf( "%d",
7131 hideIDForClient(
7132 getMapObjectNoLook(
7133 inPos.x, inPos.y ) ) );
7134
7135 buffer.appendElementString( idString );
7136
7137 delete [] idString;
7138
7139
7140 int numContained;
7141 int *contained = getContainedNoLook( inPos.x, inPos.y, &numContained );
7142
7143 for( int i=0; i<numContained; i++ ) {
7144
7145 char subCont = false;
7146
7147 if( contained[i] < 0 ) {
7148 subCont = true;
7149 contained[i] *= -1;
7150
7151 }
7152
7153 char *idString = autoSprintf( ",%d", hideIDForClient( contained[i] ) );
7154
7155 buffer.appendElementString( idString );
7156
7157 delete [] idString;
7158
7159 if( subCont ) {
7160
7161 int numSubContained;
7162 int *subContained = getContainedNoLook( inPos.x, inPos.y,
7163 &numSubContained,
7164 i + 1 );
7165 for( int s=0; s<numSubContained; s++ ) {
7166
7167 idString = autoSprintf( ":%d",
7168 hideIDForClient( subContained[s] ) );
7169
7170 buffer.appendElementString( idString );
7171
7172 delete [] idString;
7173 }
7174 if( subContained != NULL ) {
7175 delete [] subContained;
7176 }
7177 }
7178
7179 }
7180
7181 if( contained != NULL ) {
7182 delete [] contained;
7183 }
7184
7185
7186 char *player = autoSprintf( " %d", inPos.responsiblePlayerID );
7187
7188 buffer.appendElementString( player );
7189
7190 delete [] player;
7191
7192
7193 if( inPos.speed > 0 ) {
7194 r.absoluteOldX = inPos.oldX;
7195 r.absoluteOldY = inPos.oldY;
7196 r.oldCoordsUsed = true;
7197
7198 char *moveString = autoSprintf( " %%d %%d %f",
7199 inPos.speed );
7200
7201 buffer.appendElementString( moveString );
7202
7203 delete [] moveString;
7204 }
7205
7206 buffer.appendElementString( "\n" );
7207
7208 r.formatString = buffer.getElementString();
7209
7210 return r;
7211 }
7212
7213
7214
7215
7216
7217char *getMapChangeLineString( ChangePosition inPos ) {
7218 MapChangeRecord r = getMapChangeRecord( inPos );
7219
7220 char *lineString = getMapChangeLineString( &r, 0, 0 );
7221
7222 delete [] r.formatString;
7223
7224 return lineString;
7225 }
7226
7227
7228
7229
7230char *getMapChangeLineString( MapChangeRecord *inRecord,
7231 int inRelativeToX, int inRelativeToY ) {
7232
7233 char *lineString;
7234
7235 if( inRecord->oldCoordsUsed ) {
7236 lineString = autoSprintf( inRecord->formatString,
7237 inRecord->absoluteX - inRelativeToX,
7238 inRecord->absoluteY - inRelativeToY,
7239 inRecord->absoluteOldX - inRelativeToX,
7240 inRecord->absoluteOldY - inRelativeToY );
7241 }
7242 else {
7243 lineString = autoSprintf( inRecord->formatString,
7244 inRecord->absoluteX - inRelativeToX,
7245 inRecord->absoluteY - inRelativeToY );
7246 }
7247
7248 return lineString;
7249 }
7250
7251
7252
7253
7254
7255int getMapFloor( int inX, int inY ) {
7256 int id = dbFloorGet( inX, inY );
7257
7258 if( id <= 0 ) {
7259 return 0;
7260 }
7261
7262 TransRecord *t = getPTrans( -1, id );
7263
7264 if( t == NULL ) {
7265 // no auto-decay for this floor
7266 return id;
7267 }
7268
7269 timeSec_t etaTime = getFloorEtaDecay( inX, inY );
7270
7271 timeSec_t curTime = MAP_TIMESEC;
7272
7273
7274 if( etaTime == 0 ) {
7275 // not set
7276 // start decay now for future
7277
7278 setFloorEtaDecay( inX, inY, curTime + t->autoDecaySeconds );
7279
7280 return id;
7281 }
7282
7283
7284 if( etaTime > curTime ) {
7285 return id;
7286 }
7287
7288 // else eta expired, apply decay
7289
7290 int newID = t->newTarget;
7291
7292 setMapFloor( inX, inY, newID );
7293
7294 return newID;
7295 }
7296
7297
7298
7299void setMapFloor( int inX, int inY, int inID ) {
7300
7301 logMapChange( inX, inY, inID );
7302
7303
7304 dbFloorPut( inX, inY, inID );
7305
7306
7307 // further decay from here
7308 TransRecord *newT = getMetaTrans( -1, inID );
7309
7310 timeSec_t newEta = 0;
7311
7312 if( newT != NULL ) {
7313 timeSec_t curTime = MAP_TIMESEC;
7314 newEta = curTime + newT->autoDecaySeconds;
7315 }
7316
7317 setFloorEtaDecay( inX, inY, newEta );
7318 }
7319
7320
7321
7322void setFloorEtaDecay( int inX, int inY, timeSec_t inAbsoluteTimeInSeconds ) {
7323 dbFloorTimePut( inX, inY, inAbsoluteTimeInSeconds );
7324 }
7325
7326
7327timeSec_t getFloorEtaDecay( int inX, int inY ) {
7328 return dbFloorTimeGet( inX, inY );
7329 }
7330
7331
7332
7333
7334
7335int getNextDecayDelta() {
7336 if( liveDecayQueue.size() == 0 ) {
7337 return -1;
7338 }
7339
7340 timeSec_t curTime = MAP_TIMESEC;
7341
7342 timeSec_t minTime = liveDecayQueue.checkMinPriority();
7343
7344
7345 if( minTime <= curTime ) {
7346 return 0;
7347 }
7348
7349 return minTime - curTime;
7350 }
7351
7352
7353
7354
7355static void cleanMaxContainedHashTable( int inX, int inY ) {
7356
7357 ContRecord *oldCount =
7358 liveDecayRecordLastLookTimeMaxContainedHashTable.
7359 lookupPointer( inX, inY, 0, 0 );
7360
7361 if( oldCount != NULL ) {
7362
7363 int maxFoundSlot = 0;
7364 int maxFoundSubSlot = 0;
7365
7366 for( int c=1; c<= oldCount->maxSlots; c++ ) {
7367
7368 for( int s=0; s<= oldCount->maxSubSlots; s++ ) {
7369 timeSec_t *val =
7370 liveDecayRecordLastLookTimeHashTable.lookupPointer(
7371 inX, inY, c, s );
7372
7373 if( val != NULL ) {
7374 maxFoundSlot = c;
7375 maxFoundSubSlot = s;
7376 }
7377 }
7378 }
7379
7380
7381 if( maxFoundSlot == 0 && maxFoundSubSlot == 0 ) {
7382 liveDecayRecordLastLookTimeMaxContainedHashTable.
7383 remove( inX, inY, 0, 0 );
7384 }
7385 else {
7386 if( maxFoundSlot < oldCount->maxSlots ) {
7387 oldCount->maxSlots = maxFoundSlot;
7388 }
7389 if( maxFoundSubSlot < oldCount->maxSubSlots ) {
7390 oldCount->maxSubSlots = maxFoundSubSlot;
7391 }
7392 }
7393
7394
7395 }
7396 }
7397
7398
7399
7400
7401void stepMap( SimpleVector<MapChangeRecord> *inMapChanges,
7402 SimpleVector<ChangePosition> *inChangePosList ) {
7403
7404 timeSec_t curTime = MAP_TIMESEC;
7405
7406
7407 lookTimeTracking.cleanStale( curTime - noLookCountAsStaleSeconds );
7408
7409
7410 while( liveDecayQueue.size() > 0 &&
7411 liveDecayQueue.checkMinPriority() <= curTime ) {
7412
7413 // another expired
7414
7415 LiveDecayRecord r = liveDecayQueue.removeMin();
7416
7417 char storedFound;
7418 timeSec_t storedETA =
7419 liveDecayRecordPresentHashTable.lookup( r.x, r.y, r.slot,
7420 r.subCont,
7421 &storedFound );
7422
7423 if( storedFound && storedETA == r.etaTimeSeconds ) {
7424
7425 liveDecayRecordPresentHashTable.remove( r.x, r.y, r.slot,
7426 r.subCont );
7427
7428
7429 timeSec_t lastLookTime =
7430 liveDecayRecordLastLookTimeHashTable.lookup( r.x, r.y, r.slot,
7431 r.subCont,
7432 &storedFound );
7433
7434 if( storedFound ) {
7435
7436 if( MAP_TIMESEC - lastLookTime >
7437 maxSecondsNoLookDecayTracking
7438 &&
7439 ! isDecayTransAlwaysLiveTracked( r.applicableTrans ) ) {
7440
7441 // this cell or slot hasn't been looked at in too long
7442 // AND it's not a trans that's live tracked even when
7443 // not watched
7444
7445 // don't even apply this decay now
7446 liveDecayRecordLastLookTimeHashTable.remove(
7447 r.x, r.y, r.slot, r.subCont );
7448 cleanMaxContainedHashTable( r.x, r.y );
7449 continue;
7450 }
7451 // else keep lastlook time around in case
7452 // this cell will decay further and we're still tracking it
7453 // (but maybe delete it if cell is no longer tracked, below)
7454 }
7455 }
7456
7457 if( r.slot == 0 ) {
7458
7459
7460 int oldID = getMapObjectRaw( r.x, r.y );
7461
7462 // apply real eta from map (to ignore stale duplicates in live list)
7463 // and update live list if new object is decaying too
7464
7465
7466 // this call will append changes to our global lists, which
7467 // we process below
7468 checkDecayObject( r.x, r.y, oldID );
7469 }
7470 else {
7471 if( ! getSlotItemsNoDecay( r.x, r.y, r.subCont ) ) {
7472 checkDecayContained( r.x, r.y, r.subCont );
7473 }
7474 }
7475
7476
7477 char stillExists;
7478 liveDecayRecordPresentHashTable.lookup( r.x, r.y, r.slot, r.subCont,
7479 &stillExists );
7480
7481 if( !stillExists ) {
7482 // cell or slot no longer tracked
7483 // forget last look time
7484 liveDecayRecordLastLookTimeHashTable.remove(
7485 r.x, r.y, r.slot, r.subCont );
7486
7487 cleanMaxContainedHashTable( r.x, r.y );
7488 }
7489 }
7490
7491
7492 while( liveMovements.size() > 0 &&
7493 liveMovements.checkMinPriority() <= curTime ) {
7494 MovementRecord r = liveMovements.removeMin();
7495 liveMovementEtaTimes.remove( r.x, r.y, 0, 0 );
7496 }
7497
7498
7499 // all of them, including these new ones and others acculuated since
7500 // last step are accumulated in these global vectors
7501
7502 int numPos = mapChangePosSinceLastStep.size();
7503
7504 for( int i=0; i<numPos; i++ ) {
7505 ChangePosition p = mapChangePosSinceLastStep.getElementDirect(i);
7506
7507 inChangePosList->push_back( p );
7508
7509 MapChangeRecord changeRecord = getMapChangeRecord( p );
7510 inMapChanges->push_back( changeRecord );
7511 }
7512
7513
7514 mapChangePosSinceLastStep.deleteAll();
7515 }
7516
7517
7518
7519
7520void restretchDecays( int inNumDecays, timeSec_t *inDecayEtas,
7521 int inOldContainerID, int inNewContainerID ) {
7522
7523 float oldStrech = getObject( inOldContainerID )->slotTimeStretch;
7524 float newStetch = getObject( inNewContainerID )->slotTimeStretch;
7525
7526 if( oldStrech != newStetch ) {
7527 timeSec_t curTime = MAP_TIMESEC;
7528
7529 for( int i=0; i<inNumDecays; i++ ) {
7530 if( inDecayEtas[i] != 0 ) {
7531 int offset = inDecayEtas[i] - curTime;
7532
7533 offset = offset * oldStrech;
7534 offset = offset / newStetch;
7535 inDecayEtas[i] = curTime + offset;
7536 }
7537 }
7538 }
7539 }
7540
7541
7542
7543void restretchMapContainedDecays( int inX, int inY,
7544 int inOldContainerID,
7545 int inNewContainerID, int inSubCont ) {
7546
7547 float oldStrech = getObject( inOldContainerID )->slotTimeStretch;
7548 float newStetch = getObject( inNewContainerID )->slotTimeStretch;
7549
7550 if( oldStrech != newStetch ) {
7551
7552 int oldNum;
7553 timeSec_t *oldContDecay =
7554 getContainedEtaDecay( inX, inY, &oldNum, inSubCont );
7555
7556 restretchDecays( oldNum, oldContDecay,
7557 inOldContainerID, inNewContainerID );
7558
7559 setContainedEtaDecay( inX, inY, oldNum, oldContDecay, inSubCont );
7560 delete [] oldContDecay;
7561 }
7562 }
7563
7564
7565
7566
7567doublePair computeRecentCampAve( int *outNumPosFound ) {
7568 SimpleVector<doublePair> pos;
7569 SimpleVector<double> weight;
7570
7571 doublePair sum = {0,0};
7572
7573 double weightSum = 0;
7574
7575 // the exponent that we raise depth to in order to squash
7576 // down higher values
7577 double depthFactor = 0.5;
7578
7579 for( int i=0; i<NUM_RECENT_PLACEMENTS; i++ ) {
7580 if( recentPlacements[i].pos.x != 0 ||
7581 recentPlacements[i].pos.y != 0 ) {
7582
7583 doublePair p = { (double)( recentPlacements[i].pos.x ),
7584 (double)( recentPlacements[i].pos.y ) };
7585
7586 pos.push_back( p );
7587
7588 // natural objects can be moved around, and they have depth 0
7589 // this can result in a total weight sum of 0, causing NAN
7590 // push all depths up to 1 or greater
7591 int d = recentPlacements[i].depth + 1;
7592
7593 double w = pow( d, depthFactor );
7594
7595 weight.push_back( w );
7596
7597 // weighted sum, with deeper objects weighing more
7598 sum = add( sum, mult( p, w ) );
7599
7600 weightSum += w;
7601 }
7602 }
7603
7604
7605 *outNumPosFound = pos.size();
7606
7607 if( pos.size() == 0 ) {
7608 doublePair zeroPos = { 0, 0 };
7609 pos.push_back( zeroPos );
7610 weight.push_back( 1 );
7611 weightSum += 1;
7612 }
7613
7614
7615 doublePair ave = mult( sum, 1.0 / weightSum );
7616
7617 double maxDist = 2.0 * campRadius;
7618
7619 while( maxDist > campRadius ) {
7620
7621 maxDist = 0;
7622 int maxI = -1;
7623
7624 for( int i=0; i<pos.size(); i++ ) {
7625
7626 double d = distance( pos.getElementDirect( i ), ave );
7627
7628 if( d > maxDist ) {
7629 maxDist = d;
7630 maxI = i;
7631 }
7632 }
7633
7634 if( maxDist > campRadius ) {
7635
7636 double w = weight.getElementDirect( maxI );
7637
7638 sum = sub( sum, mult( pos.getElementDirect( maxI ), w ) );
7639
7640 pos.deleteElement( maxI );
7641 weight.deleteElement( maxI );
7642
7643 weightSum -= w;
7644
7645 ave = mult( sum, 1.0 / weightSum );
7646 }
7647 }
7648
7649 printf( "Found an existing camp at (%f,%f) with %d placements "
7650 "and %f max radius\n",
7651 ave.x, ave.y, pos.size(), maxDist );
7652
7653
7654 // ave is now center of camp
7655 return ave;
7656 }
7657
7658
7659
7660
7661extern char doesEveLineExist( int inEveID );
7662
7663
7664
7665void getEvePosition( const char *inEmail, int inID, int *outX, int *outY,
7666 SimpleVector<GridPos> *inOtherPeoplePos,
7667 char inAllowRespawn ) {
7668
7669 int currentEveRadius = eveRadius;
7670
7671 char forceEveToBorder = false;
7672
7673 doublePair ave = { 0, 0 };
7674
7675 printf( "Placing new Eve: " );
7676
7677
7678 int pX, pY, pR;
7679
7680 int result = eveDBGet( inEmail, &pX, &pY, &pR );
7681
7682 if( inAllowRespawn && result == 1 && pR > 0 ) {
7683 printf( "Found camp center (%d,%d) r=%d in db for %s\n",
7684 pX, pY, pR, inEmail );
7685
7686 ave.x = pX;
7687 ave.y = pY;
7688 currentEveRadius = pR;
7689 }
7690 else {
7691 // player has never been an Eve that survived to old age before
7692 // or such repawning forbidden by caller
7693
7694 maxEveLocationUsage =
7695 SettingsManager::getIntSetting( "maxEveStartupLocationUsage", 10 );
7696
7697
7698 // first try new grid placement method
7699
7700
7701 // actually skip this for now and go back to normal Eve spiral
7702 if( false )
7703 if( eveLocationUsage >= maxEveLocationUsage
7704 && evePrimaryLocObjectID > 0 ) {
7705
7706 GridPos centerP = lastEvePrimaryLocation;
7707
7708 if( inOtherPeoplePos->size() > 0 ) {
7709
7710 centerP = inOtherPeoplePos->getElementDirect(
7711 randSource.getRandomBoundedInt(
7712 0, inOtherPeoplePos->size() - 1 ) );
7713
7714 // round to nearest whole spacing multiple
7715 centerP.x /= evePrimaryLocSpacing;
7716 centerP.y /= evePrimaryLocSpacing;
7717
7718 centerP.x *= evePrimaryLocSpacing;
7719 centerP.y *= evePrimaryLocSpacing;
7720 }
7721
7722
7723 GridPos tryP = centerP;
7724 char found = false;
7725 GridPos foundP = tryP;
7726
7727 double curTime = Time::getCurrentTime();
7728
7729 int r;
7730
7731 int maxSearchRadius = 10;
7732
7733
7734 // first, clean any that have timed out
7735 // or gone extinct
7736 for( int p=0; p<recentlyUsedPrimaryEvePositions.size();
7737 p++ ) {
7738
7739 char reusePos = false;
7740
7741 if( curTime -
7742 recentlyUsedPrimaryEvePositionTimes.
7743 getElementDirect( p )
7744 > recentlyUsedPrimaryEvePositionTimeout ) {
7745 // timed out
7746 reusePos = true;
7747 }
7748 else if( ! doesEveLineExist(
7749 recentlyUsedPrimaryEvePositionPlayerIDs.
7750 getElementDirect( p ) ) ) {
7751 // eve line extinct
7752 reusePos = true;
7753 }
7754
7755 if( reusePos ) {
7756 recentlyUsedPrimaryEvePositions.
7757 deleteElement( p );
7758 recentlyUsedPrimaryEvePositionTimes.
7759 deleteElement( p );
7760 recentlyUsedPrimaryEvePositionPlayerIDs.
7761 deleteElement( p );
7762 p--;
7763 }
7764 }
7765
7766
7767 for( r=1; r<maxSearchRadius; r++ ) {
7768
7769 for( int y=-r; y<=r; y++ ) {
7770 for( int x=-r; x<=r; x++ ) {
7771 tryP = centerP;
7772
7773 tryP.x += x * evePrimaryLocSpacing;
7774 tryP.y += y * evePrimaryLocSpacing;
7775
7776 char existsAlready = false;
7777
7778 for( int p=0; p<recentlyUsedPrimaryEvePositions.size();
7779 p++ ) {
7780
7781 GridPos pos =
7782 recentlyUsedPrimaryEvePositions.
7783 getElementDirect( p );
7784
7785 if( equal( pos, tryP ) ) {
7786 existsAlready = true;
7787 break;
7788 }
7789 }
7790
7791 if( existsAlready ) {
7792 continue;
7793 }
7794 else {
7795 }
7796
7797
7798 int mapID = getMapObject( tryP.x, tryP.y );
7799
7800 if( mapID == evePrimaryLocObjectID ) {
7801 printf( "Found primary Eve object at %d,%d\n",
7802 tryP.x, tryP.y );
7803 found = true;
7804 foundP = tryP;
7805 }
7806 else if( eveSecondaryLocObjectIDs.getElementIndex(
7807 mapID ) != -1 ) {
7808 // a secondary ID, allowed
7809 printf( "Found secondary Eve object at %d,%d\n",
7810 tryP.x, tryP.y );
7811 found = true;
7812 foundP = tryP;
7813 }
7814 }
7815
7816 if( found ) break;
7817 }
7818 if( found ) break;
7819 }
7820
7821 if( found ) {
7822
7823 if( r >= maxSearchRadius / 2 ) {
7824 // exhausted central window around last eve center
7825 // save this as the new eve center
7826 // next time, we'll search a window around that
7827
7828 AppLog::infoF( "Eve pos %d,%d not in center of "
7829 "grid window, recentering window for "
7830 "next time", foundP.x, foundP.y );
7831
7832 lastEvePrimaryLocation = foundP;
7833 }
7834
7835 AppLog::infoF( "Sticking Eve at unused primary grid pos "
7836 "of %d,%d\n",
7837 foundP.x, foundP.y );
7838
7839 recentlyUsedPrimaryEvePositions.push_back( foundP );
7840 recentlyUsedPrimaryEvePositionTimes.push_back( curTime );
7841 recentlyUsedPrimaryEvePositionPlayerIDs.push_back( inID );
7842
7843 // stick Eve directly to south
7844 *outX = foundP.x;
7845 *outY = foundP.y - 1;
7846
7847 if( eveHomeMarkerObjectID > 0 ) {
7848 // stick home marker there
7849 setMapObject( *outX, *outY, eveHomeMarkerObjectID );
7850 }
7851 else {
7852 // make it empty
7853 setMapObject( *outX, *outY, 0 );
7854 }
7855
7856 // clear a few more objects to the south, to make
7857 // sure Eve's spring doesn't spawn behind a tree
7858 setMapObject( *outX, *outY - 1, 0 );
7859 setMapObject( *outX, *outY - 2, 0 );
7860 setMapObject( *outX, *outY - 3, 0 );
7861
7862
7863 // finally, prevent Eve entrapment by sticking
7864 // her at a random location around the spring
7865
7866 doublePair v = { 14, 0 };
7867 v = rotate( v, randSource.getRandomBoundedInt( 0, 2 * M_PI ) );
7868
7869 *outX += v.x;
7870 *outY += v.y;
7871
7872 return;
7873 }
7874 else {
7875 AppLog::info( "Unable to find location for Eve "
7876 "on primary grid." );
7877 }
7878 }
7879
7880
7881 // Spiral method:
7882 GridPos eveLocToUse = eveLocation;
7883
7884 int jumpUsed = 0;
7885
7886 if( eveLocationUsage < maxEveLocationUsage ) {
7887 eveLocationUsage++;
7888 // keep using same location
7889
7890 printf( "Reusing same eve start-up location "
7891 "of %d,%d for %dth time\n",
7892 eveLocation.x, eveLocation.y, eveLocationUsage );
7893
7894
7895 // remember it for when we exhaust it
7896 if( evePrimaryLocObjectID > 0 &&
7897 evePrimaryLocSpacing > 0 ) {
7898
7899 lastEvePrimaryLocation = eveLocation;
7900 // round to nearest whole spacing multiple
7901 lastEvePrimaryLocation.x /= evePrimaryLocSpacing;
7902 lastEvePrimaryLocation.y /= evePrimaryLocSpacing;
7903
7904 lastEvePrimaryLocation.x *= evePrimaryLocSpacing;
7905 lastEvePrimaryLocation.y *= evePrimaryLocSpacing;
7906
7907 printf( "Saving eve start-up location close grid pos "
7908 "of %d,%d for later\n",
7909 lastEvePrimaryLocation.x, lastEvePrimaryLocation.y );
7910 }
7911
7912 }
7913 else {
7914 // post-startup eve location has been used too many times
7915 // place eves on spiral instead
7916
7917 if( abs( eveLocToUse.x ) > 100000 ||
7918 abs( eveLocToUse.y ) > 100000 ) {
7919 // we've gotten to far away from center over time
7920
7921 // re-center spiral on center to rein things in
7922
7923 // we'll end up saving a position on the arm of this new
7924 // centered spiral for future start-ups, so the eve
7925 // location can move out from here
7926 eveLocToUse.x = 0;
7927 eveLocToUse.y = 0;
7928
7929 eveStartSpiralPosSet = false;
7930 }
7931
7932
7933
7934 if( eveStartSpiralPosSet &&
7935 longTermCullEnabled ) {
7936
7937 int longTermCullingSeconds =
7938 SettingsManager::getIntSetting(
7939 "longTermNoLookCullSeconds", 3600 * 12 );
7940
7941 // see how long center has not been seen
7942 // if it's old enough, we can reset Eve angle and restart
7943 // spiral there again
7944 // this will bring Eves closer together again, after
7945 // rim of spiral gets too far away
7946
7947 timeSec_t lastLookTime =
7948 dbLookTimeGet( eveStartSpiralPos.x,
7949 eveStartSpiralPos.y );
7950
7951 if( Time::getCurrentTime() - lastLookTime >
7952 longTermCullingSeconds * 2 ) {
7953 // double cull start time
7954 // that should be enough for the center to actually have
7955 // started getting culled, and then some
7956
7957 // restart the spiral
7958 eveAngle = 2 * M_PI;
7959 eveLocToUse = eveLocation;
7960
7961 eveStartSpiralPosSet = false;
7962 }
7963 }
7964
7965
7966 int jump = SettingsManager::getIntSetting( "nextEveJump", 2000 );
7967 jumpUsed = jump;
7968
7969 // advance eve angle along spiral
7970 // approximate recursive form
7971 eveAngle = eveAngle + ( 2 * M_PI ) / eveAngle;
7972
7973 // exact formula for radius along spiral from angle
7974 double radius = ( jump * eveAngle ) / ( 2 * M_PI );
7975
7976
7977
7978 doublePair delta = { radius, 0 };
7979 delta = rotate( delta, eveAngle );
7980
7981 // but don't update the post-startup location
7982 // keep jumping away from startup-location as center of spiral
7983 eveLocToUse.x += lrint( delta.x );
7984 eveLocToUse.y += lrint( delta.y );
7985
7986
7987 if( barrierOn &&
7988 // we use jumpUsed / 3 as randomizing radius below
7989 // so jumpUsed / 2 is safe here
7990 ( abs( eveLocToUse.x ) > barrierRadius - jumpUsed / 2 ||
7991 abs( eveLocToUse.y ) > barrierRadius - jumpUsed / 2 ) ) {
7992
7993 // Eve has gotten too close to the barrier
7994
7995 // hard reset of location back to (0,0)-centered spiral
7996 eveAngle = 2 * M_PI;
7997
7998 eveLocation.x = 0;
7999 eveLocation.y = 0;
8000 eveLocToUse = eveLocation;
8001
8002 eveStartSpiralPosSet = false;
8003 }
8004
8005
8006
8007
8008 // but do save it as a possible post-startup location for next time
8009 File eveLocFile( NULL, "lastEveLocation.txt" );
8010 char *locString =
8011 autoSprintf( "%d,%d", eveLocToUse.x, eveLocToUse.y );
8012 eveLocFile.writeToFile( locString );
8013 delete [] locString;
8014 }
8015
8016 ave.x = eveLocToUse.x;
8017 ave.y = eveLocToUse.y;
8018
8019
8020
8021
8022 // put Eve in radius 50 around this location
8023 forceEveToBorder = true;
8024 currentEveRadius = 50;
8025
8026 if( jumpUsed > 3 && currentEveRadius > jumpUsed / 3 ) {
8027 currentEveRadius = jumpUsed / 3;
8028 }
8029 }
8030
8031
8032
8033
8034
8035 // pick point in box according to eve radius
8036
8037
8038 char found = 0;
8039
8040 if( currentEveRadius < 1 ) {
8041 currentEveRadius = 1;
8042 }
8043
8044 while( !found ) {
8045 printf( "Placing new Eve: "
8046 "trying radius of %d from camp\n", currentEveRadius );
8047
8048 int tryCount = 0;
8049
8050 while( !found && tryCount < 100 ) {
8051
8052 doublePair p = {
8053 randSource.getRandomBoundedDouble(-currentEveRadius,
8054 +currentEveRadius ),
8055 randSource.getRandomBoundedDouble(-currentEveRadius,
8056 +currentEveRadius ) };
8057
8058
8059 if( forceEveToBorder ) {
8060 // or pick ap point on the circle instead
8061 p.x = currentEveRadius;
8062 p.y = 0;
8063
8064 double a = randSource.getRandomBoundedDouble( 0, 2 * M_PI );
8065 p = rotate( p, a );
8066 }
8067
8068
8069 p = add( p, ave );
8070
8071 GridPos pInt = { (int)lrint( p.x ), (int)lrint( p.y ) };
8072
8073 if( getMapObjectRaw( pInt.x, pInt.y ) == 0 ) {
8074
8075 *outX = pInt.x;
8076 *outY = pInt.y;
8077
8078 if( ! eveStartSpiralPosSet ) {
8079 eveStartSpiralPos = pInt;
8080 eveStartSpiralPosSet = true;
8081 }
8082
8083 found = true;
8084 }
8085
8086 tryCount++;
8087 }
8088
8089 // tried too many times, expand radius
8090 currentEveRadius *= 2;
8091
8092 }
8093
8094 // clear recent placements after placing a new Eve
8095 // let her make new placements in her life which we will remember
8096 // later
8097
8098 clearRecentPlacements();
8099 }
8100
8101
8102
8103
8104void mapEveDeath( const char *inEmail, double inAge, GridPos inDeathMapPos ) {
8105
8106 // record exists?
8107
8108 int pX, pY, pR;
8109
8110 pR = eveRadius;
8111
8112 printf( "Logging Eve death: " );
8113
8114
8115 if( inAge < minEveCampRespawnAge ) {
8116 printf( "Eve died too young (age=%f, min=%f), "
8117 "not remembering her camp, and clearing any old camp memory\n",
8118 inAge, minEveCampRespawnAge );
8119
8120 // 0 for radius means not set
8121 eveDBPut( inEmail, 0, 0, 0 );
8122
8123 return;
8124 }
8125
8126
8127
8128 int result = eveDBGet( inEmail, &pX, &pY, &pR );
8129
8130 if( result == 1 && pR > 0 ) {
8131
8132 // don't keep growing radius after it gets too big
8133 // if one player is dying young over and over, they will
8134 // eventually overflow 32-bit integers
8135
8136 if( inAge < 16 && pR < 1024 ) {
8137 pR *= 2;
8138 }
8139 else if( inAge > 20 ) {
8140 pR = eveRadiusStart;
8141 }
8142 }
8143 else {
8144 // not found in DB
8145
8146 // must overwrite no matter what
8147 pR = eveRadiusStart;
8148 }
8149
8150
8151 // their next camp will start where they last died
8152 pX = inDeathMapPos.x;
8153 pY = inDeathMapPos.y;
8154
8155
8156 printf( "Remembering Eve's camp in database (%d,%d) r=%d for %s\n",
8157 pX, pY, pR, inEmail );
8158
8159 eveDBPut( inEmail, pX, pY, pR );
8160 }
8161
8162
8163
8164
8165static unsigned int nextLoadID = 0;
8166
8167
8168char loadTutorialStart( TutorialLoadProgress *inTutorialLoad,
8169 const char *inMapFileName, int inX, int inY ) {
8170
8171 // don't open file yet, because we don't want to have the same
8172 // file open in parallel
8173
8174 // save info to open file on first step, which is called one player at a
8175 // time
8176 inTutorialLoad->uniqueLoadID = nextLoadID++;
8177 inTutorialLoad->fileOpened = false;
8178 inTutorialLoad->file = NULL;
8179 inTutorialLoad->mapFileName = stringDuplicate( inMapFileName );
8180 inTutorialLoad->x = inX;
8181 inTutorialLoad->y = inY;
8182 inTutorialLoad->startTime = Time::getCurrentTime();
8183 inTutorialLoad->stepCount = 0;
8184
8185 return true;
8186 }
8187
8188
8189
8190
8191char loadTutorialStep( TutorialLoadProgress *inTutorialLoad,
8192 double inTimeLimitSec ) {
8193
8194 if( ! inTutorialLoad->fileOpened ) {
8195 // first step, open file
8196
8197 char returnVal = false;
8198
8199 // only try opening it once
8200 inTutorialLoad->fileOpened = true;
8201
8202 File tutorialFolder( NULL, "tutorialMaps" );
8203
8204 if( tutorialFolder.exists() && tutorialFolder.isDirectory() ) {
8205
8206 File *mapFile = tutorialFolder.getChildFile(
8207 inTutorialLoad->mapFileName );
8208
8209 if( mapFile->exists() && ! mapFile->isDirectory() ) {
8210 char *fileName = mapFile->getFullFileName();
8211
8212 FILE *file = fopen( fileName, "r" );
8213
8214 if( file != NULL ) {
8215 inTutorialLoad->file = file;
8216
8217 returnVal = true;
8218 }
8219
8220 delete [] fileName;
8221 }
8222 delete mapFile;
8223 }
8224
8225 delete [] inTutorialLoad->mapFileName;
8226
8227 return returnVal;
8228 }
8229
8230
8231 // else file already open
8232
8233 if( inTutorialLoad->file == NULL ) {
8234 // none left
8235 return false;
8236 }
8237
8238 char moreLeft = loadIntoMapFromFile( inTutorialLoad->file,
8239 inTutorialLoad->x, inTutorialLoad->y,
8240 inTimeLimitSec );
8241
8242 inTutorialLoad->stepCount++;
8243
8244
8245 if( ! moreLeft ) {
8246 fclose( inTutorialLoad->file );
8247 inTutorialLoad->file = NULL;
8248 }
8249 return moreLeft;
8250 }
8251
8252
8253
8254
8255
8256
8257
8258
8259char getMetadata( int inMapID, unsigned char *inBuffer ) {
8260 int metaID = extractMetadataID( inMapID );
8261
8262 if( metaID == 0 ) {
8263 return false;
8264 }
8265
8266 // look up in metadata DB
8267 unsigned char key[4];
8268 intToValue( metaID, key );
8269 int result = DB_get( &metaDB, key, inBuffer );
8270
8271 if( result == 0 ) {
8272 return true;
8273 }
8274
8275 return false;
8276 }
8277
8278
8279
8280
8281// returns full map ID with embedded metadata ID for new metadata record
8282int addMetadata( int inObjectID, unsigned char *inBuffer ) {
8283 int metaID = getNewMetadataID();
8284
8285 int mapID = packMetadataID( inObjectID, metaID );
8286
8287 // insert into metadata DB
8288 unsigned char key[4];
8289 intToValue( metaID, key );
8290 DB_put( &metaDB, key, inBuffer );
8291
8292
8293 return mapID;
8294 }
8295
8296
8297
8298
8299static double distSquared( GridPos inA, GridPos inB ) {
8300 double xDiff = (double)inA.x - (double)inB.x;
8301 double yDiff = (double)inA.y - (double)inB.y;
8302
8303 return xDiff * xDiff + yDiff * yDiff;
8304 }
8305
8306
8307
8308
8309void removeLandingPos( GridPos inPos ) {
8310 for( int i=0; i<flightLandingPos.size(); i++ ) {
8311 if( equal( inPos, flightLandingPos.getElementDirect( i ) ) ) {
8312 flightLandingPos.deleteElement( i );
8313 return;
8314 }
8315 }
8316 }
8317
8318
8319char isInDir( GridPos inPos, GridPos inOtherPos, doublePair inDir ) {
8320 double dX = (double)inOtherPos.x - (double)inPos.x;
8321 double dY = (double)inOtherPos.y - (double)inPos.y;
8322
8323 if( inDir.x > 0 && dX > 0 ) {
8324 return true;
8325 }
8326 if( inDir.x < 0 && dX < 0 ) {
8327 return true;
8328 }
8329 if( inDir.y > 0 && dY > 0 ) {
8330 return true;
8331 }
8332 if( inDir.y < 0 && dY < 0 ) {
8333 return true;
8334 }
8335 return false;
8336 }
8337
8338
8339
8340GridPos getNextCloseLandingPos( GridPos inCurPos,
8341 doublePair inDir,
8342 char *outFound ) {
8343
8344 int closestIndex = -1;
8345 GridPos closestPos;
8346 double closestDist = DBL_MAX;
8347
8348 for( int i=0; i<flightLandingPos.size(); i++ ) {
8349 GridPos thisPos = flightLandingPos.getElementDirect( i );
8350
8351 if( tooClose( inCurPos, thisPos, 250 ) ) {
8352 // don't consider landing at spots closer than 250,250 manhattan
8353 // to takeoff spot
8354 continue;
8355 }
8356
8357
8358 if( isInDir( inCurPos, thisPos, inDir ) ) {
8359 double dist = distSquared( inCurPos, thisPos );
8360
8361 if( dist < closestDist ) {
8362 // check if this is still a valid landing pos
8363 int oID = getMapObject( thisPos.x, thisPos.y );
8364
8365 if( oID <=0 ||
8366 ! getObject( oID )->isFlightLanding ) {
8367
8368 // not even a valid landing pos anymore
8369 flightLandingPos.deleteElement( i );
8370 i--;
8371 continue;
8372 }
8373 closestDist = dist;
8374 closestPos = thisPos;
8375 closestIndex = i;
8376 }
8377 }
8378 }
8379
8380 if( closestIndex == -1 ) {
8381 *outFound = false;
8382 }
8383 else {
8384 *outFound = true;
8385 }
8386
8387 return closestPos;
8388 }
8389
8390
8391
8392
8393
8394GridPos getNextFlightLandingPos( int inCurrentX, int inCurrentY,
8395 doublePair inDir,
8396 int inRadiusLimit ) {
8397 int closestIndex = -1;
8398 GridPos closestPos;
8399 double closestDist = DBL_MAX;
8400
8401 GridPos curPos = { inCurrentX, inCurrentY };
8402
8403 char useLimit = false;
8404
8405 if( abs( inCurrentX ) <= inRadiusLimit &&
8406 abs( inCurrentY ) <= inRadiusLimit ) {
8407 useLimit = true;
8408 }
8409
8410
8411
8412 for( int i=0; i<flightLandingPos.size(); i++ ) {
8413 GridPos thisPos = flightLandingPos.getElementDirect( i );
8414
8415 if( useLimit &&
8416 ( abs( thisPos.x ) > inRadiusLimit ||
8417 abs( thisPos.x ) > inRadiusLimit ) ) {
8418 // out of bounds destination
8419 continue;
8420 }
8421
8422
8423 double dist = distSquared( curPos, thisPos );
8424
8425 if( dist < closestDist ) {
8426
8427 // check if this is still a valid landing pos
8428 int oID = getMapObject( thisPos.x, thisPos.y );
8429
8430 if( oID <=0 ||
8431 ! getObject( oID )->isFlightLanding ) {
8432
8433 // not even a valid landing pos anymore
8434 flightLandingPos.deleteElement( i );
8435 i--;
8436 continue;
8437 }
8438 closestDist = dist;
8439 closestPos = thisPos;
8440 closestIndex = i;
8441 }
8442 }
8443
8444
8445 if( closestIndex != -1 && flightLandingPos.size() > 1 ) {
8446 // found closest, and there's more than one
8447 // look for next valid position in chosen direction
8448
8449
8450 char found = false;
8451
8452 GridPos nextPos = getNextCloseLandingPos( curPos, inDir, &found );
8453
8454 if( found ) {
8455 return nextPos;
8456 }
8457
8458 // if we got here, we never found a nextPos that was valid
8459 // closestPos is only option
8460 return closestPos;
8461 }
8462 else if( closestIndex != -1 && flightLandingPos.size() == 1 ) {
8463 // land at closest, only option
8464 return closestPos;
8465 }
8466
8467 // got here, no place to land
8468
8469 // crash them at next Eve location
8470
8471 int eveX, eveY;
8472
8473 SimpleVector<GridPos> otherPeoplePos;
8474
8475 getEvePosition( "dummyPlaneCrashEmail@test.com", 0, &eveX, &eveY,
8476 &otherPeoplePos, false );
8477
8478 GridPos returnVal = { eveX, eveY };
8479
8480 if( inRadiusLimit > 0 &&
8481 ( abs( eveX ) >= inRadiusLimit ||
8482 abs( eveY ) >= inRadiusLimit ) ) {
8483 // even Eve pos is out of bounds
8484 // stick them in center
8485 returnVal.x = 0;
8486 returnVal.y = 0;
8487 }
8488
8489
8490
8491
8492 return returnVal;
8493 }
8494
8495
8496
8497int getGravePlayerID( int inX, int inY ) {
8498 unsigned char key[9];
8499 unsigned char value[4];
8500
8501 // look for changes to default in database
8502 intPairToKey( inX, inY, key );
8503
8504 int result = DB_get( &graveDB, key, value );
8505
8506 if( result == 0 ) {
8507 // found
8508 int returnVal = valueToInt( value );
8509
8510 return returnVal;
8511 }
8512 else {
8513 return 0;
8514 }
8515 }
8516
8517
8518void setGravePlayerID( int inX, int inY, int inPlayerID ) {
8519 unsigned char key[8];
8520 unsigned char value[4];
8521
8522
8523 intPairToKey( inX, inY, key );
8524 intToValue( inPlayerID, value );
8525
8526
8527 DB_put( &graveDB, key, value );
8528 }
8529
8530
8531
8532
8533
8534
8535static char tileCullingIteratorSet = false;
8536static DB_Iterator tileCullingIterator;
8537
8538static char floorCullingIteratorSet = false;
8539static DB_Iterator floorCullingIterator;
8540
8541static double lastSettingsLoadTime = 0;
8542static double settingsLoadInterval = 5 * 60;
8543
8544static int numTilesExaminedPerCullStep = 10;
8545static int longTermCullingSeconds = 3600 * 12;
8546
8547static int minActivePlayersForLongTermCulling = 15;
8548
8549
8550
8551static SimpleVector<int> noCullItemList;
8552
8553
8554static int numTilesSeenByIterator = 0;
8555static int numFloorsSeenByIterator = 0;
8556
8557void stepMapLongTermCulling( int inNumCurrentPlayers ) {
8558
8559 double curTime = Time::getCurrentTime();
8560
8561 if( curTime - lastSettingsLoadTime > settingsLoadInterval ) {
8562
8563 lastSettingsLoadTime = curTime;
8564
8565 numTilesExaminedPerCullStep =
8566 SettingsManager::getIntSetting(
8567 "numTilesExaminedPerCullStep", 10 );
8568 longTermCullingSeconds =
8569 SettingsManager::getIntSetting(
8570 "longTermNoLookCullSeconds", 3600 * 12 );
8571 minActivePlayersForLongTermCulling =
8572 SettingsManager::getIntSetting(
8573 "minActivePlayersForLongTermCulling", 15 );
8574
8575 longTermCullEnabled =
8576 SettingsManager::getIntSetting(
8577 "longTermNoLookCullEnabled", 1 );
8578
8579
8580 SimpleVector<int> *list =
8581 SettingsManager::getIntSettingMulti( "noCullItemList" );
8582
8583 noCullItemList.deleteAll();
8584 noCullItemList.push_back_other( list );
8585 delete list;
8586
8587 barrierRadius = SettingsManager::getIntSetting( "barrierRadius", 250 );
8588 barrierOn = SettingsManager::getIntSetting( "barrierOn", 1 );
8589 }
8590
8591
8592 if( ! longTermCullEnabled ||
8593 minActivePlayersForLongTermCulling > inNumCurrentPlayers ) {
8594 return;
8595 }
8596
8597
8598 if( !tileCullingIteratorSet ) {
8599 DB_Iterator_init( &db, &tileCullingIterator );
8600 tileCullingIteratorSet = true;
8601 numTilesSeenByIterator = 0;
8602 }
8603
8604 unsigned char tileKey[16];
8605 unsigned char floorKey[8];
8606 unsigned char value[4];
8607
8608
8609 for( int i=0; i<numTilesExaminedPerCullStep; i++ ) {
8610 int result =
8611 DB_Iterator_next( &tileCullingIterator, tileKey, value );
8612
8613 if( result <= 0 ) {
8614 // restart the iterator back at the beginning
8615 DB_Iterator_init( &db, &tileCullingIterator );
8616 if( numTilesSeenByIterator != 0 ) {
8617 AppLog::infoF( "Map cull iterated through %d tile db entries.",
8618 numTilesSeenByIterator );
8619 }
8620 numTilesSeenByIterator = 0;
8621 // end loop when we reach end of list, so we don't cycle through
8622 // a short iterator list too quickly.
8623 break;
8624 }
8625 else {
8626 numTilesSeenByIterator ++;
8627 }
8628
8629
8630 int tileID = valueToInt( value );
8631
8632 // consider 0-values too, where map has been cleared by players, but
8633 // a natural object should be there
8634 if( tileID >= 0 ) {
8635 // next value
8636
8637 int s = valueToInt( &( tileKey[8] ) );
8638 int b = valueToInt( &( tileKey[12] ) );
8639
8640 if( s == 0 && b == 0 ) {
8641 // main object
8642 int x = valueToInt( tileKey );
8643 int y = valueToInt( &( tileKey[4] ) );
8644
8645 int wildTile = getTweakedBaseMap( x, y );
8646
8647 if( wildTile != tileID ) {
8648 // tile differs from natural tile
8649 // don't keep checking/resetting tiles that are already
8650 // in wild state
8651
8652 // NOTE that we don't check/clear container slots for
8653 // already-wild tiles. So a natural container
8654 // (if one is ever
8655 // added to the game, like a hidey-hole cave) will
8656 // keep its items even after that part of the map
8657 // is culled. Seems like okay behavior.
8658
8659 timeSec_t lastLookTime = dbLookTimeGet( x, y );
8660
8661 if( curTime - lastLookTime > longTermCullingSeconds ) {
8662 // stale
8663
8664 if( noCullItemList.getElementIndex( tileID ) == -1 ) {
8665 // not on our no-cull list
8666 clearAllContained( x, y );
8667
8668 // put proc-genned map value in there
8669 setMapObject( x, y, wildTile );
8670
8671 if( wildTile != 0 &&
8672 getObject( wildTile )->permanent ) {
8673 // something nautural occurs here
8674 // this "breaks" any remaining floor
8675 // (which may be cull-proof on its own below).
8676 // this will effectively leave gaps in roads
8677 // with trees growing through, etc.
8678 setMapFloor( x, y, 0 );
8679 }
8680 }
8681 }
8682 }
8683 }
8684 }
8685 }
8686
8687
8688
8689 if( !floorCullingIteratorSet ) {
8690 DB_Iterator_init( &floorDB, &floorCullingIterator );
8691 floorCullingIteratorSet = true;
8692 numFloorsSeenByIterator = 0;
8693 }
8694
8695
8696 for( int i=0; i<numTilesExaminedPerCullStep; i++ ) {
8697 int result =
8698 DB_Iterator_next( &floorCullingIterator, floorKey, value );
8699
8700 if( result <= 0 ) {
8701 // restart the iterator back at the beginning
8702 DB_Iterator_init( &floorDB, &floorCullingIterator );
8703 if( numFloorsSeenByIterator != 0 ) {
8704 AppLog::infoF( "Map cull iterated through %d floor db entries.",
8705 numFloorsSeenByIterator );
8706 }
8707 numFloorsSeenByIterator = 0;
8708 // end loop now, avoid re-cycling through a short list
8709 // in same step
8710 break;
8711 }
8712 else {
8713 numFloorsSeenByIterator ++;
8714 }
8715
8716
8717 int floorID = valueToInt( value );
8718
8719 if( floorID > 0 ) {
8720 // next value
8721
8722 int x = valueToInt( floorKey );
8723 int y = valueToInt( &( floorKey[4] ) );
8724
8725 timeSec_t lastLookTime = dbLookTimeGet( x, y );
8726
8727 if( curTime - lastLookTime > longTermCullingSeconds ) {
8728 // stale
8729
8730 if( noCullItemList.getElementIndex( floorID ) == -1 ) {
8731 // not on our no-cull list
8732
8733 setMapFloor( x, y, 0 );
8734 }
8735 }
8736 }
8737 }
8738 }