1 (*
2  * Hedgewars, a free turn based strategy game
3  * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; version 2 of the License
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *)
18 
19 {$INCLUDE "options.inc"}
20 
21 unit uLand;
22 interface
23 uses SDLh, uLandTemplates, uConsts, uTypes, uAILandMarks;
24 
25 procedure initModule;
26 procedure freeModule;
27 procedure DrawBottomBorder;
28 procedure GenMap;
29 procedure GenPreview(out Preview: TPreview);
30 procedure GenPreviewAlpha(out Preview: TPreviewAlpha);
31 
32 implementation
33 uses uConsole, uStore, uRandom, uLandObjects, uIO, uLandTexture,
34      uVariables, uUtils, uCommands, adler32, uDebug, uLandPainted, uTextures,
35      uLandGenMaze, uPhysFSLayer, uScript, uLandGenPerlin,
36      uLandGenTemplateBased, uLandUtils, uRenderUtils;
37 
38 var digest: shortstring;
39     maskOnly: boolean;
40 
41 
42 procedure PrettifyLandAlpha();
43 begin
44     if (cReducedQuality and rqBlurryLand) <> 0 then
45         PrettifyAlpha2D(LandPixels, LAND_HEIGHT div 2, LAND_WIDTH div 2)
46     else
47         PrettifyAlpha2D(LandPixels, LAND_HEIGHT, LAND_WIDTH);
48 end;
49 
50 procedure DrawBorderFromImage(Surface: PSDL_Surface);
51 var tmpsurf: PSDL_Surface;
52     x, yd, yu: LongInt;
53     targetMask: Word;
54 begin
55     tmpsurf:= LoadDataImage(ptCurrTheme, 'Border', ifCritical or ifIgnoreCaps or ifColorKey);
56 
57     // if mask only, all land gets filled with landtex and therefore needs borders
58     if maskOnly then
59         targetMask:= lfLandMask
60     else
61         targetMask:= lfBasic;
62 
63     for x:= 0 to LAND_WIDTH - 1 do
64     begin
65         yd:= LAND_HEIGHT - 1;
66         repeat
67             while (yd > 0) and ((Land[yd, x] and targetMask) = 0) do dec(yd);
68 
69             if (yd < 0) then
70                 yd:= 0;
71 
72             while (yd < LAND_HEIGHT) and ((Land[yd, x] and targetMask) <> 0) do
73                 inc(yd);
74             dec(yd);
75             yu:= yd;
76 
77             while (yu > 0  ) and ((Land[yu, x] and targetMask) <> 0) do dec(yu);
78             while (yu < yd ) and ((Land[yu, x] and targetMask) =  0) do inc(yu);
79 
80             if (yd < LAND_HEIGHT - 1) and ((yd - yu) >= 16) then
81                 copyToXYFromRect(tmpsurf, Surface, x mod tmpsurf^.w, 16, 1, 16, x, yd - 15);
82             if (yu > 0) then
83                 copyToXYFromRect(tmpsurf, Surface, x mod tmpsurf^.w, 0, 1, Min(16, yd - yu + 1), x, yu);
84             yd:= yu - 1;
85         until yd < 0;
86     end;
87     SDL_FreeSurface(tmpsurf);
88 end;
89 
90 
91 procedure DrawShoppaBorder;
92 var x, y, s, i: Longword;
93     c1, c2, c: Longword;
94 begin
95     c1:= AMask;
96     c2:= AMask or RMask or GMask;
97 
98     // vertical
99     s:= LAND_HEIGHT;
100 
101     for x:= 0 to LAND_WIDTH - 1 do
102         for y:= 0 to LAND_HEIGHT - 1 do
103             if Land[y, x] = 0 then
104                 if s < y then
105                     begin
106                     for i:= max(s, y - 8) to y - 1 do
107                         begin
108                         if ((x + i) and 16) = 0 then c:= c1 else c:= c2;
109 
110                         if (cReducedQuality and rqBlurryLand) = 0 then
111                             LandPixels[i, x]:= c
112                         else
113                             LandPixels[i div 2, x div 2]:= c
114                         end;
115                     s:= LAND_HEIGHT
116                     end
117                 else
118             else
119                 begin
120                 if s > y then s:= y;
121                 if s + 8 > y then
122                     begin
123                     if ((x + y) and 16) = 0 then c:= c1 else c:= c2;
124 
125                     if (cReducedQuality and rqBlurryLand) = 0 then
126                         LandPixels[y, x]:= c
127                     else
128                         LandPixels[y div 2, x div 2]:= c
129                     end;
130                 end;
131 
132     // horizontal
133     s:= LAND_WIDTH;
134 
135     for y:= 0 to LAND_HEIGHT - 1 do
136         for x:= 0 to LAND_WIDTH - 1 do
137             if Land[y, x] = 0 then
138                 if s < x then
139                     begin
140                     for i:= max(s, x - 8) to x - 1 do
141                         begin
142                         if ((y + i) and 16) = 0 then c:= c1 else c:= c2;
143 
144                         if (cReducedQuality and rqBlurryLand) = 0 then
145                             LandPixels[y, i]:= c
146                         else
147                             LandPixels[y div 2, i div 2]:= c
148                         end;
149                     s:= LAND_WIDTH
150                     end
151                 else
152             else
153                 begin
154                 if s > x then s:= x;
155                 if s + 8 > x then
156                     begin
157                     if ((x + y) and 16) = 0 then c:= c1 else c:= c2;
158 
159                     if (cReducedQuality and rqBlurryLand) = 0 then
160                         LandPixels[y, x]:= c
161                     else
162                         LandPixels[y div 2, x div 2]:= c
163                     end;
164                 end
165 end;
166 
167 procedure ColorizeLandFast(mapsurf: PSDL_Surface);
168 var ltexsurf: PSDL_Surface;
169     i: LongInt;
170     ltlnp, srcp, dstp, stopp: Pointer;
171     c: SizeInt;
172 begin
173     ltexsurf:= LoadDataImage(ptCurrTheme, 'LandTex', ifCritical or ifIgnoreCaps);
174 
175     // pointer to current line of ltexsurf pixels. will be moved from line to line
176     ltlnp:= ltexsurf^.pixels;
177     // pointer to mapsurf pixels. will jump forward after every move()
178     dstp:= mapsurf^.pixels;
179 
180     // time to get serious
181     SDL_LockSurface(mapsurf);
182     SDL_LockSurface(ltexsurf);
183 
184     // for now only fill a row with the height of landtex. do vertical copies within mapsurf after
185 
186     // do this loop for each line of ltexsurf (unless we run out of map height first)
187     for i:= 1 to min(ltexsurf^.h, mapsurf^.h) do
188         begin
189         // amount of pixels to write in first move()
190         c:= ltexsurf^.pitch;
191 
192         // protect from odd cases where landtex wider than map
193         if c > mapsurf^.pitch then
194             c:= mapsurf^.pitch;
195 
196         // write line of landtex to mapsurf
197         move(ltlnp^, dstp^, c);
198 
199         // fill the rest of the line by copying left-to-right until full
200 
201         // new src is start of line that we've just written to
202         srcp:= dstp;
203         // set stop pointer to start of next pixel line of mapsurf
204         stopp:= dstp + mapsurf^.pitch;
205         // move dst pointer to after what we've just written
206         inc(dstp, c);
207 
208         // loop until dstp went past end of line
209         while dstp < stopp do
210             begin
211             // copy all from left of dstp to right of it (or just fill the gap if smaller)
212             c:= min(dstp-srcp, stopp-dstp);
213             move(srcp^, dstp^, c);
214             inc(dstp, c);
215             end;
216 
217         // move to next line in ltexsurf
218         inc(ltlnp, ltexsurf^.pitch);
219         end;
220 
221     // we don't need ltexsurf itself anymore -> cleanup
222     ltlnp:= nil;
223     SDL_UnlockSurface(ltexsurf);
224     SDL_FreeSurface(ltexsurf);
225     ltexsurf:= nil;
226 
227     // from now on only copy pixels within mapsurf
228 
229     // copy all the already written lines at once for that get number of written bytes so far
230     // already written pixels are between start and current dstp
231     srcp:= mapsurf^.pixels;
232 
233     // first byte after end of pixels
234     stopp:= srcp + (mapsurf^.pitch * mapsurf^.h);
235 
236     while dstp < stopp do
237         begin
238         // copy all from before dstp to after (or just fill the gap if smaller)
239         c:= min(dstp-srcp, stopp-dstp);
240         // worried about size of c with humongous maps? don't be:
241         //  the OS wouldn't have allowed allocation of object with size > max of SizeInt anyway
242         move(srcp^, dstp^, c);
243         inc(dstp, c);
244         end;
245 
246     // cleanup
247     srcp:= nil;
248     dstp:= nil;
249     stopp:= nil;
250     SDL_UnlockSurface(mapsurf);
251 
252     // freed in freeModule() below
253     LandBackSurface:= LoadDataImage(ptCurrTheme, 'LandBackTex', ifIgnoreCaps or ifColorKey);
254     if (LandBackSurface <> nil) and GrayScale then Surface2GrayScale(LandBackSurface);
255 end;
256 
257 procedure ColorizeLand(Surface: PSDL_Surface);
258 var tmpsurf: PSDL_Surface;
259     r: TSDL_Rect;
260     y: LongInt; // stupid SDL 1.2 uses stupid SmallInt for y which limits us to 32767.  But is even worse if LandTex is large, can overflow on 32767 map.
261 begin
262     tmpsurf:= LoadDataImage(ptCurrTheme, 'LandTex', ifCritical or ifIgnoreCaps);
263     r.y:= 0;
264     y:= 0;
265     while y < LAND_HEIGHT do
266         begin
267         r.x:= 0;
268         while r.x < LAND_WIDTH do
269             begin
270             copyToXY(tmpsurf, Surface, r.x, r.y);
271             inc(r.x, tmpsurf^.w)
272             end;
273         inc(y, tmpsurf^.h);
274         r.y:= y
275         end;
276     SDL_FreeSurface(tmpsurf);
277 
278     // freed in freeModule() below
279     LandBackSurface:= LoadDataImage(ptCurrTheme, 'LandBackTex', ifIgnoreCaps or ifColorKey);
280     if (LandBackSurface <> nil) and GrayScale then Surface2GrayScale(LandBackSurface);
281 end;
282 
283 
284 procedure GenDrawnMap;
285 var lowerX, upperX, lowerY, upperY, lowerFS, upperFS: LongInt;
286 begin
287     if (cFeatureSize <= 6) then
288         MaxHedgehogs:= 6 + (cFeatureSize-1) * 2
289     else if (cFeatureSize < 11) then
290         MaxHedgehogs:= 16 + (cFeatureSize-6) * 4
291     else if (cFeatureSize = 11) then
292         MaxHedgehogs:= 48
293     else if (cFeatureSize = 12) then
294         MaxHedgehogs:= 64
295     else
296         MaxHedgehogs:= cMaxHHs;
297 
298     if GameType = gmtLandPreview then
299         cFeatureSize:= 1;
300 
301     // Calculate map size for drawn map, use cFeatureSize to scale.
302 
303     // We have pre-determined map size for cFeatureSize 1, 6, 12 and 25.
304     // The other values will be interpolated.
305     if cFeatureSize < 6 then
306         begin
307         // reference size for cFeatureSize 1
308         lowerFS:= 1;
309         lowerX:= 1024;
310         lowerY:= 512;
311         upperFS:= 6;
312         end
313     else if cFeatureSize < 12 then
314         begin
315         // reference size for cFeatureSize 6
316         lowerFS:= 6;
317         lowerX:= 2048;
318         lowerY:= 1024;
319         upperFS:= 12;
320         end
321     else
322         begin
323         // reference size for cFeatureSize 12, size of drawn maps in pre-1.0.0 versions
324         lowerFS:= 12;
325         lowerX:= 4096;
326         lowerY:= 2048;
327         upperFS:= 25;
328         end;
329 
330     upperX:= lowerX * 2;
331     upperY:= lowerY * 2;
332 
333     if cFeatureSize = 25 then
334         begin
335         // hardcoded size for size level 25
336         playWidth:= 8192;
337         playHeight:= 4096;
338         end
339     else
340         begin
341         // Interpolation formula
342         playWidth:= lowerX + ((upperX-lowerX) div (upperFS-lowerFS))*(cFeatureSize-lowerFS);
343         playHeight:= lowerY + ((upperY-lowerY) div (upperFS-lowerFS))*(cFeatureSize-lowerFS);
344         end;
345 
346     if GameType <> gmtLandPreview then
347         WriteLnToConsole('Drawn map size: cFeatureSize='+IntToStr(cFeatureSize)+' playWidth='+IntToStr(playWidth)+' playHeight='+IntToStr(playHeight));
348 
349     ResizeLand(playWidth, playHeight);
350 
351     hasGirders:= true;
352     leftX:= ((LAND_WIDTH - playWidth) div 2);
353     rightX:= (playWidth + ((LAND_WIDTH - playWidth) div 2)) - 1;
354     topY:= LAND_HEIGHT - playHeight;
355 
356     uLandPainted.Draw;
357 end;
358 
359 function SelectTemplate: LongInt;
360 var l: LongInt;
361 begin
362     SelectTemplate:= 0;
363     if (cReducedQuality and rqLowRes) <> 0 then
364         SelectTemplate:= SmallTemplates[getrandom(Succ(High(SmallTemplates)))]
365     else
366         begin
367         if cTemplateFilter = 0 then
368             begin
369             l:= getRandom(GroupedTemplatesCount);
370             repeat
371                 inc(cTemplateFilter);
372                 dec(l, TemplateCounts[cTemplateFilter]);
373             until l < 0;
374             end
375             else getRandom(1);
376 
377             case cTemplateFilter of
378             0: OutError('Error selecting TemplateFilter. Ask unC0Rr about what you did wrong', true);
379             1: SelectTemplate:= SmallTemplates[getrandom(TemplateCounts[cTemplateFilter])];
380             2: SelectTemplate:= MediumTemplates[getrandom(TemplateCounts[cTemplateFilter])];
381             3: SelectTemplate:= LargeTemplates[getrandom(TemplateCounts[cTemplateFilter])];
382             4: SelectTemplate:= CavernTemplates[getrandom(TemplateCounts[cTemplateFilter])];
383             5: SelectTemplate:= WackyTemplates[getrandom(TemplateCounts[cTemplateFilter])];
384     // For lua only!
385             6: begin
386                SelectTemplate:= min(LuaTemplateNumber,High(EdgeTemplates));
387                GetRandom(2) // burn 1
388                end
389             end
390         end;
391 
392     WriteLnToConsole('Selected template #'+inttostr(SelectTemplate)+' using filter #'+inttostr(cTemplateFilter));
393 end;
394 
395 procedure LandSurface2LandPixels(Surface: PSDL_Surface);
396 var x, y: LongInt;
397     p: PLongwordArray;
398 begin
399 if checkFails(Surface <> nil, 'Assert (LandSurface <> nil) failed', true) then exit;
400 
401 if SDL_MustLock(Surface) then
402     if SDLCheck(SDL_LockSurface(Surface) >= 0, 'SDL_LockSurface', true) then exit;
403 
404 p:= Surface^.pixels;
405 for y:= 0 to LAND_HEIGHT - 1 do
406     begin
407     for x:= 0 to LAND_WIDTH - 1 do
408     if Land[y, x] <> 0 then
409         if (cReducedQuality and rqBlurryLand) = 0 then
410             LandPixels[y, x]:= p^[x]// or AMask
411         else
412             LandPixels[y div 2, x div 2]:= p^[x];
413 
414     p:= PLongwordArray(@(p^[Surface^.pitch div 4]));
415     end;
416 
417 if SDL_MustLock(Surface) then
418     SDL_UnlockSurface(Surface);
419 end;
420 
421 
422 procedure GenLandSurface;
423 var tmpsurf: PSDL_Surface;
424     x,y: Longword;
425 begin
426     AddProgress();
427 
428     tmpsurf:= SDL_CreateRGBSurface(SDL_SWSURFACE, LAND_WIDTH, LAND_HEIGHT, 32, RMask, GMask, BMask, AMask);
429 
430     if checkFails(tmpsurf <> nil, 'Error creating pre-land surface', true) then exit;
431     ColorizeLandFast(tmpsurf);
432     if gameFlags and gfShoppaBorder = 0 then DrawBorderFromImage(tmpsurf);
433     AddOnLandObjects(tmpsurf);
434 
435     LandSurface2LandPixels(tmpsurf);
436     SDL_FreeSurface(tmpsurf);
437 
438     if gameFlags and gfShoppaBorder <> 0 then DrawShoppaBorder;
439 
440     for x:= LongWord(leftX+2) to LongWord(rightX-2) do
441         for y:= LongWord(topY+2) to LAND_HEIGHT-3 do
442             if (Land[y, x] = 0) and
443                (((Land[y, x-1] = lfBasic) and ((Land[y+1,x] = lfBasic)) or (Land[y-1,x] = lfBasic)) or
444                ((Land[y, x+1] = lfBasic) and ((Land[y-1,x] = lfBasic) or (Land[y+1,x] = lfBasic)))) then
445             begin
446                 if (cReducedQuality and rqBlurryLand) = 0 then
447                     begin
448                     if (Land[y, x-1] = lfBasic) and (LandPixels[y, x-1] and AMask <> 0) then
449                         LandPixels[y, x]:= LandPixels[y, x-1]
450 
451                     else if (Land[y, x+1] = lfBasic) and (LandPixels[y, x+1] and AMask <> 0) then
452                         LandPixels[y, x]:= LandPixels[y, x+1]
453 
454                     else if (Land[y-1, x] = lfBasic) and (LandPixels[y-1, x] and AMask <> 0) then
455                         LandPixels[y, x]:= LandPixels[y-1, x]
456 
457                     else if (Land[y+1, x] = lfBasic) and (LandPixels[y+1, x] and AMask <> 0) then
458                         LandPixels[y, x]:= LandPixels[y+1, x];
459 
460                     if (((LandPixels[y,x] and AMask) shr AShift) > 10) then
461                         LandPixels[y,x]:= (LandPixels[y,x] and (not AMask)) or (128 shl AShift)
462                     end;
463                 Land[y,x]:= lfObject
464             end
465             else if (Land[y, x] = 0) and
466                     (((Land[y, x-1] = lfBasic) and (Land[y+1,x-1] = lfBasic) and (Land[y+2,x] = lfBasic)) or
467                     ((Land[y, x-1] = lfBasic) and (Land[y-1,x-1] = lfBasic) and (Land[y-2,x] = lfBasic)) or
468                     ((Land[y, x+1] = lfBasic) and (Land[y+1,x+1] = lfBasic) and (Land[y+2,x] = lfBasic)) or
469                     ((Land[y, x+1] = lfBasic) and (Land[y-1,x+1] = lfBasic) and (Land[y-2,x] = lfBasic)) or
470                     ((Land[y+1, x] = lfBasic) and (Land[y+1,x+1] = lfBasic) and (Land[y,x+2] = lfBasic)) or
471                     ((Land[y-1, x] = lfBasic) and (Land[y-1,x+1] = lfBasic) and (Land[y,x+2] = lfBasic)) or
472                     ((Land[y+1, x] = lfBasic) and (Land[y+1,x-1] = lfBasic) and (Land[y,x-2] = lfBasic)) or
473                     ((Land[y-1, x] = lfBasic) and (Land[y-1,x-1] = lfBasic) and (Land[y,x-2] = lfBasic))) then
474 
475                 begin
476 
477                 if (cReducedQuality and rqBlurryLand) = 0 then
478 
479                     begin
480 
481                     if (Land[y, x-1] = lfBasic) and (LandPixels[y,x-1] and AMask <> 0) then
482                         LandPixels[y, x]:= LandPixels[y, x-1]
483 
484                     else if (Land[y, x+1] = lfBasic) and (LandPixels[y,x+1] and AMask <> 0) then
485                         LandPixels[y, x]:= LandPixels[y, x+1]
486 
487                     else if (Land[y+1, x] = lfBasic) and (LandPixels[y+1,x] and AMask <> 0) then
488                         LandPixels[y, x]:= LandPixels[y+1, x]
489 
490                     else if (Land[y-1, x] = lfBasic) and (LandPixels[y-1,x] and AMask <> 0) then
491                         LandPixels[y, x]:= LandPixels[y-1, x];
492 
493                     if (((LandPixels[y,x] and AMask) shr AShift) > 10) then
494                         LandPixels[y,x]:= (LandPixels[y,x] and (not AMask)) or (64 shl AShift)
495                     end;
496                 Land[y,x]:= lfObject
497             end;
498 
499     AddProgress();
500 end;
501 
502 procedure MakeFortsPreview;
503 var gap: LongInt;
504     h1, h2, w1, w2, x, y, lastX, wbm, bmref: LongWord;
505 const fortHeight = 960;
506       fortWidth  = 704;
507       bmHeight = 53;
508       bmWidth = 64;
509 begin
510 ResizeLand(4096,2048);
511 
512 lastX:= LAND_WIDTH-1;
513 
514 gap:= (1024 - fortWidth) + 60 + 20 * cFeatureSize;
515 
516 h2:= LAND_HEIGHT-1;
517 h1:= h2 - fortHeight;
518 w2:= (LAND_WIDTH - gap) div 2;
519 w1:= w2 - fortWidth;
520 wbm:= h1 + bmHeight;
521 
522 // generate 2 forts in center
523 for y:= h1 to h2 do
524     for x:= w1 to w2 do
525         begin
526         if x mod 4 <> 0 then
527             begin
528             if (y <= wbm) and ((x - w1) mod (bmWidth * 2) >= bmWidth) then
529                 continue;
530             Land[y,x]:= lfBasic;
531             Land[y,lastX-x]:= lfBasic;
532             end;
533         end;
534 
535 w2:= w1 - gap;
536 w1:= max(0, w2 - fortWidth);
537 wbm:= h1 + bmHeight;
538 bmref:= w2 + bmWidth;
539 
540 for y:= h1 to h2 do
541     for x:= w1 to w2 do
542         begin
543         if ((y - x) mod 2) = 0 then
544             begin
545             // align battlement on inner edge, because real outer edge could be offscreen
546             if (y <= wbm) and ((LAND_WIDTH + x - bmref) mod (bmWidth * 2) >= bmWidth) then
547                 continue;
548             Land[y,x]:= lfBasic;
549             Land[y,lastX-x]:= lfBasic;
550             end;
551         end;
552 end;
553 
554 procedure MakeFortsMap;
555 var tmpsurf: PSDL_Surface;
556     sectionWidth, i, t, p: integer;
557     mirror: boolean;
558     pc: PClan;
559 begin
560 
561 // make the gaps between forts adjustable if fort map was selected
562 if cMapGen = mgForts then
563     sectionWidth:= 1024 + 60 + 20 * cFeatureSize
564 else
565     sectionWidth:= 1024 * 300;
566 
567 // mix up spawn/fort order of clans
568 for i:= 0 to ClansCount - 1 do
569     begin
570     t:= GetRandom(ClansCount);
571     p:= GetRandom(ClansCount);
572     if t <> p then
573         begin
574         pc:= SpawnClansArray[t];
575         SpawnClansArray[t]:= SpawnClansArray[p];
576         SpawnClansArray[p]:= pc;
577         end;
578     end;
579 
580 // figure out how much space we need
581 playWidth:= sectionWidth * ClansCount;
582 
583 // note: LAND_WIDTH might be bigger than specified below (rounded to next power of 2)
584 ResizeLand(playWidth, 2048);
585 
586 // For now, defining a fort is playable area as 3072x1200 - there are no tall forts.  The extra height is to avoid triggering border with current code, also if user turns on a border, it will give a bit more maneuvering room.
587 playHeight:= 1200;
588 
589 // center playable area in land array
590 leftX:= ((LAND_WIDTH - playWidth) div 2);
591 rightX:= ((playWidth + (LAND_WIDTH - playWidth) div 2) - 1);
592 topY:= LAND_HEIGHT - playHeight;
593 
594 WriteLnToConsole('Generating forts land...');
595 
596 for i := 0 to ClansCount - 1 do
597     begin
598 
599     // face in random direction
600     mirror:= (GetRandom(2) = 0);
601     // make first/last fort face inwards
602     if (WorldEdge <> weWrap) or (ClansCount = 2) then
603         mirror:= (i <> 0) and (mirror or (i = ClansCount - 1));
604 
605     if mirror then
606         begin
607         // not critical because if no R we can fallback to mirrored L
608         tmpsurf:= LoadDataImage(ptForts, SpawnClansArray[i]^.Teams[0]^.FortName + 'R', ifAlpha or ifColorKey or ifIgnoreCaps);
609         // fallback
610         if tmpsurf = nil then
611             begin
612             tmpsurf:= LoadDataImage(ptForts, SpawnClansArray[i]^.Teams[0]^.FortName + 'L', ifAlpha or ifCritical or ifColorKey or ifIgnoreCaps);
613             BlitImageAndGenerateCollisionInfo(leftX + sectionWidth * i + ((sectionWidth - tmpsurf^.w) div 2), LAND_HEIGHT - tmpsurf^.h, tmpsurf^.w, tmpsurf, 0, true);
614             end
615         else
616             BlitImageAndGenerateCollisionInfo(leftX + sectionWidth * i + ((sectionWidth - tmpsurf^.w) div 2), LAND_HEIGHT - tmpsurf^.h, tmpsurf^.w, tmpsurf);
617         SDL_FreeSurface(tmpsurf);
618         end
619     else
620         begin
621         tmpsurf:= LoadDataImage(ptForts, SpawnClansArray[i]^.Teams[0]^.FortName + 'L', ifAlpha or ifCritical or ifColorKey or ifIgnoreCaps);
622         BlitImageAndGenerateCollisionInfo(leftX + sectionWidth * i + ((sectionWidth - tmpsurf^.w) div 2), LAND_HEIGHT - tmpsurf^.h, tmpsurf^.w, tmpsurf);
623         SDL_FreeSurface(tmpsurf);
624         end;
625 
626     end;
627 end;
628 
629 procedure LoadMapConfig;
630 var f: PFSFile;
631     s: shortstring;
632 begin
633 s:= cPathz[ptMapCurrent] + '/map.cfg';
634 
635 WriteLnToConsole('Fetching map HH limit');
636 
637 f:= pfsOpenRead(s);
638 if f <> nil then
639     begin
640     pfsReadLn(f, s);
641     if not pfsEof(f) then
642         begin
643         pfsReadLn(f, s);
644         val(s, MaxHedgehogs)
645         end;
646 
647     pfsClose(f)
648     end;
649 
650 if (MaxHedgehogs = 0) then
651     MaxHedgehogs:= 18;
652 end;
653 
654 // Loads Land[] from an image, allowing overriding standard collision
655 procedure LoadMask;
656 var tmpsurf: PSDL_Surface;
657     p: PLongwordArray;
658     x, y, cpX, cpY: Longword;
659     mapName: shortstring;
660 begin
661 tmpsurf:= LoadDataImage(ptMapCurrent, 'mask', ifAlpha or ifColorKey or ifIgnoreCaps);
662 if tmpsurf = nil then
663     begin
664     mapName:= ExtractFileName(cPathz[ptMapCurrent]);
665     tmpsurf:= LoadDataImage(ptMissionMaps, mapName + '/mask', ifAlpha or ifColorKey or ifIgnoreCaps);
666     end;
667 
668 
669 if (tmpsurf <> nil) and (tmpsurf^.format^.BytesPerPixel = 4) then
670     begin
671     if LAND_WIDTH = 0 then
672         begin
673         LoadMapConfig;
674         ResizeLand(tmpsurf^.w, tmpsurf^.h);
675         playHeight:= tmpsurf^.h;
676         playWidth:= tmpsurf^.w;
677         leftX:= (LAND_WIDTH - playWidth) div 2;
678         rightX:= (playWidth + ((LAND_WIDTH - playWidth) div 2)) - 1;
679         topY:= LAND_HEIGHT - playHeight;
680         end;
681     disableLandBack:= true;
682 
683     cpX:= (LAND_WIDTH - tmpsurf^.w) div 2;
684     cpY:= LAND_HEIGHT - tmpsurf^.h;
685     if SDL_MustLock(tmpsurf) then
686         SDLCheck(SDL_LockSurface(tmpsurf) >= 0, 'SDL_LockSurface', true);
687 
688     if allOK then
689     begin
690         p:= tmpsurf^.pixels;
691         for y:= 0 to Pred(tmpsurf^.h) do
692             begin
693             for x:= 0 to Pred(tmpsurf^.w) do
694                 SetLand(Land[cpY + y, cpX + x], p^[x]);
695             p:= PLongwordArray(@(p^[tmpsurf^.pitch div 4]));
696             end;
697 
698         if SDL_MustLock(tmpsurf) then
699             SDL_UnlockSurface(tmpsurf);
700         if not disableLandBack then
701             begin
702             // freed in freeModule() below
703             LandBackSurface:= LoadDataImage(ptCurrTheme, 'LandBackTex', ifIgnoreCaps or ifColorKey);
704             if (LandBackSurface <> nil) and GrayScale then
705                 Surface2GrayScale(LandBackSurface)
706             end;
707     end;
708 end;
709 if (tmpsurf <> nil) then
710     SDL_FreeSurface(tmpsurf);
711 tmpsurf:= nil;
712 end;
713 
714 procedure LoadMap;
715 var tmpsurf: PSDL_Surface;
716     mapName: shortstring = '';
717 begin
718 WriteLnToConsole('Loading land from file...');
719 AddProgress;
720 tmpsurf:= LoadDataImage(ptMapCurrent, 'map', ifAlpha or ifColorKey or ifIgnoreCaps);
721 if tmpsurf = nil then
722     begin
723     mapName:= ExtractFileName(cPathz[ptMapCurrent]);
724     tmpsurf:= LoadDataImage(ptMissionMaps, mapName + '/map', ifAlpha or ifCritical or ifColorKey or ifIgnoreCaps);
725     if not allOK then exit;
726     end;
727 // (bare) Sanity check. Considering possible LongInt comparisons as well as just how much system memoery it would take
728 if checkFails((tmpsurf^.w < $40000000) and (tmpsurf^.h < $40000000) and (QWord(tmpsurf^.w) * tmpsurf^.h < 6*1024*1024*1024), 'Map dimensions too big!', true)
729         then exit;
730 
731 ResizeLand(tmpsurf^.w, tmpsurf^.h);
732 LoadMapConfig;
733 
734 playHeight:= tmpsurf^.h;
735 playWidth:= tmpsurf^.w;
736 leftX:= (LAND_WIDTH - playWidth) div 2;
737 rightX:= (playWidth + ((LAND_WIDTH - playWidth) div 2)) - 1;
738 topY:= LAND_HEIGHT - playHeight;
739 
740 if not checkFails(tmpsurf^.format^.BytesPerPixel = 4, 'Map should be 32bit', true) then
741     BlitImageAndGenerateCollisionInfo(
742         (LAND_WIDTH - tmpsurf^.w) div 2,
743         LAND_HEIGHT - tmpsurf^.h,
744         tmpsurf^.w,
745         tmpsurf);
746 
747 SDL_FreeSurface(tmpsurf);
748 
749 if allOK then LoadMask;
750 end;
751 
752 procedure DrawBottomBorder; // broken out from other borders for doing a floor-only map, or possibly updating bottom during SD
753 var x, w, c, y: Longword;
754 begin
755 for w:= 0 to 23 do
756     for x:= LongWord(leftX) to LongWord(rightX) do
757         begin
758         y:= Longword(cWaterLine) - 1 - w;
759         Land[y, x]:= lfIndestructible;
760         if (x + y) mod 32 < 16 then
761             c:= AMask
762         else
763             c:= AMask or RMask or GMask; // FF00FFFF
764 
765         if (cReducedQuality and rqBlurryLand) = 0 then
766             LandPixels[y, x]:= c
767         else
768             LandPixels[y div 2, x div 2]:= c
769         end
770 end;
771 
772 procedure GenMap;
773 var x, y, w, c, c2: Longword;
774     map, mask: shortstring;
775 begin
776     hasBorder:= false;
777     maskOnly:= false;
778 
779     LoadThemeConfig;
780 
781     if cPathz[ptMapCurrent] <> '' then
782         begin
783         map:= cPathz[ptMapCurrent] + '/map.png';
784         mask:= cPathz[ptMapCurrent] + '/mask.png';
785         if (not(pfsExists(map)) and pfsExists(mask)) then
786             begin
787             maskOnly:= true;
788             LoadMask;
789             GenLandSurface
790             end
791         else LoadMap;
792         end
793     else
794         begin
795         WriteLnToConsole('Generating land...');
796         case cMapGen of
797             mgRandom: GenTemplated(EdgeTemplates[SelectTemplate]);
798             mgMaze  : begin ResizeLand(4096,2048); GenMaze; end;
799             mgPerlin: begin ResizeLand(4096,2048); GenPerlin; end;
800             mgDrawn : GenDrawnMap;
801             mgForts : begin GameFlags:= (GameFlags or gfDivideTeams); MakeFortsMap(); end;
802         else
803             OutError('Unknown mapgen', true);
804         end;
805         if cMapGen <> mgForts then
806             GenLandSurface
807         end;
808 
809     AddProgress;
810 
811 // check for land near top
812 c:= 0;
813 if (GameFlags and gfBorder) <> 0 then
814     hasBorder:= true
815 else
816     for y:= LongWord(topY) to LongWord(topY + 5) do
817         for x:= LongWord(leftX) to LongWord(rightX) do
818             if Land[y, x] <> 0 then
819                 begin
820                 inc(c);
821                 if c > LongWord((LAND_WIDTH div 2)) then // avoid accidental triggering
822                     begin
823                     hasBorder:= true;
824                     break;
825                     end;
826                 end;
827 
828 // Indestructible map border (top, left, right)
829 if hasBorder then
830     begin
831     // Make land beyond the border indestructible
832     if WorldEdge = weNone then
833         begin
834         for y:= 0 to LAND_HEIGHT - 1 do
835             for x:= 0 to LAND_WIDTH - 1 do
836                 if (y < LongWord(topY)) or (x < LongWord(leftX)) or (x > LongWord(rightX)) then
837                     Land[y, x]:= lfIndestructible;
838         end
839     else if topY > 0 then
840         begin
841         for y:= 0 to LongWord(topY - 1) do
842             for x:= 0 to LAND_WIDTH - 1 do
843                 Land[y, x]:= lfIndestructible;
844         end;
845     // Render map border
846     for w:= 0 to (cBorderWidth-1) do
847         begin
848         // Left and right border
849         if (WorldEdge <> weBounce) and (WorldEdge <> weWrap) then
850             for y:= LongWord(topY) to LAND_HEIGHT - 1 do
851                     begin
852                     // set land flags
853                     Land[y, leftX + w]:= lfIndestructible;
854                     Land[y, rightX - w]:= lfIndestructible;
855 
856                     // paint black and yellow stripes
857                     if (y + leftX + w) mod 32 < 16 then
858                         c:= AMask // black
859                     else
860                         c:= AMask or RMask or GMask; // yellow
861                     if (y + rightX - w) mod 32 < 16 then
862                         c2:= AMask // black
863                     else
864                         c2:= AMask or RMask or GMask; // yellow
865 
866                     if (cReducedQuality and rqBlurryLand) = 0 then
867                         begin
868                         LandPixels[y, leftX + w]:= c;
869                         LandPixels[y, rightX - w]:= c2;
870                         end
871                     else
872                         begin
873                         LandPixels[y div 2, (leftX + w) div 2]:= c;
874                         LandPixels[y div 2, (rightX - w) div 2]:= c2;
875                         end;
876                     end;
877 
878         // Top border
879         for x:= LongWord(leftX) to LongWord(rightX) do
880             begin
881             Land[topY + w, x]:= lfIndestructible;
882             if (topY + x + w) mod 32 < 16 then
883                 c:= AMask // black
884             else
885                 c:= AMask or RMask or GMask; // yellow
886 
887             if (cReducedQuality and rqBlurryLand) = 0 then
888                 LandPixels[topY + w, x]:= c
889             else
890                 LandPixels[(topY + w) div 2, x div 2]:= c;
891             end;
892         end;
893     end;
894 
895 // Bottom border
896 if (GameFlags and gfBottomBorder) <> 0 then
897     DrawBottomBorder;
898 
899 if (GameFlags and gfDisableGirders) <> 0 then
900     hasGirders:= false;
901 
902 if (cMapGen <> mgForts) and (maskOnly or (cPathz[ptMapCurrent] = '')) then
903     AddObjects
904 
905 else
906     AddProgress();
907 
908 FreeLandObjects;
909 
910 if not allOK then exit;
911 
912 if GrayScale then
913     begin
914     if (cReducedQuality and rqBlurryLand) = 0 then
915         for x:= LongWord(leftX) to LongWord(rightX) do
916             for y:= LongWord(topY) to LAND_HEIGHT-1 do
917                 begin
918                 w:= LandPixels[y,x];
919                 w:= round(((w shr RShift and $FF) * RGB_LUMINANCE_RED +
920                       (w shr BShift and $FF) * RGB_LUMINANCE_GREEN +
921                       (w shr GShift and $FF) * RGB_LUMINANCE_BLUE));
922                 if w > 255 then
923                     w:= 255;
924                 w:= (w and $FF shl RShift) or (w and $FF shl BShift) or (w and $FF shl GShift) or (LandPixels[y,x] and AMask);
925                 LandPixels[y,x]:= w or (LandPixels[y, x] and AMask)
926                 end
927     else
928         for x:= LongWord(leftX div 2) to LongWord(rightX div 2) do
929             for y:= LongWord(topY div 2) to LAND_HEIGHT-1 div 2 do
930                 begin
931                 w:= LandPixels[y div 2,x div 2];
932                 w:= ((w shr RShift and $FF) +  (w shr BShift and $FF) + (w shr GShift and $FF)) div 3;
933                 w:= (w and $FF shl RShift) or (w and $FF shl BShift) or (w and $FF shl GShift) or (LandPixels[y div 2,x div 2] and AMask);
934                 LandPixels[y,x]:= w or (LandPixels[y div 2, x div 2] and AMask)
935                 end
936     end;
937 
938 PrettifyLandAlpha();
939 
940 // adjust world edges for borderless maps
941 if (WorldEdge <> weNone) and (not hasBorder) then
942     InitWorldEdges();
943 
944 ScriptSetMapGlobals;
945 end;
946 
947 procedure GenPreview(out Preview: TPreview);
948 var rh, rw, ox, oy, x, y, xx, yy, t, bit, cbit, lh, lw: LongInt;
949 begin
950     WriteLnToConsole('Generating preview...');
951     case cMapGen of
952         mgRandom: GenTemplated(EdgeTemplates[SelectTemplate]);
953         mgMaze: begin ResizeLand(4096,2048); GenMaze; end;
954         mgPerlin: begin ResizeLand(4096,2048); GenPerlin; end;
955         mgDrawn: begin GenDrawnMap; end;
956         mgForts: MakeFortsPreview();
957     else
958         OutError('Unknown mapgen', true);
959     end;
960 
961     ScriptSetMapGlobals;
962 
963     // strict scaling needed here since preview assumes a rectangle
964     if (cMapGen <> mgDrawn) then
965         begin
966         rh:= max(LAND_HEIGHT, 2048);
967         rw:= max(LAND_WIDTH, 4096);
968         end
969     else
970         begin
971         rh:= LAND_HEIGHT;
972         rw:= LAND_WIDTH
973         end;
974     ox:= 0;
975     if rw < rh*2 then
976         begin
977         rw:= rh*2;
978         end;
979     if rh < rw div 2 then rh:= rw * 2;
980 
981     ox:= (rw-LAND_WIDTH) div 2;
982     oy:= rh-LAND_HEIGHT;
983 
984     lh:= rh div 128;
985     lw:= rw div 32;
986     for y:= 0 to 127 do
987         for x:= 0 to 31 do
988         begin
989             Preview[y, x]:= 0;
990             for bit:= 0 to 7 do
991             begin
992                 t:= 0;
993                 cbit:= bit * 8;
994                 for yy:= y * lh to y * lh + 7 do
995                     for xx:= x * lw + cbit to x * lw + cbit + 7 do
996                         if ((yy-oy) and LAND_HEIGHT_MASK = 0) and ((xx-ox) and LAND_WIDTH_MASK = 0)
997                            and (Land[yy-oy, xx-ox] <> 0) then
998                             inc(t);
999                 if t > 8 then
1000                     Preview[y, x]:= Preview[y, x] or ($80 shr bit);
1001             end;
1002         end;
1003 end;
1004 
1005 
1006 procedure GenPreviewAlpha(out Preview: TPreviewAlpha);
1007 var rh, rw, ox, oy, x, y, xx, yy, t, lh, lw: LongInt;
1008 begin
1009     WriteLnToConsole('Generating preview...');
1010     case cMapGen of
1011         mgRandom: GenTemplated(EdgeTemplates[SelectTemplate]);
1012         mgMaze: begin ResizeLand(4096,2048); GenMaze; end;
1013         mgPerlin: begin ResizeLand(4096,2048); GenPerlin; end;
1014         mgDrawn: begin GenDrawnMap; end;
1015         mgForts: MakeFortsPreview;
1016     else
1017         OutError('Unknown mapgen', true);
1018     end;
1019 
1020     ScriptSetMapGlobals;
1021 
1022 
1023     // strict scaling needed here since preview assumes a rectangle
1024     if (cMapGen <> mgDrawn) then
1025         begin
1026         rh:= max(LAND_HEIGHT, 2048);
1027         rw:= max(LAND_WIDTH, 4096);
1028         end
1029     else
1030         begin
1031         rh:= LAND_HEIGHT;
1032         rw:= LAND_WIDTH
1033         end;
1034 
1035     ox:= 0;
1036     if rw < rh*2 then
1037         begin
1038         rw:= rh*2;
1039         end;
1040     if rh < rw div 2 then rh:= rw * 2;
1041 
1042     ox:= (rw-LAND_WIDTH) div 2;
1043     oy:= rh-LAND_HEIGHT;
1044 
1045     lh:= rh div 128;
1046     lw:= rw div 256;
1047     for y:= 0 to 127 do
1048         for x:= 0 to 255 do
1049             begin
1050             t:= 0;
1051 
1052             for yy:= y * lh - oy to y * lh + lh - 1 - oy do
1053                 for xx:= x * lw - ox to x * lw + lw - 1 - ox do
1054                     if (yy and LAND_HEIGHT_MASK = 0) and (xx and LAND_WIDTH_MASK = 0)
1055                         and (Land[yy, xx] <> 0) then
1056                         inc(t);
1057 
1058             Preview[y, x]:= t * 255 div (lh * lw);
1059             end;
1060 end;
1061 
1062 procedure chLandCheck(var s: shortstring);
1063 begin
1064     AddFileLog('CheckLandDigest: ' + s + ' digest : ' + digest);
1065     if digest = '' then
1066         digest:= s
1067     else
1068         checkFails(s = digest, 'Loaded map or other critical resource does not match across all players', true);
1069 end;
1070 
1071 procedure chSendLandDigest(var s: shortstring);
1072 var i: LongInt;
1073     landPixelDigest  : LongInt;
1074 begin
1075     landPixelDigest:= 1;
1076     for i:= 0 to LAND_HEIGHT-1 do
1077         landPixelDigest:= Adler32Update(landPixelDigest, @Land[i,0], LAND_WIDTH*2);
1078     s:= 'M' + IntToStr(syncedPixelDigest)+'|'+IntToStr(landPixelDigest);
1079 
1080     ScriptSetString('LandDigest',IntToStr(landPixelDigest));
1081 
1082     chLandCheck(s);
1083     if allOK then SendIPCRaw(@s[0], Length(s) + 1)
1084 end;
1085 
1086 procedure initModule;
1087 begin
1088     RegisterVariable('landcheck', @chLandCheck, false);
1089     RegisterVariable('sendlanddigest', @chSendLandDigest, false);
1090 
1091     LandBackSurface:= nil;
1092     digest:= '';
1093     maskOnly:= false;
1094     LAND_WIDTH:= 0;
1095     LAND_HEIGHT:= 0;
1096 (*
1097     if (cReducedQuality and rqBlurryLand) = 0 then
1098         SetLength(LandPixels, LAND_HEIGHT, LAND_WIDTH)
1099     else
1100         SetLength(LandPixels, LAND_HEIGHT div 2, LAND_WIDTH div 2);
1101 
1102     SetLength(Land, LAND_HEIGHT, LAND_WIDTH);
1103     SetLength(LandDirty, (LAND_HEIGHT div 32), (LAND_WIDTH div 32));
1104 *)
1105 end;
1106 
1107 procedure freeModule;
1108 begin
1109     SetLength(Land, 0, 0);
1110     SetLength(LandPixels, 0, 0);
1111     SetLength(LandDirty, 0, 0);
1112 end;
1113 
1114 end.
1115