From 6a90d603275125955bc933e5ab9e31117e7f64d0 Mon Sep 17 00:00:00 2001 From: DiSlord Date: Mon, 9 Mar 2009 19:37:12 +0300 Subject: [PATCH] [7428] Add new .map file format use more compact data store and use Rewrite .map extractor + extract more useful data + add "-f 0" option for disable size/accuracy optimisation + Compatability vs 2.4.3 client data More fast get .map data Implement fatigue timer Rewrite breath timer Allow absorb/resist for lava/slime environmental damage Need re-extract map for work. Signed-off-by: DiSlord --- contrib/extractor/CMakeLists.txt | 9 +- contrib/extractor/System.cpp | 707 ++++++++++++++++++++-- contrib/extractor/VC71_ad.vcproj | 10 +- contrib/extractor/VC80_ad.vcproj | 10 +- contrib/extractor/VC90_ad.vcproj | 10 +- contrib/extractor/ad.exe | Bin 159744 -> 163328 bytes contrib/extractor/adt.cpp | 380 ------------ contrib/extractor/adt.h | 128 ---- contrib/extractor/loadlib/CMakeLists.txt | 13 + contrib/extractor/loadlib/adt.cpp | 131 ++++ contrib/extractor/loadlib/adt.h | 289 +++++++++ contrib/extractor/loadlib/loadlib.cpp | 63 ++ contrib/extractor/loadlib/loadlib.h | 57 ++ contrib/extractor/loadlib/wdt.cpp | 62 ++ contrib/extractor/loadlib/wdt.h | 68 +++ contrib/extractor/mpq_libmpq.h | 2 +- src/game/Map.cpp | 724 +++++++++++++++++------ src/game/Map.h | 138 ++++- src/game/Player.cpp | 317 ++++++---- src/game/Player.h | 25 +- src/game/SpellAuras.cpp | 12 +- src/shared/revision_nr.h | 2 +- 22 files changed, 2280 insertions(+), 877 deletions(-) delete mode 100644 contrib/extractor/adt.cpp delete mode 100644 contrib/extractor/adt.h create mode 100644 contrib/extractor/loadlib/CMakeLists.txt create mode 100644 contrib/extractor/loadlib/adt.cpp create mode 100644 contrib/extractor/loadlib/adt.h create mode 100644 contrib/extractor/loadlib/loadlib.cpp create mode 100644 contrib/extractor/loadlib/loadlib.h create mode 100644 contrib/extractor/loadlib/wdt.cpp create mode 100644 contrib/extractor/loadlib/wdt.h diff --git a/contrib/extractor/CMakeLists.txt b/contrib/extractor/CMakeLists.txt index a00dda120..9052903b1 100644 --- a/contrib/extractor/CMakeLists.txt +++ b/contrib/extractor/CMakeLists.txt @@ -12,10 +12,15 @@ cmake_minimum_required (VERSION 2.6) project (MANGOS_MAP_EXTRACTOR) add_subdirectory (libmpq) +add_subdirectory (loadlib) include_directories (${MANGOS_MAP_EXTRACTOR_SOURCE_DIR}/libmpq) -link_directories (${MANGOS_MAP_EXTRACTOR_SOURCE_DIR}/libmpq) +include_directories (${MANGOS_MAP_EXTRACTOR_SOURCE_DIR}/loadlib) -add_executable (ad adt.cpp dbcfile.cpp mpq_libmpq.cpp System.cpp) +link_directories (${MANGOS_MAP_EXTRACTOR_SOURCE_DIR}/libmpq) +link_directories (${MANGOS_MAP_EXTRACTOR_SOURCE_DIR}/loadlib) + +add_executable (ad dbcfile.cpp mpq_libmpq.cpp System.cpp) target_link_libraries (ad libmpq) +target_link_libraries (ad loadlib) diff --git a/contrib/extractor/System.cpp b/contrib/extractor/System.cpp index f9f684014..49190f65b 100644 --- a/contrib/extractor/System.cpp +++ b/contrib/extractor/System.cpp @@ -14,10 +14,10 @@ #include "dbcfile.h" #include "mpq_libmpq.h" -extern unsigned int iRes; -extern ArchiveSet gOpenArchives; +#include "loadlib/adt.h" +#include "loadlib/wdt.h" -bool ConvertADT(char*, char*); +extern ArchiveSet gOpenArchives; typedef struct { @@ -25,10 +25,6 @@ typedef struct uint32 id; } map_id; -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; - map_id *map_ids; uint16 *areas; uint16 *LiqType; @@ -36,18 +32,44 @@ char output_path[128] = "."; char input_path[128] = "."; uint32 maxAreaId = 0; +//************************************************** +// Extractor options +//************************************************** enum Extract { EXTRACT_MAP = 1, EXTRACT_DBC = 2 }; -int extract = EXTRACT_MAP | EXTRACT_DBC; + +// Select data for extract +int CONF_extract = EXTRACT_MAP | EXTRACT_DBC; +// This option allow limit minimum height to some value (Allow save some memory) +bool CONF_allow_height_limit = true; +float CONF_use_minHeight = -500.0f; + +// This option allow use float to int conversion +bool CONF_allow_float_to_int = true; +float CONF_float_to_int8_limit = 2.0f; // Max accuracy = val/256 +float CONF_float_to_int16_limit = 2048.0f; // Max accuracy = val/65536 +float CONF_flat_height_delta_limit = 0.005f; // If max - min less this value - surface is flat +float CONF_flat_liquid_delta_limit = 0.001f; // If max - min less this value - liquid surface is flat + +// List MPQ for extract from +char *CONF_mpq_list[]={ + "common.MPQ", + "common-2.MPQ", + "lichking.MPQ", + "expansion.MPQ", + "patch.MPQ", + "patch-2.MPQ", + "patch-3.MPQ", + "patch-4.MPQ", + "patch-5.MPQ", +}; static char* const langs[] = {"enGB", "enUS", "deDE", "esES", "frFR", "koKR", "zhCN", "zhTW", "enCN", "enTW", "esMX", "ruRU" }; #define LANG_COUNT 12 -#define ADT_RES 64 - void CreateDir( const std::string& Path ) { #ifdef WIN32 @@ -70,7 +92,14 @@ bool FileExists( const char* FileName ) void Usage(char* prg) { - printf("Usage:\n%s -[var] [value]\n-i set input path\n-o set output path\n-e extract only MAP(1)/DBC(2) - standard: both(3)\nExample: %s -r 256 -i \"c:\\games\\game\"", prg, prg); + printf( + "Usage:\n"\ + "%s -[var] [value]\n"\ + "-i set input path\n"\ + "-o set output path\n"\ + "-e extract only MAP(1)/DBC(2) - standard: both(3)\n"\ + "-f height stored as int (less map size but lost some accuracy) 1 by default\n"\ + "Example: %s -f 0 -i \"c:\\games\\game\"", prg, prg); exit(1); } @@ -80,8 +109,9 @@ void HandleArgs(int argc, char * arg[]) { // i - input path // o - output path - // r - resolution, array of (r * r) heights will be created // e - extract only MAP(1)/DBC(2) - standard both(3) + // f - use float to int conversion + // h - limit minimum height if(arg[c][0] != '-') Usage(arg[0]); @@ -99,11 +129,17 @@ void HandleArgs(int argc, char * arg[]) else Usage(arg[0]); break; + case 'f': + if(c + 1 < argc) // all ok + CONF_allow_float_to_int=atoi(arg[(c++) + 1])!=0; + else + Usage(arg[0]); + break; case 'e': if(c + 1 < argc) // all ok { - extract=atoi(arg[(c++) + 1]); - if(!(extract > 0 && extract < 4)) + CONF_extract=atoi(arg[(c++) + 1]); + if(!(CONF_extract > 0 && CONF_extract < 4)) Usage(arg[0]); } else @@ -165,10 +201,602 @@ void ReadLiquidTypeTableDBC() printf("Done! (%u LiqTypes loaded)\n", LiqType_count); } +// +// Adt file convertor function and data +// + +// Map file format data +#define MAP_MAGIC 'SPAM' +#define MAP_VERSION_MAGIC '0.1v' +#define MAP_AREA_MAGIC 'AERA' +#define MAP_HEIGTH_MAGIC 'TGHM' +#define MAP_LIQUID_MAGIC 'QILM' + +struct map_fileheader{ + uint32 mapMagic; + uint32 versionMagic; + uint32 areaMapOffset; + uint32 areaMapSize; + uint32 heightMapOffset; + uint32 heightMapSize; + uint32 liquidMapOffset; + uint32 liquidMapSize; +}; + +#define MAP_AREA_NO_AREA 0x0001 +struct map_areaHeader{ + uint32 fourcc; + uint16 flags; + uint16 gridArea; +}; + +#define MAP_HEIGHT_NO_HIGHT 0x0001 +#define MAP_HEIGHT_AS_INT16 0x0002 +#define MAP_HEIGHT_AS_INT8 0x0004 + +struct map_heightHeader{ + uint32 fourcc; + uint32 flags; + float gridHeight; + float gridMaxHeight; +}; + +#define MAP_LIQUID_TYPE_NO_WATER 0x00 +#define MAP_LIQUID_TYPE_WATER 0x01 +#define MAP_LIQUID_TYPE_OCEAN 0x02 +#define MAP_LIQUID_TYPE_MAGMA 0x04 +#define MAP_LIQUID_TYPE_SLIME 0x08 + +#define MAP_LIQUID_TYPE_DARK_WATER 0x10 +#define MAP_LIQUID_TYPE_WMO_WATER 0x20 + + +#define MAP_LIQUID_NO_TYPE 0x0001 +#define MAP_LIQUID_NO_HIGHT 0x0002 + +struct map_liquidHeader{ + uint32 fourcc; + uint16 flags; + uint16 liquidType; + uint8 offsetX; + uint8 offsetY; + uint8 width; + uint8 height; + float liquidLevel; +}; + +float selectUInt8StepStore(float maxDiff) +{ + return 255 / maxDiff; +} + +float selectUInt16StepStore(float maxDiff) +{ + return 65535 / maxDiff; +} +// Temporary grid data store +uint16 area_flags[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]; + +float V8[ADT_GRID_SIZE][ADT_GRID_SIZE]; +float V9[ADT_GRID_SIZE+1][ADT_GRID_SIZE+1]; +uint16 uint16_V8[ADT_GRID_SIZE][ADT_GRID_SIZE]; +uint16 uint16_V9[ADT_GRID_SIZE+1][ADT_GRID_SIZE+1]; +uint8 uint8_V8[ADT_GRID_SIZE][ADT_GRID_SIZE]; +uint8 uint8_V9[ADT_GRID_SIZE+1][ADT_GRID_SIZE+1]; + +uint8 liquid_type[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]; +bool liquid_show[ADT_GRID_SIZE][ADT_GRID_SIZE]; +float liquid_height[ADT_GRID_SIZE+1][ADT_GRID_SIZE+1]; + +bool ConvertADT(char *filename, char *filename2, int cell_y, int cell_x) +{ + ADT_file adt; + + if (!adt.loadFile(filename)) + return false; + + adt_MCIN *cells = adt.a_grid->getMCIN(); + if (!cells) + { + printf("Can't find cells in '%s'\n", filename); + return false; + } + + memset(liquid_show, 0, sizeof(liquid_show)); + memset(liquid_type, 0, sizeof(liquid_type)); + memset(liquid_height, 0, sizeof(liquid_height)); + + // Prepare map header + map_fileheader map; + map.mapMagic = MAP_MAGIC; + map.versionMagic = MAP_VERSION_MAGIC; + + // Get area flags data + for (int i=0;igetMCNK(i,j); + uint32 areaid = cell->areaid; + if(areaid && areaid <= maxAreaId) + { + if(areas[areaid] != 0xffff) + { + area_flags[i][j] = areas[areaid]; + continue; + } + printf("File: filename\nCan't find area flag for areaid %u [%d, %d].\n", filename, areaid, cell->ix, cell->iy); + } + area_flags[i][j] = 0xffff; + } + } + //============================================ + // Try pack area data + //============================================ + bool fullAreaData = false; + uint32 areaflag = area_flags[0][0]; + for (int y=0;ygetMCNK(i,j); + if (!cell) + continue; + // Height values for triangles stored in order: + // 1 2 3 4 5 6 7 8 9 + // 10 11 12 13 14 15 16 17 + // 18 19 20 21 22 23 24 25 26 + // 27 28 29 30 31 32 33 34 + // . . . . . . . . + // For better get height values merge it to V9 and V8 map + // V9 height map: + // 1 2 3 4 5 6 7 8 9 + // 18 19 20 21 22 23 24 25 26 + // . . . . . . . . + // V8 height map: + // 10 11 12 13 14 15 16 17 + // 27 28 29 30 31 32 33 34 + // . . . . . . . . + + // Set map height as grid height + for (int y=0; y <= ADT_CELL_SIZE; y++) + { + int cy = i*ADT_CELL_SIZE + y; + for (int x=0; x <= ADT_CELL_SIZE; x++) + { + int cx = j*ADT_CELL_SIZE + x; + V9[cy][cx]=cell->ypos; + } + } + for (int y=0; y < ADT_CELL_SIZE; y++) + { + int cy = i*ADT_CELL_SIZE + y; + for (int x=0; x < ADT_CELL_SIZE; x++) + { + int cx = j*ADT_CELL_SIZE + x; + V8[cy][cx]=cell->ypos; + } + } + // Get custom height + adt_MCVT *v = cell->getMCVT(); + if (!v) + continue; + // get V9 height map + for (int y=0; y <= ADT_CELL_SIZE; y++) + { + int cy = i*ADT_CELL_SIZE + y; + for (int x=0; x <= ADT_CELL_SIZE; x++) + { + int cx = j*ADT_CELL_SIZE + x; + V9[cy][cx]+=v->height_map[y*(ADT_CELL_SIZE*2+1)+x]; + } + } + // get V8 height map + for (int y=0; y < ADT_CELL_SIZE; y++) + { + int cy = i*ADT_CELL_SIZE + y; + for (int x=0; x < ADT_CELL_SIZE; x++) + { + int cx = j*ADT_CELL_SIZE + x; + V8[cy][cx]+=v->height_map[y*(ADT_CELL_SIZE*2+1)+ADT_CELL_SIZE+1+x]; + } + } + } + } + //============================================ + // Try pack height data + //============================================ + float maxHeight = -20000; + float minHeight = 20000; + for (int y=0; y h) minHeight = h; + } + } + for (int y=0; y<=ADT_GRID_SIZE; y++) + { + for(int x=0;x<=ADT_GRID_SIZE;x++) + { + float h = V9[y][x]; + if (maxHeight < h) maxHeight = h; + if (minHeight > h) minHeight = h; + } + } + + // Check for allow limit minimum height (not store height in deep ochean - allow save some memory) + if (CONF_allow_height_limit && minHeight < CONF_use_minHeight) + { + for (int y=0; ygetMH2O(); + if (h2o) + { + for (int i=0;igetLiquidData(i,j); + if (!h) + continue; + + int count = 0; + uint64 show = h2o->getLiquidShowMap(h); + for (int y=0; y < h->height;y++) + { + int cy = i*ADT_CELL_SIZE + y + h->yOffset; + for (int x=0; x < h->width; x++) + { + int cx = j*ADT_CELL_SIZE + x + h->xOffset; + if (show & 1) + { + liquid_show[cy][cx] = true; + ++count; + } + show>>=1; + } + } + + uint32 type = LiqType[h->liquidType]; + switch (type) + { + case LIQUID_TYPE_WATER: liquid_type[i][j] |= MAP_LIQUID_TYPE_WATER; break; + case LIQUID_TYPE_OCEAN: liquid_type[i][j] |= MAP_LIQUID_TYPE_OCEAN; break; + case LIQUID_TYPE_MAGMA: liquid_type[i][j] |= MAP_LIQUID_TYPE_MAGMA; break; + case LIQUID_TYPE_SLIME: liquid_type[i][j] |= MAP_LIQUID_TYPE_SLIME; break; + default: + printf("\nCan't find Liquid type %u for map %s\nchunk %d,%d\n", h->liquidType, filename, i, j); + break; + } + // Dark water detect + if (type == LIQUID_TYPE_OCEAN) + { + uint8 *lm = h2o->getLiquidLightMap(h); + if (!lm) + liquid_type[i][j]|=MAP_LIQUID_TYPE_DARK_WATER; + } + + if (!count && liquid_type[i][j]) + printf("Wrong liquid detect in MH2O chunk"); + + float *height = h2o->getLiquidHeightMap(h); + int pos = 0; + for (int y=0; y<=h->height;y++) + { + int cy = i*ADT_CELL_SIZE + y + h->yOffset; + for (int x=0; x<= h->width; x++) + { + int cx = j*ADT_CELL_SIZE + x + h->xOffset; + if (height) + liquid_height[cy][cx] = height[pos]; + else + liquid_height[cy][cx] = h->heightLevel1; + pos++; + } + } + } + } + } + else + { + // Get from MCLQ chunk (old) + for (int i=0;igetMCNK(i, j); + if (!cell) + continue; + + adt_MCLQ *liquid = cell->getMCLQ(); + int count = 0; + if (!liquid || cell->sizeMCLQ <= 8) + continue; + + for (int y=0; y < ADT_CELL_SIZE; y++) + { + int cy = i*ADT_CELL_SIZE + y; + for (int x=0; x < ADT_CELL_SIZE; x++) + { + int cx = j*ADT_CELL_SIZE + x; + if (liquid->flags[y][x] != 0x0F) + { + liquid_show[cy][cx] = true; + if (liquid->flags[y][x]&(1<<7)) + liquid_type[i][j]|=MAP_LIQUID_TYPE_DARK_WATER; + ++count; + } + } + } + + uint32 c_flag = cell->flags; + if(c_flag & (1<<2)) + liquid_type[i][j]|=MAP_LIQUID_TYPE_WATER; // water + if(c_flag & (1<<3)) + liquid_type[i][j]|=MAP_LIQUID_TYPE_OCEAN; // ochean + if(c_flag & (1<<4)) + liquid_type[i][j]|=MAP_LIQUID_TYPE_MAGMA; // magma/slime + + if (!count && liquid_type[i][j]) + printf("Wrong liquid detect in MCLQ chunk"); + + for (int y=0; y <= ADT_CELL_SIZE; y++) + { + int cy = i*ADT_CELL_SIZE + y; + for (int x=0; x<= ADT_CELL_SIZE; x++) + { + int cx = j*ADT_CELL_SIZE + x; + liquid_height[cy][cx] = liquid->liquid[y][x].height; + } + } + } + } + } + + //============================================ + // Pack liquid data + //============================================ + uint8 type = liquid_type[0][0]; + bool fullType = false; + for (int y=0;y x) minX = x; + if (maxX < x) maxX = x; + if (minY > y) minY = y; + if (maxY < y) maxY = y; + float h = liquid_height[y][x]; + if (maxHeight < h) maxHeight = h; + if (minHeight > h) minHeight = h; + } + } + } + map.liquidMapOffset = map.heightMapOffset + map.heightMapSize; + map.liquidMapSize = sizeof(map_liquidHeader); + liquidHeader.fourcc = MAP_LIQUID_MAGIC; + liquidHeader.flags = 0; + liquidHeader.liquidType = 0; + liquidHeader.offsetX = minX; + liquidHeader.offsetY = minY; + liquidHeader.width = maxX - minX + 1; + liquidHeader.height = maxY - minY + 1; + liquidHeader.liquidLevel = minHeight; + + if (maxHeight == minHeight) + liquidHeader.flags|=MAP_LIQUID_NO_HIGHT; + + // Not need store if flat surface + if (CONF_allow_float_to_int && (maxHeight - minHeight) < CONF_flat_liquid_delta_limit) + liquidHeader.flags|=MAP_LIQUID_NO_HIGHT; + + if (!fullType) + liquidHeader.flags|=MAP_LIQUID_NO_TYPE; + + if (liquidHeader.flags&MAP_LIQUID_NO_TYPE) + liquidHeader.liquidType = type; + else + map.liquidMapSize+=sizeof(liquid_type); + + if (!(liquidHeader.flags&MAP_LIQUID_NO_HIGHT)) + map.liquidMapSize+=sizeof(float)*liquidHeader.width*liquidHeader.height; + } + + // Ok all data prepared - store it + FILE *output=fopen(filename2, "wb"); + if(!output) + { + printf("Can't create the output file '%s'\n", filename2); + return false; + } + fwrite(&map, sizeof(map), 1, output); + // Store area data + fwrite(&areaHeader, sizeof(areaHeader), 1, output); + if (!(areaHeader.flags&MAP_AREA_NO_AREA)) + fwrite(area_flags, sizeof(area_flags), 1, output); + + // Store height data + fwrite(&heightHeader, sizeof(heightHeader), 1, output); + if (!(heightHeader.flags&MAP_HEIGHT_NO_HIGHT)) + { + if (heightHeader.flags&MAP_HEIGHT_AS_INT16) + { + fwrite(uint16_V9, sizeof(uint16_V9), 1, output); + fwrite(uint16_V8, sizeof(uint16_V8), 1, output); + } + else if (heightHeader.flags&MAP_HEIGHT_AS_INT8) + { + fwrite(uint8_V9, sizeof(uint8_V9), 1, output); + fwrite(uint8_V8, sizeof(uint8_V8), 1, output); + } + else + { + fwrite(V9, sizeof(V9), 1, output); + fwrite(V8, sizeof(V8), 1, output); + } + } + + // Store liquid data if need + if (map.liquidMapOffset) + { + fwrite(&liquidHeader, sizeof(liquidHeader), 1, output); + if (!(liquidHeader.flags&MAP_LIQUID_NO_TYPE)) + fwrite(liquid_type, sizeof(liquid_type), 1, output); + if (!(liquidHeader.flags&MAP_LIQUID_NO_HIGHT)) + { + for (int y=0; yadt_list[y][x].exist) + continue; sprintf(mpq_filename, "World\\Maps\\%s\\%s_%u_%u.adt", map_ids[z].name, map_ids[z].name, x, y); sprintf(output_filename, "%s/maps/%03u%02u%02u.map", output_path, map_ids[z].id, y, x); - ConvertADT(mpq_filename, output_filename); - done++; + ConvertADT(mpq_filename, output_filename, y, x); } // draw progress bar - printf("Processing........................%d%%\r", (100 * done) / total); + printf("Processing........................%d%%\r", (100 * (y+1)) / WDT_MAP_SIZE); } } - delete [] areas; delete [] map_ids; } -//bool WMO(char* filename); - void ExtractDBCFiles(int locale, bool basicLocale) { printf("Extracting dbc files...\n"); @@ -277,21 +911,10 @@ void LoadLocaleMPQFiles(int const locale) void LoadCommonMPQFiles() { char filename[512]; - - sprintf(filename,"%s/Data/common-2.MPQ", input_path); - new MPQArchive(filename); - sprintf(filename,"%s/Data/lichking.MPQ", input_path); - new MPQArchive(filename); - sprintf(filename,"%s/Data/expansion.MPQ", input_path); - new MPQArchive(filename); - - for(int i = 1; i < 5; ++i) + int count = sizeof(CONF_mpq_list)/sizeof(char*); + for(int i = 0; i < count; ++i) { - char ext[3] = ""; - if(i > 1) - sprintf(ext, "-%i", i); - - sprintf(filename, "%s/Data/patch%s.MPQ", input_path, ext); + sprintf(filename, "%s/Data/%s", input_path, CONF_mpq_list[i]); if(FileExists(filename)) new MPQArchive(filename); } @@ -323,7 +946,7 @@ int main(int argc, char * arg[]) //Open MPQs LoadLocaleMPQFiles(i); - if((extract & EXTRACT_DBC) == 0) + if((CONF_extract & EXTRACT_DBC) == 0) { FirstLocale = i; break; @@ -349,7 +972,7 @@ int main(int argc, char * arg[]) return 0; } - if (extract & EXTRACT_MAP) + if (CONF_extract & EXTRACT_MAP) { printf("Using locale: %s\n", langs[FirstLocale]); diff --git a/contrib/extractor/VC71_ad.vcproj b/contrib/extractor/VC71_ad.vcproj index fd2d16120..541540cea 100644 --- a/contrib/extractor/VC71_ad.vcproj +++ b/contrib/extractor/VC71_ad.vcproj @@ -213,7 +213,15 @@ Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" > + + + + + + @@ -253,6 +257,10 @@ /> + + diff --git a/contrib/extractor/VC90_ad.vcproj b/contrib/extractor/VC90_ad.vcproj index 59fdf6d21..9a039a0fb 100644 --- a/contrib/extractor/VC90_ad.vcproj +++ b/contrib/extractor/VC90_ad.vcproj @@ -216,7 +216,15 @@ Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" > + + + + @p$4J&Sn^7xtpaF9{ zPFdR=E4yh}d8?b2=0_g}qM&7ErUhw5Wwi+_yOmOz^ZwR31EO{R@BhBf^Wt;n?6daT zYp=cbT5GTUagHSo?Mkk++c-vScxdbdcfrXdQ;(PIQ(CT&FRQ0?z;no#XQph%{ic;e zru03b8~$t=f{-n0geNjynbeZiAcTk= z#CC%4g=iFnEh@Ngm?#JaN=qzS?Ad->xFBc*VbV5<;wX1xxUg4brP57N z%km__N}$$1Nth!()Vn)t5VfObHw5W@fjbDb{fiZZ`9qg4xY>ELAQatBIg%jUf!o)t zr_{Y;apzD$SOySPID=(Mz1pEx!V!x1+#x+IK9rz!H{N!WAe^}NCs$dH*9zyvG{Kvt z@n&hgS-R?LxhGB(1sslVQ-uZFK}E26E*R|j+qZer1j`YdFWnropn~A?k8+r7(*#b* z#pd{YU$)u6&8ViN*sK$3)6HhWadydf>=xZ=0zdS!Ab5^w8%wEVy4hs&W@`B-FA-4V z&D4}vIw#9hwYfIXG4dhFf`EjM??pn6k|p`wz{#>o=Kul{?EwC`ADO#FIU5XlBdE55 zd4--MF^4rmzT*hm#wV+F`rJgSNF|k~AzIhbu_0$f{D7*o2m3l2%FA^v)QJz&z)e>Jf!Z*Rg0-27`pGpd^iI^KDv-u>P$c}) z1No6AaEDzm)}Jv8IcI^ufZ}zt83{Z^pHVqmi&I{O;e@@S#ry1G0*{>PiNpWP!65_ zt(@j)0o|;EKH4p|5lL=o^3+Kaa=bdTQ!}EnpgM1+-SVBUK%4K|p(vjvApazXfkY=? zw)O??Q)%=DH-?Fx8iOZbC~n9otrlQ&9`_%H;*;|V=k69~7gXauq7rwAymZ|3^sF4B zUs&x~qp`X!$(cHkjPPYAX!*S=$uH#0gjU%7XfU{ccemKuHPjvSM_st?56X5)h1DP= zXK7@+#+Thm>-J7H1Ub|{g1XcK=);`+HeFJhoStR7?s@8@)HcslLG$l{fThx#Y4Bzm zy_u%ku%DqWK|KLH)!%E_iGYkdezO z=mXdTgbI*}&r(1t*U*_`|2v%_u1HA~omm*@bDh%c15bZa8g{nxb5g`!c7K<5rLlWh zzVUhE>o9OReZM9XPNclj^?g2n?&UpEk)fhA{N+78qM}4;{HttaS4-4W2Y~oMTqJ%s zYtY~Mxzo9BKU>o^I;2iYV_UnfG|oFnnL1=n_pXfxCeJy@2AC|;zjv`YCR5br{X~K? zu8|P-vGt~u5DpT@Og~A}_OUm*^^yL$mwn#tG3miq*y`@Rqzil4Kf0GlGxo9A=>9#9 z>=guWc?@cS6EwpXJl^A14k0)>!SlH~-~K1&&6s^te0z;0fyZ-zNx zI{1w8PVFowbkdo_wKi|eTDGn_@rCPt? zF8GadX6e;!f(Zre{H7Kj^yS~7D{V!RQklc7|edQrp!^&hryZBF4x^ej|K~eL&@3Mnlq1bvdyu%j$$R}0T$>LlfFqw-`0xNN4}pV zP-H9;LpwsuHpeyvV^Rx-v+L~ZMYrZ@HZRuH_XPz_4>Z}YP-tHymL8r=%HcSmW?WGTI;Mor(LihcGxOJz$}jN8GU>>W9BKroBUx>nSM!1LQ*2@c6H!rCwb z@d*H3N;hjoXFEH;=M~o2`?=1|*UZUt!Awq=J>6%A6#WF7(s!;g?J25K<5^=eEZ50X zHM#uRJ*=dEEIZdXPa3h84L1+y){H^caU?&D`dO0;o6{azD!c z-G7PHX*ZiYV6)WI#eNxZOp3j0&zo_5LZsQxurCMSE?s$?O^<)p9lJbD_~~i+cmC~f z@EH*T9=-C<3Fx>;4bnajrM)(d9HFJGEUg@=Blv5HgCQ0Jk_Tzr^xAaX0Sens-U%#^ zW;h5$oTjv_rF3K+rI`-t?dKE}1CqYih8S@>s0D>X4FLc2w9m7bDsNV(9Wd!M7D3*(B0fY} zv)l~pG1$CO{H6QY6T^D+J+K|tpp)zPE=ri>i~JcR98Sb-MM9q3%Lof;9^80jmB6s&J0O=< zC#~4?fMsELcCUX>X{|cu+H$#7F{LMM9;Lp*p8BA_}jIfOd=mAAM7ZMP6jZcYccX zfEhk2m%qCn>CNPArt`y;UP~i`jo0m9?Z-}*##ots?1$YQTZJ8d1(^W(Bs1mw|}Id8V* z!~u-j0e+~A^-Ap;p7|Us1=Po;4w801$(E!>OZjDNZEDfbCJz~EouVm75A~5CUoy{|9!2s6YI!p<$URV?9K_jBKABA2CV3BJGZ>R zo|q6Loq3MEHerzD^RaIx3=2;uRekoKtVephRQN2LlJ1qhy^npE9w$}a_i)CYCK0_K zY>m^|+LEL9F>l5!=^>eYnUU9FT`94sv2%AROU+y;T`1YJGjpyejb6{fvldEgR@ zhS+smU~3Tu{Mti~<;xdSs#dh;5ATFvm7K9Z`{9#{%?3o_#-{LK)GJ3)0bin&QnMnI zF93FVz^P49ke#7CZzuaPD>JqO;Fe0uad}cqNR}xXldk2V{1PM@fc1bp%bzqr`g#Q` znRH9L1!yo_!^D-WX;O?kF2^d^9m_}vJy$A%o>wSW&&JSUckCdj_D1Mwg8qBO4Y+~^ z&~rCJlL%UKBXlW2cPmi0woP>ee(Xl%(*!NM5o!zt^o|>$*#w<+BXk`>)2@dqj`ZNq z{$z-J4yLHf!l3lQhJT)I72d}63a%${d2!u^Yc8&dxQ643&SvALL`#iNv)NP3QuWiU zbjm{M-VKaT8PF{i*8UeG^tKHwE<3C1kI03tr2M|}D>>6JFQuAX+qw1Z{_KGd=S&({ zQ6C7vV;rZfvf2CTke7iW|~rJ?~0Y}#_9R;yIfbtmgi(hq3hYfoI$p%YeV>d zp4joV>qtp_Q+2gr2OyH70HzMLsU|%v)CNBsvo2LbF4x9iWtOS&?jyjtE{ISf8!G8p zW2X2d(_rI6yh>{BIM1CU6*N)-z{u>548oOkKpaE%6|teNTY3e`CU-p8>5la z8a>sjXf5opB~wSHyo(WaUJFF20@Ibc@7e@2>22n?S?V0OsFUMFXd0`Z8tq<%@=yZ} ztx=c}N6i5H&=W-7jw`d2@XAc#RUFBIFGM@rv<5Y;YAf@$>nVmdvuPbVF*cv~z8{i5 zRTOBIy>Ft9-8Ic9o%xV?r;Q3h73}b|x#I0C)|M(|-Op~d4U%H+XAj!ON}sG{@7e}R zFRx|4+ImPG)-qGBL)yNU-ItrKH^Mb=?>5C0D~uTHbNlw&syymSexKtC3xpDe97(_V z(|PJO%zo1?($1&Z-=?3J-hGN$^P7P%t&3Q@Eh#l-?URSBx4%V2r zSo&@Un?7SumoFg>J72$@D%CYjax{SV2WRw)m=%QdvhxEatZGKT=u=o%f>VxzQ-)r} z03C`A_sq90W8%!n@cx_C8hgyNN@wn6^JYd%)fKFGX0Fkq4B51b)LDPXE@n>Wy`N&A z&rF(e9+lhpcTd8=Xu%FWH^!dJ=OP?epJ$k}rpt#NKPzaLFm?7Eq43M(X%=17&+!f+ zj3!32k%gH};&k5C&u+JOml`%PuYFbY7^PwiLi+NJw9?@;#~@-rd;hVW#pK6I^X_2d z@}G^24JS4#ATbO~;1Xk%eKY$lzlS>xIsCLk5l)fFQ=Vy28_eZ2b|t(@c~}3y`%tx+ zh=C0GzBOV~h;I@k^omfDG(=yHsjZ+tWEDM?k9(Ti>+gq<1pU5}O|z{+z+7H}TgtC` z-{XMF*{^6aUp-KgCQOtA>&10aYML8s5to`469$ImAVv~lY*Z1H{D~>QuAYes5vadZ zOsr}$CsK%26PjF_CWuV~)P&H~6Nuwf#C1TdNCc%U1>Msut&FCnm&}PNKk7G(N2oAw zZeg`s&Vh9QO`@BLxTld484y(i5+lL74%gd%vkDh*5V0&tQ?0?LMP40AGGs@Bn8zBJ-=32c#nHh>AsOIV0V5WTj(JNqauX!)h zcEAu?3bkeC-MG;oZ{DLU|Cr@6^EN!oSvp@aR#S@2apeJ5yxnm{i~jy*k)jAF?k1Wj z{{n6hNq{$k{D{iN*Se5~S}81dn8;|~N0jfNr78fD7G?^(k^maSWdOu|GC|GCEs>0hpGy2I2C zZ0ePk84oDP-Y`$C!BZo7nnc$lrB_YeCt!S#-_viPHA4OVk>JcNj5zx$acBuBT5YM6 z-_q|NnDV`=gVj?dd72Hy7n`I#qUUgI>2E}!_!BH$r<+&BqE_6Cwd#FN>hz4%Mc!=l z{IavoPL@-PFhRLR$(ID7Nq+!>6Bfa+=PyFAbPMz_4qamO7Q4-{NSJUXc&fB8E36=9 zh@cbGA|fQ^cIC(QWd}e>l~kt}=y|95oB*4T4LMWwMQG{a#Lv@(H?dbB)TgfU%}EHa z|J#+)no{+-^soj6JU@u_k6pn)0*v~{gsbjgN&|gYK-!jst|eh;Nf?8Pm?siYk83NK zF`;r}u<|jBGKDuu97^S3VO05H0d?Zp^L>d@v(Q4Tr9Ss#|EmgJ+8=v?G54hSaAW`x8)8pLXI;z|i`%lYat+)u)}l5$G1f>(lB=tEiJ8q3g5t zx^8ZI44bs0YSxc+$k3tP+18?-Dud@+QD5E;LN5K49H)LmGc4^O`GZ%n&h($f;l4p0 zf7Ph}pW<*Uz3>;cda86vn`k?!^w-2MF$g?hK{_ci2}8cV>>+AZ=oqA_8_~b|^1*Nh z2TAK*Eul=YuAVYN?~&>|d8#y^=4vYaH3`xw{`4TZhF76o?*D|BfEw3)8l>$a4S}~K z4T0XM3Z*Z94b>=0MS4iucO2SW48$4r$32y?NX+tNkA8I9;1d+{ztofuqw?4+q=U#^O%rPe-9#T|&QH9jsXD7#WffxN`F)O=-{E)trLV- zsiXcYia~E%Kee!Xz4?3am(freHFtk(Bm%R~ejM4(NF$a?nc^o2+ z>m@V>Evk}kY?0@C@w(>3FMh4XnK5t zu;yTEOFCmk@sqkHeXwChjPh_|^}+b>le%O_g1+ou6f0yU_|g#y!q280)t5a6kawvY zE0#;)fh)C5Vd~%lziYOfs`^}m2qp%BNF44a6f(91B=8uXdo(b??nLykEMW3c){)XQ z5IeP6T1C4GHO73~atfsrQw}>j%1gCcIbEv*rKL^bo_7MMc4^5Px1LslHbRY~o(9np2zjeRt+SI{UDXhdwW7qDGA+fYv<<&h zzb~_+{)KnM_Z!nvPJ;EN#ntGJBoNSBQ>svNQt=l3{xCU`6imNACe8D^J7nebfcc3T z%d9lzi^J?Ures4Bb(+|SN!yJ;sVV0MG)>dJH$b#aONb1~5z#5d>DcYFIc8CKT0UDL zImUCu==oiAS@rJycInb#(KSq-YAiqQjJtGLQsUuQ+LUdI)bF!*gk zl;te-Op@EKUtWNDoe8pw2!S3dDQXx|pax|nWANbfbZwx-iS8ImI%7a%V@t+p_0AqD zPB&7`OB(HsD8*V)rQ-&n?g~=6R{S%Tq<8khP8hVjS4(x$4OpH^ji*-QY1ZmD6vCGy zmP#z~95!G!futE@#CSjipwm{T6i=P*=Z2opvj~5z4=&VzIHVTA zPv+Qls0X?ej~+vq_#^ZOVPq18kq|-*#g@ikY-u8vJVyEv{cT$80V;;f2 zbu$*KRKBgBS4^1*xRrahBr;NM8;?SXl2a zaR&tFGGFG^Wa!iy4O$k9RNQJx#bLobW{p-RGmB}`pf6%l>Q_s0eaO*zjbfCjL_^Vo#dfs0Xrq-+0=+g{Em1gwJ|F+8OYIGx@C~=E|b5Sy; zv1lfKk)meeH{~xYBQ^Mcs!XX%Qrag5+vmPctpB-zq~ZUsb$V(v=tdZ+;{Nm-`XMw= zEj>z#Sn3XRtHXk4t$ss0K(Y8(0w@B?CGHp1JD$;qCSi&B};*@+Zswx`7eT99y? zttDeA*c6=IK5eWI&2AHE>J}jqv`S5yPwLP~@PmRYOaY0g&bm`{>KEzvjU*$XMUAv3 z-9ZCP%B$A8Q*w3D@2l4DJEh-s*rzE4^JE(G>&kkK<1}1KKJVi}{6%|Jh#6cR@*Q=6 zHkN&dgF!_i7C%=N#pZGwR=DVKhS^!9#gZ@{Jx5{@|IQa(k;rh+5`%-5aCko@&p_B% z9WTOae-}W&?0g(_*(thiL(Wdg+0!B2M@0~gareKOLf#>i07MaA(>Qp^o(?e;&lG|3 zlPwrlDumu@ic!|R92A=Jms@h-P8<~e@@;^CLyzhb1m{ZPC)=D}WXR=ZN}d7STUGAe z%9@(GqUN}kDY?=t0f>B}up(Iw==Y_YGxYn=fBJnO6jHmSl9b0jJ*3KKRLs3dGo24a zal*`>h}Nbw7azCtax@BIfKOdIM1*hE-$eK4UVp-c}b41f> zFnTN2VeLRm)a@2S$YZbv&Hu54Wfa6p-+as#7bIlOQF5PFR`+zl;t;AX7c>+SIBnpV z52We51W2G5Z{sI9t*6J}G~8&Fre+EI3^a2-X5!yQxZ~`!6-&%v-4;#ieW7(Lrt|_e z;#!kv0K0YovCK^KF11C6Y_J$w4i5`5g4G&FF&N3^yB2~GxaR3R@C`9yyvB&dfvG2Y z2DYn3w-68XhcHd2v`Rk4i?Od3%)$U3fDO@_|o8=8^{lQKv#53`SeTw{<)FLAu zs!w2u)Oc1YM_{Z}kEStzwO&LEO8Tdb0vkTZlr`^Kn>`mCEe(dnsK;trj@+(N8|%>0 z>aoS9XjM_zi){fpyqB_!f z?ru0utt`rsgyT)`YNgBsH8WAo#Mzrk8aXW?1ZPBm#R}(|!jq^SBd0w)FS6(Qmq4N# zEUjueB08^dHXi3gp8v{YyXSUu`~N#3*@*ZamHVP-%UMNCj-`NtXA|0*fF0a;xZ)Vz z?&G!++p0>;4364L%-t%ojSEzJZV?$G5RftQid|?!v3U;3lJ}>FiL zH&$J)?{F-a=jA{nvqwp}4hlE=5csL@t-=lm%5BgD;L%JWs&rTo?0 zmQ5&lw1{h)%cU2xumaO!;AQ4?Jl(L@GzXxdonN7d|4p<9Lgo{&_b4~RK4bWSD{kx& zSrDpNE*+q4Is_Lplx=jU*#_&i3O<{-UTZBo?)r7`VH~t1?@oq#@xwr%D9mAKuReSC znwO$BPA&`vJI2O$;qaTogls-Zsa6X+F{ND9ubqOJ!t$MBx6YX7g<@FVkS?n1P&J{^&g7JQg*_^4iCt1e?sWkV|@Ph8uFAYLUO|S587l64tsh z^8(_h`&P_6--2ye0%(~L<|fFWSuRz5*-qKM!hF!}DGt#J&N;0NX$GS9O=brcsU1sE z`*a?I+7-s+>^Q4EKUbB)CUXg>Io2sCzZTLE=_^yV^oaRQD4xqNd;#4&V$JkaYdu%3 z%Qqq(HCBBaHr=s<8a!e$xIw@grOB^uLA9%gOB9vk zNE+a#{NCm`(od@}`v}}{-k%o)&6)tefMGIGG^BRJS1~&NYIk?!eTRV?`?vx^K zTdu6-Q+q*-PV<};r+Ge(zB~egSWmG*`16zDGyu8}pp+(OSPH;60d?0ZR&{F^cOjsb z%9LdW9DJINbdd(K;}8T6%Zv#Fk>->p*8)De4ccI&ah}HWttRC+ml@fUxv5RI4rvHl#ECQtd0UO03@|fuyxtkktQyJe5~mscmx)`a6o*}>YY3naw-aS zTtutDk`vlVv1JHvgtmjxr8cf-cP#84f?$xz3wuO12NC(>gfPRrbULs4g}t#bPRd!q zzF9b=*Q)0O0Z>6yFKDoz9m6AVN(yarm|US;T)`}hQngn=#KzN|tawpuxPVBEyE9nD zqW{si1aphM_534sZI#m!SkMW`6I90L9)|1c`R%*R1t4zU*kGlFl#Ay9QG&X>)y77y-eCVqCNQ$a0e z2p}bp!DXA5eCt%jzWRT0{byg8*T07qZyri=*7?+fWF$Y4&5sc>sz? zG58Ys2#|Mdqf`h|vKn66ZG1SwGcfLX56)ZGyb_GSmFSs*)&nS!%Ws*bNFG7!%ZN=p zM@$G>(_63|r-|ynCI}sI!sLUCl&`xKnpnch9Er&nDDTz@icFd;1jgEzbC9f4V*Ol) z1{(x?0`l|u616C=`Vpf^Jp0iRB^{Z_+AX<7s$I@*Uy>|sSk87XNt9d#?9(L!x~1f! zL4Q6${#XI)S#-NW#3&Cw5Ic94-{ZILV2>Bg6rW{37LAe?e#8de*3fC-GKe8lQ?hEH z;0!Bl?7ob3Uph}Z_;+^i(r(f}{?0Zp{ae4Zzhml}scCuh<%BHSc+O0`(-j;}i{kk> zJBz%1nrLAQZ|@&}dMv{Av--8qqX*?R#*p9DlW%VTte$-9g=JS8oqSvT6x)CM0x5L{ z>v6}E@fBlG?mPWj9I#T#{h^lI0B}pW;EB0W+Zn$jSL&O`QvTlGeeEo!=Ylq`&3ViN zOIzGu59K`O9J5Zo+Ik2Rqqafud=CMw{46}5f(18J0zk@D=S@&w9lyN>Hmv1e9}Wb{ z&$?pyt;oD~JTv9j6^LrtNYF?ETAmg*cFyCg7;FwlStweeJ7dnrMel)V4Wb zKYTh4*hfaRp777bz`)X%EtX=f>|e`b()XZzZP*y-$c-oDc3vA#{_|Nuc<#*UEa%P` z@k>^8=cNAs*JE{^9qBsdSlv>Nci`ZRZa7x=G9>aB$Lh}Hww#Yr8ah+H0I%KY`__r0 z=R)v>1k9X{+I$&5t0SCu;YBErC6~=#K0zAzBzt;!|Bf}&6jQOB&CBDxa#{WIzS1Mp z*wy9zB9q$EOAij4Ea|RZ8M7cCbXW|6lf+=ym4L7-R^YcCQCeXn-=B!_Zvjt6yINY1 zNCyLxl!JkDY;5aY&xx6AqO*s%iY;^w5hpX|+!*O-TWeNptuAxOQb=l^tLw-`P!CAT zq>L}9!@(1)QHObs!c-E~F@}1O=OZcrAIoIUA7;k8v!!44!o z4ms=Q#VPJvC+A>U<#R+v7`19Mlp~$B8L==fVP;K+0cUw9vv*czxATB?MZH*`RmrAN zLHytm(85^zxKW}H{3$U@ZG&SOV}C3NSbol z^`p{-TPL$QcX#hp-?msHDf#_(-x~1+Vomshnv5$VU&}7v-D}*aOV<}aNzu=0PRCSp zPUrGDSxSp=Tq)=0YoowyIp>1q9e!{Wn{m%FNqdad+%reA-OSAQCX3Tp;k|Lkt)D?H9k8ZC8qvf~ta(!EKY`uuwst(9 zrZj_&B=WK8>^1jbspetE-T&;k5CegnvD4(-!X#U`sQ3p(ZHq>-h`Qr}dztkft)q_VZ8lOvz{8;YZ;cop-wQR6{5 zsX14E4|PssGX2aC(&vrWNk6B=N}v`IiuM52bW4SqV`PRI13C#vKJmope6Wyd{Qb-&rPQmOf?+?$|DhZ%RH;*ELNTA|YTT-NX6J zJ$LOmD@kv(W433zN;}%^S@P^`QK~bsnw@d(x>%gH*>_4dK2jHhG3|_8heC#`CX*ch zL>)-V1scnlegQCOF_zbvf%1WTxQZL}`#vQMl*ZW`{k}?BQ?3v=Dt`c#uD~9Q_jyK@N_}@d^(pZ#inImfa_jQaEW5 z0X1L0@0`V`G)}H-BZ_YS7fD=W!2&U~DF_)V9Q|1gZuulC3(DX+KIA&fnLl-f+7V~u zc%>_-5oi9?AJ@1bL0sd4M4~bv$Ef{q)KkU2eXiF~>P5BL#pcfPm^Y{+QT-p+%BuDL zVHtyZ+34rvNPBL6ew_Q?T`{puKd=*il*eNTaquL>3ORM$`~Zf(g;xY$MjeKL-F$(s zC0NPWZR4>fL|HB6e#S`QXbzUU0KB&4t|07D0}v|5QQZkSkXtJn@tzFtE2gtfwcRuY zQG&pR)^>9*1`C(#3Zg3utaA~G7U>+T3V|0lLy2}TCA+kEjXb5s)q}rPFTdC)S|0*&3--VYV_=1j&cHC58U-PY3{A8eAjGE2;2iZziYf?n8*6J7Q#Iao^jtW%ntN_$^InP>eg6U#o~3CzrI7brFsZUw z$e}#kQYGhTy%`r#t2g6kIm2kJ&A4#l2z&XZKJF4KdrZ#Y!4bLYcy&qyO_p8KPt}bt zQZ6w(w{dBwU{cPY_l#@sbqu`0e=C1`KC!NqvzB^0%2^KYRE^v*CBpPzntg=aC`d<Y1E z!gd{eO%l@B(pUHC)CRU!TE>RH_EPuV=yt)$*JB_0E&U-cJ>uw81V5a`etqqBBk-X_ zkV#^TFcxRAdtQJ4dP4bcY&8M1D6|lMp(}q4wjFG5btda@$m~8q-E)ePGA`xX&4AplcR_xQL9Fso7-a68O>NE%eQ+ouJH4#!P|R*0=(_r zi7#m-E!WB)HBAnE%kkS-=W2ug{_SXAUcNk*l+rVnY+G=d__7i>{+|5M8Vr>bN%4;d zp=iN&cnmhPDsi&n13r@Lu(&g{NLQE~y7P{Sf-{nOd@72PW74CB#(POALNMS$Ew8|O zyb1I0Zf%p%&Ib@0=V|PNs?noWHxZR@_k5%Ge63HpS~$;z_nqf9t^wBNT8u)1E3&Fl z@_Z5E`Bl4o&`(bX%#R#7!%`1-?OAu4-V5`cK7(TgxX+?H2h4~>(iyhm@Bj&zPaZZ; zI6_%{-(MfLF5FWi61iHD$gK;Kv(mo%Ye3qGYa6aDQ~=jHTqU?xonc|sSw^BG2dKhx zxHzsWXV@*(3+B%Mh%zqMgAD_cw2NQ+DlzZ*RkK{@X$Tp73~#V4_)uY(kT*J!-h!(V z*ZRzmzVEM_7=kmv#M=)FZ!iDx)6-Yb*!dr^zDMFD+;Wa2^rNz%Zp1AHml;<)u0&j8 zaHZqQ|A;+xB+IC>vD8z7}@q zXt$ffA^w5_ETGZAT;-Xs5j^v?u#Ol+L#k2d$#b>#eEHYfaDvGp==lCN{;wgdQ%yI? zRLbIO1|%yv?WiQa&ZWHAOGBnol0FrpWKYmykSwp1Cus7W!$D+#jgJ_@Hq>;NwwAD+ zHNCnMma|>pP7U{Au@^2S6>mqnLJyde+2=K;G_3C6c{=cv!5O-4%!dtM zcHuSKriIC?wQ#BrVqvu?s?eLD+ODS?E3J+0w-&pG^j2pTBBFfK=5lpqIeE3gxi8tJ zyQ?F&j%8J~aqg(=%eTiOJFR!rGz}!&hU5&w{Sm9MMSTjqTKUJOk8Bh^f#j3j2~}sQ z#LgFlNJozLmO*wu@@)9f)^C@n^2PdJQv{Xz3_DGVS<%D4n`c7?5YyMkt1pGxu(vB7 zO2@COnOqFeNZ=Rz!nE%`;- zfzBICgd@eCd?JHXmO?5rFvCD7q!TWhKu@$ARBQ@Axu~VG*_t!%-|U zA9`z$^hz;%?_mw6Tvte%!Z6Ws!PKjPm&h_>n>HT}z_P678 zqba;2<>1c`VD)boPac#6wi9??+@MAnr<9Og%kQA^6JH8x=2~RoG$t7sc-&q7tl!uN=*KywfZ742|<1KEElH&&HtPE#E@TD?fl=Oj;txKtA5` zy?F982lV9)=xxU=BITm4RgbfzEM^-iQR6g7)07A7m1PF>&?1!)pH_s%eB z;oU6a-O*A5XPNH~HwWc@8oKN&-$Awc%D2)@=^;h>2iWlUMoCYuVjsMF6LqQnz3y}y z{N5O;-72=^y_G2V%B-u!r#pFm_H3fcp&P$PNmJuLQQ zGVx>M6B8kfwn(r8IR`O_<(=8Q<~&<{GDdo!j6Hs`S4YcIl3R1YY1@v-g&jND!>t9V zNJNhYXe38u3(^wuvL_ zJ0eoMw%UrxHfpJscGz z@Oii?L9FUr_;h7iil)tthr*P=WwlnWC7+N;ksq7(fhC$gWQ4=~37o!^)VA<9I`+f| z-QyCQ$PX#5cw!l(Lbm+`d4;e`>EW6Zec47l@SD@vXCL(G6I6})0Abqfi~TBqkB^k$ zO;H;lfyitqQs2`{r8j4=hfc>yqhMC2`$(NV?6cD&NXfdK89=u&XY%Rx$eDRkuM&3Y z%wXx?Zr1n1s|4)((Wi6^sjH>iiMnB`gK+9XUWrF-DWAIEZqlifkS_J(dn*h%Bjs|+ z^gM0FFDxqww-&6s>iflfgK#Z4t@r^+Z$i`fz$=8ye;UO0)|(@qRS;s(vwYVe_Emka zzmLUkE@EVZV;1Sf0pz#p#a(#tKrLn|g~(M?2R!j-wU`V9s6|36o~jmo(N!26|5`0R zzJ+c0xPQbu16w&hGmyRcu}PlSc>&jX-UNQ~ zcnS!5Ga69*W*ht9Z0~mJv>Bbt{y1yu;3uSjtAQJBEcV=Bx0*Wxy$hS}jE10rV319v zrpv3(m&0qm3BHoYDv@@)oo6gW7Ie4#6viBW6_e%U)eWwt*QVnC1gO9p)-;dGdQg_v zt4K~qtw8hLraK`mG1Hz`B;49Etyej~Q3!i5rcbo*5zu|JLg&|lC;oP%YeScmGSv`d*f zk8`x0cfFSmHPrD>m)wH{e+Iuce)ms`A1JR}9STxSc&#Z0-Ps=NZ8#}g;9V2rU89rn z?M&2*8P^ulythyl1rrnmX(^&q!=N5~5i;7wKLrwd=;f~M-T}DU&b21?(#w65>?X*K zXdNJ0Q;~<4rsh&awOX6%zD5bfz8>z%*1Xbp zM8yn5kU<%jUWmikuPl;bB{}1Y=jl4UDlOy{W&6s{AyowV4X$HHKiRtoDqDor+d$+3 zqfk$H@l&wJvo0)8@q7vo99=a+Wgv16vM`d(!VQBW4T)8!$@M6m8yHDk@c9@T zDAEX-8)!grlbe0^sk!U2`;gTiS%HyDk;ECo)mM0wg?0ZdMyg%Il0NI>KAn$(DCvsw zO*Ev`F1MoLSNNeI)H%HBcgf~U+`@-r6e_47f)r2p#S=d6P=gOxnUoBLe1^6lsCmSk z9S5pnc^oLo7w!B<3^EX`LZO1NVM?JzCeti@R<%ElIuuer*HJ%5_l4z6V?Tb@H+(db z)qE(6Zs^CxH7xzl)80?#{pV@#f=kM@chxUen)W&`VHSydGafgt!_Qt^b-yUnUSQI+ zcLAaN$7wI+Vk$Wsw=7&%TuHbRaK+(@#bvsrOnV7a4p4=~xYps?dP$k~3eA+EOnY}< zR;c~ev{!>HFiOZ9jYucqnvd&$oc0RM%Cr|Zn)Xsz@VXbJUdMF=*9lywah=0;9@pi| z%CuKy$pCy4t~gv{n)m$U%e7+kyKp-h7d;1Ffc1!Lx{#?9X-EeO)7bd0hIGVgDHa<# zW9-8h79V8yd^Irooxdoer6QlDqDT}w{Z-c+i~ai5pdp(vx~e{b;;zM>h95ku4Wc(w z=jv$ZyD*&-GjyJX229QP>&uw!>x8TU%b=7tzF`#w8_+I_l^>~^Bv(DQu0#5+&BEPgeaFDj)NF z-mB{Kun^rNvwCHXY$A{}K zBC}d{owM`Db$b@_X<`R>3Wz%pxfFxvn~gD%@6Ch@fc#@-kSnOf`v0wWc-NJ9-aiM*@p5b&>39V9=X058%?=j!uV?DTxEK^h0_ z^Z6!ZC5Jh`OSwE~`Q*ST{s5ja=U9WAH!Sc`km@Q08l-kxe^%XO?ur&3D5GZ1L*P;1 z*h0^n#58ubDS9BWi#CK}aioBHh`tPic0ln3rxqC(#vr4bx9iH%F66p5Vgw>*nw3YR zvV#U`{wWq*>^w(FCn>+N=n*@IQs6gYFnZ!`0Yd-6Ob*ywLA_sCU43B3{i#Al_Cx4| zP#b>bHK|C=Kx({_nuXLVB{c`B&sPI^{#2xDLl@vT28C-w9Z3CwoZ8T(NX=7H%aFQI zoElv1;+xU4{I0dAdbL)l*ofBR^v7@If^hbWxvc)>SYvYJ^}$FyP9(C3JKMyXwsB%ZZ)ZCaGfZ4XoX?|xDN`&bot(l z)94PC@ZDp{ld4-sa?jYU5RU};+&WD)*WEcStH~#RB@2S?8y%~tZI5?mA>U7PKROyU z5-UY9{0=fQ{tJ3p0T}pP1@Q5mkUflpf1OD=tF=YZyrCJWfyfeYU$KB}Z4o-CViRiT z+tD=&mX^OtSlom_SOK)-|0Do*N9PkWXmYsQ#y1mey8}flmg2`27z{oj7Ed6d0 zD)tB)Jgd*+-9;P!2+M{1Dt?-V${82%;!rzp&N+N>_40Bw1X5ne<&1M}@ZTW(Pw*4m zFOf4E_~V@@C};x9p^ZCWQEcuH&x9+-agW-V^`epWyr_KgH0ffDy9jY8$t%kMSJ9D= zA$gHB5RGEv6RI?9`wnpW>NfnSTX>z4wXS%4mfbrU-#WVuht>)LD=_b&glCZkd$sDm zO?y}u9}TM2^2w&7ARRsmq+8znP>v?z%#5GS6wBQQ8enqO+vh{l_99~;x+nYqof>Wm zOQ~5MF4t60P7$gLz^xV(R9LYy0i>Qq8oPHSLAf=3f)flUY+Er zip5tlN`IYyKi*OMw&-dhFfIZ^myd%O&7ta64}0#9-fmktFy6(7T??zZ8FQ18vE(7i zGWVB}e@L4Mn0CkT_IECyJQ6PN8 zrzRFFSY^;IWxW?xp#m@!jO*BVSIYz~2St?jrBp5N zHCCz7R61W5a0dc|X-QL|^cx~u6j#fxLwzk}X~onCs;j#M%P3sF9YpV`)YS%`+Gpp( zZl+1j(fRC~s{_qDA&|?NIvr+IuBmyni~0f}p7FwK^z|55Y#u*pWr+d1^v!&>A%G87 z=dfde2e3@|$SiTDH0||Ac8T4j+21_!mKdj{*HH>ywQYRlH!&szW4phbDE3tn<3(|9 z_4r z|4n>GTz1~;g$O8PhIL~BgT@#moXHDT8TM;5api;~hMuK+*8zSvqHDWnP&te}Fei#}#buC`P+0UsM@rsbDuf<1__6 zP5u#of0iJ;3a=JO&eQ43rXvv~@HTHmR$~kxO55zd46O}&Am1FKJrE-*5ANpR>$vyFkES-%(r>c7PnE)<0J-zAQfGHyFg* z!x{+mh2LWk2S(hxv(*dpAN_dK{EhHxI#o39TmR}#;!Pd(;NwJ5ykpkQD6`Ffw38Ss z{rFG+H=SS)nv`H`3NINbs(dm=~;QTj(jkj{lqH)R~;YwPyhFw#hE?dL@hM7 zbfD=s?+5L~umCS!LR2;Wo4bg)U3T3G^#zi??B7s@JbY^NPXEy^;>a$GTQiA)F(f1}u2-#v)nycwk;?)2M@Vy^*<&nTl*Q!@X8rV~{AKxCh3d58(DH1XPH z{;fuFRL75S=(xH_>)1y8l=~ZuV(x%T*g0x4`7*UO!rbsX@oWpMFy--c$Y~idru%0_ ziese5D*T%x#T3b0;r}2~{F`*B!apoZjF-Zm-aj`=j25NF+x_=+6<39ygC+28+x)+F z6=O{?=ssW0X+9cyU^rB9#QUv3ini<0L_|G&D4cWR%)Hz=vk z{r$U(m-7WLbYtWO_{YG9Xk72@S3sKPB3&_DQ)b}9z)56i<&tLjyQTq$#-KX5a7-V_ zDiLmN|48i?lOrBk>I}i}j26xR4e=}gfq4H`;z*HQ zNA3jkkgmQoKt1CO&*xEu-6Gq3wKpoLBw*-COp}(PaNm5s4hzBsDPxs$X~xok73b2N zI>ZMq5ZQ$2u$4$pl(O?fPl7J5fU_liR=R1qn}%Ub+_mVcMQR#d^=Eaf`GCaKj8*Bf zLhvAuB;9hXgRSHTK6Dh*y<#uA4iC$e2QmyiIFtwK)WChGR1MNbK1N^m z8UVZ*8W0#s#d#NaMHN9|m51Q@j(|!~vE!c*)b(mr?ZmdK?JtQHyM;iH{acX;%OaJ0&i`7h*njB5 z2!eSvwz;bL1uWLe)?(Bq1x_~Z1yfL82jLbXKQQvhqKSTeZ!xap;lUXE&d^k-f?yfF z#X-`*MgBW`i?QzTMS?K5VD|CDIyLN3SBEQ3E=Hgpae{s$kI`?BBlJ76l77d&j^9Na zBDr#lIH%lt98hkddzBlGffCA)7jWD9U}ytw_*QboY2~h|sKXsWLB)AD0m>WDlG@N% zq(%JOPPrAQ(HFI$$MD+@OUt#PCy*kwRu-Aksm-Y(h!M_4{+DD0^-?$2#hf zP57z@4?!2m&D)i8pA(lkdwASS1!q@zr2~V677Ng0-X-g);V%HW8h+q!*m70E!;?#9bS-X0g0 zV1jVm%St%e3Kn{`M6Jl@-_2?G= zh@oPJaSbFU=bXnh{I=r>SkdymWb^sup<=W&#^>KTRLqppeg2C>#iC)vw7z^6{5{q% zT6f=#k0=s1I(Cx8A3TrToQ44Z?H9jyn7Ggqlo$4b9Aj09+4w>%`V@Ez9eMIt8ruRN zUCLZ0>C3-`K=4xP1ODC#Vz;y_s7;=rtq`z$EKkr?XmH2J;WEy9nnPC`7RlKY42!(F zjEg|CHgP3qEVXWR#QJPg%(`_6vcEV%>^?MULmCji4k-Q7+J9Y?4r}G;1vH|-+at;6 zKawE!3xf0b3Ig-Dzkr=6C(WBQo`nrCsRlmrODKzHtP_)OtZf(l!R<5*)`kM$?>`)o z?XrjcS;NIXnSX48Q`q-9dAoORhGG@SYxm*-Z?AVvHo3as|7o~}uvkF|?AuM)N3OvS zZu0LQF7|S70Z3j;uJp|k{0y$O@p1)pGWfJoDN~6jz7$EgN8h2-c#^}%?#7f2Rc@im zU3$DZi}^`lG|vE9L4j|Ltu{j&hOAO|a}teIo#b$^*a0kJgp4Jm#DW$-xwa1-92Ugfu*dG)V=xGjPv)hi0M+&1O9hMh&dt934c$Em=Oe6 z{97#I@I}f9iQuV#;tRPJ3mBCY*dHUcife!&`?2yTUsMe_bD6V`XAKNFhL;d{+0vjj zzm2y-$(mD8f{ytXZuXSbZqmw!y|5f629 zCFSKIXv%k(Q{d3ZVDkczm5MnzwcfFW(iEviPM+pfuJ*N|0QqYZ#UUY-xSS{s#R&xe zfRSQalju^caIdugn*>~&`7bUTMiUJ{yF6*XHjh!A$e-F zP2Hg)kNQ(biGvipJ4V6kf&b7b(JDPz=>K4pIIznbg*4eT;jBF>I4zHS9SHi-;xK9S ze7|+H7%A0k@#l|*R<3!_fA?rHQPLIqca0W@W&;sXUh>?ZaS+h;Gr#u%5?;ndP|+o4 zm?%1OD4-hO!Up>24(%@TD zfr1?o5%nCUQV#_sbi`21v9glvcD-fgykuMWjZDRLl9E@T*G}sX8ub zCM*%}5?_mV1=0`;ND4L1H!ndY5BVOZe4j-Rp=X4>Zwe%I*!w;K(U9zYLnNiH0fn0l zX|5_lWI1}M!4WI*MFqY}KN8q+oW$=&cO~!=-#?iJOkGN=7I<8vPnPnS8BvuVo2egi z#al`?6MZVDwn3EKNo*3pkkuqC06&rU`l>qcY#7tA*sl4zu{FaG# zXyDueQ606x>u1)@jX~2gRjj7EaxZTto^DYHG)Fe#x%HAxl8$}l?vUjA2C3)L(<~R& z@LG!UI><}Uf8Q%rBW}$)4>hpt*@_bgl5E&pNeQYq$)oh11j*P7wy?JZ2H#D!81qfA zp|BLYS}qElrPu?vb{i$kYIZ5b)u+A8-pIEU(ugBjuccKbd8MT)(U{?)$}p^11Zj*D z(Qb4sTch!!r$l3)WCSpSk=7|(B%a)G} zlm<*T$tbX-%!#6Tm_@7uM7H|6mxG|!pNKhqZW6K>7qi)AE_Lze^WfpdzwC%?U zsq={mEX-Lv;U!+3z8yzjprU|~kNXRtVV==*%)Otv~&i)7JFtNJFq##%|jeG{{80JpY(SIQxk;I0FxI82H65~)90~`KN z+zm-=gefSshdW|;{GVb-8y!ffX~%@$_?G#UFzCzB2h%f$o}t$plkKX+eNY8l#)DYj zw*p;(c1fo4otOcWofAzk4$$IkWu)smHj@vU#l{Ie?wnaHqmKw+Wwf`1zc7nU5oWoY zpKNnBPa)L5XR#3xV2&Ox){Jcwv^&STCr6m#(VK0?QsTo76*C+~zgRT`i%J*9^1G5* zkVs}zG8^OF3?3*{nAIhq`0#W0O!>EbPQ0%ch(y=PPEj;rbRz(xuYR z8J~h{8=#D7q>hR@BP9~vGA&uRskYgq>rc_7V}1NgX4d9Ks%i3ubYX>T{`n7kk*@2| zcSn{*Sqxz!wI64*p(Zu8GFN8{-aK#R?{qGOn%c|oR{Dy>Y_?6*2Vl>SuTq0B@jF$6A~nj!`Czfyz8$9)|6aaIHp=;t@lj!^6rT zxV6bel0*$>AqXM+2y0674g0F(@6W-UB_x!0&OwDyyQrETFGQ>b;=@zv{zea%U}IfX@w=%g~Co|m@YIKG8g{Jj(wCHseP$GI%Xqa5{f zeFlnhFa#KI;nBc~7Lz-djk~R3J~5ZYh>%Mlku%=ia~B(Ks>VoZ=!FrktIXoF{zv?{ zmu-SLRN$cG&nEe*lew2isE7kuRV)BHu@uA64Hnv z<9{$WMMWYPp5fQkd^{jnn7putdtHARgz3m^!X24iqNZa`?1F%>8$|`819uWdt3-Ma zElwe=0H-YFc#>e$Hld)yqQ14wF<%%0%YL^Q{PE4*#7@{%{fGFOU`-H!Y(r^05fwOH zf<}!tkf8WmXQ{<+@W#z$xz1gAXkRLYA{skbqptH z(523ml&*bz_dJ%&9;y3s9-Gc&{o6b!gKe5f_CQ7#y%xer8;e zUh9XpjNJ$`T(b{8K%4rG^25Wi-nIWIKQo-g1uwu73|94G+6}JC9O2htoW`~eSLG~J zK~xBsNj#OvBLX3V@2KH4bb-r!^3m>AOg+(vc7#HDbp|682lXZh&s`Bvb{MZ8KJ1hBh+oU+W zq4g=Wtpb*%M9)EzP<(eGMuaj_kdVCE_L+H$2g-4f(jPccgw{slyoU#5vymE8A)1|X z;WeI?%?A7C&|PaBWLCUXv|C5mmgmgo<=Jde()}>uwW_9P>K&oQgY%+pS?L`&&ES4lEa8@7^`^kwptxu=|l~$U`$& ze+JRKw4VQMA)AU_Lg8^a7kCrfFc`m6iE_fkJ+rZn=<*r zOxYE?q?OBh9<+#M$X%cD`xdd~lLJ6yHg;eZu|xwIkhYL(lp|t>-nt#dh=v(+#u%&& zt-HMtJ!|+^A2KfXnA@=ks$hs6%=dPNM4eJ#qD@S!P-V2o$yck=m* z*)@j+&{0lb!qUe?K?@JlO$b7Rl$s;9SR3IxCHW#3V)p_%5A&3El+uYMOmCtW%YKXK zEp<|iPlzK{w$eNtBm>8 zcvz_b>XhV04eJ2D8fWA$EM)^CPJfS7oXt%%fWvmqOW4e1WAw~^IK@(WnpMn^9q$b1 z|6a<%BU{8K3z(rOki7>g7Id}uN0ouSAq73J#)K0me?!y2A<>Cl=z=R2cPYk|eDN~o z+fP>eaCC#m)6+p**0*lkGS-KU{m*K8O+EU&V>pG9M#wcFhQ9+8O4hx62k~Q{`zVRS`WjXT~Bm5^e&&}m5QeeifV6?k}FI>R} znNYj5{?)Vj`UE;!6yP5uBuFQYrP^D_K;C!AW9wch-chw5^oUVv+o*KxE3Ci8zTs*T zVK_KqV~J5%Y|~ky!uV>i5&^th!GNWeQR*-pY~GW{R>-L@^ZYy(;7-lWmgppL369x~n3mnN7@o#{3o(2_V6+6;nUff*$8mh6_WDv zHhvVV7V@ekTp7mZ%DuE zk*c#LMpz}OAB+^%M^%o8qoWK<%rIBQ{(;QYOy$%*G?nb|kK!xmcL@@+<6(RX@%phc z!?@`A8Y9$W$#@6&kl`${57`@8A`WLNP5b!b;cV#ex1T5N9#d+Xz=b^iptZ_1W!F4v zJBw#>If#Yy`|2OJca;|&teY0Z%*@3VdIHxe()jZRSw=yypSpv93F7AGyP$1z+Otc&U!{FNr@*JwDyC8iHi*TB!&h=oK zG3g(qf-%PN-oaQdy%%=X==$BuAG48*Li-gOe(W@-LPQ~p8o;?HN;0imWY1{ZNj9!m z+Vsb+$^}u%qn`q6b|YfEuOgDe$!K50EHUFWivv)UANr`ntq6u}5aFz7vn@lg3?kzB zIkycqJ39B6Y!RCOPcRFgSpOVNHN}Hgo<&M9h@$})2iw46#|x(-`Ocr0nwF-2c3rnA z50tZf@F+INee5uNJ&ax>EDI>QVSM8#$aW!_{JBvqPd010>u5GmGkbj;&d78q;bX9j zlwB62!E1x9`X2gDD(e3{i=uA8P9R9GU)r{sJPD*KjtKEpsV)_DNfWws+mRko*gEva z8DtnuJYBhRh8Nw<##r|lNsN@J&^f;aMp$JBd$aKhB2>OdPpB$Kp!dsE?!62I^{E~T z6N%CUlF~`R)@agj8EKm5MV$;y;Qwer`ZMO5V$My5sajVSED zb)c*u1~25JR2)Ld2?wVM2{xYuSjI zGP-cJ9&9u2UpJ`@=B1;=x|V+01{|e~)Y4B|@1vBJTKYMacm*6`PdSd?TFd;P0klLX zUZE9-&)(r9idnQLJraz6&2HkMy$w5gaWV5_oL3cNWnS&kZq12tjy1 zml@O6^J@SWj%!MP4e$#^zQaGmd=hI9zRFjaKHBC5&FgE9{+cRJpIGD3F;4cH zSrgZh81&r?RMQr(Ee@4iyf9J6ygx*_foUb?6XLbRe4=e#%?ll49A5*r=5ogldGv&u z!hc_tf5C~E*XPMkO{_VHZ=PM8QPX~Xv^;uZjo*y~*(tIn_eP-nWkk(3IIl(2ym%wg z@4f`F>v4>-XO~svwY9Q}qME*)&&eK%_7CzAcA51&$TqC+*&bx;ni}7m)8ya7YO-&J zc%H+o(z-LQ>{=LJyqZUE2Kf1O$Nm*3S)8r5#g0;r7uKA=86!^*tMT})Nd8z?Q~KM# zra)uwKx%u2{R5d|uiI_ftHA~0H6W8G=vm6H`F`$HkE;57ar@^^hvRChe;*xu8TYUV z#mIq5^VT=u7CpZ~6(OB45;Pre#rSODo7b_Z&@t#qv1I_3LHg_p7`O-v3+UZ#3(!tV z1Kwh+x9-Lyu4NtbF)i4Fx4O#U%pP{e_qo3>aGaT?=-;7t8uRlhWR4Hr@EH+7N^gtD z=`o04ZcZqjIwjjMzJR4GlR*ty7vyg)?b+Q9QA>TwZdJq?%vaHL(9b#iT8ee1*DRC) zN!g+sgMc9NOJyTrPol>%Ilb-S4cS;@Ic|xh3@BMTqJoznSkD%-&pF$`uCPvibps1v zAM;)YmOO?Q*de~hLX1mi=`~A!WmpoIS~bAxovpQ8)Jj$OoGHu|5DJVC{Z|22aP`&$tyZLxq4yLI%+4Q*a z0SxcxXTCAAVN*sQ74?BP&lNKa4^XxyqRMeN6ZVzPohgdj8bEpk+fi;L-(tLo+sKAR zktt5wGFbZ3c}}5}ma!`w=gxTt!yn$RTIG=&DDG)*9*%K91|COjaYXrzihdhQQmng)XwC|3+`RkF6%Y&Cwb`-9UFf`eSk?=od3Im%`jb!K!t78C|?NIRk#H0k zccM}!ZnetG!VOgZF5F<{Y2k({T)1J%Bf^bPwh7lyF$p(H*(BUpWi4EMCd6Xes8jL; zZn}~q+$1GKxO0>_!o_zGgqxwn3U`4LA>14#RJhBOK;h;oe!|r&UcxO@T!mY#I11OG zbfFw98%vZ9;ciy0!8K7PUM;29;R`~)T29{c!dpS!M&TvpqVksTR+IN%!n>Qie;3}p znU01vIek z#rcc|D<(!@HfmxbO9UoU8AX^om~6$eK#bF7YaADqi${!8sTK3@34*Uzj8nPEiuy)H z`N9goiuq8*_+d5Lig{bbXk(nJt(X@DW@BJXy> zEes6PiZNP!_cQ`^ju`&=t7hio`Lf9>!3dE=Ik1R-VP+G3^n|u99=u{*JV1ZKgSWHI zo*!dwWVhgOdLe&hJ6k0$+RC+6%xiY{ds^U3>LGZyw@`LG5G}lu{2C@|#uA2AQMztR3iW zriAfzJJ^KrAVy!Xta5Qn+amK2(uzfIxqvglaG__ug4wLAEdd)3}rm4)Y5; zFyM?DhiqUJY3ozAIMPX&74uDIDE8hW2wC#}>3K@DKm!#?LW{u7ewN zMt`OAJ{l@$c|wS;N?R7Md4vUrs%Lo4E<1^X^RUs^6;q}dCvh0yB+TirVh&460P2IU zJ;g!zX)ha$z`6Z54pH}iHw4|6k+bE4HFM#3|d2kQBRYApReT+0_%Ghcc4eSA|j z^Pl|9z1=fo%8+s~lQ?3xX(+c;Lj{lOhbLEkx#1W-rOwUXiZXbcTj!|`hf zg5fwRlDvRTRYJi7JFzbl{s6JDTufDbRAeI|a}R`Cv9gLiBuK-o$R98QXCH{d9U6_7 zez(d$Rry%pBY>}^A6T8;6+MK=3^P-eaBzarChpAb$_RdHCyR=sR6rG$s(eFH!7a$r zgux0PzgkKSfiQMDX$^!OQI`mZ;SK_omnyh!7mFIP4>Dm}a4k8NZsKOJ@n=9lrI}h-P6;1YgLTQ+r+I7*mJnZ@$CuIlu6g`E zxF&qMn(9$`<^~ijiDzL0jC|m9P8f6V!k9PZFn;xp>ELwF5NI&WXm2Ml&~1(yYAC5~ zS088?X-gr0(E65p`7G3kEd+gN%P0h7Dw9^yEK+f&xqS8kHKKd@5EFb7;|!e0YafLj#6>6zvX7TeootK(_DUqb_$b7X6OsJ- zqik${r*3{;YN1@bi%;4Eb4$IEFWkej-K(MZZ|eiiTZ?QyoKwT!*~9j^?It9?QRHea zT~@5hL^k&yl!nHUF)}oayUBc7$vLMr=DS~0N zvCHU!HQNO!8*`QyOmPxoyr~^xI`2)b(!JalLD7*^vF_#dC_(o+r>k^R=hgxz6}MNc z?$_k5jE-!EYxG9IWq2});tL9ym)_=T^WUABIWH48)C$s-EG$pqW*2V2b~_v#JwWlQ zDIVM%V9)DY=&8^NS$Q?0 zFe)`-DX0D{{KxB?#4q8G*Dt8yo1egLL%eLyKfyvmi@@uYJP)YkCACz+qNd?;w zXdI_Jj$r%JteVBYe3JR{h9{X@zZG``W{SWNS>wrFpJG|E|4yF&6x%NEtmeNyg;k16 zt9ka*Y~9d!KOvcj)%=C4yUSG+SjtSUJk5qX25WGvpZk8cd+5zT8a))TdR#gG8cnXV z$#jJzfX@T@vHfglq%pT{sp{$Efy1}M)9OtHrC%ttwG6# zf7bdC)!*x@&`P45jD3_RSMf({SqRI z_&b&JJ7{TRlrj~ZtZiflyw(%TxaJu)MBX%xO3XOi|Gwg_Ig#Py89f)*#8molW z8*5`F?O%Zd{`JVMPM?H9)8b8Q)CXue_Ba-IF-W3IZSp^gScP)H(YZB6Q7)WRH+?|gvy zBnItAk&w#A<@$7ISlNxIa(e7(wVaYrPNHYNZK2 z8Sac#iZJF|-&WD8a^v~OWh}5?)E)Sc__{vj>~-dw4;vcF^&)hH^Tbl`|spjKXhkIvQiec@fq$)8W$0$Kk(ApZ$x&Yp{g!Q4r__<#R z3z4Dq__>7nj7;oD$G~1rrEfl^W7`;gzXEzMOPK(AY|C|0Mu~``@3*R^ZTb?k z8?QdVmU8V0Hht(-S5p6}?daB`+bjO(NmRiD_}&w&Z@A3sMVq9!r)4lbV7BnBPFaPsBC+tdGOB2PFD%hbVI zUT~cGd(}a*zCD${-QZ6iXQNHR&M+EZ48dG)3b@#J;$ag-N!25+scj@30d)EPEgp2Y z>HLz#t@opxFuy|Kh?n5iTSV<)Q$v(ZA$>M=Hl)TD_WvugFrEyKjw}Ykkghzhl_YFT zHYTeis01AyZ)BKE9m6Y0X!SUga%8IVJb7bIZ5(KJE{%Wp&n2^4OASwXiG`|(x7#Ov z&6+rnB5^gaMeeXBu872OClXihy6#B4%bIw{pC{fa5+6&i#+H{@T))Th&e->2Rik;+ zOE7HibFQxU%WS7ChxO-A{*%QFly;+&Z(UZIcqrx__wrT<(_7qbG{3g&(Yl}ii7gKC z-*w)iGe|yfjFZNnj+3?lUI$nJlirJyW&@rB>;!n6iIbcFN8q=ohMBZBE%gOtiL>2@XzeqxGph zw2^xUF35=)ukL|U&{_!*iuY5hP>=>JG{4deB7u&bq>P!1_BlEuNl{*3tqT{D=4O6a;=mi?17seWVAwsl# zS7>k1^4lg_iX90D(C8WInu=XTr}__P-v2bq2%&}#ff>67Pa5)7jSxM10nVz<#3=`f zoxo=lz}(kk4c~Q|4fp;5oT=~M;)w|f4y4AJl9_G%z0=IUUu4@YOLkkkwe0D`GIejf z%idxm%COS{!kx1)*tyOoBN@SCwmhrDtWfBeas014T-d zr4p>#6|hVt6cY~}6gA3Bq-H(JHwXF~FDpJvsm3IdCNJG8j}Lo^8o=gp^Qn-N4vE() ze0Av0`N9ubZ<8u{x|gMNSd`OaNt&gFu`FfzS7;p3i#rY2#nYu^;0`iyHXbUmqg9Ww zj<8d^v6$ec18HkF5Jf#`2LyyTH7LbdIo5befYpKrw?T3`FtGB_4Ny z^-pQMlVkvq>?e}{xpd27pXOOgRMVYkvpM+!SYEvhdhG(xmtEq=&NIJ=ql76Z%pw~H zNY<^bKirX-Jw(?`bm3}dOs{p5beNOd!F(l=f8adN0a)*fAj$e@1iMWnx+~+B-LUCszZdJ&*8K3Z_(iVId`czdp+4(N6u5v;}(EId0+FbnwA}hG4rj1YdwI+3QJ7i8i2(sshOv(XSf;B=Wi|I-mU~2~HuhMk4$4V_Skvt@Y8!e&UH#b4o*+^2D9--UNFm#G|&; zsQiuwQy;b%9QFzhRVhuaC5VsD&$bp5AJJd9nr9{N1N8@>R^y$Zcm|4>YfZVkh?Umn z-L{DB=HzRLw}+@Zi8}ma&YD?&lct;Z33GA-NLGTRyKQ6>ec%$%U%xM(fvav*2 z9|HYEB1t8Z^B}QhQ=~SSg2HkURF9yuXEBiM`2LoKjv{u9IcCtDk~;Jpf+Q@f^jhTG!mZd;I1V9JK1S|&_0HuIkfad`x0AB-s0Z4jTas~JS z#sXpiNq{AQB0wo%2jCgNtALLH?EuGBvg8ZU0ww@-t7Mau0nbXnR=^&>vw*{Zqku+0 z3!oj~Tp&wB0iyvC03Bc+U^!qtpcJqJ@EqV(KqH_T@IBx+fX8Y)C?FP)3|I&#UTs1| zc(w!f0Zss#0lxrx7a{^+DqsOX57-LW3wR#zCg1~r0_X(vUV{t*!T|9A+tq?Y?`)U0 zLnn>Z#7UbXaL(cEIOz#bm~Yvx*)PXQUuyV{&se~mxofo>j4Slom8%N#wLu$t6HVg! z^=sE_3)kl6ttz@3NW#@)@}Tq7Ny`9IM!a;mzfSV#r<3Nuuk*qw=}80@BDbFI8`Sdo~x7cy8V1&3!BU&p8Gjoh81hqtXW$$HYGJpl2reg z3F@6B6|P#LUyYpD?(<8EbBi{tvXM*0xrP;bYoPF3qpJRJ5u*Ad?EI0!pIq-XF3jJsLAxfmSi51>z4_YZMgvN81E|)n$=BwtSYceByW*Zv+VR@u_h|F- zSLPZE4ZRafa@Q0W=1)eMYr!{6J7$$OV8!H|yK~p%ZxFu$(!BiKJZ(yD@z}iOD^P3v z?XTFkqZ4wAMjCKel&4*hUsy;v(2fk+Fp{cs25NS)sNW*6;a->7a5wFupuA9RP~PIP zpx`fk#s=jE8MP}>>y%+MgB5yX(P|Kc2IYYwYyDa@gu+$pjH~jHSwsE`1Er9XG~q6- zphPnNJDP+!X(|m*`G!rnn_3jo*f4f1k{(CCagvm7#w6y-z;E;1Gt3NPQq^|zMp(=pGI_~bXFEF-aJgstyB8TuFgEKzZ}Rr2g)T*`5|FpVLYgx z98+fql=nKB3~1AZfMtLTKoTGp5C#Ya_yIfs^lTaNI0gz32yg@_aq*G{VG=-#X=>Mu zc&QW60cZzY11NyYfM&o2Koj6Bpb>ClM!ZRCfags>J>Ur7FyIj2AmCZRe!xD!UchcZ zHDEiS0#FVp1#AYC01SX)Kp{X6$O9|`WB`%?IzR-#58wglN;DyNiO2w8A7C>e2M`7D z19VPD&H;x3y8-2ZQa}kn510;!1%v^#07t-;1n>jw1{4BxfIxr*xQtRe3@8Wa0cP1J zzHaRx*{L_L4v`C4QqBExj4*jTjGEO_J=$HN#V9-Ut+tE7a~bzntT+m{T@dyxVj@rdpk zcpb_>o!BnQ*_&Y0NX!6q5z(J9JA`n}x}Lc9nU?U%+AfOgfqNT28=l<#NITSIM@$)r z#eI@p1S0yg420UnBW4YtE3w02eO#)*{a%1QKcb^_=%P%E^4HY?pm;+DD>rQS5dZBi!W*ypMLw zDMRJBuLjtPo@oA9l6Zbw#Iq+P>EgTSe0`Yo>^dej?8LzY-!o}1pb^js(6%ut2~Yw! z2>2E->>87n0yy9t!0iVntpFSZGy^<;WKtAB52yuP0(krcK7f+m_&EqT{uASG=gZ#i z%f6430-S0FQdV_Y@jHik^WyQc#=Y7OUucJKu*2_F@gC!3&5EYsI&hGr3t4LYw7~5G z&_%79E?Y5^g;n(k9ih5gwSCxyW}TpIw@b@+fyi6+IN77$TO)On8e6cC<_7bBjgv?B z&Rd|SeQ*K4I!+D<$-uqsq6SLOI2|}jOV`%(+)XNPdXBrpAfM6Yipe*{@q-%$ussVk zXu53q4W^rUI{FoF7`gUgrD^K36x*ejk=;|y`|a?hJ$Rqfz!RTOtl={F9_k5q?|-c) z|1CXu|GR*vD+?~gCztA%p2c-xce?)DtaOi9>BzOwCE3xvZKXS9r6boySKdSCbHhp} z|Dc9bejC*A)CsbygYTm5v?s-YhOPvwJKO4}!=-#Kf=hYd54XE*_QZE@vcngucxo33 z&sFM(t*B>8pXz%r=HSGO@Z$PW9=$^2o^ zQ0Di+wO-sOLiSGn77QtXO2XFquUQ#3fRXJYnnt^OdT>YJJTOV8u&x~5H$omdGKt@w z;rsUJL?MwOP6KEuoxO|W`P+H2H*dlbcZ>3XrIZPXBXP?B&Q=V!xj$iohH$!UE`@7= zCszm8RweC=yW5g`u^rxTNe_O%9X?6LQx)=-XxZ1H<+zT27A+5P34TvU3G%n2<$(^5 ze5m8U1M4v6yc#@g%XtwjkxG{6%B`;Legpq%qU`TdcSVp1E-nxK&~xK(^GB7FL;s(x zo@Se8TZa|1?ROo2V=|KcpiA}0PfGmLXnCmof&>38S{~&5ty7$I(@Ef5%r2tlL|T0V zAtuvxbx@qtK8XJ~NggoZec&zvL@q?ABpvbNgQDaBpRQ%`q7bQoU31my^@Cx8s4{(& z?A!O+5S3#N-25SYN0dCE&v}1*HvoW(|1?SVo?57llZpX!Jq!t*uCriAY@6ZQ`bruC z>7x20m-_vHAn_c17X`<47i&Q|`)n$IeX=~% zyr_wrXUyT1>?S!!k)l0h5RUT>Eeu?(Uco%GmP=L7_@+FgNmc#33{U> zPC5sm%hpmF?r1qfcx{Y4*y%-lhv$Uw(pw}=+rcMKmi_s#SlOS|@i=l;^IUQc3CAV) zA3ce?+;zAo)xl4+#7T94H}k0))GJ45MPv->|G%r97W}_bJJrQcl{4hJ=XLTDx&L)G zGvAQ1HqTg?pOjmaSC}6sahC-77{8n#ukRPfW)vDX=t&O|Z(O-DfBgnNXS#f4rUsQ& z9HtYmk8Zr$X*a+v1>OU$4sHv=_cKgwqSaSw?6kN{Vl)TJsr>aiBH*u1gZR&j1Ct$wNGq$Zr;+|!osyHbQ=tL$ca}pKRQDmBs39! zfg$+S-I^gk?wIypQWgm#gMTHLg0?s2qqpk5nF4#)s30~7*E0ObHw+V`0lk)Tj=&XTXP(B>#853FEc+lG6J^OvqF zTDcZAJ@=~ajeib&dpfev$eXfdU5qUTk{1_!_RNT_O&b7|I|{eImcg=sF1U3Y7RpB1 z`B}98-O+VF=E&=rGm#@_E0@R>JHs?}KQ58qbD)FEUtca?@0B+P2hX6_P2k5OY>8F7jZY*4wxk;{Y`@vPF#!LO-z6sYCZaLh6aC6|& zH{=504u*Se3KTnVPrw}t_gT39aCLCCa5Zp;p)e(nScm~Y6hp670yhWlJ#gp1y%%mQ zTspTd3N8)Y!Ej}`0Y@N%{deH!SIXl}R4&U=2Xs}>gI)_z0VoHQ0yYCm0L1`3U>P6- zkOa^HA^>54P(Uys5TFJ40ek^o01tpGKm%|DbfrN#2T;;5(Qko=A~eHo0yF~N1RMbz z1ndLs1?&b?01N;<07v7C3Z#rvg^q<=xCgDGh Sxu!^tldE0oW*5n?u>S?1nnR5M delta 49116 zcmb5X4O~>k_dmY(vcRf~i@L}nsHm%=qN0F;K&}Xg3c83ck`GwoX#`8EJaP5T=9*^auB%z${G*lAU5RI4t($q&9p zC^YAM=lwZORlW?x=YnS9*VQwfo_kv5XOsNvNWqP0)0IZ}s8sbn?un{=wR^fTavEq2 z;S3N>f&1@FUxBP-_()mnYvP^h<4CMz81Kn_#bkI^9`;7 zVes-yy=F<+Y_x3+T};QXc8#<0=~T?wZ#$}mRy zR5lNh1I76UiwwF3&$_+cx9wb;jJw+WzSpZ#EljAi_mYB$bIpT0&1~KI$2b+Ql1EBg zKYC$lg{|L0XOm#-iC@v?hhHCi_%^wYJb*=7jaaX{Jwtdu3&c3&L6mM@elyxzv|6WH zK32CUO{?28rL)fwsW#z+V}Lv)$P750ruM0EYY+MyNj^Q>t!jfyII;SB-5!f>Lc(9G zKh2gIt3_ApJIgJ!HXsGa1XVigbarj!1$E^Q9bEzP_)7sK1#WGnD}bcJovQf&6016G zYLaB>Ivw_eB$p`3aSGUJ(~bX8p!y&ACiUM695UY1Ce7bUnhUi5t03@y73fUXK#Qu}yt#8`6(5Si7`9hrS?Gtx%ww;6y-+YE6?QSV%Hoi0#=sowMF_NoC%pw4 zn?Opb*B)uHHjtG61TIJcq+_!9zh0(3kVcxuU&gcZP+;E3in+N}*)wwTHTi^b-a%BZJCtu7L-0Df@`mV|&6jV)$F^r~d79XOJQC!`^(&F|k)jggE zS&)Jp)SDbx_2vuH_|?}zCS{p<)wY(7qu4}REsLK*u@pqin8wRd&O>4IwoR8&sMUfh zUoi9C1QVd-k+LqXR7$e=vnX*MO3&fDprEB70-5IEe*q6zK4(_mP64cJXL;~AL%QfZ z-&B0@7_m<=IIa@o5u~!EIa-Tk*W~1@rQkTH^zC zXe~k=2v0^9pbMZ;Uq;#K5~E`u@)tt*3r^2A#vo%SrCp4M=$!9N48ZA%?|wXe6s3k! z+l{+XipTKNDaP=#vQhcIuJMrXqndL)$->l&=T4!>a5l(_RBI6>s0ad>e%jYeNP*6k zVoZs5tk&q9dtiBBG%}1PP{%uhGT8)0Ohf8M0{W0)G)ie2w_by%L>Pqd;%n0_<6$Or z0!(ZLFa=7dqP@jGAzAc@?_}qvx5i7CuYMQW=&bHg+Nj&`Ip{`R(=$U3o@jFGE8QiP zWw0===?-HUYU}0Jqp|=J$0f9gPDGxF0-XL_*ZN`Tb0DTC!JBik_|iMz9kP`>*Q4GKfMMqXiF>C` zE1dHqE)n0HpwNV$u=EKDw{+{xB)LFqayE6LuIttdlw*TchW)G$ExtBw%J@usC*KON z0|4Rmn&N9&8RIi;gF#(5@wjP>N)->*Kkit2%j?aXI}0R|R7D@-^Iin2{xhcvEP2XU zOWg$gBY#(^aHn|RRXRlE~&9pb1H2aySW#o>zh zu9;_=)4qwoTdziDvCpf)X1;^G+H>NZm)Y|Se!How4+wvTcQZm$vnLgtDL1*t& z$<>v09m`3zOF@c}{G{1Pj#rGNT$$u=kqk==#S_5|SA8U}A z{O1Cg$$u_@nf&Jhn91gXW;5xy-A-on9a~8=sUaFPbPy6qM?V@qNXKnz%6ZX19D6}Av_&?xihvEgf_e4+|8uv^=sz2SM@{*6w@AqG#zBzwFj}!n$3n8~5fgZkt z7WYh1CI-w_wGiXBivKTa{Qd!vkBmrzHh{<>Guv(T`8%fKgW>RHU`p}Z#`JJ7y{Vb$ z?fa;fv0nUd8?W5f8-GW7JEFh54rTb-WAc?TH9%*!b@b-nF*z$Vw8I{oaG zYhG{h^&+rc_xSVV^QFM9G~8c7195+&gB%5)SQ0F^{-{soxstDUra-z#S|PcoU$_Ag zk^-na`CLh*X`)&M1O~WmrwzU|7_xi=8Y@BV@Xa5Q;60f|%qPh&|Ie$BNQW}O?4)JVwNDXPoFmm#fCeWkU;fqZ?w;M*B&He0naM28j#Tkx#gSvPJy zd$7BqpFsxu94wMNGKP-%Lh->#Bpgf$|FAEQSj#GV zWQr$VW`Uuj^tJ72(q_wXA9ibW8D`nMTW5jn#nAr1v@#>50fu2u`$LBNOUPr{*dv|@_ ztFMoEeKWh<=K*o6#P03;nxpofWYy(Yr0e|GzepABMA9W)iNeSQ?Z&g)NzX2`S_;@l zMd=zT=p)MiFlGq&Qh~?!p}_&6qxMW-Qm(SA6YB zR%jd^9zMpTvew0$NMlAoTSoB>$_hNv;tJ*%%45c1;=vj025wKzV3GYzK1EPF5B3ip zxNs)1Tbod0?}s7Q(4vgxf&@4bQWGXzpxM=(|L_2NyMNDqGdE#6Xr)tpDm@qwElR_K z)dpWSAM2+k-ldFP>OZ~n(6zMaABf*<-e)bFIv`EV*~B~pdO4n20KczxGRTZ9J!11c zAfW44PxPyl0=A;0^b?zqrX;uDo_y=}I1S#8C0ir`Yo&o)je~8!@t* zc%_)7jm#5&lGtk_GY4oML(euCp`@@5dKAx3le(G4@1^H8WE#x;uBTX!=t*MaB(^a6 z>mD;FnpF4U>Nm-xT8euCE{5xeiOdr7t*(ejOj;weV_(r4Ha6BkRwFkyRXkbBUW)A_ z-uoo0iXAQPUczp~hEiF`sOh3_DSL2KU-8LJjE#y7`*|_=bx4EOtL8$P(MpI&foVCm zn4KRrwAb@(04O7)nFmjJAAAfiFJ8=sj}G?V0~1c#Ds%K;vHKJ3k*0~zr~T?xioC1>YD0&DGTSlkq`@o}~~uAf-Enw7`(5r4g( zRmWwIxQQ4%#t}m;uKWOlt~K+)8%S&uZSwfFkgh>XAbsKeY*KuWo|EoxZJkI*Su5qk z?`OsFy#qpAAmA;Gw}t=o5PLg5Ox(7VeHuSl99qb(#t--3NunD33>!KoQat`Nn>EHI zu3OH29upzXTh0O#9x`mBK~=hAv$b|*!E*LY!c1|*W9)K5cBhDkh(@)A_kM_FB`y%x zFWa>*agHE<^9T#RYk^pPA6tFbP|H??f24qy@N4lok|Q-FRIcdAW6Z5(@Vhg$R)x?$>l?E2U+hatzLvRKa&7Y5uXb2@LXR5c66 z!(t7*g51D6b4`@{*S&YZE}`7>cjj)V+~arVo}%1+a;`(uuDSmr`{g@G%nYN%bMFS8zRn>k(Y{;mX7{ z8do1&oh)q1xKL4IZ1FgwxPY-u;}(dQ=CP)614Djz1S0o$8|_!X;*!&Yljg~?l5lmWo`2<(9By{d6+8RHiIp6CVr>_}jAR^6B z>|tf&)TG1BJfB@2AK5VqFbaL_{gl{*xfGYVjb3a-Mbx!!L^&^}v}-wCX*s`(HLh_S z8jbNqJ&S!YAw#zvGn?NJEeHElzI`nln%=K}ft=J9 zU7v0D^ZF8m6hyx{(|DgYNPBv;SjN~J>F33NZDboKcK3f8)}1u;+Y?8NF2;VI7$7aF<4Av;e@yUHM zVC%_F;~Z&Sf}$)w3q@F(#9*DZhV9SjA#N;Tr!!WChRF@Xi0Dc;LJ+XB4THr;e^)=j zmSl#D_s(D&GG7Y{yh>!SIWprxC2Y;Tce6=TdO8d!;bAjm=Hv@J^r;rHnE{VQW0C!% zgx_=Jn5ZFRI2jxsGAP^k~RUmZ!0^c zO-T4}!4rA2=FX{-e9SzVVxyh1_!zo(o5n}te(V@Td~2JC7hlwh@@UaaMu1&|R6D6J zF&dCDzdVZOko1u;0x3zozmCSiY$f_I)55J!T_e5?0!h}yAWXK6+3c(N;o_nOc6EMK z`cAp@Fq{d9N^@e7rhW!2Ip>QPG%4Axqi#?$awl-5kZkHro9c7CWM z_;;udxv$fW1}g`o^E2->*IUL&G|!)Ct<#t#*MVGWWqEMzHiiR&XGur+vESOG zQvHQKx8Rx3isxT#c(CCW9tIrv|290Oz=Mk6BI~>$#9;s)GrtU%2Mm@u+d^)bHH_Ne z-=GY9h02`(@6CBg557h#Jsm2yrUuo4)EZjCp2Tg9saqPu!2;(kU)_2;3DkN;G_dhD z0r0tV=OBlq2YTRDbGyN5r(K~f)Xr7%a?wN$sxR!Nd0C-6zsWvc*dyK4HjvJX)@DTy z*m=qVw8h}8Qk1cEC(X+b(H>?&e$W9jG0|S@k*niapGDm!&TNoPK_ICJlj|dSFM{{0 z0SB;M5QxZp5#Yt;0kn0I#m`KY(Ev6PW-J08m$4dN5)K_4W`&D-(!Ko9=rb~(tokNw zLnu+{DEK;&Wr#NO(->?}+|07A3d^=1zMW-`R-VmPc+$$V*+wJK=y^ZtYJqV!74TQ9 zkZ6cL>P{0rAW6RZqi$p6)zh7hWO1KN()OdoEGwYRUO?}19-A@&Y#$KE$6Q)VyKZDntcwTwD_ zgb9ngJA!Bk{*j+I(SEy46DaDR+E`nBF$NKpmMLChq;2~<&KJ>to!XnA@Pm)1 zfb=N{Z5ZEo!-3^!G+Fh_l|O5vFYP0XiV1AQc8LLwo{5wKo)a;dBUNs*uufwtIcmQ) zr6DwFKT7I8DZuxQ{1Tv1H8CmKGab-beSRAD<>QX zM4VZ%3LFSSG$vfYl!|T?rW^G*QM8TJK+X)GYO~qB-Z)(i`%-~6viR#*Jt;~;O6hKz zk<&`c66;wRpbP*IecLp5iE$O`nR(P}q`Kpr)hTpN;uSjC14qUvIiRGXzqs0Hwl)!u z27u=i;5a(HCf(!np{i4zH}RB7N?V>IS^V3HEsam($MH1xwLb0|hY!vsy#A27v-=zy0)386GAdW>=WkFCbod2v_@d}?vB3=SAp-w6k?Qa1mHmH6#TW!h;AYd(CT ztgPKJg1k`P1K5P??H088dKRJZ2%XVhC-$%`XAjSbg_@jQy~*n~H0{m;CF0ai_|L&B&!6yk4AV3Rl@fN#rqiBPfz;G5egL(=ZXTG7q%y zahOct6?zsvo{vH$%o8hl4n&@hJYS}p`O?&8C}v$gq}M8$uASr+e= z%Kp7PDtuN5hGD%?o&c>j9{wXSZEJf~ZeKch!s{|ab(8^1KEbCiTPUlQ>_%#`| zmS_C|_52a*V(Tt?#Y<_bu99z!DtjrS^ zFK5rK3=z%C*`Aem_rLNGMM>0iTlAHLLrbrZh4b2+ksrw`#8b&d`n%u= zpV6&(lXT5$z0Y+eN^&!anxZI4Zz)Z0rYOnEz3iJ+^TpA*Ea{OKBY#Rny-T_^Sb59! zZYuSTA-knsb95zb57RuFC5FsovmPDbXo~|nFKV*e#RBsmY*BDMn%NF59dKBYzqx0Tw9*(%f*(4 zCe&|}d@$dC%A1Q|Sh5Ondd-E%v$TM0`?EkNYTE7YxD&Cv6BN+8zm!FBhxv~!5-%sP z&mIeNbUWwu*7zNQjNBI9`KRF4`w6m)wGVC~v?U#%d*K}{!rMroC+O5wZ5HQ$$EAFs z+{+T!(1I|boJ}g2IN<*oRO)Iy_Lm$~TA~5-pv~?KDk1luf=ZU#NW@V#kofO55}g;D zx6`niIc#>PUQvvoYe54f&_0Hl3df7rcCpgJ0iA!0SNL!yJDbm#!QLF(2+gt*chbwshw$0Z*8^6=yL^}8e0?`y9V-wHVm?X zLK{{-{S?}mIZ^hs;;4;85&`bU5*u_ZlQ^Hoq@w$T&g}Q1erTs#@pi$^_7#s5o@DjK zq1^{u@p|i56s?L-*}Ij^jIdOvMX2~aEbQ_0iAOF&rv0Ec_3JDVW#)*oxe=1e0w8`1 zC1rULvi{dfnSojSW{k|rnp8PjT9XNoU~a|=EdmGc*aN;Q@Tvta+bYXI$Jz=A5*W17j*2dH4%b^En2fp z(6bI}9~RT^Wvkcr5+m+qFRYyta`Yakr~>$vI2n{M+ZbW%NrJ>b`Hktz-bwjn7`3OD z{LD3JaE!5~oEE6u^mOBI$mMF~ET<1ba5{T#-4OBV*VvJDuXlcqyp_})W_RXp{{5@WAx%qp>=lgoKXiBV5P<$?&cVRj zl>;UoZ%=|&-dhh^A1{s_!p!US;-DdH;rdBIdYD9rQJCacdeo*)z?LT$=CH%-6N9Q| zqhu5vsi*yyKzUPOAnWe_Nc?jaJLw*tZaCj=N<_AKyAii}yI6-PvmEY*S-NHoye>~t zW0v$emJ>$qtYVWl^b)H+V2d}5=x)ZU3oS-v?du4}&R7VLI6j8GyCGZr<_PnDV(8Sp zR$>~t?Qwxp1142E{CltLW*D=0LWIoHgv+|p`;a|@uOBF9po^w9Ns%3n2sj)!2kvq{ zF;o!C9%Aoqd`;9m#Aa=(?Na-?N#$A{5x?4K_s1SX!|S^epPVd;4Lg|YsjtK(zHHv+ zLE=nbCT&g?O}^}l%_+f4e3Mld=)_cGb)78`UCpR58H~QWhCY2u6a#U8X1E~Ef1S;J zwr5~CI8ZY_qXRlvGd?}she^+l7H_E8p=XzfXVtq#J~vYkU-Dt=wajKZ1-qri1F9k75z3a-h zErMuJvkm2I#3r9z-M3c>Vx12=^U6H&h|jL1SDD~=bF)cx?P-&0oyVkV#C7);lj;zz zh-XZyr*YlFRs5_;^&ebapEId?;<}D2f2&E=fa}5MO{)1Xm{b|tOsaw9*d)UB>2{N9 z{3|Av_^L@Y1XsXoCe?emKE`F(VNw;L{$^a&&zXEwXP#rzcHA$H?#K4;2obFzyH4%+ z*=IzPPqL~;6$)layq^43Gj>!A)N#X(a9f9(R8=RG)psTF0?(t=?Dw}8(7vzb?We@2 zIQC-!~6gLq&L; zBzyZ_^4pdD&KXfO`?9EagT>LlyC%LnOAzPzvsZUTIOgeaynW9Xl792NPOxcTVQG#K zQf0kCihO<^P)fP#=&$-ygeO{GI8RSL z^+$xzWWyZrO~LQwn04~ z5qa{0p)uaB(QU3ssJ8dyN9TeZ6@m~d3;pq&5u{=CP%rg){5A9=KYW%?l^Hu`?-Fsj zF8d9>5!uM&N!WPE#nGj_RIS-UB(G{Ncn*%CEA>3LD(M`)Q{JL5)TG`(7{bfRVn`_e z4vkmwWI#D9!l{k@avN2$33eV4qL0S=)*`+x?#t=A-~(uAmDjHI+MB4YYeu$sU#9rj zV)ntlzUohK2#eM23rf6eG0`7R8)zgT&Wmx`#f6%F5Kw#9eJ_7#Qc0Owmk?cHy08;O zI2W8!)uf(1j(qZLM!bKwIAbYuzduWT0J|*g;`?JoO+Fj?!6I?#GWPTbgqo#x$+dHrm3^0+j`a&dTY| z7vtB=yY(ipz|x9BUtp^5BBg~T)Yv+jc~@VIzH9Z|FxP6WRD~n9d)UJs}^CWFGkhX2PEz%}a!iOs~khEz*gvq8QWRQ#wz))r3<0rHKp6}B( zwf_y*4+Mhtop^7EO2yQTHuWsT7+FDM4{`QVwxzLGr_&FhikX`RAiJ`0p!ocQ?AOK& ze=bjhnJnpQX#bB9MQ<9=^ta+(YpfCO+s1~Fjh>pyYLxe?bJtN2S; zYbs>q-*Trmn60En_?r(CG1~fav)3MI$>GmnFfa_5Rd-IJyMPnKYWRh8_T7K_S+E$d zhI5Z|*7xxt@G)I!8Yz8=tFx4Dbxl!Aof87KtcK?5Dh2E=#3oq!GP^%luLhhSqAv-) z`wCFl9j`34m1APBV2eJQEoME=4u6!_`;}s_gH%)3b*Y+su`KmQ*_@lzskc17cydP8M*W*T{z-C-z$~`(w9D$trJmpacrsbFd?0_E>KM2cH+j@*RHU4E zV}Dr|Aea}jAm>tbWs`4?JF&C~wIqM%&%G;u_VcM;m#%KC6d)&w3YNavY}v>6h18Pz zGw?mQDFds>LrQ;k@#Bylx(#w4uRtJr@zpYnmYQ5MjrFehP;3~-&Q|QvDn0Bdca6PJ z`R^WqFdr%#U)N2y=M&ul7bQ!NLZk+n+5HC}&;#C;+5o1+v~c72RQBt^6SpI(soH7) zWWMiQDF0yIcM4G`cX;p=_Shk#Bb~-64%t$w#@Tes9?JiuKq}bw4ekmoG35X##_nEq zfhNIM=42m!6acO${!S~LVo;L8Wlq)~m5x+i^L2;0WfnF$f9YNMi=Wg&9y{y;aj=_&9i0+XQk!7lf&HvWT#vUHfWk@S_9xa1;veE>-!-Hpe4j75KivG*W zl2YZ@3|*;B2p{KIT&XwsrS+iZb8jxRQ5RmD(b6TUM*60H5@x(R^F|g7jM*EcaI^`4 z-4u?JOq%&Y)XA;?Towp8yVM!y&khm?cS@AHDEy{`)@Izun|rh>UvIS%JNKZ9^SIY# zKkKsBHS;x$FQQ!VHKUD)YU%3>wZvF&LQOlyCIP|~$NRMcD!TLEy%feCk&bj|*3toZO|I*a81l4Sr2?8okARR zSiW{{ysv&E)K(Cw!Kn#=#9-;r#D&Mm-N>ILOXhv@Ee|IF>-82ML1299BsTuY=+TO* z=$>P7{-SgKtV_5xH_xt_n>Xiz8FXB&@c{+)pvnuP^G6@&HO=zDmtXN3H&6eZ{rgC8 zFY9rW%4>Ab|J`vio+F!D*{(IE(5MWT$<0>ilgaz!XtU9!6mp>;adMW`{L;QISsW`aP>6E zcnOcdCe?gE#5=F4musDMK0}Upjipym0T2YdQH`GnTxMMB(tP@zD4XEp{7t6qcbT?# zZ(e@o20GhN%h)FoB5wOXiRw>vfqgq}Z{gaF>myu;a2>;S64x)atm~)gdW9s_2@sFV zfGhF@Tl(pAG5QI1=+k~(Ur%dQyfoGM=n!`8(`a$s7&h!!$e6td>g49atC|I;rqVf2 zt#Z!OK&W?MozU#%NOLrn9O+t(Kjle282ABZo;ipW9}5xJY+zfD4UCsdI#5klx>`Oj zNv$BwNCuku7ZL2nu^wW_4b1Ra?;ZqY>)?Gz&E42i zff$dYSDAeG?HtImJ~J40{|A}m{vmcQd9!MSBY@`}HZbp=5VaG9ENpLxE*0wO>Z#n& zid+)`vZBv=bfFiu^1IJ{gTguY|{≪Hv*~p1647BZuw`(8n z%o#U{MptWqy+eQ7WH{|hiAO3VhV9nOq@b|C#7++ z5HDdg^P_SsyuY%IBxP!x*KWa9&Q_`>%T>wt)Ym3_4s{nG+Y#7OrMz!jdAU>x^Xpc$ ztt(Y4oo0tW*Be$jAsnv48&LBS)+3E3wO}{$Gs*0a&%?A+P`%0<T~oBTzj_EB=3z-tRH?Z=#76gWN|3tJn-58;MUfkBX0 zI2zt{y*xHJ%}x7TI2i8dijr#Tjn25fHg$AGeJJ0Ae0f3SibB}+M+tZ`@#Y0qH?6~2 ze9PCLdb|9t?(KHa>*PaTt+hCxZkh zTXS-_*rAN=JvmzJae+0Q9MP+J2u}*wkSN_wt+`9L(oGr2v0o*MP$cEyjx zX@z<*8nX8!Y<4>jhMoAPn?svGEn*=iD*7Nl(0YU>d=KQeI)f+2fD3S1-p9So8N{I9 z>9f2E0JtEVTHzG^&^tgDsXLs(2Kdv=QysvTol=YE9%U;|^%6&o!8F!;=A;RlPOk%t z?2n9kjO{=5xqnAQL1_`pPDh3Gh(|+FT!owLZz|e|Z|KC%p6=lT-mz<^qm%y~NA4=% zHshf^T)$3=jF0z2#M`6}w{^jXIpk@j;Y)F%2um)~l=V-trQe2#*C#U9w|z+@-u|{1 z-9Gzvi73uz31=e2Z#S_8XZnhJHZj+kkyN(-%s{&Rd?tr(W6$P^RU6n#XR$T1fqi!N zmR$YZcXWIEyBfN!_0Qx=>EG5}&_DsCRho~Y~n;Im(l=wW0zjqKehZyi}Z5;U^c z5dUc#+2{X0vh@JK$ew70Q${vV{r8cDGulla)1J%Os2>J&S=w5MuaL3G&U?tWc$SBE z(Q=4j%ho=&h1td*hB-8uAax=@V6Nfqb;dm5b!2QW9K#%z?Ap>syEdDLU{>W`*o&;@ zrKPTvArb9$6`q$J&2>n@2YoINZNO+`^F#7eYF$E&uH*&uGePlbCt9-2{MGr?8#=8p z$`=d$`dACMFQSU6=P|zqO5<;&CsZ#NrD}7w*M6Rd{_OQ;a~lx4QtMFt**x~}k9|5Q zi&cy%d;Uj5ry39q=sKR6$0~jt;!sMbV}fE@c11_fK@fP1T%Iba@O&vW{}8w(q*=1_ zRrj?lrWFcsPRG*hMaNipfYOq!H(0mgGYh}j8;H3P$P_$?k?cUCig&^9g#ze8)bGxH zkZPl5*D&_kPvH(-SISJY@Q;4N#$OXO)OA~5OezF5(pR>yIY3z=_XLgPywoUK2q7hT zwE!tCKe;wp)toH87y*;iX4agqoIZaz0|& z-k|w-a+gLZreU6<9JLTDgfaNTW$2_eSgoq?2F*qh)aOjxptwnG|M*#}+V;j0G4dyB zXrbXV$6JT$F=Z_C7h`a3If_aWVa{WZ;ZbUWuyI==EBhr({An$F^_RYmGlom@WJXQpx3nkaSQnQZ7HdvJXL!3uN@En%rhykxjAS zd|Egk4op&mMP3O(ie(Jt1dYM>%{QO#H?d19Y zdquQ!+5cV$txm2(u(K`75&c@5tU+)SwoZ0axU2@``BV?^GbKh)j6=A1k7#Pys6<0^YbC z&qr`I;QIev5pDckUJ>C&D?ycq;F^wWHm>=&mf*7CTKl`aB2q|t2^l+Z?Zb8K z_g$SYt`S0OF+WlpocrFwED%m_2%ChTc}pF z#S&Ka`-r%ua8`2>Or`j-eT9Xq;Vr;>^x8rZz(ORmw(>7u37^F2UHOBbk9IS;Q}w>@ z*E(2aLwG+WwXfEZ4D)X+vW4=IcQjJL9&8vWmORe3G>p*BL}8U=KWpJ)_g!B#Ocgpo zL4e)fpe4}q!~YBmI<*ipAN=pXfHXi9HjwxWBiOt@^&#pA3S9z@Ud;%yUV+~W4P!$+ z_n+Zik$o&*!w;EdPH6ZKQ`yIVW;nJ0N@WYN(w*xuFr;ug$}Lh>E`KnPw$1SEGOLw7 zV73jaSXM2`R_{urO*ntDZ#GJ5)W-P)K!*vONvEI;RJpcLD?RX!FsOuA3(p7Mk2i>N zqbo454uiFfhGcRd7*7qan+iaR^+cZ;AKz0`hCSnw!w^}p-MK5xLoY_E8vBL8Rj5&W(;~5A4bNH3vuxx&|O9@v8FQ4Qc z#eYK@{_S_Tx%|A}G-Ev_=Qd;ebU2H?VhlzP_mxmL^I#!!ty?Jl)F0#6(kr2Zh+Ooc z^Ar$6BSas>K|dh)Dw_rc|II`}6@Mt4y?Z6g(J%~cLWfQSe=XcvLceubf?If|{2V9$ zhGRr5eAdU5|3C~zVpfw$#gkFjdB|8!qu;#6M+-GbNr622^h6PBQryILGfyS^im5oK zir5aD*Ne8UnvbH;$_?{@7H`@dSL#XrMikRDJsW@WK&wS^Ch5^T5!;a`?^Wcl&pn{V zi!tCau!y<27)-r&2aV<8UhGWMR7Xby<;VtU^27L2k{Z4rBO+%6@;P$Gyh8K?qs?>f zrjkNUekf1B4p47U5y&q;M8b0q7DnY-w9dDdHbZH6UxMP@;DX52I`A%(fepSRbVr~4 z9cF$DA&%T_d~LF-{33qL-qBT%Wtfb;~!80&cgF5yPuhlrB&8K{NTf6sTc8z zP6t=!S$qW`Z#6msk7)syQqQ)7?@#cl_-WoSky7jUVmKI8{AZ9HaA7+{gbp9l9~=7D ztNzL|*~|j3>)CIA$*<#Uu7@QwZ@UFzhg0j$Y~UjfL+4@x^#EvkVJ$e+w4h#_rrU$$ zk2vR6T9Ir2LoO^{>lyfvkS-oLsq2TyR;ehz{5X`~ud9ogb1HSH}=lF6VTD=$9 z9^Z!rTUb~`vMSrck6`4Xprg-#&CPDrbH92vKP*h_K7A7%_SF}~N7;4xKJIY>*w^=x zXUZc&UmP^?7;Hk{u=(gx^cgr|3D*&aU4{^mtZcD%66-w{n_w0TNU3m#Y<6LTcyF;sy+Zg| z9K6PJafL8V{JPX*S}CN7w<xjA9*0Jv0+B&x5Kc1bB2xG2t&H`*wr?%E=Ix6^bCCzoS6zWsUWzFeUVvkYZcDK@k{4Diysvd!~%zB$D_hh zG5lSR;W6RexjE5Z_g*@c044S%rns|44fovXjabg}NHJL8sQKUpS(Sj*DRw*207j0+ zO|h27A88WkTaO7C&iO`Rkk8F&IOiJ?;qfmJ20NxB!y8m0D;AiQI3yLbf~jZoh2E*H zy6_C9vnD*Co@IPyu&&#f*3%)yfcX)R8VOG zPbMHMA-~O;Zw|cBf+7*#RP%zu8%qi9!UhQ`Cpv!Nt)(Yl928J=@;)nuv1(fdRGuYY zKNot^e;=LdchvhOR2TY7)#X%_kN0}FVVfa0xBMk+kN~PzzS`o7r7~W2TY+zWkC}6; zqQ7{b5ZC)|BY2lqbXJBt52}l=@fFFcf?xA*%>~3NK;+<@Q=i)nBD5N+LTzCpwT2&FcQs2>89gg^0893Y$CkFqVP5Q#_&Wb=Y|9B>#q1B4h zl?q@gyvK@gh#}Q!<}s?sT}`N5-aAQd(NH{3>ved&Lnyi{W7Gtq!zC4{J z-Li-e^<|rHjS-LZV<&EHpfh&&cxUwFyP;AV?Xf%`K_SIEZqX@?=h&Cth=H$$gT4>Q zS#0NDl4~h7RNnHH{mb{@QEu)l^rO(z=5%3B6p1H3TO8e3ua0hvuFxGQsVqWPzJFyC zltJlJ(D@0Ky0vd35{|u@gTe28C3F+TM`QN>Dhe@zcyWa%TrF6|t&e!tsfCixBp0sI z^QdL!lP`F}e1%2g1Fv~XeT8k}T9YTjPgv$hhnjek=LJ7ukRKgk;zvE7`3b%K=wK7y zzV}Z*VWA*ivU~2<2zHUL*n2`F^c2KVHcvwbp;&Bu)AMjgA#2o6FOe49_?d2;>Uh97 z)h%4JJC9TCz_kn4HCzJ&u{+?EwBEd#R3lL0LgKy_K-#jREItTXjj0-ZhRh!kn-@Iq1_&`e zX)v%C0)*9mN66O;^gPg6cvpDY6Rr~;@N1x~Up-7G3<_{Q-|86gYo4PzVZZ-z26VN7TH9(YIjkyh&z)Y;+rDo_X)uf5>88Ys*VC%ovH(nWYG?E7n8Z1qO#i=ccFz-qOAHCS~RySc(=kKqg2l*Q7PNK){Y3L?o4ZO7lE~deq zrZE%bdU9A1Nnc^e=I2q;a{A_Ok5w-uiZ5^RysC%gkKE$7&d=_B zAV>%m#9XUqU3X!X|9pr$Pk6`EAy^1ET*UCXGf(o-c)7rO3~jqpPn!A7cRb^Q;R^li z*}FMd7$At!Q=a_>;UP^jUMrw6_lyb=uI7{hlN7rVn$`O?Slee?2{K3@1;c};Lhsk! zS`=ucq9!Q9`hf?ugJ85;mY()bcqi_-GAst`4N#)E3!ai5g7N=?{Ko%4UX4H*jk&=Z zfp(jbdwU2$j;BDgygh1ZCNzk8AvHkhhI(VoWP%r9KICHE)dH}E0B}ySwUgb^$(aP1 zK#<;AX8?8IyNxz&|QP2VOnDh!f(VwtY;e(73697Vr8=R$=(j*VzDKY({5w}IA4-W67F&`W@9 z5t6$gxA|KBCunHisC1<*p(WNfI)a5~?}{aSY!FIYYTy|wG>};Z8bA=xe+%_PlZKy= zAK*3SD9cB+H@0Mzr#MUqapVoA)hcFDqiqa$C$l7WIlc z@DX&_HX%tq8K*DyR^ViuO$*1t4kStN^jnc&tii^)gqOd0$?GL~mWL_S0euet3JERO zDbE_iJYo zL7*8_kOvU#claU?Wx*5V^89h^A(F z^BmHZ9789S327!BqbvCk8GH*gt2dUa^KQ_J3V=*1_d$B8S3xMx&A z#$KA-8qdO#6~nv0+BgWaO2L>!de&Q8-lELXt$GoYN8rT+TSYsjy=Z$j_7*~XAjh7! z@ZdL-MDktFr@e&%!^AvL`zdzss`y1LBqUP-S`)ps7vSJ|A-<~!b|K2Wv9qOQPw#Lc zqVv0vFn(WM0}!?D_zeeIZNDP)Lx0Wt zj^ch#Ql1bYuG;KblqXCPYxjEg<_QZOn?X{peDZ^WJ`^+6TelDy?_IzGpEjvng=cBm zg7r433T#%|>k{O3gU(GmIr93zt=<_QXPYY_R*3_<7Bl|YgEOEwH~NcY*Nhu7h@{>r zrE422ownieGc@)tWhs0yY`oz2tmnZ@#3Ewf^RP@I z!ryr@aNEpH$)29~2t&GFrcE~bYi4!v`+M)cN00>n`Is0yX`biey~2QWD-sqi$qpS> z(sr%0+P64UOaG`({Wy&MGP!(l`*Mw3E~@JL*vkG>Is-H z^mhy+EkkqdnU>L(hsyVB8sL_pmB$^2?fGmuJFR{bg;mejTPuGS^W*qL#29k%wFFv9 zsyoY-e5&IOwu#o4beKi*DSN&p9YAPFF2>=;7(f2$25(P<;^cZ^e)XY}+OKW>M%kX@ z^M&ZxPqBeVJCo>85`1=S)#8&rcxj7X6Vu~mB*Tnp=BzAhSu9?s^TwhZv`6O|y+Fv& zy)hGCUzIiPcn$w(rsv58kcPh=_Z(ax%pa zcdGN~j6!gNd?b1lY-nx@AMED=3Rl2$k5L#heOVd( zgDdl(Ii0s|*?ZOa(j#%h8?+CKH*U@iTPh-h&U=>KtWR{_x@qs)9%MH_>d!cD-Jq?X z2FawB3q5s4p${#PeEJKqVnu;xOn)Id@*}{K5KRMicPcl-q=ON1Ry4A#0_Ben!`bSO zT%Kq83#lXQF7!R~JmOA^k48c1xc^;CJ+Ys!AG|J2D55eBeTtHg!I-P4!$Z$w?wM6b7uzFoczr?R=C< zF32VFzf@HixNg0nCm_no_B}>rKYQgtZ5{h=@?f`8UcQDX@42tQGbBRj-<+MzAE0bL z2nt$JSoLb>f;ucD>-ek4t8yltL{JFZdHFZFg}kyFUu1aP5m;!|7JJ@_5c;NFDTO}X z^AUQ@*XGi!mDcP=0w0zPjyKr5nfaG(0DWWt@1C6m{b5`F&QeeCAfdP831mrY$g+Q2 zgr6ZbW?mp?)i$S=%5^G`;`iYZ*4lmGBvO)pQeZJ0jNte!)VN(|=KgS{yrX#{z;bil ztIaj38b1^jJDT>9lG8=bL2@4VERB{9CC8k+ce$ z)8Qmgufp(2hrqV58od9EWg9H_ zX36t&q>$(Xl%D=Wg?v$+>)A9^2ok?s=h-n-7%UfjH56(908K*$lW4Jc#tah%b+cK> zJ{er8c+0?cQi`nrz=~nQ@Q~jya?%Z)z&KaqPt7=MH1$2qe80F3?UgW78Cd7zU zGd$gg3&S!{59^BfIahG<)qaI9cM!W$8-P?BpK2g~JWtN5b}y*IILb#Q_|Vcn@Ai(aq5azl!e&Lj_*l*#KuY{ zTrM1Ne@?G#)8P{a|9S_^&Bgk`9Rrd*7e~O$di;dPf27cNre&1UH+ygn-&g=Ro^gWo z3j7)sololIx~m>56+0EREO+Q=Exk&$3y@X$9eD(FNwaSUS}krO)`L$p1oeLTUnud8 zx+BCJPy+sMD`Uxf-x~?hax|@{!O@kRBD?`xP$n#6scNrZ>5h{9@7Jh9vQE)cfe`yTXK$b6;3oP zaNx|6cbV4vwVdZ&7KmiK*Maa6cp{ftTzpiI&%h%!al^?;y=Hxva?R7kKRB9fk?mKRX4q$AqJTsE0OIx}Q9oEpJN4aHF_ zr^rb0GKuEfy9q057_}nL`+L?t2M^4A^Y;Dm{q=4AvG#h_vz~R`>-wy9?8`=g#23Z0 z`}R3hdpG`-FPz8{aSa0v9+_5VrwwMi^6>$;V6 z_;CGCKBRRvG-T<(s*&M@@f;~l7tGU@Qkspl>xxNPS7q{ktS7aigcfR9# zadd)+zmkAe&5r~5(F8U^Nc}Q_jds5dFUTsBNk19w)Fce$!VwX_M9fSh1Nn?ZHr$_7 z=Ao_uM%20fXz^$p+}r>laZ1H0{#+s(7)X3;aeXQFcaY$(eJkVCV^}UYw^SAfoyy3v z_;Gsv5oCJbreN(Wy!+rn`SwAI5tn(X3Zcpi4ZQDU=KGkCmM)a?jX(U$p*YgR*GHB` zne^c#l-lc|qdwNVPSb0q83v{obhc~K3;x+|lU{Jw90KCQl#Iwz5^Q>}e$PMHq`)WRdSp!dQs z&(y_%w*b^FOyQ7A?XrM?drmzHYFrW~68-^vX%fnZO1&~E z$_X`O2jv;s+9y8X)k!SChnx_H$|Odv=I+2l*Ff@rJjuUGV$s4oEuK*WiE%fs)upO1 zCYeRagNE~!$!vrxiM=}o+3AIW1*GsQP(=kfpUlE~R6BVJixn=~DQtxN?g{?b6gFIT zE#*6>V13YgGL4w;GnaC4RNqE%Bm%lxtQCnOaw+%1@MY^SU5JcJc>>;b^Gp z(?5_Z(aI^K?PySeNwsBrTHYnHP)ENxaB>KEa76R0-Oyba)|%WE#2oZ-!9Nv!)t zrggJ+TZ;%6%huY-N+Bde+4w579}vm7bx|2g5iDij#oC-LusKHRue$w+&8YKe#|z>} z1WmpBN8A+!s%%t6U}fd5ZXfv4GnH+zmVjt0y@U?7t4zk^E_BmwrGjg54=yl`Rhe|I zehilwKvbskq*h33A6w9gT_Gyxme?=#0cF3K^uDCNe;=xvzmh!Dc;(mubfjsf@hGh% zaAHJ+iKdNGH3SVlWxw7Xwin2PobeT(Tx<&SuG^KuHnNeAETbOE70WJk08lSighQ1g zL^*H_mkQBiUiS#u$;YO$RCcy*b1F+>@~?HgaXMR{xQPZ9_PQ#JK-zIF+8Y=cBcCk0 zuQN625!W&@V?Wm0W3vWV)O#uE*lH4*6Jk|GxmCeiXF{RCgUIHiLZ|2~fO@c7p}Lzw zZvB+M{}<9JCScg~nPrO$cfzW@QA&UDz#mM#sBoe+h7g=*uSms~xXq(9z*uLi{G3Z3 zm)mx)kYgr?GhdL}Xl!c#D@51WVc{gJ@rC`r?BqVPpi9xYjqjSpzL00f^8DGD$AqHH z=(xL^@#;&NHs=fC242O=5veB<)uRVhCMpzeyg{|OKk%xu3RRPeOzFf63PImp4NqZa zg6zUoUbwkh{Wu;v9r|CkG#v*r?H3$cTfeQ7i$cQ zkiS?*g&w2%Bx#!RG&D>i8rn`E^~TMvvG>=@qM=_mK$-e3znR7cj+>H%2x3>F3*N>l z0pK@la9<-2zpVFETtO2hXRa3qw)5aQEGX7}2{KUm>`&yw9|5%QfQZ3}h|BibCOl9z z{&6u_+7e*F=Rj9ah(iu5Ci7?Juth1mu!m^gOqi;>gO4CXsY#~TAipAXw*`S)2>!b3 zg7B1u&|lXq2!>=ndM*p|=w6;F`+Wzdx!B;tY0nO{W%{`+*d_g2#GwVEuNhpF%}4kf zbJ_UR%xnD;Y&0aW6@O-8YPwHpjmA@sVHV@Vm~$nD_*rdbN2OZ z{Il!KjgOhfPWSJN+@YK>t5*H4l-tf{ab7=R3|-+-*3YC|#~VH?Q8egxR&M2s=CeCC zEl(%%)C`tB`to<+>_ejtPvzOSEWXy_?VRL+ShVe;s6fXzz7x6hQ3lhMP^}^( z%65>>NzPPv4&qvR13!|<2D$05V2+#Gny90NMy(rZ((-GWY^dmVe#vAJOZV7|wz48w z!60^s()9*@vo5L(a`ZFNI_4FdOl?1-4&aw>>D1Hs9gDjZ#|8A=(Mqjn>EZXvgv4f%{W6B7^>AB!W?ZZK>Y_MASw>*lqiM0pEDz zCl7}!YT07RR@|c@PL6Blpt*gqhTmAk2K65U$x!{Ph*JBzh9`NSY_?3ERl`?iGk+JV z9+p5RU=w72HVYlm{oP@t6dl=w4oW|}EIyoF4Gx`B}({I`b^K+Q7{-YNbO(IG=e{~n2nKJXY&(_*;u*wBL9Kx$mW10 zSc>@M_k7_JMu#=|^GjGnN$&5da?%ivRAtp+pyd(*Z)GxEMJAbtte>qH%{m5`|7M8j zauOv|)~dzeuWZByWFk(fyXt}|=chsruJ>3>x0l?o++OnaA(JUwZEZz|tmswgG=@yK z+-MPnGdzFz9OaDZL%Qz4Y%Pze-R$cb1JL= zWGMO63sUWJRZ{uL$Dn3HIZcy+e^nliLA1olJ+J@M=l-jCdgH1~p~Qv315Jg+{SIvh zkwX|?e*&|6eBJg6<*)TM1fz#34d8&{=^wfo)l3+-PGoV9Gkx7Bzkd9R_tmk${*${s zDbs}*arDda9lVPZ0CBhZQXTUc{tIU5)}2~~K?p|^@7MC-1uS-`25oCryB!pTphLdS!?X^$yx}Nv4y|Z9 z-&nwg#m*utGF=~is8D_PYL9ZCTDO;$G!LbE%QHcI9cvfsO1!732#xx184 zL3)iD+10J3++h`4AJmMG#u(p1e?PPNDfaT_fR!IQ8MkDYbv z%nt*xg8}p>nEr&|k7XoCr$-=jeG#TGmVNKhe5k7}fsvNfKR6f7%C~q=;10pLU0n z6qRcFb1wNdwE=0$ZeFySc{^e4DmwW#ttsCAi0@s^qWe>lpyO?H7KwIm#^NLx***U2 zYN#Kt`H+VevyhqRUZ&BbSqtspI7oh&(u3Nb)+AxYM_xloO@U9Lw;@IXCU>VF68ImB z*=UbL_}J*C3-e5xW_B?Y3eVcaqC$Q~2amP~JO8>(o3T?N^iZ)RVp-9*Q^tRQR>&9$ zt$2Q76B}HTzZp4Psqe#{=~Lcd_@=;iYNqn#7nHvk;>D8&{C1Q6W8pzcZ%@on#=(QJ zWpe4n_$>XH0+z1Sf*ZW{Xi#U-0ma8|Q9jdLta}6-vr=R|bc}8OQ94mF95Yb_pzxJ6 z8V)1qT`CKL0>F9fJ5bI?G-P3ctI-rm5m3$~5f@&0>1MW=eZs%o%x<$b-nfPNv$Oo` zEi833?O34m_|tKyEs05A3i2}}eaFxl&r$P6Q@(0C6i^X)A95C#V7>R|MgCd|3wVT- z`YVKjKiXnicoCB$QEb&|@GWlCHAZ@)2s%ob+i;>$B-@8S?2H@=*2dUbj{5e z^g45-iCly>8~PAEOe{-05_kR)#FILmezWIFc8yHq4KF_sp zunfmQwD{UI+#3eUIZ!#S9kKu2>$$Z?CNXvlI_ps^@ew5#Q6Kb;#W9sg{ z$@(%n%^9`|xyB#7#YTE6sQL8qC{uTfag-P!peMVO%M;#agG%b*4n-(CSy5i z`y6~0Y49R0`@*Jo>j|}L71%7T*xVW$Pg>oZGmZDK`7ZX(#a?@oSvv#UWO5|gzRs<;F_;#=fev5j6jU^5b+a>Z* z^p==soXnwYp?g1YP{o6`T*@Z!*SE1?_gV->TcKR9q}CdmVv_P%CI5OG^XY#Cqyqo$ zxr#Q2yO%-16W>%}8NMl)&y-=S{Yn%+T*gN6)-v{PboapWu3ZNjXqJJ~Qx1yh6U1Ke z{hkiMfG|iIhSw~WMRqCw2%)}^_H_pL-_FA2msQDrK$oj zU+4gMRHBi^Ol`_sesntvlph_%zue9u`uTxwrDx##6k9ifXa-l~94F&kr|3Bv+|NRd zNMrh(_d5~Sc1Q8T|MC8$J_Jpc#XEWqtlAgyza3%$1A=pXwYkPl5^$eYcb(MvUkK(R_7rp!?@_!obG%;DJK7WrZxY8_d??f(d!?2hd{>01`? zm_IUQp=dtm(4F$wLZDM^)!Asw)h*SZ&^X6XI~s9!N`}}_MbjH4vI%>sk;K?;zS>Oe zrBYWQh;}4|k9k_%OU-?nO&lMCX=v}gR44knuy5J-ob-375kT>rE|1c$sI;AwhF{cG z&W7egq&aI1FGvi12|2!Cw{n^H0y{1|pBUcz)z1`5+vaTKPTVk1ls%t6CnuQszToC3s<%}@BmE*#UQr;70obo2jvNKv!$vUl4Eoe!~OTtW1 zo)hK_r9zl#%2r{{Q=Sm!0_AaGW-BX%nWN+i6PL~l6W@<83zccYELIYPsaK+fxj`8t z%uUKjm?ad6qovgRcoF$%Ila1y*B$g~CthhGM)^5}7@noq@5SpLdc7@P_tEPW@w%U0 zKM}80^x7z157BGAc&(w=xA0naCWPldgPY=-NRY&KIi2+BH5Y-Ed| zWGKstg2|z%w{+YDDnMJH2^VX3-RvhxmEsy}*J`GWRVi+gYpM00sm#IBsBx$hx|(&BnD*!A^4BY&IQj8d{!=9y`^_Wyuw88I7%-z_$*EY} z+P=s*l-3=hH#ZyHXr_n$?l!K-ax#66pA)*LkHclV%HU(Xd>2%-R|F#(=&aiv%Qjy( z>Fta&n<^Mx*^z)5I8+&0EAwfETaDT4?1Td)?IiIpH`9n`~yO5>)pRpqBCr0kY;uo(1e`h)&pcJxqU z(@%vm6&y{GWW&#`pXKYHV^Ikd3b?}4lrr)PX(LQc=(+IXT}`3E5qen5&5lr1=o0Rx zNQ$*GtAckxOdy@kZV#>|qrzEOyp%mpbT3uW3|jG@!Dl_srt@3RvoZd`RGVgkgT|ic zD;76ncu=wUdd1=vd;taa$|ry1kuNa+lBiTsY&4KpW}(}oF3xh7ip8lVO)2c@B&$x! zclWTOlYF6KXdZ?;xE8Y_-g_Hj81IU|x@Qv*g~xF54$fm#7|x*2+Z5%a-%zasFG3jx zE!5hQQ^*wLic9%4l$*r~&Fczugt$B+XAuqNm1=BN;7FwE)1{nr>#*528ID<6C{Yky zsSwi^r`Sf9a++GIno-q`F{?N0Y;^?1Ac*E8P0N$nppNdY2;SimG7Fan}^S^+oLDzZS-S_ae4$b+j3dn0O9x7!-4Sbg zt+KK9_%{CjUbf%4lZf~^EGJ`W!#1ABS&XCLvp0_WGk9L-s1$Bgh|V7LR}^LYN?LEP zi1ZWm0Q^Hu}AGGcdPxj z@slsH*(H?8F0Y(wcf|2WkrgicPZ>|OzXwh8l)1>~s5SV6vHo5N$%aP?`Y4T6RCNtq zW|E_F;yzj(J94*7_(+vfNrJ~l@E?1X>Ja)D9f@mu9DPk6WeW+RHkR-F9rJRqG4pPC zl^^*XbMu=AF7qO|XjyJe7G+y7XhFLJR_nKMpO@JXKd)X~ulL{zSGgwM!lsS2tl}PA z!AO3EMogINL-5{MA1?k9y|KQ28#la+!*KX=4!+Dr&0hjp=Ok#N<6aWHnx_u5&!l{k zD58ix(eKDQ^(4;V*`Ob!s|um5I2ynI6Z>(AucCSAPb6(`A|Jk=jkm#GE6?7~hRRi| z`NsV$$a(#0OJA#$tmg0RXTkm$Go&fYK+i&im(tceF1DpyHGqaT)!_jDX+L|!;b~YK zVei_{N58^67drfiPQ8Ox+wK}XqLaJy1CLOMJ-z zwo@Md62EhR<;fow^0}|F$9+C_7b6a_Z<$)yov9*oS|;+Fud?B`r_g_MO%>ZCZ}Z^? zs+gy|+=u_AijDP4L92=BC{)le=|QK410J3$yED#ZC3mW35pv}io>0v~ea+k&a7SEg zdx{F`Z~@9K%Ff`ZR2J}E)ojG9l^8?~KaF+~3m-1!SX87f3nk|;vK=8MA;5brUJdtJ zQeMJ+)x%F$%5ELLc}8f?AO?PC4*M<%*sQt z)-}8sACtLO(z5KY(v~n=R$Gb!@#HRmMY)F;C9x*j0yd@N7Ik${ECs_3Vh*u~m*M zUToX?2&al1n7gpt=+LA!4^cKHN@G|0Bom9pjd!HQ`5(oO{U*Q*_?p^WvaO+Jt zl{#KAI%~_g&v6#Q`_$nKcbawJGt7a5C<0dYzA0gD}y19@d_ITguo#-4U5 zZ$l@v+4)|}v4--x(;q-BF>&R+2XyA#>HP#L2RI6}0inkeq~*Yqz!yLRFzG~sG!FO* z@8<7LPjibYjqHltnZSpgVS^UkL3y`3Lt`_XR0wsqVXd5OTQCUfpa-bNI7Qs0?pu77 zij5f3x}BYDW7qzO?)pPs+kfS~9LS$P1FhJcGTwX!Yqdzh{WC1UUQYk6&gTNVCEGwx zlG}WWxU9D0Uw+C?^4ssQq26!(_`pOuA)!hrhBnrVC&#_`&{{Tn!1pOV>6^WHb}btb zS&rR(M3~NsBw5ai#G-|CIf_|S2Na`(}?85E5l{lFY# zCd6AeLf1U`gIauI-`Mb>N0>Li_BKoM8S}#f3_R6%)?&}rI-V0fdHg$AIC`=dUE|5u zzQbbN?Elm|8vi`PPrbuN=El>;1AT3@-ypPPmt~?!^@@({070Lw*dd8%gHlxvOt!W3 zLo50iYCt+hq5;80i9u<~f6;5~xphN~eM;k^;tl=je&jUeEgSyw5f)@izmHM(#zl65 z1qS{XRVVbtHPp<1jm9XVA=-{KSoIk=Ij_~HMB9yfitlR1;t|Y~&1|UMS#UM-swVch zJnmaQ@G~U!CG>gLSr9acvS2XJ`HZ>E&hF*-4H{(H0h?q@?u=fbF}Ptt4p+*Hg|iEy z3|-sDn~H6T255AQCep=DV)1>2EpPmc%^O7-fOl+2

