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