f9N;B?N8oH0`CeS86AzW584WJ529zF>=OFp1~3 zm)KhB~Z+uSSO!;nN1vs&?}N# zGL^ROBB8&Zzr9hLd#yXW|UH>gUO8w|?|okrm$EzoiP>wojpEhz9u zCiX1wdRMi;U9K>vu-26rmUx#lv@@8t8YU;_R^JTJL|RcosJ z?ZYzDs8X-`nKxWv!v_-W)P4D03oSOC9c4$maCfh;N4$@1pq>zW7ltkxd=&Imc6(xE z)DKq-JM*}!EPTKrY{6L9&9N?g%T*Q?K_aodguey{1^W=DgM!j%$f3LR0-}eCZw^00 z@RJzHq$(rhc-vLx9ljen4OwPp{g)LUDyLx}X(qTE=|_g-U>CBkwLp3rX5ylw!Zf!{``bgHlDUy)^tg_4RPA|#5^25g-bunc8} z52=A-(%7_)N?BYNnvL1wI5Ry{*-|DXQo}7$uB1DO5}>=a&W$>2lYD1~hZemhPrc3@ zN<^JDxRlN8G%2+)G{&GEkWA$k+J>y?#rO5le(qA5VMAk@g+wKHw&@-qrl6kerea{4 z4x**rU`mJqUWd~BmGCAmDGXNTK)iKX+(5kQKEKYkupEpp)Kw+*Z)If@rrvGa!{L2i zfTnWW1z3&GoX6jY^NQ=02d4TCXb-ggc=ds}qpWh~?Y0FL=EN3bYBSi{TEJGE47QK2 z^8PoN_qbC;X(q}+v&Rd@nH_B(qSFzgOwQ#aW_@07U~mgC0M zda$e@mONsa&P%^!Zb>V;g>)KIJHQc594*A*WeLoZMB{e4b|6g|0TygmY`-IHgIoB` z8*K3G?=L^VkztK7wF^>i!BX!u6;jS1)fSJ}KqZf=n7{R~ePFh!LXTV)LQ5(@?H0NT zmijajx&%TiXYOo^nnPxNTh1&?I(HgVcOd*Z!k?2d^*$^~IIV`x|2I z&*^drJf0_y-yz8q;Q2H)z>b-{0=#*NB5Dq>8J2q66!<$z{wkZPR#d{PS}Tr=T2Zgo ziW=e3Rn4}S$zwQq+$;hN*w;4oO*OYLNaMLTnS1hFYdOgzXCBE3 zf*ecviCVJ*a(dL7A;eolyuV!LKip&flgaH%pB%^$=Jph^3fVR$k^?Z(&<*_`eGG*ImV747IJ04n(q7anqQ@cL1OC)9fo&XEZ7O7=iNc;7*y+ZfW zxJak?RcR$-?E_OadBvjb=k(E@v!h?B+whMB=@`AVoyG9u-L0}6D@mCrOY?v{pcr@x zcpf+e)B>l0t3W3(V7@H*1K~g%FauZu6apK7UBCgL7B~r91il933|aC6#sMin8t^Dk zoFSJ;<#^c(90pDUR{;g+1msLvasmQ?aX>2YC{P4!0Lp>Az#o94z!~5o@D1=Y;Iu%N zyn#?)A}|lo0j0n$;J|_sS$YpIXMiT470_fMl|T$I4=4h50Q-Qqf%Cw1;5$HCh+F|e zfH+_lkOQm%EKe!Q(enJMT`P^~n;^NyCrB@+CrBszLs!Z2%zY<8y408N`fuhR?JzmF zC`j+Od__^7-;5QH8&>4`>7OXh_glVtjo+%=V!sh<9hT_~MJxSAX+&sv zxWqsE9y$Sm-?QOkLdm6LxcpHlLws#p>}~*ssh|5Ti*Fc?JUOS(cC=0+`_`u%X0NARu@Tp z_Z^lSR(dx<+5tQZ>;tNR8lV=a2O5C$Koih%H-YDu1o`>yL}H?7w6G-NzyojtTmdIQ z1K0u*&@~YW0y=>X;119VGy@la2A~GmKd}Tc#)}R}0fGSypu{7UKn+k1sGuJ#2wi0Tk)r@7D2>Y^O65z_zgz=wLI9WkfLBu0j&KKpDdyk zZ_Q5Llwzc&mxvv(@9QOk_*44pKIe~TbJS+ z36j6v3qy#iBqcsK%$+yH%bH~kXwK;gNJj3#42L-bpoi)fJ(yjY$fTX;(r_)ks`Ut_ zwObwrPVmRFOE6=No=AfubNq!6dn4Qejf&=&ks4X1-!GADI@uS}VRPGhdCFk4y{Soriob zEoM3uKyZqPPlr}^ve}@%Q`{!>zoo`)h}j%wHb=sw_>F-{aV~~wseo0gf2tId&u-Cz zOD$x|CkpEVW-a80==4;XWzf4No*m#J-s>reeDx&R-KEJYfRJhbS~K=w${~b)^)Mp3 zZa%eUlpF$+bc1bREhHcZf-Dbt)>+v}Fjbu-@RD12D!-a2`vmEB_Kfh$My<$3 z+PUlmFGc!7Y9gPsR(9w6C(BNQ_JK+v6G$RS+mXsoPL>CSRUt(3L660xnAMn**b389 z&;irCE3u0!M!ObzniXAZMc<^-(K6Zc(y6kWBL&iOOiKnYnJT*r!lx&D5#DR)Mc8oa zAz>8!O7)F`Akj(@*_+I!#qRsDaOb*pZ$~UI|jG7yojKJZR7f(9Qx{;iCRIG?Y6`mHh`hVFpWuOclc$?^JO@ zW|^zjf~m4czw17##2GN>`|vGO75EmC{uT01|LBKz9+jeh9Y{&JjZ>8{07wY>;7Qy&sW z^}!s_AE3t)%ucJ)9+a6Nc>;LEhcTAk;bY>ECBe>_$8njXJ7L7E59V`1$b#Nmqn z?A~v;8aCyn7$c|i&k!f<+WkPr8s2|8jCX}$ANIP+OcC{Z<8#zB;LcpCO6s$drsN$A zL!$rR*E|;dzgqLC8!%IzCl9>KrsnHsuFf+Q=BMNquopN~H-? z3!^DU%csthhh_XmW-0Fb+hKnzq6h58)8oB_^qjw z956e4P&Z+bJVaCSTV(&X$l_PKkRkM&xb%chO2{Z&{n(0SOY_&PS-nQPR-dQUg5JYD zzleXjOm?6DTOv`6)t%oO5gLMoB9SG$K2I*COkbBT`_%2rmv1w#9K?_?>uc@%M}~Zm zWPrB$S^zY7`H&auWbHBwlXY}y$!zJ0qUEcR5c&}yhEOcet2+{;-+~5gOw$9iu3aY^ z_3`x5CCc}xi<`66;wJXl2OipCNH&sELn4=I2ahyp@@0Kfxq0yKava6bxben1O= zUp?ure^geXNWle|IZ$}~-%w(Ha0TUS>*S+-qP8J&+v=9Amk-;b#jN6*O>%-$QH4+I`CjQ{`u diff --git a/contrib/extractor/adt.cpp b/contrib/extractor/adt.cpp deleted file mode 100644 index 74293624b..000000000 --- a/contrib/extractor/adt.cpp +++ /dev/null @@ -1,380 +0,0 @@ -#define _CRT_SECURE_NO_DEPRECATE - -#ifdef WIN32 -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include "adt.h" -#include "mpq_libmpq.h" - -extern uint16 *areas; -extern uint16 *LiqType; -extern uint32 maxAreaId; - -vec wmoc; - -Cell *cell; -mcell *mcells; -int holetab_h[4] = {0x1111, 0x2222, 0x4444, 0x8888}; -int holetab_v[4] = {0x000F, 0x00F0, 0x0F00, 0xF000}; - -bool LoadADT(char* filename) -{ - size_t size; - MPQFile mf(filename); - - if(mf.isEof()) - { - //printf("No such file %s\n", filename); - return false; - } - - MapLiqFlag = new uint8[256]; - for(uint32 j = 0; j < 256; ++j) - MapLiqFlag[j] = 0; // no water - - MapLiqHeight = new float[16384]; - for(uint32 j = 0; j < 16384; ++j) - MapLiqHeight[j] = -999999; // no water - - mcells = new mcell; - - wmoc.x = 65 * TILESIZE; - wmoc.z = 65 * TILESIZE; - - size_t mcnk_offsets[256], mcnk_sizes[256]; - - chunk_num = 0; - k = 0; - m = 0; - while (!mf.isEof()) - { - uint32 fourcc; - mf.read(&fourcc, 4); - mf.read(&size, 4); - - size_t nextpos = mf.getPos() + size; - - //if(fourcc==0x4d484452) // MHDR header - //if(fourcc==0x4d564552) // MVER - if(fourcc == 0x4d43494e) // MCIN - { - for (uint32 i = 0; i < 256; ++i) - { - mf.read(&mcnk_offsets[i], 4); - mf.read(&mcnk_sizes[i], 4); - mf.seekRelative(8); - } - } - //if(fourcc == 0x4d544558) // MTEX textures (strings) - //if(fourcc == 0x4d4d4458) // MMDX m2 models (strings) - //if(fourcc == 0x4d4d4944) // MMID offsets for strings in MMDX - //if(fourcc == 0x4d574d4f) // MWMO - //if(fourcc == 0x4d574944) // MWID offsets for strings in MWMO - //if(fourcc == 0x4d444446) // MDDF - //if(fourcc == 0x4d4f4446) // MODF - if(fourcc == 0x4d48324f) // MH2O new in WotLK - { - // çäåñü íàäî çàïîìíèòü áàçîâóþ ïîçèöèþ â ôàéëå òê âñå ñìåùåíèÿ áóäóò îò íåãî - uint32 base_pos = mf.getPos(); - uint32 header_pos = 0; - MH2O_offsData *LiqOffsData = new MH2O_offsData; - MH2O_Data1 *LiqChunkData1 = new MH2O_Data1; - float *ChunkLiqHeight = new float[81]; - for(chunk_num = 0; chunk_num < 256; ++chunk_num) - { - mf.read(LiqOffsData, 0x0C); - header_pos = mf.getPos(); - if(LiqOffsData->offsData1 != 0) // åñëè äàííûå â Data1 î âîäå åñòü, òî èõ íàäî êîíâåðòèðîâàòü - { - // ïåðåõîäèì ïî ñìåùåíèþ èç offsData1 ÎÒ ÍÀ×ÀËÀ êóñêà - mf.seek(base_pos + LiqOffsData->offsData1); - mf.read(LiqChunkData1, 0x18); // ñ÷èòûâàåì ñàìè äàííûå â ñòðóêòóðó òèïà MH2O_Data1 - // çàíîñèì äàííûå ôëàãà äëÿ êóñêà - if(LiqType[LiqChunkData1->LiquidTypeId] == 0xffff) - printf("\nCan't find Liquid type for map %s\nchunk %d\n", filename, chunk_num); - else if(LiqType[LiqChunkData1->LiquidTypeId] == LIQUID_TYPE_WATER || LiqType[LiqChunkData1->LiquidTypeId] == LIQUID_TYPE_OCEAN) - MapLiqFlag[chunk_num] |= 1; // water/ocean - else if(LiqType[LiqChunkData1->LiquidTypeId] == LIQUID_TYPE_MAGMA || LiqType[LiqChunkData1->LiquidTypeId] == LIQUID_TYPE_SLIME) - MapLiqFlag[chunk_num] |= 2; // magma/slime - // ïðåäâàðèòåëüíî çàïîëíÿåì âåñü êóñîê äàííûìè - íåò âîäû - for(int j = 0; j < 81; ++j) - { - ChunkLiqHeight[j] = -999999; // no liquid/water - } - // òåïåðü âû÷èñëÿåì òå ÷òî ñ âîäîé è ïåðåçàïèñûâàåì èõ â êóñêå - for(int b = 0; b <= LiqChunkData1->height; ++b) - { - for(int c = LiqChunkData1->xOffset; c <= (LiqChunkData1->xOffset + LiqChunkData1->width); ++c) - { - int n = (9 * (LiqChunkData1->yOffset + b)) + c; - ChunkLiqHeight[n] = LiqChunkData1->heightLevel1; - } - } - mf.seek(header_pos); // è íå çàáûòü âåðíóòüñÿ íà èñõîäíóþ ïîçèöèþ èìåííî  ÕÈÄÅÐÅ - } - else // åñëè äàííûõ â Data1 íåò, òî íàäî çàïîëíèòü âåñü êóñîê, íî äàííûìè - íåò âîäû - { - for(int j = 0; j < 81; ++j) - ChunkLiqHeight[j] = -999999; // no liquid/water - } - - if(!(chunk_num % 16)) - m = 1024 * (chunk_num / 16); // ñìåùåíèå ïî ðÿäàì êóñêîâ ñ ïåðåêðûòèåì = 1024 - k = m + (chunk_num % 16) * 8; // óñòàíàâëèâàåìñÿ íà íà÷àëüíûé èíäåêñ äëÿ çàïîëíåíèÿ ðÿäà - // çàíîñèì äàííûå êóñêà â ìàññèâ äëÿ êàðòû, ñ ïåðåêðûòèåì è îáðåçàíèåì êóñêîâ òê äàííûõ 81 - // ýòî àíàëîã ñòàðîãî îáðåçàíèÿ ãðàíè÷íûõ ïðàâûõ-áîêîâûõ è íèæíèõ äàííûõ - for(int p = 0; p < 72; p += 9) // íèæíèå 8 íå çàíîñèì òê îíè äóáëèðóåòñÿ ñëåä êóñêîì - { - for(int s = 0; s < 8; ++s) // 9 çíà÷åíèå â ñòðîêå íå çàíîñèì òê îíî äóáëèðóåòñÿ ñëåä êóñêîì, à â ïðàâûõ-áîêîâûõ îáðåçàåòñÿ äëÿ 128õ128 - { - MapLiqHeight[k] = ChunkLiqHeight[p + s]; - ++k; - } - k = k + 120; - } - } - delete LiqOffsData; - delete LiqChunkData1; - delete []ChunkLiqHeight; - - } - //case 0x4d434e4b: // MCNK - //case 0x4d46424f: // MFBO new in BC - //case 0x4d545846: // MTXF new in WotLK - mf.seek(nextpos); - } - - //printf("Loading chunks info\n"); - // read individual map chunks - chunk_num = 0; - k = 0; - m = 0; - for (int j = 0; j < 16; ++j) - { - for (int i = 0; i < 16; ++i) - { - mf.seek((int)mcnk_offsets[j * 16 + i]); - LoadMapChunk(mf, &(mcells->ch[i][j])); - ++chunk_num; - } - } - mf.close(); - return true; -} - -bool isHole(int holes, int i, int j) -{ - int testi = i / 2; - int testj = j / 4; - if(testi > 3) testi = 3; - if(testj > 3) testj = 3; - return (holes & holetab_h[testi] & holetab_v[testj]) != 0; -} - -inline void LoadMapChunk(MPQFile &mf, chunk *_chunk) -{ - float h; - uint32 fourcc; - uint32 size; - MapChunkHeader header; - - mf.seekRelative(4); - mf.read(&size, 4); - - size_t lastpos = mf.getPos() + size; - mf.read(&header, 0x80); // what if header size got changed? - _chunk->area_id = header.areaid; - - float xbase = header.xpos; - float ybase = header.ypos; - float zbase = header.zpos; - zbase = TILESIZE * 32 - zbase; - xbase = TILESIZE * 32 - xbase; - if(wmoc.x > xbase) wmoc.x = xbase; - if(wmoc.z > zbase) wmoc.z = zbase; - int chunkflags = header.flags; - //printf("LMC: flags %X\n", chunkflags); - float zmin = 999999999.0f; - float zmax = -999999999.0f; - // must be there, bl!zz uses some crazy format - while (mf.getPos() < lastpos) - { - mf.read(&fourcc, 4); - mf.read(&size, 4); - size_t nextpos = mf.getPos() + size; - if(fourcc == 0x4d435654) // MCVT - { - for (int j = 0; j < 17; ++j) - { - for (int i = 0; i < ((j % 2) ? 8 : 9); ++i) - { - mf.read(&h, 4); - float z = h + ybase; - if (j % 2) - { - if(isHole(header.holes, i, j)) - _chunk->v8[i][j / 2] = -1000; - else - _chunk->v8[i][j / 2] = z; - } - else - { - if(isHole(header.holes, i, j)) - _chunk->v9[i][j / 2] = -1000; - else - _chunk->v9[i][j / 2] = z; - } - - if(z > zmax) zmax = z; - //if(z < zmin) zmin = z; - } - } - } - else if(fourcc == 0x4d434e52) // MCNR - { - nextpos = mf.getPos() + 0x1C0; // size fix - } - else if(fourcc == 0x4d434c51) // íå áóäåì ó÷èòûâàòü åñëè óæå áûëè äàííûå â MH2O, ïåðåñòðàõîâêà :) // MCLQ - { - // liquid / water level - char fcc1[5]; - mf.read(fcc1, 4); - flipcc(fcc1); - fcc1[4] = 0; - float *ChunkLiqHeight = new float[81]; - - if (!strcmp(fcc1, "MCSE")) - { - for(int j = 0; j < 81; ++j) - { - ChunkLiqHeight[j] = -999999; // no liquid/water - } - } - else - { - float maxheight; - mf.read(&maxheight, 4); - for(int j = 0; j < 81; ++j) - { - LiqData liq; - mf.read(&liq, 8); - - if(liq.height > maxheight) - ChunkLiqHeight[j] = -999999; - else - ChunkLiqHeight[j] = h; - } - - if(chunkflags & 4 || chunkflags & 8) - MapLiqFlag[chunk_num] |= 1; // water - if(chunkflags & 16) - MapLiqFlag[chunk_num] |= 2; // magma/slime - } - // çàïîëíåì òàê æå êàê â MH2O - if(!(chunk_num % 16)) - m = 1024 * (chunk_num / 16); - k = m + (chunk_num % 16) * 8; - - for(int p = 0; p < 72; p += 9) - { - for(int s = 0; s < 8; ++s) - { - MapLiqHeight[k] = ChunkLiqHeight[p + s]; - ++k; - } - k = k + 120; - } - delete []ChunkLiqHeight; - break; - } - mf.seek(nextpos); - } -} - -inline void TransformData() -{ - cell = new Cell; - - for(uint32 x = 0; x < 128; ++x) - { - for(uint32 y = 0; y < 128; ++y) - { - cell->v8[y][x] = (float)mcells->ch[x / 8][y / 8].v8[x % 8][y % 8]; - cell->v9[y][x] = (float)mcells->ch[x / 8][y / 8].v9[x % 8][y % 8]; - } - - // extra 1 point on bounds - cell->v9[128][x] = (float)mcells->ch[x / 8][15].v9[x % 8][8]; - // x == y - cell->v9[x][128] = (float)mcells->ch[15][x / 8].v9[8][x % 8]; - - } - - // and the last 1 - cell->v9[128][128] = (float)mcells->ch[15][15].v9[8][8]; - - delete mcells; -} - -const char MAP_MAGIC[] = "MAP_3.00"; - -bool ConvertADT(char *filename, char *filename2) -{ - if(!LoadADT(filename)) - return false; - - FILE *output=fopen(filename2, "wb"); - if(!output) - { - printf("Can't create the output file '%s'\n", filename2); - delete [] MapLiqHeight; - delete [] MapLiqFlag; - return false; - } - - // write magic header - fwrite(MAP_MAGIC, 1, 8, output); - - for(uint32 x = 0; x < 16; ++x) - { - for(uint32 y = 0; y < 16; ++y) - { - if(mcells->ch[y][x].area_id && mcells->ch[y][x].area_id <= maxAreaId) - { - if(areas[mcells->ch[y][x].area_id] == 0xffff) - printf("\nCan't find area flag for areaid %u.\n", mcells->ch[y][x].area_id); - - fwrite(&areas[mcells->ch[y][x].area_id], 1, 2, output); - } - else - { - uint16 flag = 0xffff; - fwrite(&flag, 1, 2, output); - } - } - } - - fwrite(MapLiqFlag, 1, 256, output); - delete [] MapLiqFlag; - - fwrite(MapLiqHeight, sizeof(float), 16384, output); - delete [] MapLiqHeight; - - TransformData(); - - fwrite(&cell->v9, 1, sizeof(cell->v9), output); - fwrite(&cell->v8, 1, sizeof(cell->v8), output); - fclose(output); - delete cell; - - return true; -} diff --git a/contrib/extractor/adt.h b/contrib/extractor/adt.h deleted file mode 100644 index 52196c4e7..000000000 --- a/contrib/extractor/adt.h +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef ADT_H -#define ADT_H - -#define TILESIZE (533.33333f) -#define CHUNKSIZE ((TILESIZE) / 16.0f) -#define UNITSIZE (CHUNKSIZE / 8.0f) - -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; -class Liquid; -typedef struct -{ - float x; - float y; - float z; -} svec; - -typedef struct -{ - double x; - double y; - double z; -} vec; - -typedef struct -{ - vec v[3]; -} triangle; - -typedef struct -{ - float v9[16 * 8 + 1][16 * 8 + 1]; - float v8[16 * 8][16 * 8]; -} Cell; - -typedef struct -{ - double v9[9][9]; - double v8[8][8]; - uint16 area_id; -} chunk; - -typedef struct -{ - chunk ch[16][16]; -} mcell; - -struct MapChunkHeader -{ - uint32 flags; - uint32 ix; - uint32 iy; - uint32 nLayers; - uint32 nDoodadRefs; - uint32 ofsHeight; - uint32 ofsNormal; - uint32 ofsLayer; - uint32 ofsRefs; - uint32 ofsAlpha; - uint32 sizeAlpha; - uint32 ofsShadow; - uint32 sizeShadow; - uint32 areaid; - uint32 nMapObjRefs; - uint32 holes; - uint16 s1; - uint16 s2; - uint32 d1; - uint32 d2; - uint32 d3; - uint32 predTex; - uint32 nEffectDoodad; - uint32 ofsSndEmitters; - uint32 nSndEmitters; - uint32 ofsLiquid; // not use in WotLK - uint32 sizeLiquid; // not use in WotLK - float zpos; - float xpos; - float ypos; - uint32 textureId; // new offsColorValues in WotLK - uint32 props; - uint32 effectId; -}; - -typedef struct -{ - uint32 offsData1; - uint32 used; - uint32 offsData2; -} MH2O_offsData; - -typedef struct -{ - uint16 LiquidTypeId; - uint16 type; - float heightLevel1; - float heightLevel2; - uint8 xOffset; - uint8 yOffset; - uint8 width; - uint8 height; - uint32 ofsData2a; - uint32 ofsData2b; -} MH2O_Data1; - -typedef struct -{ - uint16 unk1; - uint16 unk2; - float height; -} LiqData; - -enum LiquidType -{ - LIQUID_TYPE_WATER = 0, - LIQUID_TYPE_OCEAN = 1, - LIQUID_TYPE_MAGMA = 2, - LIQUID_TYPE_SLIME = 3 -}; - -class MPQFile; - -float *MapLiqHeight; -uint8 *MapLiqFlag; -uint32 k, m, chunk_num; -void LoadMapChunk(MPQFile &, chunk*); -#endif diff --git a/contrib/extractor/loadlib/CMakeLists.txt b/contrib/extractor/loadlib/CMakeLists.txt new file mode 100644 index 000000000..5680c61d4 --- /dev/null +++ b/contrib/extractor/loadlib/CMakeLists.txt @@ -0,0 +1,13 @@ +# Copyright (C) 2005-2009 MaNGOS project +# +# This file is free software; as a special exception the author gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +add_library (loadlib loadlib.cpp adt.cpp wdt.cpp) +# link loadlib with zlib +target_link_libraries (loadlib z) diff --git a/contrib/extractor/loadlib/adt.cpp b/contrib/extractor/loadlib/adt.cpp new file mode 100644 index 000000000..fde706811 --- /dev/null +++ b/contrib/extractor/loadlib/adt.cpp @@ -0,0 +1,131 @@ +#define _CRT_SECURE_NO_DEPRECATE + +#include "adt.h" + +// Helper +int holetab_h[4] = {0x1111, 0x2222, 0x4444, 0x8888}; +int holetab_v[4] = {0x000F, 0x00F0, 0x0F00, 0xF000}; + +bool isHole(int holes, int i, int j) +{ + int testi = i / 2; + int testj = j / 4; + if(testi > 3) testi = 3; + if(testj > 3) testj = 3; + return (holes & holetab_h[testi] & holetab_v[testj]) != 0; +} + +// +// Adt file loader class +// +ADT_file::ADT_file() +{ + a_grid = 0; +} + +ADT_file::~ADT_file() +{ + free(); +} + +void ADT_file::free() +{ + a_grid = 0; + FileLoader::free(); +} + +// +// Adt file check function +// +bool ADT_file::prepareLoadedData() +{ + // Check parent + if (!FileLoader::prepareLoadedData()) + return false; + + // Check and prepare MHDR + a_grid = (adt_MHDR *)(GetData()+8+version->size); + if (!a_grid->prepareLoadedData()) + return false; + + return true; +} + +bool adt_MHDR::prepareLoadedData() +{ + if (fcc != 'MHDR') + return false; + + if (size!=sizeof(adt_MHDR)-8) + return false; + + // Check and prepare MCIN + if (offsMCIN && !getMCIN()->prepareLoadedData()) + return false; + + // Check and prepare MH2O + if (offsMH2O && !getMH2O()->prepareLoadedData()) + return false; + + return true; +} + +bool adt_MCIN::prepareLoadedData() +{ + if (fcc != 'MCIN') + return false; + + // Check cells data + for (int i=0; iprepareLoadedData()) + return false; + + return true; +} + +bool adt_MH2O::prepareLoadedData() +{ + if (fcc != 'MH2O') + return false; + + // Check liquid data +// for (int i=0; iprepareLoadedData()) + return false; + // Check liquid data + if (offsMCLQ && !getMCLQ()->prepareLoadedData()) + return false; + + return true; +} + +bool adt_MCVT::prepareLoadedData() +{ + if (fcc != 'MCVT') + return false; + + if (size != sizeof(adt_MCVT)-8) + return false; + + return true; +} + +bool adt_MCLQ::prepareLoadedData() +{ + if (fcc != 'MCLQ') + return false; + + return true; +} \ No newline at end of file diff --git a/contrib/extractor/loadlib/adt.h b/contrib/extractor/loadlib/adt.h new file mode 100644 index 000000000..725c5b994 --- /dev/null +++ b/contrib/extractor/loadlib/adt.h @@ -0,0 +1,289 @@ +#ifndef ADT_H +#define ADT_H + +#include "loadlib.h" + +#define TILESIZE (533.33333f) +#define CHUNKSIZE ((TILESIZE) / 16.0f) +#define UNITSIZE (CHUNKSIZE / 8.0f) + +enum LiquidType +{ + LIQUID_TYPE_WATER = 0, + LIQUID_TYPE_OCEAN = 1, + LIQUID_TYPE_MAGMA = 2, + LIQUID_TYPE_SLIME = 3 +}; + +//************************************************************************************** +// ADT file class +//************************************************************************************** +#define ADT_CELLS_PER_GRID 16 +#define ADT_CELL_SIZE 8 +#define ADT_GRID_SIZE (ADT_CELLS_PER_GRID*ADT_CELL_SIZE) + +// +// Adt file height map chunk +// +class adt_MCVT +{ + union{ + uint32 fcc; + char fcc_txt[4]; + }; + uint32 size; +public: + float height_map[(ADT_CELL_SIZE+1)*(ADT_CELL_SIZE+1)+ADT_CELL_SIZE*ADT_CELL_SIZE]; + + bool prepareLoadedData(); +}; + +// +// Adt file liquid map chunk (old) +// +class adt_MCLQ +{ + union{ + uint32 fcc; + char fcc_txt[4]; + }; + uint32 size; +public: + float height1; + float height2; + struct liquid_data{ + uint32 light; + float height; + } liquid[ADT_CELL_SIZE+1][ADT_CELL_SIZE+1]; + + // 1<<0 - ochen + // 1<<1 - lava/slime + // 1<<2 - water + // 1<<6 - all water + // 1<<7 - dark water + // == 0x0F - not show liquid + uint8 flags[ADT_CELL_SIZE][ADT_CELL_SIZE]; + uint8 data[84]; + bool prepareLoadedData(); +}; + +// +// Adt file cell chunk +// +class adt_MCNK +{ + union{ + uint32 fcc; + char fcc_txt[4]; + }; + uint32 size; +public: + uint32 flags; + uint32 ix; + uint32 iy; + uint32 nLayers; + uint32 nDoodadRefs; + uint32 offsMCVT; // height map + uint32 offsMCNR; // Normal vectors for each vertex + uint32 offsMCLY; // Texture layer definitions + uint32 offsMCRF; // A list of indices into the parent file's MDDF chunk + uint32 offsMCAL; // Alpha maps for additional texture layers + uint32 sizeMCAL; + uint32 offsMCSH; // Shadow map for static shadows on the terrain + uint32 sizeMCSH; + uint32 areaid; + uint32 nMapObjRefs; + uint32 holes; + uint16 s[2]; + uint32 data1; + uint32 data2; + uint32 data3; + uint32 predTex; + uint32 nEffectDoodad; + uint32 offsMCSE; + uint32 nSndEmitters; + uint32 offsMCLQ; // Liqid level (old) + uint32 sizeMCLQ; // + float zpos; + float xpos; + float ypos; + uint32 offsMCCV; // offsColorValues in WotLK + uint32 props; + uint32 effectId; + + bool prepareLoadedData(); + adt_MCVT *getMCVT() + { + if (offsMCVT) + return (adt_MCVT *)((uint8 *)this + offsMCVT); + return 0; + } + adt_MCLQ *getMCLQ() + { + if (offsMCLQ) + return (adt_MCLQ *)((uint8 *)this + offsMCLQ); + return 0; + } +}; + +// +// Adt file grid chunk +// +class adt_MCIN +{ + union{ + uint32 fcc; + char fcc_txt[4]; + }; + uint32 size; +public: + struct adt_CELLS{ + uint32 offsMCNK; + uint32 size; + uint32 flags; + uint32 asyncId; + } cells[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]; + + bool prepareLoadedData(); + // offset from begin file (used this-84) + adt_MCNK *getMCNK(int x, int y) + { + if (cells[x][y].offsMCNK) + return (adt_MCNK *)((uint8 *)this + cells[x][y].offsMCNK - 84); + return 0; + } +}; + +#define ADT_LIQUID_HEADER_FULL_LIGHT 0x01 +#define ADT_LIQUID_HEADER_NO_HIGHT 0x02 + +struct adt_liquid_header{ + uint16 liquidType; // Index from LiquidType.dbc + uint16 formatFlags; + float heightLevel1; + float heightLevel2; + uint8 xOffset; + uint8 yOffset; + uint8 width; + uint8 height; + uint32 offsData2a; + uint32 offsData2b; +}; + +// +// Adt file liquid data chunk (new) +// +class adt_MH2O +{ +public: + union{ + uint32 fcc; + char fcc_txt[4]; + }; + uint32 size; + + struct adt_LIQUID{ + uint32 offsData1; + uint32 used; + uint32 offsData2; + } liquid[ADT_CELLS_PER_GRID][ADT_CELLS_PER_GRID]; + + bool prepareLoadedData(); + + adt_liquid_header *getLiquidData(int x, int y) + { + if (liquid[x][y].used && liquid[x][y].offsData1) + return (adt_liquid_header *)((uint8*)this + 8 + liquid[x][y].offsData1); + return 0; + } + + float *getLiquidHeightMap(adt_liquid_header *h) + { + if (h->formatFlags & ADT_LIQUID_HEADER_NO_HIGHT) + return 0; + if (h->offsData2b) + return (float *)((uint8*)this + 8 + h->offsData2b); + return 0; + } + + uint8 *getLiquidLightMap(adt_liquid_header *h) + { + if (h->formatFlags&ADT_LIQUID_HEADER_FULL_LIGHT) + return 0; + if (h->offsData2b) + { + if (h->formatFlags & ADT_LIQUID_HEADER_NO_HIGHT) + return (uint8 *)((uint8*)this + 8 + h->offsData2b); + return (uint8 *)((uint8*)this + 8 + h->offsData2b + (h->width+1)*(h->height+1)*4); + } + return 0; + } + + uint32 *getLiquidFullLightMap(adt_liquid_header *h) + { + if (!(h->formatFlags&ADT_LIQUID_HEADER_FULL_LIGHT)) + return 0; + if (h->offsData2b) + { + if (h->formatFlags & ADT_LIQUID_HEADER_NO_HIGHT) + return (uint32 *)((uint8*)this + 8 + h->offsData2b); + return (uint32 *)((uint8*)this + 8 + h->offsData2b + (h->width+1)*(h->height+1)*4); + } + return 0; + } + + uint64 getLiquidShowMap(adt_liquid_header *h) + { + if (h->offsData2a) + return *((uint64 *)((uint8*)this + 8 + h->offsData2a)); + else + return 0xFFFFFFFFFFFFFFFFLL; + } + +}; + +// +// Adt file header chunk +// +class adt_MHDR +{ + union{ + uint32 fcc; + char fcc_txt[4]; + }; + uint32 size; + + uint32 pad; + uint32 offsMCIN; // MCIN + uint32 offsTex; // MTEX + uint32 offsModels; // MMDX + uint32 offsModelsIds; // MMID + uint32 offsMapObejcts; // MWMO + uint32 offsMapObejctsIds; // MWID + uint32 offsDoodsDef; // MDDF + uint32 offsObjectsDef; // MODF + uint32 offsMFBO; // MFBO + uint32 offsMH2O; // MH2O + uint32 data1; + uint32 data2; + uint32 data3; + uint32 data4; + uint32 data5; +public: + bool prepareLoadedData(); + adt_MCIN *getMCIN(){ return (adt_MCIN *)((uint8 *)&pad+offsMCIN);} + adt_MH2O *getMH2O(){ return offsMH2O ? (adt_MH2O *)((uint8 *)&pad+offsMH2O) : 0;} + +}; + +class ADT_file : public FileLoader{ +public: + bool prepareLoadedData(); + ADT_file(); + ~ADT_file(); + void free(); + + adt_MHDR *a_grid; +}; + +#endif diff --git a/contrib/extractor/loadlib/loadlib.cpp b/contrib/extractor/loadlib/loadlib.cpp new file mode 100644 index 000000000..2ff045ed7 --- /dev/null +++ b/contrib/extractor/loadlib/loadlib.cpp @@ -0,0 +1,63 @@ +#define _CRT_SECURE_NO_DEPRECATE + +#include "loadlib.h" +#include "../mpq_libmpq.h" + +class MPQFile; + +FileLoader::FileLoader() +{ + data = 0; + data_size = 0; + version = 0; +} + +FileLoader::~FileLoader() +{ + free(); +} + +bool FileLoader::loadFile(char *filename) +{ + free(); + MPQFile mf(filename); + if(mf.isEof()) + { + printf("No such file %s\n", filename); + return false; + } + + data_size = mf.getSize(); + + data = new uint8 [data_size]; + if (data) + { + mf.read(data, data_size); + mf.close(); + if (prepareLoadedData()) + return true; + } + printf("Error loading %s", filename); + mf.close(); + free(); + return false; +} + +bool FileLoader::prepareLoadedData() +{ + // Check version + version = (file_MVER *) data; + if (version->fcc != 'MVER') + return false; + if (version->ver != FILE_FORMAT_VERSION) + return false; + return true; +} + +void FileLoader::free() +{ + if (data) delete[] data; + data = 0; + data_size = 0; + version = 0; +} \ No newline at end of file diff --git a/contrib/extractor/loadlib/loadlib.h b/contrib/extractor/loadlib/loadlib.h new file mode 100644 index 000000000..7f38768cf --- /dev/null +++ b/contrib/extractor/loadlib/loadlib.h @@ -0,0 +1,57 @@ +#ifndef LOAD_LIB_H +#define LOAD_LIB_H + +#ifdef WIN32 +typedef __int64 int64; +typedef long int32; +typedef short int16; +typedef char int8; +typedef unsigned __int64 uint64; +typedef unsigned long uint32; +typedef unsigned short uint16; +typedef unsigned char uint8; +#else +#include +#ifndef uint64_t +#include +#endif +typedef int64_t int64; +typedef long int32; +typedef short int16; +typedef char int8; +typedef uint64_t uint64; +typedef unsigned long uint32; +typedef unsigned short uint16; +typedef unsigned char uint8; +#endif + +#define FILE_FORMAT_VERSION 18 + +// +// File version chunk +// +struct file_MVER +{ + union{ + uint32 fcc; + char fcc_txt[4]; + }; + uint32 size; + uint32 ver; +}; + +class FileLoader{ + uint8 *data; + uint32 data_size; +public: + virtual bool prepareLoadedData(); + uint8 *GetData() {return data;} + uint32 GetDataSize() {return data_size;} + + file_MVER *version; + FileLoader(); + ~FileLoader(); + bool loadFile(char *filename); + virtual void free(); +}; +#endif diff --git a/contrib/extractor/loadlib/wdt.cpp b/contrib/extractor/loadlib/wdt.cpp new file mode 100644 index 000000000..dedefbb64 --- /dev/null +++ b/contrib/extractor/loadlib/wdt.cpp @@ -0,0 +1,62 @@ +#define _CRT_SECURE_NO_DEPRECATE + +#include "wdt.h" + +bool wdt_MWMO::prepareLoadedData() +{ + if (fcc != 'MWMO') + return false; + return true; +} + +bool wdt_MPHD::prepareLoadedData() +{ + if (fcc != 'MPHD') + return false; + return true; +} + +bool wdt_MAIN::prepareLoadedData() +{ + if (fcc != 'MAIN') + return false; + return true; +} + +WDT_file::WDT_file() +{ + mphd = 0; + main = 0; + wmo = 0; +} + +WDT_file::~WDT_file() +{ + free(); +} + +void WDT_file::free() +{ + mphd = 0; + main = 0; + wmo = 0; + FileLoader::free(); +} + +bool WDT_file::prepareLoadedData() +{ + // Check parent + if (!FileLoader::prepareLoadedData()) + return false; + + mphd = (wdt_MPHD *)((uint8*)version+version->size+8); + if (!mphd->prepareLoadedData()) + return false; + main = (wdt_MAIN *)((uint8*)mphd + mphd->size+8); + if (!main->prepareLoadedData()) + return false; + wmo = (wdt_MWMO *)((uint8*)main+ main->size+8); + if (!wmo->prepareLoadedData()) + return false; + return true; +} \ No newline at end of file diff --git a/contrib/extractor/loadlib/wdt.h b/contrib/extractor/loadlib/wdt.h new file mode 100644 index 000000000..fcee8ac64 --- /dev/null +++ b/contrib/extractor/loadlib/wdt.h @@ -0,0 +1,68 @@ +#ifndef WDT_H +#define WDT_H +#include "loadlib.h" + +//************************************************************************************** +// WDT file class and structures +//************************************************************************************** +#define WDT_MAP_SIZE 64 + +class wdt_MWMO{ + union{ + uint32 fcc; + char fcc_txt[4]; + }; +public: + uint32 size; + bool prepareLoadedData(); +}; + +class wdt_MPHD{ + union{ + uint32 fcc; + char fcc_txt[4]; + }; +public: + uint32 size; + + uint32 data1; + uint32 data2; + uint32 data3; + uint32 data4; + uint32 data5; + uint32 data6; + uint32 data7; + uint32 data8; + bool prepareLoadedData(); +}; + +class wdt_MAIN{ + union{ + uint32 fcc; + char fcc_txt[4]; + }; +public: + uint32 size; + + struct adtData{ + uint32 exist; + uint32 data1; + } adt_list[64][64]; + + bool prepareLoadedData(); +}; + +class WDT_file : public FileLoader{ +public: + bool prepareLoadedData(); + + WDT_file(); + ~WDT_file(); + void free(); + + wdt_MPHD *mphd; + wdt_MAIN *main; + wdt_MWMO *wmo; +}; + +#endif \ No newline at end of file diff --git a/contrib/extractor/mpq_libmpq.h b/contrib/extractor/mpq_libmpq.h index 9b4984a50..7b5258e6a 100644 --- a/contrib/extractor/mpq_libmpq.h +++ b/contrib/extractor/mpq_libmpq.h @@ -4,6 +4,7 @@ #ifndef MPQ_H #define MPQ_H +#include "loadlib/loadlib.h" #include "libmpq/mpq.h" #include #include @@ -13,7 +14,6 @@ using namespace std; -typedef unsigned int uint32; class MPQArchive { diff --git a/src/game/Map.cpp b/src/game/Map.cpp index d4134f710..da32317e5 100644 --- a/src/game/Map.cpp +++ b/src/game/Map.cpp @@ -41,9 +41,6 @@ #define DEFAULT_GRID_EXPIRY 300 #define MAX_GRID_LOAD_TIME 50 -// magic *.map header -const char MAP_MAGIC[] = "MAP_3.00"; - GridState* si_GridStates[MAX_GRID_STATE]; Map::~Map() @@ -66,9 +63,9 @@ bool Map::ExistMap(uint32 mapid,int x,int y) return false; } - char magic[8]; - fread(magic,1,8,pf); - if(strncmp(MAP_MAGIC,magic,8)) + map_fileheader header; + fread(&header, sizeof(header), 1, pf); + if (header.mapMagic != MAP_MAGIC || header.versionMagic != MAP_VERSION_MAGIC) { sLog.outError("Map file '%s' is non-compatible version (outdated?). Please, create new using ad.exe program.",tmp); delete [] tmp; @@ -78,7 +75,6 @@ bool Map::ExistMap(uint32 mapid,int x,int y) delete [] tmp; fclose(pf); - return true; } @@ -157,29 +153,13 @@ void Map::LoadMap(uint32 mapid, uint32 instanceid, int x,int y) snprintf(tmp, len, (char *)(sWorld.GetDataPath()+"maps/%03u%02u%02u.map").c_str(),mapid,x,y); sLog.outDetail("Loading map %s",tmp); // loading data - FILE *pf=fopen(tmp,"rb"); - if(!pf) + GridMaps[x][y] = new GridMap(); + if (!GridMaps[x][y]->loadData(tmp)) { - delete [] tmp; - return; + sLog.outError("Error load map file: \n %s\n", tmp); } - - char magic[8]; - fread(magic,1,8,pf); - if(strncmp(MAP_MAGIC,magic,8)) - { - sLog.outError("Map file '%s' is non-compatible version (outdated?). Please, create new using ad.exe program.",tmp); - delete [] tmp; - fclose(pf); //close file before return - return; - } - delete [] tmp; - - GridMap * buf= new GridMap; - fread(buf,1,sizeof(GridMap),pf); - fclose(pf); - - GridMaps[x][y] = buf; + delete [] tmp; + return; } void Map::LoadMapAndVMap(uint32 mapid, uint32 instanceid, int x,int y) @@ -1031,7 +1011,11 @@ bool Map::UnloadGrid(const uint32 &x, const uint32 &y, bool pForce) { if (i_InstanceId == 0) { - if(GridMaps[gx][gy]) delete (GridMaps[gx][gy]); + if(GridMaps[gx][gy]) + { + GridMaps[gx][gy]->unloadData(); + delete GridMaps[gx][gy]; + } // x and y are swapped VMAP::VMapFactory::createOrGetVMapManager()->unloadMap(GetId(), gy, gx); } @@ -1056,94 +1040,527 @@ void Map::UnloadAll(bool pForce) } } -float Map::GetHeight(float x, float y, float z, bool pUseVmaps) const +//***************************** +// Grid function +//***************************** +GridMap::GridMap() { - GridPair p = MaNGOS::ComputeGridPair(x, y); + m_flags = 0; + // Area data + m_gridArea = 0; + m_area_map = NULL; + // Height level data + m_gridHeight = INVALID_HEIGHT; + m_gridGetHeight = &GridMap::getHeightFromFlat; + m_V9 = NULL; + m_V8 = NULL; + // Liquid data + m_liquidType = 0; + m_liquid_offX = 0; + m_liquid_offY = 0; + m_liquid_width = 0; + m_liquid_height = 0; + m_liquidLevel = INVALID_HEIGHT; + m_liquid_type = NULL; + m_liquid_map = NULL; +} +GridMap::~GridMap() +{ + unloadData(); +} + +bool GridMap::loadData(char *filename) +{ + // Unload old data if exist + unloadData(); + + map_fileheader header; + // Not return error if file not found + FILE *in = fopen(filename, "rb"); + if (!in) + return true; + fread(&header, sizeof(header),1,in); + if (header.mapMagic == MAP_MAGIC && header.versionMagic == MAP_VERSION_MAGIC) + { + // loadup area data + if (header.areaMapOffset && !loadAreaData(in, header.areaMapOffset, header.areaMapSize)) + { + sLog.outError("Error loading map area data\n"); + fclose(in); + return false; + } + // loadup height data + if (header.heightMapOffset && !loadHeihgtData(in, header.heightMapOffset, header.heightMapSize)) + { + sLog.outError("Error loading map height data\n"); + fclose(in); + return false; + } + // loadup liquid data + if (header.liquidMapOffset && !loadLiquidData(in, header.liquidMapOffset, header.liquidMapSize)) + { + sLog.outError("Error loading map liquids data\n"); + fclose(in); + return false; + } + fclose(in); + return true; + } + sLog.outError("Map file '%s' is non-compatible version (outdated?). Please, create new using ad.exe program.", filename); + fclose(in); + return false; +} + +void GridMap::unloadData() +{ + if (m_area_map) delete[] m_area_map; + if (m_V9) delete[] m_V9; + if (m_V8) delete[] m_V8; + if (m_liquid_type) delete[] m_liquid_type; + if (m_liquid_map) delete[] m_liquid_map; + m_area_map = NULL; + m_V9 = NULL; + m_V8 = NULL; + m_liquid_type = NULL; + m_liquid_map = NULL; + m_gridGetHeight = &GridMap::getHeightFromFlat; +} + +bool GridMap::loadAreaData(FILE *in, uint32 offset, uint32 size) +{ + map_areaHeader header; + fseek(in, offset, SEEK_SET); + fread(&header, sizeof(header), 1, in); + if (header.fourcc != MAP_AREA_MAGIC) + return false; + + m_gridArea = header.gridArea; + if (!(header.flags&MAP_AREA_NO_AREA)) + { + m_area_map = new uint16 [16*16]; + fread(m_area_map, sizeof(uint16), 16*16, in); + } + return true; +} + +bool GridMap::loadHeihgtData(FILE *in, uint32 offset, uint32 size) +{ + map_heightHeader header; + fseek(in, offset, SEEK_SET); + fread(&header, sizeof(header), 1, in); + if (header.fourcc != MAP_HEIGTH_MAGIC) + return false; + + m_gridHeight = header.gridHeight; + if (!(header.flags&MAP_HEIGHT_NO_HIGHT)) + { + if ((header.flags&MAP_HEIGHT_AS_INT16)) + { + m_uint16_V9 = new uint16 [129*129]; + m_uint16_V8 = new uint16 [128*128]; + fread(m_uint16_V9, sizeof(uint16), 129*129, in); + fread(m_uint16_V8, sizeof(uint16), 128*128, in); + m_gridIntHeightMultiplier = (header.gridMaxHeight - header.gridHeight) / 65535; + m_gridGetHeight = &GridMap::getHeightFromUint16; + } + else if ((header.flags&MAP_HEIGHT_AS_INT8)) + { + m_uint8_V9 = new uint8 [129*129]; + m_uint8_V8 = new uint8 [128*128]; + fread(m_uint8_V9, sizeof(uint8), 129*129, in); + fread(m_uint8_V8, sizeof(uint8), 128*128, in); + m_gridIntHeightMultiplier = (header.gridMaxHeight - header.gridHeight) / 255; + m_gridGetHeight = &GridMap::getHeightFromUint8; + } + else + { + m_V9 = new float [129*129]; + m_V8 = new float [128*128]; + fread(m_V9, sizeof(float), 129*129, in); + fread(m_V8, sizeof(float), 128*128, in); + m_gridGetHeight = &GridMap::getHeightFromFloat; + } + } + else + m_gridGetHeight = &GridMap::getHeightFromFlat; + return true; +} + +bool GridMap::loadLiquidData(FILE *in, uint32 offset, uint32 size) +{ + map_liquidHeader header; + fseek(in, offset, SEEK_SET); + fread(&header, sizeof(header), 1, in); + if (header.fourcc != MAP_LIQUID_MAGIC) + return false; + + m_liquidType = header.liquidType; + m_liquid_offX = header.offsetX; + m_liquid_offY = header.offsetY; + m_liquid_width = header.width; + m_liquid_height= header.height; + m_liquidLevel = header.liquidLevel; + + if (!(header.flags&MAP_LIQUID_NO_TYPE)) + { + m_liquid_type = new uint8 [16*16]; + fread(m_liquid_type, sizeof(uint8), 16*16, in); + } + if (!(header.flags&MAP_LIQUID_NO_HIGHT)) + { + m_liquid_map = new float [m_liquid_width*m_liquid_height]; + fread(m_liquid_map, sizeof(float), m_liquid_width*m_liquid_height, in); + } + return true; +} + +uint16 GridMap::getArea(float x, float y) +{ + if (!m_area_map) + return m_gridArea; + + x = 16 * (32 - x/SIZE_OF_GRIDS); + y = 16 * (32 - y/SIZE_OF_GRIDS); + int lx = (int)x & 15; + int ly = (int)y & 15; + return m_area_map[lx*16 + ly]; +} + +float GridMap::getHeightFromFlat(float x, float y) const +{ + return m_gridHeight; +} + +float GridMap::getHeightFromFloat(float x, float y) const +{ + if (!m_V8 || !m_V9) + return m_gridHeight; + + x = MAP_RESOLUTION * (32 - x/SIZE_OF_GRIDS); + y = MAP_RESOLUTION * (32 - y/SIZE_OF_GRIDS); + + int x_int = (int)x; + int y_int = (int)y; + x -= x_int; + y -= y_int; + x_int&=(MAP_RESOLUTION - 1); + y_int&=(MAP_RESOLUTION - 1); + + // Height stored as: h5 - its v8 grid, h1-h4 - its v9 grid + // +--------------> X + // | h1-------h2 Coordinates is: + // | | \ 1 / | h1 0,0 + // | | \ / | h2 0,1 + // | | 2 h5 3 | h3 1,0 + // | | / \ | h4 1,1 + // | | / 4 \ | h5 1/2,1/2 + // | h3-------h4 + // V Y + // For find height need + // 1 - detect triangle + // 2 - solve linear equation from triangle points + // Calculate coefficients for solve h = a*x + b*y + c + + float a,b,c; + // Select triangle: + if (x+y < 1) + { + if (x > y) + { + // 1 triangle (h1, h2, h5 points) + float h1 = m_V9[(x_int )*129 + y_int]; + float h2 = m_V9[(x_int+1)*129 + y_int]; + float h5 = 2 * m_V8[x_int*128 + y_int]; + a = h2-h1; + b = h5-h1-h2; + c = h1; + } + else + { + // 2 triangle (h1, h3, h5 points) + float h1 = m_V9[x_int*129 + y_int ]; + float h3 = m_V9[x_int*129 + y_int+1]; + float h5 = 2 * m_V8[x_int*128 + y_int]; + a = h5 - h1 - h3; + b = h3 - h1; + c = h1; + } + } + else + { + if (x > y) + { + // 3 triangle (h2, h4, h5 points) + float h2 = m_V9[(x_int+1)*129 + y_int ]; + float h4 = m_V9[(x_int+1)*129 + y_int+1]; + float h5 = 2 * m_V8[x_int*128 + y_int]; + a = h2 + h4 - h5; + b = h4 - h2; + c = h5 - h4; + } + else + { + // 4 triangle (h3, h4, h5 points) + float h3 = m_V9[(x_int )*129 + y_int+1]; + float h4 = m_V9[(x_int+1)*129 + y_int+1]; + float h5 = 2 * m_V8[x_int*128 + y_int]; + a = h4 - h3; + b = h3 + h4 - h5; + c = h5 - h4; + } + } + // Calculate height + return a * x + b * y + c; +} + +float GridMap::getHeightFromUint8(float x, float y) const +{ + if (!m_uint8_V8 || !m_uint8_V9) + return m_gridHeight; + + x = MAP_RESOLUTION * (32 - x/SIZE_OF_GRIDS); + y = MAP_RESOLUTION * (32 - y/SIZE_OF_GRIDS); + + int x_int = (int)x; + int y_int = (int)y; + x -= x_int; + y -= y_int; + x_int&=(MAP_RESOLUTION - 1); + y_int&=(MAP_RESOLUTION - 1); + + int32 a, b, c; + uint8 *V9_h1_ptr = &m_uint8_V9[x_int*128 + x_int + y_int]; + if (x+y < 1) + { + if (x > y) + { + // 1 triangle (h1, h2, h5 points) + int32 h1 = V9_h1_ptr[ 0]; + int32 h2 = V9_h1_ptr[129]; + int32 h5 = 2 * m_uint8_V8[x_int*128 + y_int]; + a = h2-h1; + b = h5-h1-h2; + c = h1; + } + else + { + // 2 triangle (h1, h3, h5 points) + int32 h1 = V9_h1_ptr[0]; + int32 h3 = V9_h1_ptr[1]; + int32 h5 = 2 * m_uint8_V8[x_int*128 + y_int]; + a = h5 - h1 - h3; + b = h3 - h1; + c = h1; + } + } + else + { + if (x > y) + { + // 3 triangle (h2, h4, h5 points) + int32 h2 = V9_h1_ptr[129]; + int32 h4 = V9_h1_ptr[130]; + int32 h5 = 2 * m_uint8_V8[x_int*128 + y_int]; + a = h2 + h4 - h5; + b = h4 - h2; + c = h5 - h4; + } + else + { + // 4 triangle (h3, h4, h5 points) + int32 h3 = V9_h1_ptr[ 1]; + int32 h4 = V9_h1_ptr[130]; + int32 h5 = 2 * m_uint8_V8[x_int*128 + y_int]; + a = h4 - h3; + b = h3 + h4 - h5; + c = h5 - h4; + } + } + // Calculate height + return (float)((a * x) + (b * y) + c)*m_gridIntHeightMultiplier + m_gridHeight; +} + +float GridMap::getHeightFromUint16(float x, float y) const +{ + if (!m_uint16_V8 || !m_uint16_V9) + return m_gridHeight; + + x = MAP_RESOLUTION * (32 - x/SIZE_OF_GRIDS); + y = MAP_RESOLUTION * (32 - y/SIZE_OF_GRIDS); + + int x_int = (int)x; + int y_int = (int)y; + x -= x_int; + y -= y_int; + x_int&=(MAP_RESOLUTION - 1); + y_int&=(MAP_RESOLUTION - 1); + + int32 a, b, c; + uint16 *V9_h1_ptr = &m_uint16_V9[x_int*128 + x_int + y_int]; + if (x+y < 1) + { + if (x > y) + { + // 1 triangle (h1, h2, h5 points) + int32 h1 = V9_h1_ptr[ 0]; + int32 h2 = V9_h1_ptr[129]; + int32 h5 = 2 * m_uint16_V8[x_int*128 + y_int]; + a = h2-h1; + b = h5-h1-h2; + c = h1; + } + else + { + // 2 triangle (h1, h3, h5 points) + int32 h1 = V9_h1_ptr[0]; + int32 h3 = V9_h1_ptr[1]; + int32 h5 = 2 * m_uint16_V8[x_int*128 + y_int]; + a = h5 - h1 - h3; + b = h3 - h1; + c = h1; + } + } + else + { + if (x > y) + { + // 3 triangle (h2, h4, h5 points) + int32 h2 = V9_h1_ptr[129]; + int32 h4 = V9_h1_ptr[130]; + int32 h5 = 2 * m_uint16_V8[x_int*128 + y_int]; + a = h2 + h4 - h5; + b = h4 - h2; + c = h5 - h4; + } + else + { + // 4 triangle (h3, h4, h5 points) + int32 h3 = V9_h1_ptr[ 1]; + int32 h4 = V9_h1_ptr[130]; + int32 h5 = 2 * m_uint16_V8[x_int*128 + y_int]; + a = h4 - h3; + b = h3 + h4 - h5; + c = h5 - h4; + } + } + // Calculate height + return (float)((a * x) + (b * y) + c)*m_gridIntHeightMultiplier + m_gridHeight; +} + +float GridMap::getLiquidLevel(float x, float y) +{ + if (!m_liquid_map) + return m_liquidLevel; + + x = MAP_RESOLUTION * (32 - x/SIZE_OF_GRIDS); + y = MAP_RESOLUTION * (32 - y/SIZE_OF_GRIDS); + + int cx_int = ((int)x & (MAP_RESOLUTION-1)) - m_liquid_offY; + int cy_int = ((int)y & (MAP_RESOLUTION-1)) - m_liquid_offX; + + if (cx_int < 0 || cx_int >=m_liquid_height) + return INVALID_HEIGHT; + if (cy_int < 0 || cy_int >=m_liquid_width ) + return INVALID_HEIGHT; + + return m_liquid_map[cx_int*m_liquid_width + cy_int]; +} + +uint8 GridMap::getTerrainType(float x, float y) +{ + if (!m_liquid_type) + return m_liquidType; + + x = 16 * (32 - x/SIZE_OF_GRIDS); + y = 16 * (32 - y/SIZE_OF_GRIDS); + int lx = (int)x & 15; + int ly = (int)y & 15; + return m_liquid_type[lx*16 + ly]; +} + +// Get water state on map +inline ZLiquidStatus GridMap::getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData *data) +{ + // Check water type (if no water return) + if (!m_liquid_type && !m_liquidType) + return LIQUID_MAP_NO_WATER; + + // Get cell + float cx = MAP_RESOLUTION * (32 - x/SIZE_OF_GRIDS); + float cy = MAP_RESOLUTION * (32 - y/SIZE_OF_GRIDS); + + int x_int = (int)cx & (MAP_RESOLUTION-1); + int y_int = (int)cy & (MAP_RESOLUTION-1); + + // Check water type in cell + uint8 type = m_liquid_type ? m_liquid_type[(x_int>>3)*16 + (y_int>>3)] : m_liquidType; + if (type == 0) + return LIQUID_MAP_NO_WATER; + + // Check req liquid type mask + if (ReqLiquidType && !(ReqLiquidType&type)) + return LIQUID_MAP_NO_WATER; + + // Check water level: + // Check water height map + int lx_int = x_int - m_liquid_offY; + int ly_int = y_int - m_liquid_offX; + if (lx_int < 0 || lx_int >=m_liquid_height) + return LIQUID_MAP_NO_WATER; + if (ly_int < 0 || ly_int >=m_liquid_width ) + return LIQUID_MAP_NO_WATER; + + // Get water level + float liquid_level = m_liquid_map ? m_liquid_map[lx_int*m_liquid_width + ly_int] : m_liquidLevel; + // Get ground level (sub 0.2 for fix some errors) + float ground_level = getHeight(x, y); + + // Check water level and ground level + if (liquid_level < ground_level || z < ground_level - 2) + return LIQUID_MAP_NO_WATER; + + // All ok in water -> store data + if (data) + { + data->type = type; + data->level = liquid_level; + data->depth_level = ground_level; + } + + // For speed check as int values + int delta = (liquid_level - z) * 10; + + // Get position delta + if (delta > 20) // Under water + return LIQUID_MAP_UNDER_WATER; + if (delta > 0 ) // In water + return LIQUID_MAP_IN_WATER; + if (delta > -1) // Walk on water + return LIQUID_MAP_WATER_WALK; + // Above water + return LIQUID_MAP_ABOVE_WATER; +} + +inline GridMap *Map::GetGrid(float x, float y) +{ // half opt method int gx=(int)(32-x/SIZE_OF_GRIDS); //grid x int gy=(int)(32-y/SIZE_OF_GRIDS); //grid y - float lx=MAP_RESOLUTION*(32 -x/SIZE_OF_GRIDS - gx); - float ly=MAP_RESOLUTION*(32 -y/SIZE_OF_GRIDS - gy); - // ensure GridMap is loaded - const_cast(this)->EnsureGridCreated(GridPair(63-gx,63-gy)); + EnsureGridCreated(GridPair(63-gx,63-gy)); + return GridMaps[gx][gy]; +} + +float Map::GetHeight(float x, float y, float z, bool pUseVmaps) const +{ // find raw .map surface under Z coordinates float mapHeight; - if(GridMap* gmap = GridMaps[gx][gy]) + if(GridMap *gmap = const_cast(this)->GetGrid(x, y)) { - int lx_int = (int)lx; - int ly_int = (int)ly; - lx -= lx_int; - ly -= ly_int; - - // Height stored as: h5 - its v8 grid, h1-h4 - its v9 grid - // +--------------> X - // | h1-------h2 Coordinates is: - // | | \ 1 / | h1 0,0 - // | | \ / | h2 0,1 - // | | 2 h5 3 | h3 1,0 - // | | / \ | h4 1,1 - // | | / 4 \ | h5 1/2,1/2 - // | h3-------h4 - // V Y - // For find height need - // 1 - detect triangle - // 2 - solve linear equation from triangle points - - // Calculate coefficients for solve h = a*x + b*y + c - float a,b,c; - // Select triangle: - if (lx+ly < 1) - { - if (lx > ly) - { - // 1 triangle (h1, h2, h5 points) - float h1 = gmap->v9[lx_int][ly_int]; - float h2 = gmap->v9[lx_int+1][ly_int]; - float h5 = 2 * gmap->v8[lx_int][ly_int]; - a = h2-h1; - b = h5-h1-h2; - c = h1; - } - else - { - // 2 triangle (h1, h3, h5 points) - float h1 = gmap->v9[lx_int][ly_int]; - float h3 = gmap->v9[lx_int][ly_int+1]; - float h5 = 2 * gmap->v8[lx_int][ly_int]; - a = h5 - h1 - h3; - b = h3 - h1; - c = h1; - } - } - else - { - if (lx > ly) - { - // 3 triangle (h2, h4, h5 points) - float h2 = gmap->v9[lx_int+1][ly_int]; - float h4 = gmap->v9[lx_int+1][ly_int+1]; - float h5 = 2 * gmap->v8[lx_int][ly_int]; - a = h2 + h4 - h5; - b = h4 - h2; - c = h5 - h4; - } - else - { - // 4 triangle (h3, h4, h5 points) - float h3 = gmap->v9[lx_int][ly_int+1]; - float h4 = gmap->v9[lx_int+1][ly_int+1]; - float h5 = 2 * gmap->v8[lx_int][ly_int]; - a = h4 - h3; - b = h3 + h4 - h5; - c = h5 - h4; - } - } - // Calculate height - float _mapheight = a * lx + b * ly + c; + float _mapheight = gmap->getHeight(x,y); // look from a bit higher pos to find the floor, ignore under surface case if(z + 2.0f > _mapheight) @@ -1202,25 +1619,9 @@ float Map::GetHeight(float x, float y, float z, bool pUseVmaps) const uint16 Map::GetAreaFlag(float x, float y, float z) const { - //local x,y coords - float lx,ly; - int gx,gy; - GridPair p = MaNGOS::ComputeGridPair(x, y); - - // half opt method - gx=(int)(32-x/SIZE_OF_GRIDS) ; //grid x - gy=(int)(32-y/SIZE_OF_GRIDS); //grid y - - lx=16*(32 -x/SIZE_OF_GRIDS - gx); - ly=16*(32 -y/SIZE_OF_GRIDS - gy); - //DEBUG_LOG("my %d %d si %d %d",gx,gy,p.x_coord,p.y_coord); - - // ensure GridMap is loaded - const_cast(this)->EnsureGridCreated(GridPair(63-gx,63-gy)); - uint16 areaflag; - if(GridMaps[gx][gy]) - areaflag = GridMaps[gx][gy]->area_flag[(int)(lx)][(int)(ly)]; + if(GridMap *gmap = const_cast(this)->GetGrid(x, y)) + areaflag = gmap->getArea(x, y); // this used while not all *.map files generated (instances) else areaflag = GetAreaFlagByMapId(i_id); @@ -1251,44 +1652,24 @@ uint16 Map::GetAreaFlag(float x, float y, float z) const uint8 Map::GetTerrainType(float x, float y ) const { - //local x,y coords - float lx,ly; - int gx,gy; - - // half opt method - gx=(int)(32-x/SIZE_OF_GRIDS) ; //grid x - gy=(int)(32-y/SIZE_OF_GRIDS); //grid y - - lx=16*(32 -x/SIZE_OF_GRIDS - gx); - ly=16*(32 -y/SIZE_OF_GRIDS - gy); - - // ensure GridMap is loaded - const_cast(this)->EnsureGridCreated(GridPair(63-gx,63-gy)); - - if(GridMaps[gx][gy]) - return GridMaps[gx][gy]->terrain_type[(int)(lx)][(int)(ly)]; + if(GridMap *gmap = const_cast(this)->GetGrid(x, y)) + return gmap->getTerrainType(x, y); else return 0; } +ZLiquidStatus Map::getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData *data) const +{ + if(GridMap* gmap = const_cast(this)->GetGrid(x, y)) + return gmap->getLiquidStatus(x, y, z, ReqLiquidType, data); + else + return LIQUID_MAP_NO_WATER; +} + float Map::GetWaterLevel(float x, float y ) const { - //local x,y coords - float lx,ly; - int gx,gy; - - // half opt method - gx=(int)(32-x/SIZE_OF_GRIDS) ; //grid x - gy=(int)(32-y/SIZE_OF_GRIDS); //grid y - - lx=128*(32 -x/SIZE_OF_GRIDS - gx); - ly=128*(32 -y/SIZE_OF_GRIDS - gy); - - // ensure GridMap is loaded - const_cast(this)->EnsureGridCreated(GridPair(63-gx,63-gy)); - - if(GridMaps[gx][gy]) - return GridMaps[gx][gy]->liquid_level[(int)(lx)][(int)(ly)]; + if(GridMap* gmap = const_cast(this)->GetGrid(x, y)) + return gmap->getLiquidLevel(x, y); else return 0; } @@ -1315,24 +1696,27 @@ uint32 Map::GetZoneId(uint16 areaflag,uint32 map_id) bool Map::IsInWater(float x, float y, float pZ) const { - // This method is called too often to use vamps for that (4. parameter = false). - // The pZ pos is taken anyway for future use - float z = GetHeight(x,y,pZ,false); // use .map base surface height - - // underground or instance without vmap - if(z <= INVALID_HEIGHT) - return false; - - float water_z = GetWaterLevel(x,y); - uint8 flag = GetTerrainType(x,y); - return (z < (water_z-2)) && (flag & 0x01); + // Check surface in x, y point for liquid + if (GridMap* gmap = const_cast(this)->GetGrid(x, y)) + { + LiquidData liquid_status; + if (getLiquidStatus(x, y, pZ, MAP_ALL_LIQUIDS, &liquid_status)) + { + if (liquid_status.level - liquid_status.depth_level > 2) + return true; + } + } + return false; } bool Map::IsUnderWater(float x, float y, float z) const { - float water_z = GetWaterLevel(x,y); - uint8 flag = GetTerrainType(x,y); - return (z < (water_z-2)) && (flag & 0x01); + if (GridMap* gmap = const_cast(this)->GetGrid(x, y)) + { + if (getLiquidStatus(x, y, z, MAP_LIQUID_TYPE_WATER|MAP_LIQUID_TYPE_OCEAN)&LIQUID_MAP_UNDER_WATER) + return true; + } + return false; } bool Map::CheckGridIntegrity(Creature* c, bool moved) const diff --git a/src/game/Map.h b/src/game/Map.h index 80f042674..8019534ed 100644 --- a/src/game/Map.h +++ b/src/game/Map.h @@ -68,14 +68,135 @@ typedef RGuard GridReadGuard; typedef WGuard GridWriteGuard; typedef MaNGOS::SingleThreaded::Lock NullGuard; -typedef struct +//****************************************** +// Map file format defines +//****************************************** +#define MAP_MAGIC 'SPAM' +#define MAP_VERSION_MAGIC '0.1v' +#define MAP_AREA_MAGIC 'AERA' +#define MAP_HEIGTH_MAGIC 'TGHM' +#define MAP_LIQUID_MAGIC 'QILM' + +struct map_fileheader{ + uint32 mapMagic; + uint32 versionMagic; + uint32 areaMapOffset; + uint32 areaMapSize; + uint32 heightMapOffset; + uint32 heightMapSize; + uint32 liquidMapOffset; + uint32 liquidMapSize; +}; + +#define MAP_AREA_NO_AREA 0x0001 +struct map_areaHeader{ + uint32 fourcc; + uint16 flags; + uint16 gridArea; +}; + +#define MAP_HEIGHT_NO_HIGHT 0x0001 +#define MAP_HEIGHT_AS_INT16 0x0002 +#define MAP_HEIGHT_AS_INT8 0x0004 + +struct map_heightHeader{ + uint32 fourcc; + uint32 flags; + float gridHeight; + float gridMaxHeight; +}; + +#define MAP_LIQUID_NO_TYPE 0x0001 +#define MAP_LIQUID_NO_HIGHT 0x0002 +struct map_liquidHeader{ + uint32 fourcc; + uint16 flags; + uint16 liquidType; + uint8 offsetX; + uint8 offsetY; + uint8 width; + uint8 height; + float liquidLevel; +}; + +enum ZLiquidStatus{ + LIQUID_MAP_NO_WATER = 0x00000000, + LIQUID_MAP_ABOVE_WATER = 0x00000001, + LIQUID_MAP_WATER_WALK = 0x00000002, + LIQUID_MAP_IN_WATER = 0x00000004, + LIQUID_MAP_UNDER_WATER = 0x00000008 +}; + +#define MAP_LIQUID_TYPE_NO_WATER 0x00 +#define MAP_LIQUID_TYPE_WATER 0x01 +#define MAP_LIQUID_TYPE_OCEAN 0x02 +#define MAP_LIQUID_TYPE_MAGMA 0x04 +#define MAP_LIQUID_TYPE_SLIME 0x08 + +#define MAP_ALL_LIQUIDS (MAP_LIQUID_TYPE_WATER | MAP_LIQUID_TYPE_OCEAN | MAP_LIQUID_TYPE_MAGMA | MAP_LIQUID_TYPE_SLIME) + +#define MAP_LIQUID_TYPE_DARK_WATER 0x10 +#define MAP_LIQUID_TYPE_WMO_WATER 0x20 + +struct LiquidData{ + uint32 type; + float level; + float depth_level; +}; + +class GridMap { - uint16 area_flag[16][16]; - uint8 terrain_type[16][16]; - float liquid_level[128][128]; - float v9[MAP_RESOLUTION + 1][MAP_RESOLUTION + 1]; - float v8[MAP_RESOLUTION][MAP_RESOLUTION]; -}GridMap; + uint32 m_flags; + // Area data + uint16 m_gridArea; + uint16 *m_area_map; + // Height level data + float m_gridHeight; + float m_gridIntHeightMultiplier; + union{ + float *m_V9; + uint16 *m_uint16_V9; + uint8 *m_uint8_V9; + }; + union{ + float *m_V8; + uint16 *m_uint16_V8; + uint8 *m_uint8_V8; + }; + // Liquid data + uint16 m_liquidType; + uint8 m_liquid_offX; + uint8 m_liquid_offY; + uint8 m_liquid_width; + uint8 m_liquid_height; + float m_liquidLevel; + uint8 *m_liquid_type; + float *m_liquid_map; + + bool loadAreaData(FILE *in, uint32 offset, uint32 size); + bool loadHeihgtData(FILE *in, uint32 offset, uint32 size); + bool loadLiquidData(FILE *in, uint32 offset, uint32 size); + + // Get height functions and pointers + typedef float (GridMap::*pGetHeightPtr) (float x, float y) const; + pGetHeightPtr m_gridGetHeight; + float getHeightFromFloat(float x, float y) const; + float getHeightFromUint16(float x, float y) const; + float getHeightFromUint8(float x, float y) const; + float getHeightFromFlat(float x, float y) const; + +public: + GridMap(); + ~GridMap(); + bool loadData(char *filaname); + void unloadData(); + + uint16 getArea(float x, float y); + inline float getHeight(float x, float y) {return (this->*m_gridGetHeight)(x, y);} + float getLiquidLevel(float x, float y); + uint8 getTerrainType(float x, float y); + ZLiquidStatus getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData *data = 0); +}; struct CreatureMover { @@ -190,6 +311,8 @@ class MANGOS_DLL_SPEC Map : public GridRefManager, public MaNGOS::Obj float GetHeight(float x, float y, float z, bool pCheckVMap=true) const; bool IsInWater(float x, float y, float z) const; // does not use z pos. This is for future use + ZLiquidStatus getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData *data = 0) const; + uint16 GetAreaFlag(float x, float y, float z) const; uint8 GetTerrainType(float x, float y ) const; float GetWaterLevel(float x, float y ) const; @@ -277,6 +400,7 @@ class MANGOS_DLL_SPEC Map : public GridRefManager, public MaNGOS::Obj private: void LoadVMap(int pX, int pY); void LoadMap(uint32 mapid, uint32 instanceid, int x,int y); + GridMap *GetGrid(float x, float y); void SetTimer(uint32 t) { i_gridExpiry = t < MIN_GRID_DELAY ? MIN_GRID_DELAY : t; } //uint64 CalculateGridMask(const uint32 &y) const; diff --git a/src/game/Player.cpp b/src/game/Player.cpp index bf6fba258..af71a306d 100644 --- a/src/game/Player.cpp +++ b/src/game/Player.cpp @@ -351,8 +351,11 @@ Player::Player (WorldSession *session): Unit(), m_achievementMgr(this) m_regenTimer = 0; m_weaponChangeTimer = 0; - m_breathTimer = 0; - m_isunderwater = UNDERWATER_NONE; + for (int i=0; iSendPacket(&data); -} - -void Player::ModifyMirrorTimer(MirrorTimerType Type, uint32 MaxValue, uint32 CurrentValue, uint32 Regen) -{ - if(Type==BREATH_TIMER) - m_breathTimer = ((MaxValue + 1*IN_MILISECONDS) - CurrentValue) / Regen; - + if (MaxValue == DISABLED_MIRROR_TIMER) + { + if (CurrentValue!=DISABLED_MIRROR_TIMER) + StopMirrorTimer(Type); + return; + } WorldPacket data(SMSG_START_MIRROR_TIMER, (21)); data << (uint32)Type; data << CurrentValue; @@ -828,9 +820,7 @@ void Player::ModifyMirrorTimer(MirrorTimerType Type, uint32 MaxValue, uint32 Cur void Player::StopMirrorTimer(MirrorTimerType Type) { - if(Type==BREATH_TIMER) - m_breathTimer = 0; - + m_MirrorTimer[Type] = DISABLED_MIRROR_TIMER; WorldPacket data(SMSG_STOP_MIRROR_TIMER, 4); data << (uint32)Type; GetSession()->SendPacket( &data ); @@ -841,12 +831,22 @@ void Player::EnvironmentalDamage(uint64 guid, EnviromentalDamage type, uint32 da if(!isAlive() || isGameMaster()) return; + // Absorb, resist some environmental damage type + uint32 absorb = 0; + uint32 resist = 0; + if (type == DAMAGE_LAVA) + CalcAbsorbResist(this, SPELL_SCHOOL_MASK_FIRE, DIRECT_DAMAGE, damage, &absorb, &resist); + else if (type == DAMAGE_SLIME) + CalcAbsorbResist(this, SPELL_SCHOOL_MASK_NATURE, DIRECT_DAMAGE, damage, &absorb, &resist); + + damage-=absorb+resist; + WorldPacket data(SMSG_ENVIRONMENTALDAMAGELOG, (21)); data << (uint64)guid; data << (uint8)(type!=DAMAGE_FALL_TO_VOID ? type : DAMAGE_FALL); data << (uint32)damage; - data << (uint32)0; - data << (uint32)0; + data << (uint32)absorb; // absorb + data << (uint32)resist; // resist SendMessageToSet(&data, true); DealDamage(this, damage, NULL, SELF_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, false); @@ -866,96 +866,153 @@ void Player::EnvironmentalDamage(uint64 guid, EnviromentalDamage type, uint32 da } } -void Player::HandleDrowning() +int32 Player::getMaxTimer(MirrorTimerType timer) { - if(!(m_isunderwater&~UNDERWATER_INLAVA)) - return; - - //if player is GM, have waterbreath, is dead or if breathing is disabled then return - if(isGameMaster() || !isAlive() || HasAuraType(SPELL_AURA_WATER_BREATHING) || GetSession()->GetSecurity() >= sWorld.getConfig(CONFIG_DISABLE_BREATHING)) + switch (timer) { - StopMirrorTimer(BREATH_TIMER); - // drop every flag _except_ LAVA - otherwise waterbreathing will prevent lava damage - m_isunderwater &= UNDERWATER_INLAVA; - return; - } - - uint32 UnderWaterTime = 3*MINUTE*IN_MILISECONDS; // default duration - - AuraList const& mModWaterBreathing = GetAurasByType(SPELL_AURA_MOD_WATER_BREATHING); - for(AuraList::const_iterator i = mModWaterBreathing.begin(); i != mModWaterBreathing.end(); ++i) - UnderWaterTime = uint32(UnderWaterTime * (100.0f + (*i)->GetModifier()->m_amount) / 100.0f); - - if ((m_isunderwater & UNDERWATER_INWATER) && !(m_isunderwater & UNDERWATER_INLAVA) && isAlive()) - { - //single trigger timer - if (!(m_isunderwater & UNDERWATER_WATER_TRIGGER)) + case FATIGUE_TIMER: + return MINUTE*IN_MILISECONDS; + case BREATH_TIMER: { - m_isunderwater|= UNDERWATER_WATER_TRIGGER; - m_breathTimer = UnderWaterTime + 1*IN_MILISECONDS; + if (!isAlive() || HasAuraType(SPELL_AURA_WATER_BREATHING) || GetSession()->GetSecurity() >= sWorld.getConfig(CONFIG_DISABLE_BREATHING)) + return DISABLED_MIRROR_TIMER; + int32 UnderWaterTime = 3*MINUTE*IN_MILISECONDS; + AuraList const& mModWaterBreathing = GetAurasByType(SPELL_AURA_MOD_WATER_BREATHING); + for(AuraList::const_iterator i = mModWaterBreathing.begin(); i != mModWaterBreathing.end(); ++i) + UnderWaterTime = uint32(UnderWaterTime * (100.0f + (*i)->GetModifier()->m_amount) / 100.0f); + return UnderWaterTime; } - //single trigger "show Breathbar" - if ( m_breathTimer <= UnderWaterTime && !(m_isunderwater & UNDERWATER_WATER_BREATHB)) + case FIRE_TIMER: { - m_isunderwater|= UNDERWATER_WATER_BREATHB; - StartMirrorTimer(BREATH_TIMER, UnderWaterTime); - } - //continuous trigger drowning "Damage" - if ((m_breathTimer == 0) && (m_isunderwater & UNDERWATER_INWATER)) - { - //TODO: Check this formula - uint64 guid = GetGUID(); - uint32 damage = GetMaxHealth() / 5 + urand(0, getLevel()-1); - - EnvironmentalDamage(guid, DAMAGE_DROWNING,damage); - m_breathTimer = 2000; + if (!isAlive()) + return DISABLED_MIRROR_TIMER; + return 1*IN_MILISECONDS; } + default: + return 0; } - //single trigger retract bar - else if (!(m_isunderwater & UNDERWATER_INWATER) && (m_isunderwater & UNDERWATER_WATER_TRIGGER) && (m_breathTimer > 0) && isAlive()) - { - uint32 BreathRegen = 10; - // m_breathTimer will be reduced in ModifyMirrorTimer - ModifyMirrorTimer(BREATH_TIMER, UnderWaterTime, m_breathTimer,BreathRegen); - m_isunderwater = UNDERWATER_WATER_BREATHB_RETRACTING; - } - //remove bar - else if ((m_breathTimer < 50) && !(m_isunderwater & UNDERWATER_INWATER) && (m_isunderwater == UNDERWATER_WATER_BREATHB_RETRACTING)) - { - StopMirrorTimer(BREATH_TIMER); - m_isunderwater = UNDERWATER_NONE; - } + return 0; } -void Player::HandleLava() +void Player::UpdateMirrorTimers() { - if ((m_isunderwater & UNDERWATER_INLAVA) && isAlive()) + // Desync flags for update on next HandleDrowning + if (m_MirrorTimerFlags) + m_MirrorTimerFlagsLast = ~m_MirrorTimerFlags; +} + +void Player::HandleDrowning(uint32 time_diff) +{ + if (!m_MirrorTimerFlags) + return; + + // In water + if (m_MirrorTimerFlags & UNDERWATER_INWATER) { - /* - * arrai: how is this supposed to work? UNDERWATER_INLAVA is always set in this scope! - // Single trigger Set BreathTimer - if (!(m_isunderwater & UNDERWATER_INLAVA)) + // Breath timer not activated - activate it + if (m_MirrorTimer[BREATH_TIMER] == DISABLED_MIRROR_TIMER) { - m_isunderwater|= UNDERWATER_WATER_BREATHB; - m_breathTimer = 1*IN_MILISECONDS; + m_MirrorTimer[BREATH_TIMER] = getMaxTimer(BREATH_TIMER); + SendMirrorTimer(BREATH_TIMER, m_MirrorTimer[BREATH_TIMER], m_MirrorTimer[BREATH_TIMER], -1); } - */ - // Reset BreathTimer and still in the lava - if (!m_breathTimer) + else // If activated - do tick { - uint64 guid = GetGUID(); - uint32 damage = urand(600, 700); // TODO: Get more detailed information about lava damage - - EnvironmentalDamage(guid, DAMAGE_LAVA, damage); - - m_breathTimer = 1*IN_MILISECONDS; + m_MirrorTimer[BREATH_TIMER]-=time_diff; + // Timer limit - need deal damage + if (m_MirrorTimer[BREATH_TIMER] < 0) + { + m_MirrorTimer[BREATH_TIMER]+= 1*IN_MILISECONDS; + // Calculate and deal damage + // TODO: Check this formula + uint32 damage = GetMaxHealth() / 5 + urand(0, getLevel()-1); + EnvironmentalDamage(GetGUID(), DAMAGE_DROWNING, damage); + } + else if (!(m_MirrorTimerFlagsLast & UNDERWATER_INWATER)) // Update time in client if need + SendMirrorTimer(BREATH_TIMER, getMaxTimer(BREATH_TIMER), m_MirrorTimer[BREATH_TIMER], -1); } } - else if (!isAlive()) // Disable breath timer and reset underwater flags + else if (m_MirrorTimer[BREATH_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer { - m_breathTimer = 0; - m_isunderwater = UNDERWATER_NONE; + int32 UnderWaterTime = getMaxTimer(BREATH_TIMER); + // Need breath regen + m_MirrorTimer[BREATH_TIMER]+=10*time_diff; + if (m_MirrorTimer[BREATH_TIMER] >= UnderWaterTime || !isAlive()) + StopMirrorTimer(BREATH_TIMER); + else if (m_MirrorTimerFlagsLast & UNDERWATER_INWATER) + SendMirrorTimer(BREATH_TIMER, UnderWaterTime, m_MirrorTimer[BREATH_TIMER], 10); } + + // In dark water + if (m_MirrorTimerFlags & UNDERWARER_INDARKWATER) + { + // Fatigue timer not activated - activate it + if (m_MirrorTimer[FATIGUE_TIMER] == DISABLED_MIRROR_TIMER) + { + m_MirrorTimer[FATIGUE_TIMER] = getMaxTimer(FATIGUE_TIMER); + SendMirrorTimer(FATIGUE_TIMER, m_MirrorTimer[FATIGUE_TIMER], m_MirrorTimer[FATIGUE_TIMER], -1); + } + else + { + m_MirrorTimer[FATIGUE_TIMER]-=time_diff; + // Timer limit - need deal damage or teleport ghost to graveyard + if (m_MirrorTimer[FATIGUE_TIMER] < 0) + { + m_MirrorTimer[FATIGUE_TIMER]+= 1*IN_MILISECONDS; + if (isAlive()) // Calculate and deal damage + { + uint32 damage = GetMaxHealth() / 5 + urand(0, getLevel()-1); + EnvironmentalDamage(GetGUID(), DAMAGE_EXHAUSTED, damage); + } + else if (HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST)) // Teleport ghost to graveyard + RepopAtGraveyard(); + } + else if (!(m_MirrorTimerFlagsLast & UNDERWARER_INDARKWATER)) + SendMirrorTimer(FATIGUE_TIMER, getMaxTimer(FATIGUE_TIMER), m_MirrorTimer[FATIGUE_TIMER], -1); + } + } + else if (m_MirrorTimer[FATIGUE_TIMER] != DISABLED_MIRROR_TIMER) // Regen timer + { + int32 DarkWaterTime = getMaxTimer(FATIGUE_TIMER); + m_MirrorTimer[FATIGUE_TIMER]+=10*time_diff; + if (m_MirrorTimer[FATIGUE_TIMER] >= DarkWaterTime || !isAlive()) + StopMirrorTimer(FATIGUE_TIMER); + else if (m_MirrorTimerFlagsLast & UNDERWARER_INDARKWATER) + SendMirrorTimer(FATIGUE_TIMER, DarkWaterTime, m_MirrorTimer[FATIGUE_TIMER], 10); + } + + if (m_MirrorTimerFlags & (UNDERWATER_INLAVA|UNDERWATER_INSLIME)) + { + // Breath timer not activated - activate it + if (m_MirrorTimer[FIRE_TIMER] == DISABLED_MIRROR_TIMER) + m_MirrorTimer[FIRE_TIMER] = getMaxTimer(FIRE_TIMER); + else + { + m_MirrorTimer[FIRE_TIMER]-=time_diff; + if (m_MirrorTimer[FIRE_TIMER] < 0) + { + m_MirrorTimer[FIRE_TIMER]+= 1*IN_MILISECONDS; + // Calculate and deal damage + // TODO: Check this formula + uint32 damage = urand(600, 700); + if (m_MirrorTimerFlags&UNDERWATER_INLAVA) + EnvironmentalDamage(GetGUID(), DAMAGE_LAVA, damage); + else + EnvironmentalDamage(GetGUID(), DAMAGE_SLIME, damage); + } + } + } + else + m_MirrorTimer[FIRE_TIMER] = DISABLED_MIRROR_TIMER; + + // Recheck timers flag + m_MirrorTimerFlags&=~UNDERWATER_EXIST_TIMERS; + for (int i = 0; i< MAX_TIMERS; ++i) + if (m_MirrorTimer[i]!=DISABLED_MIRROR_TIMER) + { + m_MirrorTimerFlags|=UNDERWATER_EXIST_TIMERS; + break; + } + m_MirrorTimerFlagsLast = m_MirrorTimerFlags; } ///The player sobers by 256 every 10 seconds @@ -1227,21 +1284,8 @@ void Player::Update( uint32 p_time ) } } - //Breathtimer - if(m_breathTimer > 0) - { - if(p_time >= m_breathTimer) - m_breathTimer = 0; - else - m_breathTimer -= p_time; - - } - //Handle Water/drowning - HandleDrowning(); - - //Handle lava - HandleLava(); + HandleDrowning(p_time); //Handle detect stealth players if (m_DetectInvTimer > 0) @@ -19352,21 +19396,48 @@ PartyResult Player::CanUninviteFromGroup() const void Player::UpdateUnderwaterState( Map* m, float x, float y, float z ) { - float water_z = m->GetWaterLevel(x,y); - float terrain_z = m->GetHeight(x,y,z, false); // use .map base surface height - uint8 flag1 = m->GetTerrainType(x,y); + LiquidData liquid_status; + ZLiquidStatus res = m->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &liquid_status); + if (!res) + { + m_MirrorTimerFlags &= ~(UNDERWATER_INWATER|UNDERWATER_INLAVA|UNDERWATER_INSLIME|UNDERWARER_INDARKWATER); + // Small hack for enable breath in WMO + if (IsInWater()) + m_MirrorTimerFlags|=UNDERWATER_INWATER; + return; + } - //!Underwater check, not in water if underground or above water level - take UC royal quater for example - if (terrain_z <= INVALID_HEIGHT || z < (terrain_z-2) || z > (water_z - 2) ) - m_isunderwater &= ~UNDERWATER_INWATER; - else if ((z < (water_z - 2)) && (flag1 & 0x01)) - m_isunderwater |= UNDERWATER_INWATER; + // All liquids type - check under water position + if (liquid_status.type&(MAP_LIQUID_TYPE_WATER|MAP_LIQUID_TYPE_OCEAN|MAP_LIQUID_TYPE_MAGMA|MAP_LIQUID_TYPE_SLIME)) + { + if ( res & LIQUID_MAP_UNDER_WATER) + m_MirrorTimerFlags |= UNDERWATER_INWATER; + else + m_MirrorTimerFlags &= ~UNDERWATER_INWATER; + } - //!in lava check, anywhere under lava level - if ((terrain_z <= INVALID_HEIGHT || z < (terrain_z - 0)) && (flag1 == 0x00) && IsInWater()) - m_isunderwater |= UNDERWATER_INLAVA; + // Allow travel in dark water on taxi or transport + if (liquid_status.type & MAP_LIQUID_TYPE_DARK_WATER && !isInFlight() && !(GetUnitMovementFlags()&MOVEMENTFLAG_ONTRANSPORT)) + m_MirrorTimerFlags |= UNDERWARER_INDARKWATER; else - m_isunderwater &= ~UNDERWATER_INLAVA; + m_MirrorTimerFlags &= ~UNDERWARER_INDARKWATER; + + // in lava check, anywhere in lava level + if (liquid_status.type&MAP_LIQUID_TYPE_MAGMA) + { + if (res & (LIQUID_MAP_UNDER_WATER|LIQUID_MAP_IN_WATER|LIQUID_MAP_WATER_WALK)) + m_MirrorTimerFlags |= UNDERWATER_INLAVA; + else + m_MirrorTimerFlags &= ~UNDERWATER_INLAVA; + } + // in slime check, anywhere in slime level + if (liquid_status.type&MAP_LIQUID_TYPE_SLIME) + { + if (res & (LIQUID_MAP_UNDER_WATER|LIQUID_MAP_IN_WATER|LIQUID_MAP_WATER_WALK)) + m_MirrorTimerFlags |= UNDERWATER_INSLIME; + else + m_MirrorTimerFlags &= ~UNDERWATER_INSLIME; + } } void Player::SetCanParry( bool value ) diff --git a/src/game/Player.h b/src/game/Player.h index 3dc26cd79..63681ada0 100644 --- a/src/game/Player.h +++ b/src/game/Player.h @@ -69,10 +69,11 @@ enum PlayerUnderwaterState { UNDERWATER_NONE = 0x00, UNDERWATER_INWATER = 0x01, // terrain type is water and player is afflicted by it - UNDERWATER_WATER_TRIGGER = 0x02, // m_breathTimer has been initialized - UNDERWATER_WATER_BREATHB = 0x04, // breathbar has been send to client - UNDERWATER_WATER_BREATHB_RETRACTING = 0x10, // breathbar is currently refilling - the player is above water level - UNDERWATER_INLAVA = 0x80 // terrain type is lava and player is afflicted by it + UNDERWATER_INLAVA = 0x02, // terrain type is lava and player is afflicted by it + UNDERWATER_INSLIME = 0x04, // terrain type is lava and player is afflicted by it + UNDERWARER_INDARKWATER = 0x08, // terrain type is dark water and player is afflicted by it + + UNDERWATER_EXIST_TIMERS = 0x10 }; enum PlayerSpellState @@ -500,6 +501,8 @@ enum MirrorTimerType BREATH_TIMER = 1, FIRE_TIMER = 2 }; +#define MAX_TIMERS 3 +#define DISABLED_MIRROR_TIMER -1 // 2^n values enum PlayerExtraFlags @@ -1709,6 +1712,7 @@ class MANGOS_DLL_SPEC Player : public Unit uint32 DurabilityRepairAll(bool cost, float discountMod, bool guildBank); uint32 DurabilityRepair(uint16 pos, bool cost, float discountMod, bool guildBank); + void UpdateMirrorTimers(); void StopMirrorTimers() { StopMirrorTimer(FATIGUE_TIMER); @@ -2020,8 +2024,6 @@ class MANGOS_DLL_SPEC Player : public Unit bool IsFlying() const { return HasUnitMovementFlag(MOVEMENTFLAG_FLYING); } bool IsAllowUseFlyMountsHere() const; - void HandleDrowning(); - void SetClientControl(Unit* target, uint8 allowMove); void EnterVehicle(Vehicle *vehicle); @@ -2236,12 +2238,14 @@ class MANGOS_DLL_SPEC Player : public Unit /*********************************************************/ /*** ENVIRONMENTAL SYSTEM ***/ /*********************************************************/ - void HandleLava(); void HandleSobering(); - void StartMirrorTimer(MirrorTimerType Type, uint32 MaxValue); - void ModifyMirrorTimer(MirrorTimerType Type, uint32 MaxValue, uint32 CurrentValue, uint32 Regen); + void SendMirrorTimer(MirrorTimerType Type, uint32 MaxValue, uint32 CurrentValue, int32 Regen); void StopMirrorTimer(MirrorTimerType Type); - uint8 m_isunderwater; + void HandleDrowning(uint32 time_diff); + int32 getMaxTimer(MirrorTimerType timer); + int32 m_MirrorTimer[MAX_TIMERS]; + uint8 m_MirrorTimerFlags; + uint8 m_MirrorTimerFlagsLast; bool m_isInWater; /*********************************************************/ @@ -2325,7 +2329,6 @@ class MANGOS_DLL_SPEC Player : public Unit bool m_DailyQuestChanged; time_t m_lastDailyQuestTime; - uint32 m_breathTimer; uint32 m_drunkTimer; uint16 m_drunk; uint32 m_weaponChangeTimer; diff --git a/src/game/SpellAuras.cpp b/src/game/SpellAuras.cpp index 0bdbbda25..0f6cae650 100644 --- a/src/game/SpellAuras.cpp +++ b/src/game/SpellAuras.cpp @@ -2490,15 +2490,9 @@ void Aura::HandleAuraHover(bool apply, bool Real) void Aura::HandleWaterBreathing(bool apply, bool Real) { - if(!apply && !m_target->HasAuraType(SPELL_AURA_WATER_BREATHING)) - { - // update for enable timer in case not moving target - if(m_target->GetTypeId()==TYPEID_PLAYER && m_target->IsInWorld()) - { - ((Player*)m_target)->UpdateUnderwaterState(m_target->GetMap(),m_target->GetPositionX(),m_target->GetPositionY(),m_target->GetPositionZ()); - ((Player*)m_target)->HandleDrowning(); - } - } + // update timers in client + if(m_target->GetTypeId()==TYPEID_PLAYER) + ((Player*)m_target)->UpdateMirrorTimers(); } void Aura::HandleAuraModShapeshift(bool apply, bool Real) diff --git a/src/shared/revision_nr.h b/src/shared/revision_nr.h index 9c6e60a2b..c10f61e60 100644 --- a/src/shared/revision_nr.h +++ b/src/shared/revision_nr.h @@ -1,4 +1,4 @@ #ifndef __REVISION_NR_H__ #define __REVISION_NR_H__ - #define REVISION_NR "7427" + #define REVISION_NR "7428" #endif // __REVISION_NR_H__