diff --git a/3rdparty/mainui b/3rdparty/mainui index d6a54bbf..e456555c 160000 --- a/3rdparty/mainui +++ b/3rdparty/mainui @@ -1 +1 @@ -Subproject commit d6a54bbf0c4aa7acb5d90aff08b6f18f740f392b +Subproject commit e456555cf159d2e858694b53fa92717c84e8870e diff --git a/Documentation/donate.md b/Documentation/donate.md new file mode 100644 index 00000000..7cc11784 --- /dev/null +++ b/Documentation/donate.md @@ -0,0 +1,24 @@ +# Developers donation page + +On this page you can find links where you can support each developer individually, who has provided public sponsorship information. + +### [a1batross](https://github.com/a1batross) + +* Initial Xash3D SDL2/Linux port author, Xash3D FWGS engine maintainer, creator of non-commercial Flying With Gauss organization. +* Boosty page: https://boosty.to/a1ba + +### [nekonomicon](https://github.com/nekonomicon) + +* [hlsdk-portable](https://github.com/FWGS/hlsdk-portable), [mdldec](../utils/mdldec), [opensource-mods.md](opensource-mods.md) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=nekonomicon) (*BSD/clang port, PNG support, etc). +* Boosty page: https://boosty.to/nekonomicon + +### [Velaron](https://github.com/Velaron) +* [cs16-client](https://github.com/Velaron/cs16-client) & [tf15-client](https://github.com/Velaron/tf15-client) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=Velaron) (Android port, voice chat, etc). +* BuyMeACoffee page: https://www.buymeacoffee.com/velaron + +### [SNMetamorph](https://github.com/SNMetamorph) +* [PrimeXT](https://github.com/SNMetamorph/PrimeXT) & [GoldSrc Monitor](https://github.com/SNMetamorph/goldsrc-monitor) maintainer and Xash3D FWGS [contributor](https://github.com/FWGS/xash3d-fwgs/commits?author=SNMetamorph) (Windows port, voice chat, etc). +* BTC: `16GAzK3qei5AwBW7sggXp3yNcFHBtdpxXj` +* ETH (ERC20): `0xb580eeca9756e3881f9d6d026e28db28eb72a383` +* USDT (ERC20): `0xb580eeca9756e3881f9d6d026e28db28eb72a383` +* USDC (ERC20): `0xb580eeca9756e3881f9d6d026e28db28eb72a383` diff --git a/Documentation/opensource-mods.md b/Documentation/opensource-mods.md index d7f37992..a72d6707 100644 --- a/Documentation/opensource-mods.md +++ b/Documentation/opensource-mods.md @@ -133,22 +133,8 @@ Available in mod archive on ModDB - https://www.moddb.com/mods/overturn ## Oz Deathmatch Mirrored on github - https://github.com/nekonomicon/OZDM -## Spirit of Half-Life -[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life - -## Threewave CTF -*Unfinished mod by Valve Software* - -Available in Valve's Half-Life repository with Deathmatch Classic sources - https://github.com/ValveSoftware/halflife/tree/master/dmc - -## Trinity Render -Available on ModDB: https://www.moddb.com/mods/half-life-episode-two/downloads/trinity-rendering-engine-v308f - -## Tyrian: Ground Assault -Available on ModDB: https://www.moddb.com/mods/tyriangroundassault/downloads/tyrianga-v1-0-src - ## Paranoia -Available on ModDB: https://www.moddb.com/mods/paranoia/downloads/paranoia-toolkit +Available on ModDB - https://www.moddb.com/mods/paranoia/downloads/paranoia-toolkit ## Paranoia 2: The Savior Prealpha, mirrored on github - https://github.com/a1batross/Paranoia2_ancient @@ -163,12 +149,37 @@ Was found on HLFX - http://hlfx.ru/forum/showthread.php?s=2c892dfc52f72be52a89c3 ## Ricochet Available in Valve's Half-Life repository - https://github.com/ValveSoftware/halflife/tree/master/ricochet +## Spirit of Half-Life +[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life + +## The Wastes +Version 1.5: mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-thewastes-sdk + +## Threewave CTF +*Unfinished mod by Valve Software* + +Available in Valve's Half-Life repository with Deathmatch Classic sources - https://github.com/ValveSoftware/halflife/tree/master/dmc + +## Trinity Render +Available on ModDB - https://www.moddb.com/mods/half-life-episode-two/downloads/trinity-rendering-engine-v308f + +## Tyrian: Ground Assault +Available on ModDB - https://www.moddb.com/mods/tyriangroundassault/downloads/tyrianga-v1-0-src + +## Wasteland +Mirrored on code.idtech.space - https://code.idtech.space/vera/halflife-wasteland-sdk + ## Wizard Wars Download page on official site - http://www.thothie.com/ww/ ## XashXT Mirrored on github - https://github.com/a1batross/XashXT_original +## Xen-Warrior +*Source code is a part of Spirit of Half-Life 1.0-1.2 under XENWARRIOR macro* + +[Logic&Trick's](https://github.com/LogicAndTrick) mirror - https://files.logic-and-trick.com/#/Half-Life/Mods/Spirit%20of%20Half-Life + ## Zombie-X Available in mod archive on ModDB - https://www.moddb.com/mods/zombie-x-10-final/downloads/zombie-x-10-dle-beta6-last-version @@ -190,12 +201,12 @@ malortie's recreation - https://github.com/malortie/hl-aom Branch **aom** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aom ## Afraid of Monsters: Director's cut -Reverse-engineered code, branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc +Reverse-engineered code: branch **aomdc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/aomdc ## Azure Sheep malortie's recreation - https://github.com/malortie/hl-asheep -Reverse-engineered code, branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep +Reverse-engineered code: branch **asheep** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/asheep ## Big Lolly malortie's recreation - https://github.com/malortie/hl-biglolly @@ -232,12 +243,17 @@ Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/crack_lif ## Escape from the Darkness malortie's recreation - https://github.com/malortie/hl-eftd -Reverse-engineered code, branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd +Reverse-engineered code: branch **eftd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/eftd + +## Half-Life: Black Guard +*This mod uses dlls from Cleaner's Adventures* + +Branch **CAd** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/CAd ## Half-Life: Blue Shift Unkle Mike's recreation - https://hlfx.ru/forum/showthread.php?s=&threadid=5253 -Reverse-engineered code, branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift +Reverse-engineered code: branch **bshift** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/bshift ## Half-Life: Induction Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/induction @@ -245,12 +261,12 @@ Branch **induction** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/ ## Half-Life: Opposing Force Recreation by lostgamer aka nillerusr - https://github.com/LostGamerHL/hlsdk-xash3d -Reverse-engineered code, clean branch **opfor** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/opfor +Reverse-engineered code: clean branch **opfor** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/opfor Spirit of Half Life: Opposing-Force Edition - https://github.com/Hammermaps-DEV/SOHL-V1.9-Opposing-Force-Edition ## Half-Life: Rebellion -Reverse-engineered code, branch **rebellion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/rebellion +Reverse-engineered code: branch **rebellion** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/rebellion ## Half-Life: Urbicide Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/hl_urbicide @@ -258,7 +274,7 @@ Branch **hl_urbicide** in hlsdk-portable - https://github.com/FWGS/hlsdk-portabl ## Half-Life: Visitors malortie's recreation - https://github.com/malortie/hl-visitors -Reverse-engineered code, branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors +Reverse-engineered code: branch **visitors** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/visitors ## Half-Secret Branch **half-secret** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/half-secret @@ -271,18 +287,18 @@ Branch **noffice** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tr ## Poke 646 malortie's recreation - https://github.com/malortie/hl-poke646 -Reverse-engineered code, branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646 +Reverse-engineered code: branch **poke646** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646 ## Poke 646: Vendetta malortie's recreation - https://github.com/malortie/hl-poke646-vendetta -Reverse-engineered code, branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta +Reverse-engineered code: branch **poke646_vendetta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/poke646_vendetta ## Residual Life -Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point +Reverse-engineered code: branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Residual Point -Reverse-engineered code, branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point +Reverse-engineered code: branch **residual_point** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/residual_point ## Sewer Beta Branch **sewer_beta** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sewer_beta @@ -293,12 +309,12 @@ Reverse-engineered code by Velaron - https://github.com/Velaron/tf15-client ## The Gate malortie's recreation - https://github.com/malortie/hl-thegate -Reverse-engineered code, branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate +Reverse-engineered code: branch **thegate** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/thegate ## They Hunger malortie's recreation - https://github.com/malortie/hl-theyhunger -Reverse-engineered code, branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger +Reverse-engineered code: branch **theyhunger** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/theyhunger They Hunger: Tactical - https://www.moddb.com/mods/they-hunger-tactical/downloads/tht-source-code-documentation @@ -341,16 +357,22 @@ Branch **half-screwed** in hlsdk-portable - https://github.com/FWGS/hlsdk-portab Port to Linux - https://github.com/fmoraw/NS ## Spirit of Half-Life -Version 1.2, branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 +Version 1.2: branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 ## Swiss Cheese Halloween 2002 Just more playable version by malortie - https://github.com/malortie/hl-shall Branch **halloween** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/halloween +## The Wastes +Version 1.5: Port to Linux - https://git.mentality.rip/a1batross/halflife-thewastes-sdk + ## Threewave CTF Branch **dmc** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/dmc +## Xen-Warrior +Branch **sohl1.2** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/sohl1.2 + ## Zombie-X Branch **zombie-x** in hlsdk-portable - https://github.com/FWGS/hlsdk-portable/tree/zombie-x diff --git a/README.md b/README.md index 9b61877f..0ef030cd 100644 --- a/README.md +++ b/README.md @@ -39,36 +39,30 @@ Regular upstream Xash3D README.md follows. --- -# Xash3D FWGS Engine +# Xash3D FWGS Engine Xash3D FWGS icon [![GitHub Actions Status](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/FWGS/xash3d-fwgs/actions/workflows/c-cpp.yml) [![FreeBSD Build Status](https://img.shields.io/cirrus/github/FWGS/xash3d-fwgs?label=freebsd%20build)](https://cirrus-ci.com/github/FWGS/xash3d-fwgs) [![Discord Server](https://img.shields.io/discord/355697768582610945.svg)](http://fwgsdiscord.mentality.rip/) \ -[![Download Stable](https://img.shields.io/badge/download-stable-yellow)](https://github.com/FWGS/xash3d-fwgs/releases/latest) [![Download Testing](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) +[![Download Stable](https://img.shields.io/badge/download-stable-yellow)](https://github.com/FWGS/xash3d-fwgs/releases/latest) [![Download Testing](https://img.shields.io/badge/downloads-testing-orange)](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) -Xash3D FWGS is a fork of Xash3D Engine by Unkle Mike with extended features and crossplatform. +Xash3D FWGS is a game engine, aimed to provide compatibility with Half-Life Engine and extend it, as well as to give game developers well known workflow. -``` -Xash3D is a game engine, aimed to provide compatibility with Half-Life Engine, -as well as to give game developers well known workflow and extend it. -Read more about Xash3D on ModDB: https://www.moddb.com/engines/xash3d-engine -``` +Xash3D FWGS is a heavily modified fork of an original [Xash3D Engine](https://www.moddb.com/engines/xash3d-engine) by Unkle Mike. + +## Donate +[![Donate to FWGS button](https://img.shields.io/badge/Donate_to_FWGS-%3C3-magenta)](Documentation/donate.md) \ +If you like Xash3D FWGS, consider supporting individual engine maintainers. By supporting us, you help to continue developing this game engine further. The sponsorship links are available in [documentation](Documentation/donate.md). ## Fork features -* HLSDK 2.4 support. -* Crossplatform: supported x86 and ARM on Windows/Linux/BSD/Android. ([see docs for more info](Documentation/ports.md)) -* Modern compilers support: say no more to MSVC6. -* Better multiplayer support: multiple master servers, headless dedicated server. -* Mobility API: allows better game integration on mobile devices(vibration, touch controls) -* Different input methods: touch, gamepad and classic mouse & keyboard. +* Steam Half-Life (HLSDK 2.4) support. +* Crossplatform and modern compilers support: supports Windows, Linux, BSD & Android on x86 & ARM and [many more](Documentation/ports.md). +* Better multiplayer support: multiple master servers, headless dedicated server, voice chat and IPv6 support. +* Multiple renderers support: OpenGL, GLESv1, GLESv2 and Software. +* Advanced virtual filesystem: `.pk3` and `.pk3dir` support, compatibility with GoldSrc FS module, fast case-insensitivity emulation for crossplatform. +* Mobility API: better game integration on mobile devices (vibration, touch controls) +* Different input methods: touch and gamepad in addition to mouse & keyboard. * TrueType font rendering, as a part of mainui_cpp. -* Multiple renderers support: OpenGL, GLESv1, GLESv2, Software. -* Voice support. -* External filesystem module like in GoldSrc engine. -* External vgui support module. -* PNG image format support. -* A set of small improvements, without broken compatibility. - -## Planned fork features -* Virtual Reality support and game API. -* Vulkan renderer. +* External VGUI support module. +* PNG & KTX2 image format support. +* [A set of small improvements](Documentation/), without broken compatibility. ## Installation & Running 0) Get Xash3D FWGS binaries: you can use [testing](https://github.com/FWGS/xash3d-fwgs/releases/tag/continuous) build or you can compile engine from source code. diff --git a/common/xash3d_types.h b/common/xash3d_types.h index 3cd1cfec..ad46778e 100644 --- a/common/xash3d_types.h +++ b/common/xash3d_types.h @@ -57,6 +57,7 @@ typedef uint64_t longtime_t; #define MAX_USERMSG_LENGTH 2048 // don't modify it's relies on a client-side definitions #define BIT( n ) ( 1U << ( n )) +#define BIT64( n ) ( 1ULL << ( n )) #define GAMMA ( 2.2f ) // Valve Software gamma #define INVGAMMA ( 1.0f / 2.2f ) // back to 1.0 #define TEXGAMMA ( 0.9f ) // compensate dim textures @@ -86,16 +87,19 @@ typedef uint64_t longtime_t; #endif #define _format(x) __attribute__((format(printf, x, x+1))) #define NORETURN __attribute__((noreturn)) + #define NONNULL __attribute__((nonnull)) #elif defined(_MSC_VER) #define EXPORT __declspec( dllexport ) #define GAME_EXPORT #define _format(x) #define NORETURN + #define NONNULL #else #define EXPORT #define GAME_EXPORT #define _format(x) #define NORETURN + #define NONNULL #endif #if ( __GNUC__ >= 3 ) @@ -177,6 +181,7 @@ typedef struct dll_info_s } dll_info_t; typedef void (*setpair_t)( const char *key, const void *value, const void *buffer, void *numpairs ); +typedef void *(*pfnCreateInterface_t)( const char *, int * ); // config strings are a general means of communication from // the server to all connected clients. diff --git a/engine/client/cl_demo.c b/engine/client/cl_demo.c index 44ad36cd..e947c76d 100644 --- a/engine/client/cl_demo.c +++ b/engine/client/cl_demo.c @@ -1124,7 +1124,10 @@ void CL_FinishTimeDemo( void ) time = host.realtime - cls.td_starttime; if( !time ) time = 1.0; - Con_Printf( "%i frames %5.3f seconds %5.3f fps\n", frames, time, frames / time ); + Con_Printf( "timedemo result: %i frames %5.3f seconds %5.3f fps\n", frames, time, frames / time ); + + if( Sys_CheckParm( "-timedemo" )) + CL_Quit_f(); } /* diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index afc229b7..4563c04e 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -3336,6 +3336,9 @@ static void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, if( remote_address->type != NA_IPX && remote_address->type != NA_BROADCAST_IPX ) return; // IPX no longer support + if( request == NETAPI_REQUEST_SERVERLIST ) + return; // no support for server list requests + // find a free request for( i = 0; i < MAX_REQUESTS; i++ ) { @@ -3372,28 +3375,9 @@ static void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, nr->resp.remote_address = *remote_address; nr->flags = flags; - if( request == NETAPI_REQUEST_SERVERLIST ) - { - char fullquery[512]; - size_t len; - - len = CL_BuildMasterServerScanRequest( fullquery, sizeof( fullquery ), false ); - - // make sure that port is specified - if( !nr->resp.remote_address.port ) - nr->resp.remote_address.port = MSG_BigShort( PORT_MASTER ); - - // grab the list from the master server - NET_SendPacket( NS_CLIENT, len, fullquery, nr->resp.remote_address ); - clgame.request_type = NET_REQUEST_CLIENT; - clgame.master_request = nr; // holds the master request unitl the master acking - } - else - { - // local servers request - Q_snprintf( req, sizeof( req ), "netinfo %i %i %i", PROTOCOL_VERSION, context, request ); - Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, "%s", req ); - } + // local servers request + Q_snprintf( req, sizeof( req ), "netinfo %i %i %i", PROTOCOL_VERSION, context, request ); + Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, "%s", req ); } /* @@ -3421,13 +3405,6 @@ static void GAME_EXPORT NetAPI_CancelRequest( int context ) nr->pfnFunc( &nr->resp ); } - if( clgame.net_requests[i].resp.type == NETAPI_REQUEST_SERVERLIST && &clgame.net_requests[i] == clgame.master_request ) - { - if( clgame.request_type == NET_REQUEST_CLIENT ) - clgame.request_type = NET_REQUEST_CANCEL; - clgame.master_request = NULL; - } - memset( &clgame.net_requests[i], 0, sizeof( net_request_t )); break; } @@ -3456,8 +3433,6 @@ void GAME_EXPORT NetAPI_CancelAllRequests( void ) } memset( clgame.net_requests, 0, sizeof( clgame.net_requests )); - clgame.request_type = NET_REQUEST_CANCEL; - clgame.master_request = NULL; } /* diff --git a/engine/client/cl_gameui.c b/engine/client/cl_gameui.c index 59351bee..6263c266 100644 --- a/engine/client/cl_gameui.c +++ b/engine/client/cl_gameui.c @@ -983,28 +983,21 @@ pfnCheckGameDll */ int GAME_EXPORT pfnCheckGameDll( void ) { - string dllpath; - void *hInst; - -#if TARGET_OS_IPHONE - // loading server library drains too many ram - // so 512MB iPod Touch cannot even connect to - // to servers in cstrike +#ifdef XASH_INTERNAL_GAMELIBS return true; -#endif +#else + string dllpath; if( svgame.hInstance ) return true; COM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath )); - if(( hInst = COM_LoadLibrary( dllpath, true, false )) != NULL ) - { - COM_FreeLibrary( hInst ); // don't increase linker's reference counter + if( FS_FileExists( dllpath, false )) return true; - } - Con_Reportf( S_WARN "Could not load server library: %s\n", COM_GetLibraryError() ); + return false; +#endif } /* diff --git a/engine/client/cl_main.c b/engine/client/cl_main.c index df78338c..182a8354 100644 --- a/engine/client/cl_main.c +++ b/engine/client/cl_main.c @@ -34,7 +34,7 @@ GNU General Public License for more details. CVAR_DEFINE_AUTO( mp_decals, "300", FCVAR_ARCHIVE, "decals limit in multiplayer" ); CVAR_DEFINE_AUTO( dev_overview, "0", 0, "draw level in overview-mode" ); CVAR_DEFINE_AUTO( cl_resend, "6.0", 0, "time to resend connect" ); -CVAR_DEFINE_AUTO( cl_allow_download, "1", FCVAR_ARCHIVE, "allow to downloading resources from the server" ); +CVAR_DEFINE( cl_allow_download, "cl_allowdownload", "1", FCVAR_ARCHIVE, "allow to downloading resources from the server" ); CVAR_DEFINE_AUTO( cl_allow_upload, "1", FCVAR_ARCHIVE, "allow to uploading resources to the server" ); CVAR_DEFINE_AUTO( cl_download_ingame, "1", FCVAR_ARCHIVE, "allow to downloading resources while client is active" ); CVAR_DEFINE_AUTO( cl_logofile, "lambda", FCVAR_ARCHIVE, "player logo name" ); @@ -92,7 +92,7 @@ client_t cl; client_static_t cls; clgame_static_t clgame; -void CL_InternetServers_f( void ); +static void CL_SendMasterServerScanRequest( void ); //====================================================================== int GAME_EXPORT CL_Active( void ) @@ -1081,7 +1081,7 @@ void CL_CheckForResend( void ) qboolean bandwidthTest; if( cls.internetservers_wait ) - CL_InternetServers_f(); + CL_SendMasterServerScanRequest(); // if the local server is running and we aren't then connect if( cls.state == ca_disconnected && SV_Active( )) @@ -1574,10 +1574,10 @@ void CL_LocalServers_f( void ) CL_BuildMasterServerScanRequest ================= */ -size_t CL_BuildMasterServerScanRequest( char *buf, size_t size, qboolean nat ) +static size_t NONNULL CL_BuildMasterServerScanRequest( char *buf, size_t size, uint32_t *key, qboolean nat, const char *filter ) { size_t remaining; - char *info; + char *info, temp[32]; if( unlikely( size < sizeof( MS_SCAN_REQUEST ))) return 0; @@ -1587,7 +1587,9 @@ size_t CL_BuildMasterServerScanRequest( char *buf, size_t size, qboolean nat ) info = buf + sizeof( MS_SCAN_REQUEST ) - 1; remaining = size - sizeof( MS_SCAN_REQUEST ); - info[0] = 0; + Q_strncpy( info, filter, remaining ); + + *key = COM_RandomLong( 0, 0x7FFFFFFF ); #ifndef XASH_ALL_SERVERS Info_SetValueForKey( info, "gamedir", GI->gamefolder, remaining ); @@ -1595,9 +1597,24 @@ size_t CL_BuildMasterServerScanRequest( char *buf, size_t size, qboolean nat ) Info_SetValueForKey( info, "clver", XASH_VERSION, remaining ); // let master know about client version Info_SetValueForKey( info, "nat", nat ? "1" : "0", remaining ); + Q_snprintf( temp, sizeof( temp ), "%x", *key ); + Info_SetValueForKey( info, "key", temp, remaining ); + return sizeof( MS_SCAN_REQUEST ) + Q_strlen( info ); } +/* +================= +CL_SendMasterServerScanRequest +================= +*/ +static void CL_SendMasterServerScanRequest( void ) +{ + cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, + cls.internetservers_query_len, cls.internetservers_query ); + cls.internetservers_pending = true; +} + /* ================= CL_InternetServers_f @@ -1605,26 +1622,24 @@ CL_InternetServers_f */ void CL_InternetServers_f( void ) { - char fullquery[512]; - size_t len; qboolean nat = cl_nat.value != 0.0f; + uint32_t key; - len = CL_BuildMasterServerScanRequest( fullquery, sizeof( fullquery ), nat ); + if( Cmd_Argc( ) > 2 || ( Cmd_Argc( ) == 2 && !Info_IsValid( Cmd_Argv( 1 )))) + { + Con_Printf( S_USAGE "internetservers [filter]\n" ); + return; + } + + cls.internetservers_query_len = CL_BuildMasterServerScanRequest( + cls.internetservers_query, sizeof( cls.internetservers_query ), + &cls.internetservers_key, nat, Cmd_Argv( 1 )); Con_Printf( "Scanning for servers on the internet area...\n" ); NET_Config( true, true ); // allow remote - cls.internetservers_wait = NET_SendToMasters( NS_CLIENT, len, fullquery ); - cls.internetservers_pending = true; - - if( !cls.internetservers_wait ) - { - // now we clearing the vgui request - if( clgame.master_request != NULL ) - memset( clgame.master_request, 0, sizeof( net_request_t )); - clgame.request_type = NET_REQUEST_GAMEUI; - } + CL_SendMasterServerScanRequest(); } /* @@ -2152,6 +2167,25 @@ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) return; } + // check the extra header + if( MSG_ReadByte( msg ) == 0x7f ) + { + uint32_t key = MSG_ReadDword( msg ); + + if( cls.internetservers_key != key ) + { + Con_Printf( S_WARN "unexpected server list packet from %s (invalid key)\n", NET_AdrToString( from )); + return; + } + + MSG_ReadByte( msg ); // reserved byte + } + else + { + Con_Printf( S_WARN "invalid server list packet from %s (missing extra header)\n", NET_AdrToString( from )); + return; + } + // serverlist got from masterserver while( MSG_GetNumBitsLeft( msg ) > 8 ) { @@ -2161,58 +2195,10 @@ void CL_ConnectionlessPacket( netadr_t from, sizebuf_t *msg ) // list is ends here if( !servadr.port ) - { - if( clgame.request_type == NET_REQUEST_CLIENT && clgame.master_request != NULL ) - { - net_request_t *nr = clgame.master_request; - net_adrlist_t *list, **prev; - - // setup the answer - nr->resp.remote_address = from; - nr->resp.error = NET_SUCCESS; - nr->resp.ping = host.realtime - nr->timesend; - - if( nr->timeout <= host.realtime ) - SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); - - Con_Printf( "serverlist call: %s\n", NET_AdrToString( from )); - nr->pfnFunc( &nr->resp ); - - // throw the list, now it will be stored in user area - prev = (net_adrlist_t**)&nr->resp.response; - - while( 1 ) - { - list = *prev; - if( !list ) break; - - // throw out any variables the game created - *prev = list->next; - Mem_Free( list ); - } - memset( nr, 0, sizeof( *nr )); // done - clgame.request_type = NET_REQUEST_CANCEL; - clgame.master_request = NULL; - } break; - } - if( clgame.request_type == NET_REQUEST_CLIENT && clgame.master_request != NULL ) - { - net_request_t *nr = clgame.master_request; - net_adrlist_t *list; - - // adding addresses into list - list = Z_Malloc( sizeof( *list )); - list->remote_address = servadr; - list->next = nr->resp.response; - nr->resp.response = list; - } - else if( clgame.request_type == NET_REQUEST_GAMEUI ) - { - NET_Config( true, false ); // allow remote - Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); - } + NET_Config( true, false ); // allow remote + Netchan_OutOfBandPrint( NS_CLIENT, servadr, "info %i", PROTOCOL_VERSION ); } if( cls.internetservers_pending ) @@ -2261,10 +2247,13 @@ void CL_ReadNetMessage( void ) while( CL_GetMessage( net_message_buffer, &curSize )) { - if( cls.legacymode && *((int *)&net_message_buffer) == 0xFFFFFFFE ) + const int split_header = LittleLong( 0xFFFFFFFE ); + if( cls.legacymode && !memcmp( &split_header, net_message_buffer, sizeof( split_header ))) + { // Will rewrite existing packet by merged if( !NetSplit_GetLong( &cls.netchan.netsplit, &net_from, net_message_buffer, &curSize ) ) continue; + } MSG_Init( &net_message, "ServerData", net_message_buffer, curSize ); diff --git a/engine/client/cl_mobile.c b/engine/client/cl_mobile.c index 480f3c1c..69c61b5d 100644 --- a/engine/client/cl_mobile.c +++ b/engine/client/cl_mobile.c @@ -87,15 +87,6 @@ static int pfnDrawScaledCharacter( int x, int y, int number, int r, int g, int b return CL_DrawCharacter( x, y, number, color, &g_scaled_font, flags ); } -static void *pfnGetNativeObject( const char *obj ) -{ - if( !obj ) - return NULL; - - // Backend should consider that obj is case-sensitive - return Platform_GetNativeObject( obj ); -} - static void pfnTouch_HideButtons( const char *name, byte state ) { Touch_HideButtons( name, state, true ); @@ -124,7 +115,7 @@ static mobile_engfuncs_t gpMobileEngfuncs = Touch_ResetDefaultButtons, pfnDrawScaledCharacter, Sys_Warn, - pfnGetNativeObject, + Sys_GetNativeObject, ID_SetCustomClientID, pfnParseFileSafe }; diff --git a/engine/client/cl_parse.c b/engine/client/cl_parse.c index a84734f4..81fd545f 100644 --- a/engine/client/cl_parse.c +++ b/engine/client/cl_parse.c @@ -1534,7 +1534,8 @@ void CL_SendConsistencyInfo( sizebuf_t *msg ) { case force_exactfile: MD5_HashFile( md5, filename, NULL ); - pc->value = *(int *)md5; + memcpy( &pc->value, md5, sizeof( pc->value )); + LittleLongSW( pc->value ); if( user_changed_diskfile ) MSG_WriteUBitLong( msg, 0, 32 ); diff --git a/engine/client/cl_scrn.c b/engine/client/cl_scrn.c index a7d9e0ea..4004a305 100644 --- a/engine/client/cl_scrn.c +++ b/engine/client/cl_scrn.c @@ -726,6 +726,7 @@ void SCR_VidInit( void ) // restart console size Con_VidInit (); + Touch_NotifyResize(); } /* diff --git a/engine/client/client.h b/engine/client/client.h index f607189e..c1e8aba7 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -423,13 +423,6 @@ typedef struct float applied_angle; } screen_shake_t; -typedef enum -{ - NET_REQUEST_CANCEL = 0, // request was cancelled for some reasons - NET_REQUEST_GAMEUI, // called from GameUI - NET_REQUEST_CLIENT, // called from Client -} net_request_type_t; - typedef struct { net_response_t resp; @@ -491,9 +484,7 @@ typedef struct client_textmessage_t *titles; // title messages, not network messages int numTitles; - net_request_type_t request_type; // filter the requests net_request_t net_requests[MAX_REQUESTS]; // no reason to keep more - net_request_t *master_request; // queued master request efrag_t *free_efrags; // linked efrags cl_entity_t viewent; // viewmodel @@ -627,7 +618,10 @@ typedef struct file_t *demofile; file_t *demoheader; // contain demo startup info in case we record a demo on this level qboolean internetservers_wait; // internetservers is waiting for dns request - qboolean internetservers_pending; // internetservers is waiting for dns request + qboolean internetservers_pending; // if true, clean master server pings + uint32_t internetservers_key; // compare key to validate master server reply + char internetservers_query[512]; // cached query + uint32_t internetservers_query_len; // legacy mode support qboolean legacymode; // one-way 48 protocol compatibility @@ -765,7 +759,6 @@ int CL_IsDevOverviewMode( void ); void CL_PingServers_f( void ); void CL_SignonReply( void ); void CL_ClearState( void ); -size_t CL_BuildMasterServerScanRequest( char *buf, size_t size, qboolean nat ); // // cl_demo.c diff --git a/engine/client/console.c b/engine/client/console.c index eadeef71..64ddf883 100644 --- a/engine/client/console.c +++ b/engine/client/console.c @@ -1983,7 +1983,7 @@ void Con_DrawConsole( void ) if( cls.state == ca_connecting || cls.state == ca_connected ) { - if( !cl_allow_levelshots.value ) + if( !cl_allow_levelshots.value && !cls.timedemo ) { if(( Cvar_VariableInteger( "cl_background" ) || Cvar_VariableInteger( "sv_background" )) && cls.key_dest != key_console ) con.vislines = con.showlines = 0; @@ -1993,7 +1993,7 @@ void Con_DrawConsole( void ) { con.showlines = 0; - if( host_developer.value >= DEV_EXTENDED ) + if( host_developer.value >= DEV_EXTENDED && !cls.timedemo ) Con_DrawNotify(); // draw notify lines } } @@ -2025,7 +2025,7 @@ void Con_DrawConsole( void ) { if( con.vislines ) Con_DrawSolidConsole( con.vislines ); - else if( cls.state == ca_active && ( cls.key_dest == key_game || cls.key_dest == key_message )) + else if( cls.state == ca_active && ( cls.key_dest == key_game || cls.key_dest == key_message ) && !cls.timedemo ) Con_DrawNotify(); // draw notify lines } break; diff --git a/engine/client/in_touch.c b/engine/client/in_touch.c index 04df74d4..a2ed8163 100644 --- a/engine/client/in_touch.c +++ b/engine/client/in_touch.c @@ -132,6 +132,8 @@ static struct touch_s int whitetexture; int joytexture; // touch indicator qboolean configchanged; + float actual_aspect_ratio; // maximum aspect ratio from launch, or aspect ratio when entering editor + float config_aspect_ratio; // aspect ratio set by command from config or after entering editor } touch; // private to the engine flags @@ -168,13 +170,42 @@ CVAR_DEFINE_AUTO( touch_emulate, "0", FCVAR_ARCHIVE | FCVAR_FILTERABLE, "emulate #define B(x) (button->x) #define SCR_W ((float)refState.width) #define SCR_H ((float)refState.height) -#define TO_SCRN_Y(x) (refState.height * (x)) +#define TO_SCRN_Y(x) (refState.width * (x) * Touch_AspectRatio()) #define TO_SCRN_X(x) (refState.width * (x)) static void IN_TouchCheckCoords( float *x1, float *y1, float *x2, float *y2 ); static void IN_TouchEditClear( void ); static void Touch_InitConfig( void ); +void Touch_NotifyResize( void ) +{ + if( refState.width && refState.height && ( !touch.configchanged || !touch.actual_aspect_ratio )) + { + float aspect_ratio = SCR_H/SCR_W; + if( aspect_ratio < 0.99 && aspect_ratio > touch.actual_aspect_ratio ) + touch.actual_aspect_ratio = aspect_ratio; + } +} + +static inline float Touch_AspectRatio( void ) +{ + if( touch.config_aspect_ratio ) + return touch.config_aspect_ratio; + else if( touch.actual_aspect_ratio ) + return touch.actual_aspect_ratio; + else if( refState.width && refState.height ) + return SCR_H/SCR_W; + else + return 9.0f / 16.0f; +} + + +static void Touch_ConfigAspectRatio_f( void ) +{ + touch.config_aspect_ratio = Q_atof( Cmd_Argv( 1 )); +} + + /* ========================== Touch_ExportButtonToConfig @@ -206,7 +237,7 @@ static inline int Touch_ExportButtonToConfig( file_t *f, touch_button_t *button, if( keepAspect ) { - float aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(SCR_H/SCR_W) ); + float aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(Touch_AspectRatio()) ); FS_Printf( f, " %f\n", aspect ); } else FS_Printf( f, "\n" ); @@ -271,6 +302,8 @@ qboolean Touch_DumpConfig( const char *name, const char *profilename ) FS_Printf( f, "touch_setclientonly 0\n" ); FS_Printf( f, "\n// touch buttons\n" ); FS_Printf( f, "touch_removeall\n" ); + FS_Printf( f, "touch_aspectratio %f\n", Touch_AspectRatio()); + for( button = touch.list_user.first; button; button = button->next ) { @@ -376,7 +409,7 @@ static void Touch_GenerateCode_f( void ) if( FBitSet( flags, TOUCH_FL_DEF_HIDE )) SetBits( flags, TOUCH_FL_HIDE ); - aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(SCR_H/SCR_W) ); + aspect = ( B(y2) - B(y1) ) / ( ( B(x2) - B(x1) ) /(Touch_AspectRatio()) ); if( memcmp( &c, &B(color), sizeof( rgba_t ) ) ) { Con_Printf( "unsigned char color[] = { %d, %d, %d, %d };\n", B(color[0]), B(color[1]), B(color[2]), B(color[3]) ); @@ -549,6 +582,7 @@ static void Touch_RemoveAll_f( void ) { IN_TouchEditClear(); Touch_ClearList( &touch.list_user ); + touch.config_aspect_ratio = 0.0f; } static void Touch_SetColor( touchbuttonlist_t *list, const char *name, byte *color ) @@ -726,6 +760,8 @@ static void Touch_SetCommand_f( void ) Con_Printf( S_USAGE "touch_setcommand \n" ); } +static void Touch_LoadDefaults_f( void ); + static void Touch_ReloadConfig_f( void ) { touch.state = state_none; @@ -735,8 +771,15 @@ static void Touch_ReloadConfig_f( void ) touch.selection->finger = -1; touch.edit = touch.selection = NULL; touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1; - - Cbuf_AddTextf( "exec %s\n", touch_config_file.string ); + if( FS_FileExists( touch_config_file.string, true ) ) + { + Cbuf_AddTextf( "exec \"%s\"\n", touch_config_file.string ); + } + else + { + Touch_LoadDefaults_f(); + touch.configchanged = true; + } } static touch_button_t *Touch_AddButton( touchbuttonlist_t *list, @@ -784,7 +827,7 @@ void Touch_AddClientButton( const char *name, const char *texture, const char *c IN_TouchCheckCoords( &x1, &y1, &x2, &y2 ); if( round == round_aspect ) { - y2 = y1 + ( x2 - x1 ) * (SCR_W/SCR_H) * aspect; + y2 = y1 + ( x2 - x1 ) / (Touch_AspectRatio()) * aspect; } button = Touch_AddButton( &touch.list_user, name, texture, command, x1, y1, x2, y2, color, true ); button->flags |= flags | TOUCH_FL_CLIENT | TOUCH_FL_NOEDIT; @@ -809,7 +852,7 @@ static void Touch_LoadDefaults_f( void ) if( g_DefaultButtons[i].texturefile[0] == '#' ) y2 = y1 + ( (float)clgame.scrInfo.iCharHeight / (float)clgame.scrInfo.iHeight ) * g_DefaultButtons[i].aspect + touch.swidth*2/SCR_H; else - y2 = y1 + ( x2 - x1 ) * (SCR_W/SCR_H) * g_DefaultButtons[i].aspect; + y2 = y1 + (( x2 - x1 ) / Touch_AspectRatio()) * g_DefaultButtons[i].aspect; } IN_TouchCheckCoords( &x1, &y1, &x2, &y2 ); @@ -817,6 +860,7 @@ static void Touch_LoadDefaults_f( void ) button->flags |= g_DefaultButtons[i].flags; button->aspect = g_DefaultButtons[i].aspect; } + touch.configchanged = true; } // Add default button from client @@ -909,7 +953,7 @@ static void Touch_AddButton_f( void ) if( aspect ) { if( B( texturefile )[0] != '#' ) - B( y2 ) = B( y1 ) + ( B( x2 ) - B( x1 )) * ( SCR_W / SCR_H ) * aspect; + B( y2 ) = B( y1 ) + (( B( x2 ) - B( x1 )) / Touch_AspectRatio() ) * aspect; B( aspect ) = aspect; } } @@ -917,11 +961,39 @@ static void Touch_AddButton_f( void ) static void Touch_EnableEdit_f( void ) { + touch_button_t *button; + float current_ratio = SCR_H/SCR_W; if( touch.state == state_none ) touch.state = state_edit; touch.resize_finger = touch.move_finger = touch.look_finger = touch.wheel_finger = -1; touch.move_button = NULL; touch.configchanged = true; + /* try determine the best ratio + * User enters editor. Window now have correct size. Need to fix aspect ratio in some cases */ + // Case A: no config was loaded, touch was generated with lower height, but window was resized higher, reset it to actual size + if( touch.actual_aspect_ratio > current_ratio ) + touch.actual_aspect_ratio = current_ratio; + if( !touch.config_aspect_ratio ) + touch.config_aspect_ratio = touch.actual_aspect_ratio; + // Case B: config was loaded, but window may be resized later, so keep y coordinate as is + touch.actual_aspect_ratio = current_ratio; + // convert coordinates to actual aspect ratio after it was updated + if( touch.config_aspect_ratio != touch.actual_aspect_ratio ) + { + for( button = touch.list_user.first; button; button = button->next ) + { + B(y1) /= touch.actual_aspect_ratio / touch.config_aspect_ratio; + B(y2) /= touch.actual_aspect_ratio / touch.config_aspect_ratio; + + // clamp positions to make buttons visible by user + if( B(y2) > 1.0f ) + { + B(y1) -= B(y2) - 1.0f; + B(y2) -= B(y2) - 1.0f; + } + } + touch.config_aspect_ratio = touch.actual_aspect_ratio; + } } static void Touch_DisableEdit_f( void ) @@ -956,7 +1028,7 @@ static void Touch_DeleteProfile_f( void ) static void Touch_InitEditor( void ) { - float x = 0.1f * (SCR_H/SCR_W); + float x = 0.1f * (Touch_AspectRatio()); float y = 0.05f; touch_button_t *temp; rgba_t color; @@ -1051,6 +1123,7 @@ void Touch_Init( void ) Cmd_AddRestrictedCommand( "touch_generate_code", Touch_GenerateCode_f, "create code sample for mobility API" ); Cmd_AddCommand( "touch_fade", Touch_Fade_f, "start fade animation for selected buttons" ); Cmd_AddRestrictedCommand( "touch_toggleselection", Touch_ToggleSelection_f, "toggle vidibility on selected button in editor" ); + Cmd_AddRestrictedCommand( "touch_aspectratio", Touch_ConfigAspectRatio_f, "set current aspect ratio" ); // not saved, just runtime state for scripting Cvar_RegisterVariable( &touch_in_menu ); @@ -1087,7 +1160,7 @@ void Touch_Init( void ) #if SDL_VERSION_ATLEAST( 2, 0, 10 ) SDL_SetHint( SDL_HINT_MOUSE_TOUCH_EVENTS, "0" ); SDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, "0" ); -#else +#elif defined(SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH) SDL_SetHint( SDL_HINT_ANDROID_SEPARATE_MOUSE_AND_TOUCH, "1" ); #endif @@ -1170,9 +1243,9 @@ static void Touch_DrawTexture ( float x1, float y1, float x2, float y2, int text } #define GRID_COUNT_X ((int)touch_grid_count.value) -#define GRID_COUNT_Y (((int)touch_grid_count.value) * SCR_H / SCR_W) +#define GRID_COUNT_Y (((int)touch_grid_count.value) * Touch_AspectRatio()) #define GRID_X (1.0f/GRID_COUNT_X) -#define GRID_Y (SCR_W/SCR_H/GRID_COUNT_X) +#define GRID_Y (1.0f/Touch_AspectRatio()/GRID_COUNT_X) #define GRID_ROUND_X(x) ((float)round( x * GRID_COUNT_X ) / GRID_COUNT_X) #define GRID_ROUND_Y(x) ((float)round( x * GRID_COUNT_Y ) / GRID_COUNT_Y) @@ -1250,7 +1323,7 @@ static float Touch_DrawText( float x1, float y1, float x2, float y2, const char { while( *s && ( *s != '\n' ) && ( *s != ';' ) && ( x1 < maxx ) ) x1 += Touch_DrawCharacter( x1, y1, *s++, size ); - y1 += cls.creditsFont.charHeight / 1024.f * size / SCR_H * SCR_W; + y1 += cls.creditsFont.charHeight / 1024.f * size / Touch_AspectRatio(); if( y1 >= maxy ) break; @@ -1357,11 +1430,12 @@ void Touch_Draw( void ) if( !touch.initialized || ( !touch_enable.value && !touch.clientonly )) return; - Touch_InitConfig(); - if( cls.key_dest != key_game && !touch_in_menu.value ) return; + Touch_InitConfig(); + + ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture ); if( touch.state >= state_edit && touch_grid_enable.value ) @@ -1948,6 +2022,8 @@ static int Touch_ControlsEvent( touchEventType type, int fingerID, float x, floa int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ) { + y *= SCR_H/SCR_W/Touch_AspectRatio(); +// Con_Printf("%f %f\n", TO_SCRN_X(x), TO_SCRN_Y(y)); // simulate menu mouse click if( cls.key_dest != key_game && !touch_in_menu.value ) { diff --git a/engine/client/input.c b/engine/client/input.c index 71017882..0312952c 100644 --- a/engine/client/input.c +++ b/engine/client/input.c @@ -172,6 +172,8 @@ void IN_ToggleClientMouse( int newstate, int oldstate ) { if( newstate == oldstate ) return; + if( m_ignore.value ) + return; // since SetCursorType controls cursor visibility // execute it first, and then check mouse grab state @@ -212,6 +214,9 @@ void IN_CheckMouseState( qboolean active ) qboolean useRawInput = true; // always use SDL code #endif + if( m_ignore.value ) + return; + if( active && useRawInput && !host.mouse_visible && cls.state == ca_active ) { if( !s_bRawInput ) diff --git a/engine/client/input.h b/engine/client/input.h index 98c871fb..bd5aafb1 100644 --- a/engine/client/input.h +++ b/engine/client/input.h @@ -75,6 +75,7 @@ void Touch_ResetDefaultButtons( void ); int IN_TouchEvent( touchEventType type, int fingerID, float x, float y, float dx, float dy ); void Touch_KeyEvent( int key, int down ); qboolean Touch_WantVisibleCursor( void ); +void Touch_NotifyResize( void ); // // in_joy.c diff --git a/engine/client/mod_dbghulls.c b/engine/client/mod_dbghulls.c index e1c7e6f2..3b5dd2b5 100644 --- a/engine/client/mod_dbghulls.c +++ b/engine/client/mod_dbghulls.c @@ -659,7 +659,7 @@ static void make_hull_windings( hull_t *hull, hull_model_t *model ) Con_Reportf( "%i hull polys\n", model->num_polys ); } -void Mod_InitDebugHulls( void ) +void Mod_InitDebugHulls( model_t *loadmodel ) { int i; diff --git a/engine/client/ref_common.c b/engine/client/ref_common.c index 8300af29..4783937b 100644 --- a/engine/client/ref_common.c +++ b/engine/client/ref_common.c @@ -99,16 +99,6 @@ static void *pfnMod_Extradata( int type, model_t *m ) return NULL; } -static model_t *pfnMod_GetCurrentLoadingModel( void ) -{ - return loadmodel; -} - -static void pfnMod_SetCurrentLoadingModel( model_t *m ) -{ - loadmodel = m; -} - static void pfnGetPredictedOrigin( vec3_t v ) { VectorCopy( cl.simorg, v ); @@ -283,8 +273,6 @@ static ref_api_t gEngfuncs = Mod_ForName, pfnMod_Extradata, CL_ModelHandle, - pfnMod_GetCurrentLoadingModel, - pfnMod_SetCurrentLoadingModel, CL_GetRemapInfoForEntity, CL_AllocRemapInfo, diff --git a/engine/common/common.h b/engine/common/common.h index 08160029..3ae43936 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -381,6 +381,7 @@ typedef void (*xcommand_t)( void ); qboolean FS_LoadProgs( void ); void FS_Init( void ); void FS_Shutdown( void ); +void *FS_GetNativeObject( const char *obj ); // // cmd.c @@ -747,8 +748,6 @@ void SV_ShutdownGame( void ); void SV_ExecLoadLevel( void ); void SV_ExecLoadGame( void ); void SV_ExecChangeLevel( void ); -qboolean SV_InitGameProgs( void ); -void SV_FreeGameProgs( void ); void CL_WriteMessageHistory( void ); void CL_SendCmd( void ); void CL_Disconnect( void ); diff --git a/engine/common/con_utils.c b/engine/common/con_utils.c index 1be94c0a..7a763d9e 100644 --- a/engine/common/con_utils.c +++ b/engine/common/con_utils.c @@ -1423,15 +1423,11 @@ save serverinfo variables into server.cfg (using for dedicated server too) */ void GAME_EXPORT Host_WriteServerConfig( const char *name ) { - qboolean already_loaded; file_t *f; string newconfigfile; Q_snprintf( newconfigfile, MAX_STRING, "%s.new", name ); - // TODO: remove this mechanism, make it safer for now - already_loaded = SV_InitGameProgs(); // collect user variables - // FIXME: move this out until menu parser is done CSCR_LoadDefaultCVars( "settings.scr" ); @@ -1448,10 +1444,6 @@ void GAME_EXPORT Host_WriteServerConfig( const char *name ) Host_FinalizeConfig( f, name ); } else Con_DPrintf( S_ERROR "Couldn't write %s.\n", name ); - - // don't unload library that wasn't loaded by us - if( !already_loaded ) - SV_FreeGameProgs(); // release progs with all variables } /* diff --git a/engine/common/filesystem_engine.c b/engine/common/filesystem_engine.c index 19925792..2b1e341e 100644 --- a/engine/common/filesystem_engine.c +++ b/engine/common/filesystem_engine.c @@ -23,8 +23,17 @@ GNU General Public License for more details. fs_api_t g_fsapi; fs_globals_t *FI; +static pfnCreateInterface_t fs_pfnCreateInterface; static HINSTANCE fs_hInstance; +void *FS_GetNativeObject( const char *obj ) +{ + if( fs_pfnCreateInterface ) + return fs_pfnCreateInterface( obj, NULL ); + + return NULL; +} + static void FS_Rescan_f( void ) { FS_Rescan(); @@ -53,7 +62,7 @@ static fs_interface_t fs_memfuncs = _Mem_Realloc, _Mem_Free, - Platform_GetNativeObject, + Sys_GetNativeObject, }; static void FS_UnloadProgs( void ) @@ -98,6 +107,13 @@ qboolean FS_LoadProgs( void ) return false; } + if( !( fs_pfnCreateInterface = (pfnCreateInterface_t)COM_GetProcAddress( fs_hInstance, "CreateInterface" ))) + { + FS_UnloadProgs(); + Host_Error( "FS_LoadProgs: can't find CreateInterface entry point in %s\n", name ); + return false; + } + Con_DPrintf( "FS_LoadProgs: filesystem_stdio successfully loaded\n" ); return true; diff --git a/engine/common/host.c b/engine/common/host.c index 6c3e11cd..f734e52b 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -33,6 +33,7 @@ GNU General Public License for more details. #include "common.h" #include "base_cmd.h" #include "client.h" +#include "server.h" #include "netchan.h" #include "protocol.h" #include "mod_local.h" @@ -127,6 +128,7 @@ void Sys_PrintUsage( void ) O("-noenginejoy ", "disable engine builtin joystick support") O("-noenginemouse ", "disable engine builtin mouse support") O("-nosound ", "disable sound output") + O("-timedemo ", "run timedemo and exit") #endif "\nPlatform-specific options:\n" @@ -1016,21 +1018,31 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha else { #if TARGET_OS_IOS - const char *IOS_GetDocsDir(); - Q_strncpy( host.rootdir, IOS_GetDocsDir(), sizeof(host.rootdir) ); + Q_strncpy( host.rootdir, IOS_GetDocsDir(), sizeof( host.rootdir )); +#elif XASH_ANDROID && XASH_SDL + Q_strncpy( host.rootdir, SDL_AndroidGetExternalStoragePath(), sizeof( host.rootdir )); #elif XASH_PSVITA - if ( !PSVita_GetBasePath( host.rootdir, sizeof( host.rootdir ) ) ) + if ( !PSVita_GetBasePath( host.rootdir, sizeof( host.rootdir ))) { Sys_Error( "couldn't find xash3d data directory" ); host.rootdir[0] = 0; } #elif (XASH_SDL == 2) && !XASH_NSWITCH // GetBasePath not impl'd in switch-sdl2 - char *szBasePath; - - if( !( szBasePath = SDL_GetBasePath() ) ) + char *szBasePath = SDL_GetBasePath(); + if( szBasePath ) + { + Q_strncpy( host.rootdir, szBasePath, sizeof( host.rootdir )); + SDL_free( szBasePath ); + } + else + { +#if XASH_POSIX || XASH_WIN32 + if( !getcwd( host.rootdir, sizeof( host.rootdir ))) + Sys_Error( "couldn't determine current directory: %s, getcwd: %s", SDL_GetError(), strerror( errno )); +#else Sys_Error( "couldn't determine current directory: %s", SDL_GetError() ); - Q_strncpy( host.rootdir, szBasePath, sizeof( host.rootdir )); - SDL_free( szBasePath ); +#endif + } #else if( !getcwd( host.rootdir, sizeof( host.rootdir ))) { @@ -1149,6 +1161,7 @@ Host_Main int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGame, pfnChangeGame func ) { static double oldtime, newtime; + string demoname; host.starttime = Sys_DoubleTime(); @@ -1245,6 +1258,9 @@ int EXPORT Host_Main( int argc, char **argv, const char *progname, int bChangeGa Cbuf_ExecStuffCmds(); // execute stuffcmds (commandline) SCR_CheckStartupVids(); // must be last + if( Sys_GetParmFromCmdLine( "-timedemo", demoname )) + Cbuf_AddTextf( "timedemo %s\n", demoname ); + oldtime = Sys_DoubleTime() - 0.1; if( Host_IsDedicated( )) @@ -1293,6 +1309,7 @@ void EXPORT Host_Shutdown( void ) #endif SV_Shutdown( "Server shutdown\n" ); + SV_UnloadProgs(); SV_ShutdownFilter(); CL_Shutdown(); diff --git a/engine/common/imagelib/img_dds.c b/engine/common/imagelib/img_dds.c index 9eacf673..1562f847 100644 --- a/engine/common/imagelib/img_dds.c +++ b/engine/common/imagelib/img_dds.c @@ -362,10 +362,10 @@ qboolean Image_LoadDDS( const char *name, const byte *buffer, fs_offset_t filesi SetBits( image.flags, IMAGE_HAS_ALPHA ); else if( image.type == PF_DXT5 && Image_CheckDXT5Alpha( &header, fin )) SetBits( image.flags, IMAGE_HAS_ALPHA ); - else if ( image.type == PF_BC5_SIGNED || image.type == PF_BC5_UNSIGNED ) - SetBits(image.flags, IMAGE_HAS_ALPHA); - else if ( image.type == PF_BC7_UNORM || image.type == PF_BC7_SRGB ) - SetBits(image.flags, IMAGE_HAS_ALPHA); + else if( image.type == PF_BC5_SIGNED || image.type == PF_BC5_UNSIGNED ) + SetBits( image.flags, IMAGE_HAS_ALPHA ); + else if( image.type == PF_BC7_UNORM || image.type == PF_BC7_SRGB ) + SetBits( image.flags, IMAGE_HAS_ALPHA ); if( !FBitSet( header.dsPixelFormat.dwFlags, DDS_LUMINANCE )) SetBits( image.flags, IMAGE_HAS_COLOR ); break; diff --git a/engine/common/imagelib/img_ktx2.c b/engine/common/imagelib/img_ktx2.c index ecbea904..a33f563b 100644 --- a/engine/common/imagelib/img_ktx2.c +++ b/engine/common/imagelib/img_ktx2.c @@ -1,5 +1,5 @@ /* -img_ktx2.c - KTX2 format load +img_ktx2.c - ktx2 format load Copyright (C) 2023 Provod This program is free software: you can redistribute it and/or modify @@ -15,51 +15,49 @@ GNU General Public License for more details. #include "imagelib.h" #include "xash3d_mathlib.h" -#include "ktx2.h" +#include "img_ktx2.h" static void Image_KTX2Format( uint32_t ktx2_format ) { - switch ( ktx2_format ) + switch( ktx2_format ) { case KTX2_FORMAT_BC4_UNORM_BLOCK: - // 1 component for ref_gl - ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA ); image.type = PF_BC4_UNSIGNED; + // 1 component for ref_gl break; case KTX2_FORMAT_BC4_SNORM_BLOCK: - // 1 component for ref_gl - ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA ); image.type = PF_BC4_SIGNED; + // 1 component for ref_gl break; case KTX2_FORMAT_BC5_UNORM_BLOCK: - ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_LUMA ); - image.flags |= IMAGE_HAS_ALPHA; // 2 components for ref_gl image.type = PF_BC5_UNSIGNED; + // 2 components for ref_gl + SetBits( image.flags, IMAGE_HAS_ALPHA ); break; case KTX2_FORMAT_BC5_SNORM_BLOCK: - ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_LUMA ); - image.flags |= IMAGE_HAS_ALPHA; // 2 components for ref_gl image.type = PF_BC5_SIGNED; + // 2 components for ref_gl + SetBits( image.flags, IMAGE_HAS_ALPHA ); break; case KTX2_FORMAT_BC6H_UFLOAT_BLOCK: - ClearBits( image.flags, IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA ); - image.flags |= IMAGE_HAS_COLOR; image.type = PF_BC6H_UNSIGNED; + // 3 components for ref_gl + SetBits( image.flags, IMAGE_HAS_COLOR ); break; case KTX2_FORMAT_BC6H_SFLOAT_BLOCK: - ClearBits( image.flags, IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA ); - image.flags |= IMAGE_HAS_COLOR; image.type = PF_BC6H_SIGNED; + // 3 components for ref_gl + SetBits( image.flags, IMAGE_HAS_COLOR ); break; case KTX2_FORMAT_BC7_UNORM_BLOCK: - ClearBits( image.flags, IMAGE_HAS_LUMA ); - image.flags |= IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA; image.type = PF_BC7_UNORM; + // 4 components for ref_gl + SetBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA ); break; case KTX2_FORMAT_BC7_SRGB_BLOCK: - ClearBits( image.flags, IMAGE_HAS_LUMA ); - image.flags |= IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA; image.type = PF_BC7_SRGB; + // 4 components for ref_gl + SetBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA ); break; default: image.type = PF_UNKNOWN; @@ -77,7 +75,6 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer // Sets image.type and image.flags Image_KTX2Format( header->vkFormat ); - // TODO add logs for these if( image.type == PF_UNKNOWN ) { Con_DPrintf( S_ERROR "%s: unsupported KTX2 format %d\n", __FUNCTION__, header->vkFormat ); @@ -120,7 +117,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer return false; } - memcpy( &index, buffer + KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ), sizeof index ); + memcpy( &index, buffer + KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ), sizeof( index )); for( int mip = 0; mip < header->levelCount; ++mip ) { @@ -129,7 +126,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer const uint32_t mip_size = Image_ComputeSize( image.type, width, height, image.depth ); ktx2_level_t level; - memcpy( &level, levels_begin + mip * sizeof level, sizeof level ); + memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level )); if( mip_size != level.byteLength ) { @@ -154,7 +151,7 @@ static qboolean Image_KTX2Parse( const ktx2_header_t *header, const byte *buffer for( int mip = 0, cursor = 0; mip < header->levelCount; ++mip ) { ktx2_level_t level; - memcpy( &level, levels_begin + mip * sizeof level, sizeof level ); + memcpy( &level, levels_begin + mip * sizeof( level ), sizeof( level )); memcpy( image.rgba + cursor, buffer + level.byteOffset, level.byteLength ); cursor += level.byteLength; } @@ -175,13 +172,15 @@ qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t files return false; } - memcpy( &header, buffer + KTX2_IDENTIFIER_SIZE, sizeof header ); + memcpy( &header, buffer + KTX2_IDENTIFIER_SIZE, sizeof( header )); image.width = header.pixelWidth; image.height = header.pixelHeight; image.depth = Q_max( 1, header.pixelDepth ); image.num_mips = 1; + ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA ); + if( !Image_KTX2Parse( &header, buffer, filesize )) { if( !Image_CheckFlag( IL_KTX2_RAW )) @@ -190,7 +189,6 @@ qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t files // If KTX2 to imagelib conversion failed, try passing the file as raw data. // This is useful for ref_vk which can directly support hundreds of formats which we don't convert to pixformat_t here - // TODO something like Image_CheckFlag( IL_SUPPORTS_KTX2_RAW )? Con_DPrintf( S_WARN "%s: (%s) could not be converted to supported imagelib format, passing as raw KTX2 data\n", __FUNCTION__, name ); // This is a catch-all for ref_vk, which can do this format directly and natively image.type = PF_KTX2_RAW; @@ -198,9 +196,6 @@ qboolean Image_LoadKTX2( const char *name, const byte *buffer, fs_offset_t files image.size = filesize; //image.encode = TODO custom encode type? - // Unknown format, no idea what's inside - ClearBits( image.flags, IMAGE_HAS_COLOR | IMAGE_HAS_ALPHA | IMAGE_HAS_LUMA ); - image.rgba = Mem_Malloc( host.imagepool, image.size ); memcpy( image.rgba, buffer, image.size ); } diff --git a/engine/common/imagelib/img_ktx2.h b/engine/common/imagelib/img_ktx2.h new file mode 100644 index 00000000..e1183315 --- /dev/null +++ b/engine/common/imagelib/img_ktx2.h @@ -0,0 +1,79 @@ +/* +img_ktx2.h - ktx2 format reference +Copyright (C) 2023 Provod + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef IMG_KTX2_H +#define IMG_KTX2_H + +#include + +#define KTX2_IDENTIFIER_SIZE 12 +#define KTX2_IDENTIFIER "\xABKTX 20\xBB\r\n\x1A\n" + +/* +static const char k_ktx2_identifier[KTX2_IDENTIFIER_SIZE] = +{ + '\xAB', 'K', 'T', 'X', ' ', '2', '0', '\xBB', '\r', '\n', '\x1A', '\n' +}; +*/ + +typedef struct +{ + uint32_t vkFormat; + uint32_t typeSize; + uint32_t pixelWidth; + uint32_t pixelHeight; + uint32_t pixelDepth; + uint32_t layerCount; + uint32_t faceCount; + uint32_t levelCount; + uint32_t supercompressionScheme; +} ktx2_header_t; + +typedef struct +{ + uint32_t dfdByteOffset; + uint32_t dfdByteLength; + uint32_t kvdByteOffset; + uint32_t kvdByteLength; + uint64_t sgdByteOffset; + uint64_t sgdByteLength; +} ktx2_index_t; + +typedef struct +{ + uint64_t byteOffset; + uint64_t byteLength; + uint64_t uncompressedByteLength; +} ktx2_level_t; + +#define KTX2_LEVELS_OFFSET ( KTX2_IDENTIFIER_SIZE + sizeof( ktx2_header_t ) + sizeof( ktx2_index_t )) + +#define KTX2_MINIMAL_HEADER_SIZE ( KTX2_LEVELS_OFFSET + sizeof( ktx2_level_t )) + +// These have the same values as enum VkFormat in vulkan_core.h +// There are hundreds of formats which can be contained in KTX2. +// Below are listed the ones which are supported here. This list can be extended. +typedef enum +{ + KTX2_FORMAT_BC4_UNORM_BLOCK = 139, + KTX2_FORMAT_BC4_SNORM_BLOCK = 140, + KTX2_FORMAT_BC5_UNORM_BLOCK = 141, + KTX2_FORMAT_BC5_SNORM_BLOCK = 142, + KTX2_FORMAT_BC6H_UFLOAT_BLOCK = 143, + KTX2_FORMAT_BC6H_SFLOAT_BLOCK = 144, + KTX2_FORMAT_BC7_UNORM_BLOCK = 145, + KTX2_FORMAT_BC7_SRGB_BLOCK = 146, +} ktx2_format_t; + +#endif // IMG_KTX2_H diff --git a/engine/common/imagelib/img_utils.c b/engine/common/imagelib/img_utils.c index a6cd189e..29107e65 100644 --- a/engine/common/imagelib/img_utils.c +++ b/engine/common/imagelib/img_utils.c @@ -284,8 +284,10 @@ int Image_ComparePalette( const byte *pal ) void Image_SetPalette( const byte *pal, uint *d_table ) { byte rgba[4]; + uint uirgba; // TODO: palette looks byte-swapped on big-endian int i; + // setup palette switch( image.d_rendermode ) { @@ -296,7 +298,8 @@ void Image_SetPalette( const byte *pal, uint *d_table ) rgba[1] = pal[i*3+1]; rgba[2] = pal[i*3+2]; rgba[3] = 0xFF; - d_table[i] = *(uint *)rgba; + memcpy( &uirgba, rgba, sizeof( uirgba )); + d_table[i] = uirgba; } break; case LUMP_GRADIENT: @@ -306,7 +309,8 @@ void Image_SetPalette( const byte *pal, uint *d_table ) rgba[1] = pal[766]; rgba[2] = pal[767]; rgba[3] = i; - d_table[i] = *(uint *)rgba; + memcpy( &uirgba, rgba, sizeof( uirgba )); + d_table[i] = uirgba; } break; case LUMP_MASKED: @@ -316,7 +320,8 @@ void Image_SetPalette( const byte *pal, uint *d_table ) rgba[1] = pal[i*3+1]; rgba[2] = pal[i*3+2]; rgba[3] = 0xFF; - d_table[i] = *(uint *)rgba; + memcpy( &uirgba, rgba, sizeof( uirgba )); + d_table[i] = uirgba; } d_table[255] = 0; break; @@ -327,7 +332,8 @@ void Image_SetPalette( const byte *pal, uint *d_table ) rgba[1] = pal[i*4+1]; rgba[2] = pal[i*4+2]; rgba[3] = pal[i*4+3]; - d_table[i] = *(uint *)rgba; + memcpy( &uirgba, rgba, sizeof( uirgba )); + d_table[i] = uirgba; } break; } diff --git a/engine/common/launcher.c b/engine/common/launcher.c index 4c2f9d66..c24b2b2f 100644 --- a/engine/common/launcher.c +++ b/engine/common/launcher.c @@ -23,15 +23,6 @@ GNU General Public License for more details. #if XASH_EMSCRIPTEN #include -#elif XASH_WIN32 -extern "C" -{ -// Enable NVIDIA High Performance Graphics while using Integrated Graphics. -__declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; - -// Enable AMD High Performance Graphics while using Integrated Graphics. -__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; -} #endif #define E_GAME "XASH3D_GAME" // default env dir to start from @@ -39,6 +30,10 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; #define XASH_GAMEDIR "valve" #endif +#if XASH_WIN32 +#error "Single-binary or dedicated builds aren't supported for Windows!" +#endif + static char szGameDir[128]; // safe place to keep gamedir static int szArgc; static char **szArgv; @@ -47,7 +42,7 @@ static void Sys_ChangeGame( const char *progname ) { // a1ba: may never be called within engine // if platform supports execv() function - Q_strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); + Q_strncpy( szGameDir, progname, sizeof( szGameDir )); Host_Shutdown( ); exit( Host_Main( szArgc, szArgv, szGameDir, 1, &Sys_ChangeGame ) ); } @@ -88,7 +83,6 @@ _inline int Sys_Start( void ) return ret; } -#if !XASH_WIN32 int main( int argc, char **argv ) { #if XASH_PSVITA @@ -100,36 +94,4 @@ int main( int argc, char **argv ) #endif // XASH_PSVITA return Sys_Start(); } -#else -int __stdcall WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR cmdLine, int nShow) -{ - LPWSTR* lpArgv; - int ret, i; - - lpArgv = CommandLineToArgvW( GetCommandLineW(), &szArgc ); - szArgv = ( char** )malloc( (szArgc + 1) * sizeof( char* )); - - for( i = 0; i < szArgc; ++i ) - { - size_t size = wcslen(lpArgv[i]) + 1; - - // just in case, allocate some more memory - szArgv[i] = ( char * )malloc( size * sizeof( wchar_t )); - wcstombs( szArgv[i], lpArgv[i], size ); - } - szArgv[szArgc] = 0; - - LocalFree( lpArgv ); - - ret = Sys_Start(); - - for( ; i < szArgc; ++i ) - free( szArgv[i] ); - free( szArgv ); - - return ret; -} -#endif // XASH_WIN32 - - -#endif +#endif // SINGLE_BINARY diff --git a/engine/common/masterlist.c b/engine/common/masterlist.c index 0900918b..895e6713 100644 --- a/engine/common/masterlist.c +++ b/engine/common/masterlist.c @@ -419,5 +419,6 @@ void NET_InitMasters( void ) // keep main master always there NET_AddMaster( MASTERSERVER_ADR, false ); + NET_AddMaster( MASTERSERVER_ADR_TEST, false ); NET_LoadMasters( ); } diff --git a/engine/common/mod_bmodel.c b/engine/common/mod_bmodel.c index 05dc1625..834f8420 100644 --- a/engine/common/mod_bmodel.c +++ b/engine/common/mod_bmodel.c @@ -265,7 +265,7 @@ static fs_offset_t Mod_CalculateMipTexSize( mip_t *mt, qboolean palette ) ( palette ? MIPTEX_CUSTOM_PALETTE_SIZE_BYTES : 0 ); } -static qboolean Mod_CalcMipTexUsesCustomPalette( dbspmodel_t *bmod, int textureIndex ) +static qboolean Mod_CalcMipTexUsesCustomPalette( model_t *mod, dbspmodel_t *bmod, int textureIndex ) { int nextTextureIndex = 0; mip_t *mipTex; @@ -280,7 +280,7 @@ static qboolean Mod_CalcMipTexUsesCustomPalette( dbspmodel_t *bmod, int textureI size = Mod_CalculateMipTexSize( mipTex, false ); // Compute next data offset to determine allocated miptex space - for( nextTextureIndex = textureIndex + 1; nextTextureIndex < loadmodel->numtextures; nextTextureIndex++ ) + for( nextTextureIndex = textureIndex + 1; nextTextureIndex < mod->numtextures; nextTextureIndex++ ) { int nextOffset = bmod->textures->dataofs[nextTextureIndex]; @@ -317,7 +317,7 @@ static qboolean Mod_NameImpliesTextureIsAnimated( texture_t *tex ) return true; } -static void Mod_CreateDefaultTexture( texture_t **texture ) +static void Mod_CreateDefaultTexture( model_t *mod, texture_t **texture ) { texture_t *tex; @@ -325,7 +325,7 @@ static void Mod_CreateDefaultTexture( texture_t **texture ) if( !texture || *texture != NULL ) return; - *texture = tex = Mem_Calloc( loadmodel->mempool, sizeof( *tex )); + *texture = tex = Mem_Calloc( mod->mempool, sizeof( *tex )); Q_strncpy( tex->name, REF_DEFAULT_TEXTURE, sizeof( tex->name )); #if !XASH_DEDICATED @@ -945,7 +945,7 @@ converted maps potential may don't support water transparency ================== */ -static qboolean Mod_CheckWaterAlphaSupport( dbspmodel_t *bmod ) +static qboolean Mod_CheckWaterAlphaSupport( model_t *mod, dbspmodel_t *bmod ) { mleaf_t *leaf; int i, j; @@ -956,15 +956,15 @@ static qboolean Mod_CheckWaterAlphaSupport( dbspmodel_t *bmod ) // check all liquid leafs to see if they can see into empty leafs, if any // can we can assume this map supports r_wateralpha - for( i = 0, leaf = loadmodel->leafs; i < loadmodel->numleafs; i++, leaf++ ) + for( i = 0, leaf = mod->leafs; i < mod->numleafs; i++, leaf++ ) { if(( leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME ) && leaf->cluster >= 0 ) { pvs = Mod_DecompressPVS( leaf->compressed_vis, world.visbytes ); - for( j = 0; j < loadmodel->numleafs; j++ ) + for( j = 0; j < mod->numleafs; j++ ) { - if( CHECKVISBIT( pvs, loadmodel->leafs[j].cluster ) && loadmodel->leafs[j].contents == CONTENTS_EMPTY ) + if( CHECKVISBIT( pvs, mod->leafs[j].cluster ) && mod->leafs[j].contents == CONTENTS_EMPTY ) return true; } } @@ -1132,7 +1132,7 @@ Mod_CalcSurfaceExtents Fills in surf->texturemins[] and surf->extents[] ================= */ -static void Mod_CalcSurfaceExtents( msurface_t *surf ) +static void Mod_CalcSurfaceExtents( model_t *mod, msurface_t *surf ) { // this place is VERY critical to precision // keep it as float, don't use double, because it causes issues with lightmap @@ -1141,7 +1141,7 @@ static void Mod_CalcSurfaceExtents( msurface_t *surf ) int bmins[2], bmaxs[2]; int i, j, e, sample_size; mextrasurf_t *info = surf->info; - int facenum = surf - loadmodel->surfaces; + int facenum = surf - mod->surfaces; mtexinfo_t *tex; mvertex_t *v; @@ -1155,13 +1155,13 @@ static void Mod_CalcSurfaceExtents( msurface_t *surf ) for( i = 0; i < surf->numedges; i++ ) { - e = loadmodel->surfedges[surf->firstedge + i]; + e = mod->surfedges[surf->firstedge + i]; - if( e >= loadmodel->numedges || e <= -loadmodel->numedges ) + if( e >= mod->numedges || e <= -mod->numedges ) Host_Error( "Mod_CalcSurfaceExtents: bad edge\n" ); - if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; - else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + if( e >= 0 ) v = &mod->vertexes[mod->edges[e].v[0]]; + else v = &mod->vertexes[mod->edges[-e].v[1]]; for( j = 0; j < 2; j++ ) { @@ -1215,7 +1215,7 @@ Mod_CalcSurfaceBounds fills in surf->mins and surf->maxs ================= */ -static void Mod_CalcSurfaceBounds( msurface_t *surf ) +static void Mod_CalcSurfaceBounds( model_t *mod, msurface_t *surf ) { int i, e; mvertex_t *v; @@ -1224,13 +1224,13 @@ static void Mod_CalcSurfaceBounds( msurface_t *surf ) for( i = 0; i < surf->numedges; i++ ) { - e = loadmodel->surfedges[surf->firstedge + i]; + e = mod->surfedges[surf->firstedge + i]; - if( e >= loadmodel->numedges || e <= -loadmodel->numedges ) + if( e >= mod->numedges || e <= -mod->numedges ) Host_Error( "Mod_CalcSurfaceBounds: bad edge\n" ); - if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]]; - else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]]; + if( e >= 0 ) v = &mod->vertexes[mod->edges[e].v[0]]; + else v = &mod->vertexes[mod->edges[-e].v[1]]; AddPointToBounds( v->position, surf->info->mins, surf->info->maxs ); } @@ -1242,7 +1242,7 @@ static void Mod_CalcSurfaceBounds( msurface_t *surf ) Mod_CreateFaceBevels ================= */ -static void Mod_CreateFaceBevels( msurface_t *surf ) +static void Mod_CreateFaceBevels( model_t *mod, msurface_t *surf ) { vec3_t delta, edgevec; byte *facebevel; @@ -1258,7 +1258,7 @@ static void Mod_CreateFaceBevels( msurface_t *surf ) else contents = CONTENTS_SOLID; size = sizeof( mfacebevel_t ) + surf->numedges * sizeof( mplane_t ); - facebevel = (byte *)Mem_Calloc( loadmodel->mempool, size ); + facebevel = (byte *)Mem_Calloc( mod->mempool, size ); fb = (mfacebevel_t *)facebevel; facebevel += sizeof( mfacebevel_t ); fb->edges = (mplane_t *)facebevel; @@ -1275,8 +1275,8 @@ static void Mod_CreateFaceBevels( msurface_t *surf ) { mplane_t *dest = &fb->edges[i]; - v0 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + i ); - v1 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + (i + 1) % surf->numedges ); + v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i ); + v1 = Mod_GetVertexByNumber( mod, surf->firstedge + (i + 1) % surf->numedges ); VectorSubtract( v1->position, v0->position, edgevec ); CrossProduct( faceNormal, edgevec, dest->normal ); VectorNormalize( dest->normal ); @@ -1290,7 +1290,7 @@ static void Mod_CreateFaceBevels( msurface_t *surf ) // compute face radius for( i = 0; i < surf->numedges; i++ ) { - v0 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + i ); + v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i ); VectorSubtract( v0->position, fb->origin, delta ); radius = DotProduct( delta, delta ); fb->radius = Q_max( radius, fb->radius ); @@ -1386,24 +1386,24 @@ Mod_MakeHull0 Duplicate the drawing hull structure as a clipping hull ================= */ -static void Mod_MakeHull0( void ) +static void Mod_MakeHull0( model_t *mod ) { mnode_t *in, *child; mclipnode_t *out; hull_t *hull; int i, j; - hull = &loadmodel->hulls[0]; - hull->clipnodes = out = Mem_Malloc( loadmodel->mempool, loadmodel->numnodes * sizeof( *out )); - in = loadmodel->nodes; + hull = &mod->hulls[0]; + hull->clipnodes = out = Mem_Malloc( mod->mempool, mod->numnodes * sizeof( *out )); + in = mod->nodes; hull->firstclipnode = 0; - hull->lastclipnode = loadmodel->numnodes - 1; - hull->planes = loadmodel->planes; + hull->lastclipnode = mod->numnodes - 1; + hull->planes = mod->planes; - for( i = 0; i < loadmodel->numnodes; i++, out++, in++ ) + for( i = 0; i < mod->numnodes; i++, out++, in++ ) { - out->planenum = in->plane - loadmodel->planes; + out->planenum = in->plane - mod->planes; for( j = 0; j < 2; j++ ) { @@ -1411,7 +1411,7 @@ static void Mod_MakeHull0( void ) if( child->contents < 0 ) out->children[j] = child->contents; - else out->children[j] = child - loadmodel->nodes; + else out->children[j] = child - mod->nodes; } } } @@ -1475,7 +1475,7 @@ static void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, poolhandle_t mempool Mod_LoadColoredLighting ================= */ -static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod ) +static qboolean Mod_LoadColoredLighting( model_t *mod, dbspmodel_t *bmod ) { char modelname[64]; char path[64]; @@ -1483,11 +1483,11 @@ static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod ) fs_offset_t litdatasize; byte *in; - COM_FileBase( loadmodel->name, modelname, sizeof( modelname )); + COM_FileBase( mod->name, modelname, sizeof( modelname )); Q_snprintf( path, sizeof( path ), "maps/%s.lit", modelname ); // make sure what deluxemap is actual - if( !COM_CompareFileTime( path, loadmodel->name, &iCompare )) + if( !COM_CompareFileTime( path, mod->name, &iCompare )) return false; if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg @@ -1513,9 +1513,9 @@ static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod ) return false; } - loadmodel->lightdata = Mem_Malloc( loadmodel->mempool, litdatasize ); - memcpy( loadmodel->lightdata, in + 8, litdatasize ); - SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING ); + mod->lightdata = Mem_Malloc( mod->mempool, litdatasize ); + memcpy( mod->lightdata, in + 8, litdatasize ); + SetBits( mod->flags, MODEL_COLORED_LIGHTING ); bmod->lightdatasize = litdatasize; Mem_Free( in ); @@ -1527,7 +1527,7 @@ static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod ) Mod_LoadDeluxemap ================= */ -static void Mod_LoadDeluxemap( dbspmodel_t *bmod ) +static void Mod_LoadDeluxemap( model_t *mod, dbspmodel_t *bmod ) { char modelname[64]; fs_offset_t deluxdatasize; @@ -1538,11 +1538,11 @@ static void Mod_LoadDeluxemap( dbspmodel_t *bmod ) if( !FBitSet( host.features, ENGINE_LOAD_DELUXEDATA )) return; - COM_FileBase( loadmodel->name, modelname, sizeof( modelname )); + COM_FileBase( mod->name, modelname, sizeof( modelname )); Q_snprintf( path, sizeof( path ), "maps/%s.dlit", modelname ); // make sure what deluxemap is actual - if( !COM_CompareFileTime( path, loadmodel->name, &iCompare )) + if( !COM_CompareFileTime( path, mod->name, &iCompare )) return; if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg @@ -1568,7 +1568,7 @@ static void Mod_LoadDeluxemap( dbspmodel_t *bmod ) return; } - bmod->deluxedata_out = Mem_Malloc( loadmodel->mempool, deluxdatasize ); + bmod->deluxedata_out = Mem_Malloc( mod->mempool, deluxdatasize ); memcpy( bmod->deluxedata_out, in + 8, deluxdatasize ); bmod->deluxdatasize = deluxdatasize; Mem_Free( in ); @@ -1582,22 +1582,21 @@ duplicate the basic information for embedded submodels ================= */ -static void Mod_SetupSubmodels( dbspmodel_t *bmod ) +static void Mod_SetupSubmodels( model_t *mod, dbspmodel_t *bmod ) { qboolean colored = false; poolhandle_t mempool; char *ents; - model_t *mod; dmodel_t *bm; + const char *name = mod->name; int i, j; - ents = loadmodel->entities; - mempool = loadmodel->mempool; - if( FBitSet( loadmodel->flags, MODEL_COLORED_LIGHTING )) + ents = mod->entities; + mempool = mod->mempool; + if( FBitSet( mod->flags, MODEL_COLORED_LIGHTING )) colored = true; - mod = loadmodel; - loadmodel->numframes = 2; // regular and alternate animation + mod->numframes = 2; // regular and alternate animation // set up the submodels for( i = 0; i < mod->numsubmodels; i++ ) @@ -1640,7 +1639,7 @@ static void Mod_SetupSubmodels( dbspmodel_t *bmod ) SetBits( mod->flags, MODEL_HAS_ORIGIN ); #ifdef HACKS_RELATED_HLMODS // c2a1 doesn't have origin brush it's just placed at center of the level - if( !Q_stricmp( loadmodel->name, "maps/c2a1.bsp" ) && ( i == 11 )) + if( !Q_stricmp( name, "maps/c2a1.bsp" ) && ( i == 11 )) SetBits( mod->flags, MODEL_HAS_ORIGIN ); #endif } @@ -1663,14 +1662,15 @@ static void Mod_SetupSubmodels( dbspmodel_t *bmod ) if( i < mod->numsubmodels - 1 ) { char name[8]; + model_t *submod; // duplicate the basic information Q_snprintf( name, sizeof( name ), "*%i", i + 1 ); - loadmodel = Mod_FindName( name, true ); - *loadmodel = *mod; - Q_strncpy( loadmodel->name, name, sizeof( loadmodel->name )); - loadmodel->mempool = 0; - mod = loadmodel; + submod = Mod_FindName( name, true ); + *submod = *mod; + Q_strncpy( submod->name, name, sizeof( submod->name )); + submod->mempool = 0; + mod = submod; } } @@ -1690,17 +1690,17 @@ static void Mod_SetupSubmodels( dbspmodel_t *bmod ) Mod_LoadSubmodels ================= */ -static void Mod_LoadSubmodels( dbspmodel_t *bmod ) +static void Mod_LoadSubmodels( model_t *mod, dbspmodel_t *bmod ) { dmodel_t *in, *out; int oldmaxfaces; int i, j; // allocate extradata for each dmodel_t - out = Mem_Malloc( loadmodel->mempool, bmod->numsubmodels * sizeof( *out )); + out = Mem_Malloc( mod->mempool, bmod->numsubmodels * sizeof( *out )); - loadmodel->numsubmodels = bmod->numsubmodels; - loadmodel->submodels = out; + mod->numsubmodels = bmod->numsubmodels; + mod->submodels = out; in = bmod->submodels; if( bmod->isworld ) @@ -1748,7 +1748,7 @@ static void Mod_LoadSubmodels( dbspmodel_t *bmod ) Mod_LoadEntities ================= */ -static void Mod_LoadEntities( dbspmodel_t *bmod ) +static void Mod_LoadEntities( model_t *mod, dbspmodel_t *bmod ) { byte *entpatch = NULL; char token[MAX_TOKEN]; @@ -1763,11 +1763,11 @@ static void Mod_LoadEntities( dbspmodel_t *bmod ) size_t ft1, ft2; // world is check for entfile too - Q_strncpy( entfilename, loadmodel->name, sizeof( entfilename )); + Q_strncpy( entfilename, mod->name, sizeof( entfilename )); COM_ReplaceExtension( entfilename, ".ent", sizeof( entfilename )); // make sure what entity patch is never than bsp - ft1 = FS_FileTime( loadmodel->name, false ); + ft1 = FS_FileTime( mod->name, false ); ft2 = FS_FileTime( entfilename, true ); if( ft2 != -1 ) @@ -1786,12 +1786,12 @@ static void Mod_LoadEntities( dbspmodel_t *bmod ) } // make sure what we really has terminator - loadmodel->entities = Mem_Calloc( loadmodel->mempool, bmod->entdatasize + 1 ); - memcpy( loadmodel->entities, bmod->entdata, bmod->entdatasize ); // moving to private model pool + mod->entities = Mem_Calloc( mod->mempool, bmod->entdatasize + 1 ); + memcpy( mod->entities, bmod->entdata, bmod->entdatasize ); // moving to private model pool if( entpatch ) Mem_Free( entpatch ); // release entpatch if present if( !bmod->isworld ) return; - pfile = (char *)loadmodel->entities; + pfile = (char *)mod->entities; world.generator[0] = '\0'; world.compiler[0] = '\0'; world.message[0] = '\0'; @@ -1863,15 +1863,15 @@ static void Mod_LoadEntities( dbspmodel_t *bmod ) Mod_LoadPlanes ================= */ -static void Mod_LoadPlanes( dbspmodel_t *bmod ) +static void Mod_LoadPlanes( model_t *mod, dbspmodel_t *bmod ) { dplane_t *in; mplane_t *out; int i, j; in = bmod->planes; - loadmodel->planes = out = Mem_Malloc( loadmodel->mempool, bmod->numplanes * sizeof( *out )); - loadmodel->numplanes = bmod->numplanes; + mod->planes = out = Mem_Malloc( mod->mempool, bmod->numplanes * sizeof( *out )); + mod->numplanes = bmod->numplanes; for( i = 0; i < bmod->numplanes; i++, in++, out++ ) { @@ -1897,15 +1897,15 @@ static void Mod_LoadPlanes( dbspmodel_t *bmod ) Mod_LoadVertexes ================= */ -static void Mod_LoadVertexes( dbspmodel_t *bmod ) +static void Mod_LoadVertexes( model_t *mod, dbspmodel_t *bmod ) { dvertex_t *in; mvertex_t *out; int i; in = bmod->vertexes; - out = loadmodel->vertexes = Mem_Malloc( loadmodel->mempool, bmod->numvertexes * sizeof( mvertex_t )); - loadmodel->numvertexes = bmod->numvertexes; + out = mod->vertexes = Mem_Malloc( mod->mempool, bmod->numvertexes * sizeof( mvertex_t )); + mod->numvertexes = bmod->numvertexes; if( bmod->isworld ) ClearBounds( world.mins, world.maxs ); @@ -1933,13 +1933,13 @@ static void Mod_LoadVertexes( dbspmodel_t *bmod ) Mod_LoadEdges ================= */ -static void Mod_LoadEdges( dbspmodel_t *bmod ) +static void Mod_LoadEdges( model_t *mod, dbspmodel_t *bmod ) { medge_t *out; int i; - loadmodel->edges = out = Mem_Malloc( loadmodel->mempool, bmod->numedges * sizeof( medge_t )); - loadmodel->numedges = bmod->numedges; + mod->edges = out = Mem_Malloc( mod->mempool, bmod->numedges * sizeof( medge_t )); + mod->numedges = bmod->numedges; if( bmod->version == QBSP2_VERSION ) { @@ -1968,11 +1968,11 @@ static void Mod_LoadEdges( dbspmodel_t *bmod ) Mod_LoadSurfEdges ================= */ -static void Mod_LoadSurfEdges( dbspmodel_t *bmod ) +static void Mod_LoadSurfEdges( model_t *mod, dbspmodel_t *bmod ) { - loadmodel->surfedges = Mem_Malloc( loadmodel->mempool, bmod->numsurfedges * sizeof( dsurfedge_t )); - memcpy( loadmodel->surfedges, bmod->surfedges, bmod->numsurfedges * sizeof( dsurfedge_t )); - loadmodel->numsurfedges = bmod->numsurfedges; + mod->surfedges = Mem_Malloc( mod->mempool, bmod->numsurfedges * sizeof( dsurfedge_t )); + memcpy( mod->surfedges, bmod->surfedges, bmod->numsurfedges * sizeof( dsurfedge_t )); + mod->numsurfedges = bmod->numsurfedges; } /* @@ -1980,13 +1980,13 @@ static void Mod_LoadSurfEdges( dbspmodel_t *bmod ) Mod_LoadMarkSurfaces ================= */ -static void Mod_LoadMarkSurfaces( dbspmodel_t *bmod ) +static void Mod_LoadMarkSurfaces( model_t *mod, dbspmodel_t *bmod ) { msurface_t **out; int i; - loadmodel->marksurfaces = out = Mem_Malloc( loadmodel->mempool, bmod->nummarkfaces * sizeof( *out )); - loadmodel->nummarksurfaces = bmod->nummarkfaces; + mod->marksurfaces = out = Mem_Malloc( mod->mempool, bmod->nummarkfaces * sizeof( *out )); + mod->nummarksurfaces = bmod->nummarkfaces; if( bmod->version == QBSP2_VERSION ) { @@ -1994,9 +1994,9 @@ static void Mod_LoadMarkSurfaces( dbspmodel_t *bmod ) for( i = 0; i < bmod->nummarkfaces; i++, in++ ) { - if( *in < 0 || *in >= loadmodel->numsurfaces ) - Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name ); - out[i] = loadmodel->surfaces + *in; + if( *in < 0 || *in >= mod->numsurfaces ) + Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", mod->name ); + out[i] = mod->surfaces + *in; } } else @@ -2005,14 +2005,28 @@ static void Mod_LoadMarkSurfaces( dbspmodel_t *bmod ) for( i = 0; i < bmod->nummarkfaces; i++, in++ ) { - if( *in < 0 || *in >= loadmodel->numsurfaces ) - Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name ); - out[i] = loadmodel->surfaces + *in; + if( *in < 0 || *in >= mod->numsurfaces ) + Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", mod->name ); + out[i] = mod->surfaces + *in; } } } -static void Mod_LoadTextureData( dbspmodel_t *bmod, int textureIndex ) +static qboolean Mod_LooksLikeWaterTexture( const char *name ) +{ + if(( name[0] == '*' && Q_stricmp( name, REF_DEFAULT_TEXTURE )) || name[0] == '!' ) + return true; + + if( !Host_IsQuakeCompatible( )) + { + if( !Q_strncmp( name, "water", 5 ) || !Q_strnicmp( name, "laser", 5 )) + return true; + } + + return false; +} + +static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureIndex ) { #if !XASH_DEDICATED texture_t *texture = NULL; @@ -2026,13 +2040,17 @@ static void Mod_LoadTextureData( dbspmodel_t *bmod, int textureIndex ) if( Host_IsDedicated( )) return; - texture = loadmodel->textures[textureIndex]; + texture = mod->textures[textureIndex]; mipTex = Mod_GetMipTexForTexture( bmod, textureIndex ); if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mipTex->name[0] == '{' ) SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing - usesCustomPalette = Mod_CalcMipTexUsesCustomPalette( bmod, textureIndex ); + // check if this is water to keep the source texture and expand it to RGBA (so ripple effect works) + if( Mod_LooksLikeWaterTexture( mipTex->name )) + SetBits( txFlags, TF_KEEP_SOURCE | TF_EXPAND_SOURCE ); + + usesCustomPalette = Mod_CalcMipTexUsesCustomPalette( mod, bmod, textureIndex ); // check for multi-layered sky texture (quake1 specific) if( bmod->isworld && Q_strncmp( mipTex->name, "sky", 3 ) == 0 && ( mipTex->width / mipTex->height ) == 2 ) @@ -2120,12 +2138,12 @@ static void Mod_LoadTextureData( dbspmodel_t *bmod, int textureIndex ) #endif // !XASH_DEDICATED } -static void Mod_LoadTexture( dbspmodel_t *bmod, int textureIndex ) +static void Mod_LoadTexture( model_t *mod, dbspmodel_t *bmod, int textureIndex ) { texture_t *texture; mip_t *mipTex; - if( textureIndex < 0 || textureIndex >= loadmodel->numtextures ) + if( textureIndex < 0 || textureIndex >= mod->numtextures ) return; mipTex = Mod_GetMipTexForTexture( bmod, textureIndex ); @@ -2134,15 +2152,15 @@ static void Mod_LoadTexture( dbspmodel_t *bmod, int textureIndex ) { // No data for this texture. // Create default texture (some mods require this). - Mod_CreateDefaultTexture( &loadmodel->textures[textureIndex] ); + Mod_CreateDefaultTexture( mod, &mod->textures[textureIndex] ); return; } if( mipTex->name[0] == '\0' ) Q_snprintf( mipTex->name, sizeof( mipTex->name ), "miptex_%i", textureIndex ); - texture = (texture_t *)Mem_Calloc( loadmodel->mempool, sizeof( *texture )); - loadmodel->textures[textureIndex] = texture; + texture = (texture_t *)Mem_Calloc( mod->mempool, sizeof( *texture )); + mod->textures[textureIndex] = texture; // Ensure texture name is lowercase. Q_strnlwr( mipTex->name, texture->name, sizeof( texture->name )); @@ -2150,18 +2168,18 @@ static void Mod_LoadTexture( dbspmodel_t *bmod, int textureIndex ) texture->width = mipTex->width; texture->height = mipTex->height; - Mod_LoadTextureData( bmod, textureIndex ); + Mod_LoadTextureData( mod, bmod, textureIndex ); } -static void Mod_LoadAllTextures( dbspmodel_t *bmod ) +static void Mod_LoadAllTextures( model_t *mod, dbspmodel_t *bmod ) { int i; - for( i = 0; i < loadmodel->numtextures; i++ ) - Mod_LoadTexture( bmod, i ); + for( i = 0; i < mod->numtextures; i++ ) + Mod_LoadTexture( mod, bmod, i ); } -static void Mod_SequenceAnimatedTexture( int baseTextureIndex ) +static void Mod_SequenceAnimatedTexture( model_t *mod, int baseTextureIndex ) { texture_t *anims[10]; texture_t *altanims[10]; @@ -2170,10 +2188,10 @@ static void Mod_SequenceAnimatedTexture( int baseTextureIndex ) int altmax = 0; int candidateIndex; - if( baseTextureIndex < 0 || baseTextureIndex >= loadmodel->numtextures ) + if( baseTextureIndex < 0 || baseTextureIndex >= mod->numtextures ) return; - baseTexture = loadmodel->textures[baseTextureIndex]; + baseTexture = mod->textures[baseTextureIndex]; if( !Mod_NameImpliesTextureIsAnimated( baseTexture )) return; @@ -2204,9 +2222,9 @@ static void Mod_SequenceAnimatedTexture( int baseTextureIndex ) } // Now search the rest of the textures to find all other frames. - for( candidateIndex = baseTextureIndex + 1; candidateIndex < loadmodel->numtextures; candidateIndex++ ) + for( candidateIndex = baseTextureIndex + 1; candidateIndex < mod->numtextures; candidateIndex++ ) { - texture_t *altTexture = loadmodel->textures[candidateIndex]; + texture_t *altTexture = mod->textures[candidateIndex]; if( !Mod_NameImpliesTextureIsAnimated( altTexture )) continue; @@ -2287,12 +2305,12 @@ static void Mod_SequenceAnimatedTexture( int baseTextureIndex ) } } -static void Mod_SequenceAllAnimatedTextures( void ) +static void Mod_SequenceAllAnimatedTextures( model_t *mod ) { int i; - for( i = 0; i < loadmodel->numtextures; i++ ) - Mod_SequenceAnimatedTexture( i ); + for( i = 0; i < mod->numtextures; i++ ) + Mod_SequenceAnimatedTexture( mod, i ); } /* @@ -2300,7 +2318,7 @@ static void Mod_SequenceAllAnimatedTextures( void ) Mod_LoadTextures ================= */ -static void Mod_LoadTextures( dbspmodel_t *bmod ) +static void Mod_LoadTextures( model_t *mod, dbspmodel_t *bmod ) { dmiptexlump_t *lump; @@ -2318,15 +2336,15 @@ static void Mod_LoadTextures( dbspmodel_t *bmod ) if( bmod->texdatasize < 1 || !lump || lump->nummiptex < 1 ) { // no textures - loadmodel->textures = NULL; + mod->textures = NULL; return; } - loadmodel->textures = (texture_t **)Mem_Calloc( loadmodel->mempool, lump->nummiptex * sizeof( texture_t * )); - loadmodel->numtextures = lump->nummiptex; + mod->textures = (texture_t **)Mem_Calloc( mod->mempool, lump->nummiptex * sizeof( texture_t * )); + mod->numtextures = lump->nummiptex; - Mod_LoadAllTextures( bmod ); - Mod_SequenceAllAnimatedTextures(); + Mod_LoadAllTextures( mod, bmod ); + Mod_SequenceAllAnimatedTextures( mod ); } /* @@ -2334,7 +2352,7 @@ static void Mod_LoadTextures( dbspmodel_t *bmod ) Mod_LoadTexInfo ================= */ -static void Mod_LoadTexInfo( dbspmodel_t *bmod ) +static void Mod_LoadTexInfo( model_t *mod, dbspmodel_t *bmod ) { mfaceinfo_t *fout, *faceinfo; int i, j, k, miptex; @@ -2343,7 +2361,7 @@ static void Mod_LoadTexInfo( dbspmodel_t *bmod ) dtexinfo_t *in; // trying to load faceinfo - faceinfo = fout = Mem_Calloc( loadmodel->mempool, bmod->numfaceinfo * sizeof( *fout )); + faceinfo = fout = Mem_Calloc( mod->mempool, bmod->numfaceinfo * sizeof( *fout )); fin = bmod->faceinfo; for( i = 0; i < bmod->numfaceinfo; i++, fin++, fout++ ) @@ -2354,8 +2372,8 @@ static void Mod_LoadTexInfo( dbspmodel_t *bmod ) fout->groupid = fin->groupid; } - loadmodel->texinfo = out = Mem_Calloc( loadmodel->mempool, bmod->numtexinfo * sizeof( *out )); - loadmodel->numtexinfo = bmod->numtexinfo; + mod->texinfo = out = Mem_Calloc( mod->mempool, bmod->numtexinfo * sizeof( *out )); + mod->numtexinfo = bmod->numtexinfo; in = bmod->texinfo; for( i = 0; i < bmod->numtexinfo; i++, in++, out++ ) @@ -2365,9 +2383,9 @@ static void Mod_LoadTexInfo( dbspmodel_t *bmod ) out->vecs[j][k] = in->vecs[j][k]; miptex = in->miptex; - if( miptex < 0 || miptex > loadmodel->numtextures ) + if( miptex < 0 || miptex > mod->numtextures ) miptex = 0; // this is possible? - out->texture = loadmodel->textures[miptex]; + out->texture = mod->textures[miptex]; out->flags = in->flags; // make sure what faceinfo is really exist @@ -2381,7 +2399,7 @@ static void Mod_LoadTexInfo( dbspmodel_t *bmod ) Mod_LoadSurfaces ================= */ -static void Mod_LoadSurfaces( dbspmodel_t *bmod ) +static void Mod_LoadSurfaces( model_t *mod, dbspmodel_t *bmod ) { int test_lightsize = -1; int next_lightofs = -1; @@ -2390,9 +2408,9 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) mextrasurf_t *info; msurface_t *out; - loadmodel->surfaces = out = Mem_Calloc( loadmodel->mempool, bmod->numsurfaces * sizeof( msurface_t )); - info = Mem_Calloc( loadmodel->mempool, bmod->numsurfaces * sizeof( mextrasurf_t )); - loadmodel->numsurfaces = bmod->numsurfaces; + mod->surfaces = out = Mem_Calloc( mod->mempool, bmod->numsurfaces * sizeof( msurface_t )); + info = Mem_Calloc( mod->mempool, bmod->numsurfaces * sizeof( mextrasurf_t )); + mod->numsurfaces = bmod->numsurfaces; // predict samplecount based on bspversion if( bmod->version == Q1BSP_VERSION || bmod->version == QBSP2_VERSION ) @@ -2411,13 +2429,13 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) { dface32_t *in = &bmod->surfaces32[i]; - if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges ) + if(( in->firstedge + in->numedges ) > mod->numsurfedges ) continue; // corrupted level? out->firstedge = in->firstedge; out->numedges = in->numedges; if( in->side ) SetBits( out->flags, SURF_PLANEBACK ); - out->plane = loadmodel->planes + in->planenum; - out->texinfo = loadmodel->texinfo + in->texinfo; + out->plane = mod->planes + in->planenum; + out->texinfo = mod->texinfo + in->texinfo; for( j = 0; j < MAXLIGHTMAPS; j++ ) out->styles[j] = in->styles[j]; @@ -2427,7 +2445,7 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) { dface_t *in = &bmod->surfaces[i]; - if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges ) + if(( in->firstedge + in->numedges ) > mod->numsurfedges ) { Con_Reportf( S_ERROR "bad surface %i from %zu\n", i, bmod->numsurfaces ); continue; @@ -2436,8 +2454,8 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) out->firstedge = in->firstedge; out->numedges = in->numedges; if( in->side ) SetBits( out->flags, SURF_PLANEBACK ); - out->plane = loadmodel->planes + in->planenum; - out->texinfo = loadmodel->texinfo + in->texinfo; + out->plane = mod->planes + in->planenum; + out->texinfo = mod->texinfo + in->texinfo; for( j = 0; j < MAXLIGHTMAPS; j++ ) out->styles[j] = in->styles[j]; @@ -2449,15 +2467,9 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) if( !Q_strncmp( tex->name, "sky", 3 )) SetBits( out->flags, SURF_DRAWSKY ); - if(( tex->name[0] == '*' && Q_stricmp( tex->name, REF_DEFAULT_TEXTURE )) || tex->name[0] == '!' ) + if( Mod_LooksLikeWaterTexture( tex->name )) SetBits( out->flags, SURF_DRAWTURB ); - if( !Host_IsQuakeCompatible( )) - { - if( !Q_strncmp( tex->name, "water", 5 ) || !Q_strnicmp( tex->name, "laser", 5 )) - SetBits( out->flags, SURF_DRAWTURB ); - } - if( !Q_strncmp( tex->name, "scroll", 6 )) SetBits( out->flags, SURF_CONVEYOR ); @@ -2474,9 +2486,9 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) if( FBitSet( out->texinfo->flags, TEX_SPECIAL )) SetBits( out->flags, SURF_DRAWTILED ); - Mod_CalcSurfaceBounds( out ); - Mod_CalcSurfaceExtents( out ); - Mod_CreateFaceBevels( out ); + Mod_CalcSurfaceBounds( mod, out ); + Mod_CalcSurfaceExtents( mod, out ); + Mod_CreateFaceBevels( mod, out ); // grab the second sample to detect colored lighting if( test_lightsize > 0 && lightofs != -1 ) @@ -2505,7 +2517,7 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) #if !XASH_DEDICATED // TODO: Do we need subdivide on server? if( FBitSet( out->flags, SURF_DRAWTURB ) && !Host_IsDedicated() ) - ref.dllFuncs.GL_SubdivideSurface( out ); // cut up polygon for warps + ref.dllFuncs.GL_SubdivideSurface( mod, out ); // cut up polygon for warps #endif } @@ -2535,15 +2547,15 @@ static void Mod_LoadSurfaces( dbspmodel_t *bmod ) Mod_LoadNodes ================= */ -static void Mod_LoadNodes( dbspmodel_t *bmod ) +static void Mod_LoadNodes( model_t *mod, dbspmodel_t *bmod ) { mnode_t *out; int i, j, p; - loadmodel->nodes = out = (mnode_t *)Mem_Calloc( loadmodel->mempool, bmod->numnodes * sizeof( *out )); - loadmodel->numnodes = bmod->numnodes; + mod->nodes = out = (mnode_t *)Mem_Calloc( mod->mempool, bmod->numnodes * sizeof( *out )); + mod->numnodes = bmod->numnodes; - for( i = 0; i < loadmodel->numnodes; i++, out++ ) + for( i = 0; i < mod->numnodes; i++, out++ ) { if( bmod->version == QBSP2_VERSION ) { @@ -2556,15 +2568,15 @@ static void Mod_LoadNodes( dbspmodel_t *bmod ) } p = in->planenum; - out->plane = loadmodel->planes + p; + out->plane = mod->planes + p; out->firstsurface = in->firstface; out->numsurfaces = in->numfaces; for( j = 0; j < 2; j++ ) { p = in->children[j]; - if( p >= 0 ) out->children[j] = loadmodel->nodes + p; - else out->children[j] = (mnode_t *)(loadmodel->leafs + ( -1 - p )); + if( p >= 0 ) out->children[j] = mod->nodes + p; + else out->children[j] = (mnode_t *)(mod->leafs + ( -1 - p )); } } else @@ -2578,21 +2590,21 @@ static void Mod_LoadNodes( dbspmodel_t *bmod ) } p = in->planenum; - out->plane = loadmodel->planes + p; + out->plane = mod->planes + p; out->firstsurface = in->firstface; out->numsurfaces = in->numfaces; for( j = 0; j < 2; j++ ) { p = in->children[j]; - if( p >= 0 ) out->children[j] = loadmodel->nodes + p; - else out->children[j] = (mnode_t *)(loadmodel->leafs + ( -1 - p )); + if( p >= 0 ) out->children[j] = mod->nodes + p; + else out->children[j] = (mnode_t *)(mod->leafs + ( -1 - p )); } } } // sets nodes and leafs - Mod_SetParent( loadmodel->nodes, NULL ); + Mod_SetParent( mod->nodes, NULL ); } /* @@ -2600,18 +2612,18 @@ static void Mod_LoadNodes( dbspmodel_t *bmod ) Mod_LoadLeafs ================= */ -static void Mod_LoadLeafs( dbspmodel_t *bmod ) +static void Mod_LoadLeafs( model_t *mod, dbspmodel_t *bmod ) { mleaf_t *out; int i, j, p; int visclusters = 0; - loadmodel->leafs = out = (mleaf_t *)Mem_Calloc( loadmodel->mempool, bmod->numleafs * sizeof( *out )); - loadmodel->numleafs = bmod->numleafs; + mod->leafs = out = (mleaf_t *)Mem_Calloc( mod->mempool, bmod->numleafs * sizeof( *out )); + mod->numleafs = bmod->numleafs; if( bmod->isworld ) { - visclusters = loadmodel->submodels[0].visleafs; + visclusters = mod->submodels[0].visleafs; world.visbytes = (visclusters + 7) >> 3; world.fatbytes = (visclusters + 31) >> 3; refState.visbytes = world.visbytes; @@ -2635,7 +2647,7 @@ static void Mod_LoadLeafs( dbspmodel_t *bmod ) for( j = 0; j < 4; j++ ) out->ambient_sound_level[j] = in->ambient_level[j]; - out->firstmarksurface = loadmodel->marksurfaces + in->firstmarksurface; + out->firstmarksurface = mod->marksurfaces + in->firstmarksurface; out->nummarksurfaces = in->nummarksurfaces; } else @@ -2654,7 +2666,7 @@ static void Mod_LoadLeafs( dbspmodel_t *bmod ) for( j = 0; j < 4; j++ ) out->ambient_sound_level[j] = in->ambient_level[j]; - out->firstmarksurface = loadmodel->marksurfaces + in->firstmarksurface; + out->firstmarksurface = mod->marksurfaces + in->firstmarksurface; out->nummarksurfaces = in->nummarksurfaces; } @@ -2666,17 +2678,17 @@ static void Mod_LoadLeafs( dbspmodel_t *bmod ) out->cluster = -1; // ignore visofs errors on leaf 0 (solid) - if( p >= 0 && out->cluster >= 0 && loadmodel->visdata ) + if( p >= 0 && out->cluster >= 0 && mod->visdata ) { if( p < bmod->visdatasize ) - out->compressed_vis = loadmodel->visdata + p; + out->compressed_vis = mod->visdata + p; else Con_Reportf( S_WARN "Mod_LoadLeafs: invalid visofs for leaf #%i\n", i ); } } else out->cluster = -1; // no visclusters on bmodels if( p == -1 ) out->compressed_vis = NULL; - else out->compressed_vis = loadmodel->visdata + p; + else out->compressed_vis = mod->visdata + p; // gl underwater warp if( out->contents != CONTENTS_EMPTY ) @@ -2689,11 +2701,11 @@ static void Mod_LoadLeafs( dbspmodel_t *bmod ) } } - if( bmod->isworld && loadmodel->leafs[0].contents != CONTENTS_SOLID ) - Host_Error( "Mod_LoadLeafs: Map %s has leaf 0 is not CONTENTS_SOLID\n", loadmodel->name ); + if( bmod->isworld && mod->leafs[0].contents != CONTENTS_SOLID ) + Host_Error( "Mod_LoadLeafs: Map %s has leaf 0 is not CONTENTS_SOLID\n", mod->name ); // do some final things for world - if( bmod->isworld && Mod_CheckWaterAlphaSupport( bmod )) + if( bmod->isworld && Mod_CheckWaterAlphaSupport( mod, bmod )) SetBits( world.flags, FWORLD_WATERALPHA ); } @@ -2702,12 +2714,12 @@ static void Mod_LoadLeafs( dbspmodel_t *bmod ) Mod_LoadClipnodes ================= */ -static void Mod_LoadClipnodes( dbspmodel_t *bmod ) +static void Mod_LoadClipnodes( model_t *mod, dbspmodel_t *bmod ) { dclipnode32_t *out; int i; - bmod->clipnodes_out = out = (dclipnode32_t *)Mem_Malloc( loadmodel->mempool, bmod->numclipnodes * sizeof( *out )); + bmod->clipnodes_out = out = (dclipnode32_t *)Mem_Malloc( mod->mempool, bmod->numclipnodes * sizeof( *out )); if(( bmod->version == QBSP2_VERSION ) || ( bmod->version == HLBSP_VERSION && bmod->isbsp30ext && bmod->numclipnodes >= MAX_MAP_CLIPNODES_HLBSP )) { @@ -2739,8 +2751,8 @@ static void Mod_LoadClipnodes( dbspmodel_t *bmod ) } } - // FIXME: fill loadmodel->clipnodes? - loadmodel->numclipnodes = bmod->numclipnodes; + // FIXME: fill mod->clipnodes? + mod->numclipnodes = bmod->numclipnodes; } /* @@ -2748,10 +2760,10 @@ static void Mod_LoadClipnodes( dbspmodel_t *bmod ) Mod_LoadVisibility ================= */ -static void Mod_LoadVisibility( dbspmodel_t *bmod ) +static void Mod_LoadVisibility( model_t *mod, dbspmodel_t *bmod ) { - loadmodel->visdata = Mem_Malloc( loadmodel->mempool, bmod->visdatasize ); - memcpy( loadmodel->visdata, bmod->visdata, bmod->visdatasize ); + mod->visdata = Mem_Malloc( mod->mempool, bmod->visdatasize ); + memcpy( mod->visdata, bmod->visdata, bmod->visdatasize ); } /* @@ -2759,17 +2771,17 @@ static void Mod_LoadVisibility( dbspmodel_t *bmod ) Mod_LoadLightVecs ================= */ -static void Mod_LoadLightVecs( dbspmodel_t *bmod ) +static void Mod_LoadLightVecs( model_t *mod, dbspmodel_t *bmod ) { if( bmod->deluxdatasize != bmod->lightdatasize ) { if( bmod->deluxdatasize > 0 ) Con_Printf( S_ERROR "Mod_LoadLightVecs: has mismatched size (%zu should be %zu)\n", bmod->deluxdatasize, bmod->lightdatasize ); - else Mod_LoadDeluxemap( bmod ); // old method + else Mod_LoadDeluxemap( mod, bmod ); // old method return; } - bmod->deluxedata_out = Mem_Malloc( loadmodel->mempool, bmod->deluxdatasize ); + bmod->deluxedata_out = Mem_Malloc( mod->mempool, bmod->deluxdatasize ); memcpy( bmod->deluxedata_out, bmod->deluxdata, bmod->deluxdatasize ); } @@ -2778,7 +2790,7 @@ static void Mod_LoadLightVecs( dbspmodel_t *bmod ) Mod_LoadShadowmap ================= */ -static void Mod_LoadShadowmap( dbspmodel_t *bmod ) +static void Mod_LoadShadowmap( model_t *mod, dbspmodel_t *bmod ) { if( bmod->shadowdatasize != ( bmod->lightdatasize / 3 )) { @@ -2787,7 +2799,7 @@ static void Mod_LoadShadowmap( dbspmodel_t *bmod ) return; } - bmod->shadowdata_out = Mem_Malloc( loadmodel->mempool, bmod->shadowdatasize ); + bmod->shadowdata_out = Mem_Malloc( mod->mempool, bmod->shadowdatasize ); memcpy( bmod->shadowdata_out, bmod->shadowdata, bmod->shadowdatasize ); } @@ -2796,7 +2808,7 @@ static void Mod_LoadShadowmap( dbspmodel_t *bmod ) Mod_LoadLighting ================= */ -static void Mod_LoadLighting( dbspmodel_t *bmod ) +static void Mod_LoadLighting( model_t *mod, dbspmodel_t *bmod ) { int i, lightofs; msurface_t *surf; @@ -2809,9 +2821,9 @@ static void Mod_LoadLighting( dbspmodel_t *bmod ) switch( bmod->lightmap_samples ) { case 1: - if( !Mod_LoadColoredLighting( bmod )) + if( !Mod_LoadColoredLighting( mod, bmod )) { - loadmodel->lightdata = out = (color24 *)Mem_Malloc( loadmodel->mempool, bmod->lightdatasize * sizeof( color24 )); + mod->lightdata = out = (color24 *)Mem_Malloc( mod->mempool, bmod->lightdatasize * sizeof( color24 )); in = bmod->lightdata; // expand the white lighting data @@ -2820,9 +2832,9 @@ static void Mod_LoadLighting( dbspmodel_t *bmod ) } break; case 3: // load colored lighting - loadmodel->lightdata = Mem_Malloc( loadmodel->mempool, bmod->lightdatasize ); - memcpy( loadmodel->lightdata, bmod->lightdata, bmod->lightdatasize ); - SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING ); + mod->lightdata = Mem_Malloc( mod->mempool, bmod->lightdatasize ); + memcpy( mod->lightdata, bmod->lightdata, bmod->lightdatasize ); + SetBits( mod->flags, MODEL_COLORED_LIGHTING ); break; default: Host_Error( "Mod_LoadLighting: bad lightmap sample count %i\n", bmod->lightmap_samples ); @@ -2832,29 +2844,29 @@ static void Mod_LoadLighting( dbspmodel_t *bmod ) // not supposed to be load ? if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA )) { - Mod_LoadLightVecs( bmod ); - Mod_LoadShadowmap( bmod ); + Mod_LoadLightVecs( mod, bmod ); + Mod_LoadShadowmap( mod, bmod ); if( bmod->isworld && bmod->deluxdatasize ) SetBits( world.flags, FWORLD_HAS_DELUXEMAP ); } - surf = loadmodel->surfaces; + surf = mod->surfaces; // setup lightdata pointers - for( i = 0; i < loadmodel->numsurfaces; i++, surf++ ) + for( i = 0; i < mod->numsurfaces; i++, surf++ ) { if( bmod->version == QBSP2_VERSION ) lightofs = bmod->surfaces32[i].lightofs; else lightofs = bmod->surfaces[i].lightofs; - if( loadmodel->lightdata && lightofs != -1 ) + if( mod->lightdata && lightofs != -1 ) { int offset = (lightofs / bmod->lightmap_samples); // NOTE: we divide offset by three because lighting and deluxemap keep their pointers // into three-bytes structs and shadowmap just monochrome - surf->samples = loadmodel->lightdata + offset; + surf->samples = mod->lightdata + offset; // if deluxemap is present setup it too if( bmod->deluxedata_out ) @@ -2886,12 +2898,11 @@ Mod_LoadBmodelLumps loading and processing bmodel ================= */ -qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) +static qboolean Mod_LoadBmodelLumps( model_t *mod, const byte *mod_base, qboolean isworld ) { const dheader_t *header = (const dheader_t *)mod_base; const dextrahdr_t *extrahdr = (const dextrahdr_t *)(mod_base + sizeof( dheader_t )); dbspmodel_t *bmod = &srcmodel; - model_t *mod = loadmodel; char wadvalue[2048]; size_t len = 0; int i, ret, flags = 0; @@ -2900,13 +2911,13 @@ qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) memset( bmod, 0, sizeof( dbspmodel_t )); memset( &loadstat, 0, sizeof( loadstat_t )); - Q_strncpy( loadstat.name, loadmodel->name, sizeof( loadstat.name )); + Q_strncpy( loadstat.name, mod->name, sizeof( loadstat.name )); wadvalue[0] = '\0'; #ifndef SUPPORT_BSP2_FORMAT if( header->version == QBSP2_VERSION ) { - Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, loadmodel->name ); + Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, mod->name ); return false; } #endif @@ -2934,7 +2945,7 @@ qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) srclumps[1].lumpnumber = LUMP_PLANES; break; default: - Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", loadmodel->name, header->version, HLBSP_VERSION ); + Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, header->version, HLBSP_VERSION ); loadstat.numerrors++; return false; } @@ -2965,31 +2976,30 @@ qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld ) Con_DPrintf( "Mod_Load%s: %i warning(s)\n", isworld ? "World" : "Brush", loadstat.numwarnings ); // load into heap - Mod_LoadEntities( bmod ); - Mod_LoadPlanes( bmod ); - Mod_LoadSubmodels( bmod ); - Mod_LoadVertexes( bmod ); - Mod_LoadEdges( bmod ); - Mod_LoadSurfEdges( bmod ); - Mod_LoadTextures( bmod ); - Mod_LoadVisibility( bmod ); - Mod_LoadTexInfo( bmod ); - Mod_LoadSurfaces( bmod ); - Mod_LoadLighting( bmod ); - Mod_LoadMarkSurfaces( bmod ); - Mod_LoadLeafs( bmod ); - Mod_LoadNodes( bmod ); - Mod_LoadClipnodes( bmod ); + Mod_LoadEntities( mod, bmod ); + Mod_LoadPlanes( mod, bmod ); + Mod_LoadSubmodels( mod, bmod ); + Mod_LoadVertexes( mod, bmod ); + Mod_LoadEdges( mod, bmod ); + Mod_LoadSurfEdges( mod, bmod ); + Mod_LoadTextures( mod, bmod ); + Mod_LoadVisibility( mod, bmod ); + Mod_LoadTexInfo( mod, bmod ); + Mod_LoadSurfaces( mod, bmod ); + Mod_LoadLighting( mod, bmod ); + Mod_LoadMarkSurfaces( mod, bmod ); + Mod_LoadLeafs( mod, bmod ); + Mod_LoadNodes( mod, bmod ); + Mod_LoadClipnodes( mod, bmod ); // preform some post-initalization - Mod_MakeHull0 (); - Mod_SetupSubmodels( bmod ); + Mod_MakeHull0( mod ); + Mod_SetupSubmodels( mod, bmod ); if( isworld ) { - loadmodel = mod; // restore pointer to world #if !XASH_DEDICATED - Mod_InitDebugHulls(); // FIXME: build hulls for separate bmodels (shells, medkits etc) + Mod_InitDebugHulls( mod ); // FIXME: build hulls for separate bmodels (shells, medkits etc) world.deluxedata = bmod->deluxedata_out; // deluxemap data pointer world.shadowdata = bmod->shadowdata_out; // occlusion data pointer #endif // XASH_DEDICATED @@ -3151,15 +3161,15 @@ void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded ) { char poolname[MAX_VA_STRING]; - Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", loadmodel->name ); + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); if( loaded ) *loaded = false; - loadmodel->mempool = Mem_AllocPool( poolname ); - loadmodel->type = mod_brush; + mod->mempool = Mem_AllocPool( poolname ); + mod->type = mod_brush; // loading all the lumps into heap - if( !Mod_LoadBmodelLumps( buffer, world.loading )) + if( !Mod_LoadBmodelLumps( mod, buffer, world.loading )) return; // there were errors if( world.loading ) worldmodel = mod; diff --git a/engine/common/mod_local.h b/engine/common/mod_local.h index 40a34ce4..80d8b580 100644 --- a/engine/common/mod_local.h +++ b/engine/common/mod_local.h @@ -118,7 +118,6 @@ typedef struct world_static_s #ifndef REF_DLL extern world_static_t world; extern poolhandle_t com_studiocache; -extern model_t *loadmodel; extern convar_t mod_studiocache; extern convar_t r_wadtextures; extern convar_t r_showhull; @@ -164,7 +163,7 @@ void Mod_PrintWorldStats_f( void ); // // mod_dbghulls.c // -void Mod_InitDebugHulls( void ); +void Mod_InitDebugHulls( model_t *mod ); void Mod_CreatePolygonsForHull( int hullnum ); void Mod_ReleaseHullPolygons( void ); diff --git a/engine/common/mod_studio.c b/engine/common/mod_studio.c index 21f0e2db..4b2cd48e 100644 --- a/engine/common/mod_studio.c +++ b/engine/common/mod_studio.c @@ -851,11 +851,11 @@ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ) char poolname[MAX_VA_STRING]; studiohdr_t *phdr; - Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", loadmodel->name ); + Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name ); if( loaded ) *loaded = false; - loadmodel->mempool = Mem_AllocPool( poolname ); - loadmodel->type = mod_studio; + mod->mempool = Mem_AllocPool( poolname ); + mod->type = mod_studio; phdr = R_StudioLoadHeader( mod, buffer ); if( !phdr ) return; // bad model @@ -886,9 +886,9 @@ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ) // give space for textures and skinrefs size1 = thdr->numtextures * sizeof( mstudiotexture_t ); size2 = thdr->numskinfamilies * thdr->numskinref * sizeof( short ); - mod->cache.data = Mem_Calloc( loadmodel->mempool, phdr->length + size1 + size2 ); - memcpy( loadmodel->cache.data, buffer, phdr->length ); // copy main mdl buffer - phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr + mod->cache.data = Mem_Calloc( mod->mempool, phdr->length + size1 + size2 ); + memcpy( mod->cache.data, buffer, phdr->length ); // copy main mdl buffer + phdr = (studiohdr_t *)mod->cache.data; // get the new pointer on studiohdr phdr->numskinfamilies = thdr->numskinfamilies; phdr->numtextures = thdr->numtextures; phdr->numskinref = thdr->numskinref; @@ -905,52 +905,52 @@ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ) else { // NOTE: don't modify source buffer because it's used for CRC computing - loadmodel->cache.data = Mem_Calloc( loadmodel->mempool, phdr->length ); - memcpy( loadmodel->cache.data, buffer, phdr->length ); - phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr + mod->cache.data = Mem_Calloc( mod->mempool, phdr->length ); + memcpy( mod->cache.data, buffer, phdr->length ); + phdr = (studiohdr_t *)mod->cache.data; // get the new pointer on studiohdr #if !XASH_DEDICATED ref.dllFuncs.Mod_StudioLoadTextures( mod, phdr ); #endif // NOTE: we wan't keep raw textures in memory. just cutoff model pointer above texture base - loadmodel->cache.data = Mem_Realloc( loadmodel->mempool, loadmodel->cache.data, phdr->texturedataindex ); - phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr + mod->cache.data = Mem_Realloc( mod->mempool, mod->cache.data, phdr->texturedataindex ); + phdr = (studiohdr_t *)mod->cache.data; // get the new pointer on studiohdr phdr->length = phdr->texturedataindex; // update model size } } else { // just copy model into memory - loadmodel->cache.data = Mem_Calloc( loadmodel->mempool, phdr->length ); - memcpy( loadmodel->cache.data, buffer, phdr->length ); + mod->cache.data = Mem_Calloc( mod->mempool, phdr->length ); + memcpy( mod->cache.data, buffer, phdr->length ); - phdr = loadmodel->cache.data; + phdr = mod->cache.data; } // setup bounding box if( !VectorCompare( vec3_origin, phdr->bbmin )) { // clipping bounding box - VectorCopy( phdr->bbmin, loadmodel->mins ); - VectorCopy( phdr->bbmax, loadmodel->maxs ); + VectorCopy( phdr->bbmin, mod->mins ); + VectorCopy( phdr->bbmax, mod->maxs ); } else if( !VectorCompare( vec3_origin, phdr->min )) { // movement bounding box - VectorCopy( phdr->min, loadmodel->mins ); - VectorCopy( phdr->max, loadmodel->maxs ); + VectorCopy( phdr->min, mod->mins ); + VectorCopy( phdr->max, mod->maxs ); } else { // well compute bounds from vertices and round to nearest even values - Mod_StudioComputeBounds( phdr, loadmodel->mins, loadmodel->maxs, true ); - RoundUpHullSize( loadmodel->mins ); - RoundUpHullSize( loadmodel->maxs ); + Mod_StudioComputeBounds( phdr, mod->mins, mod->maxs, true ); + RoundUpHullSize( mod->mins ); + RoundUpHullSize( mod->maxs ); } - loadmodel->numframes = Mod_StudioBodyVariations( loadmodel ); - loadmodel->radius = RadiusFromBounds( loadmodel->mins, loadmodel->maxs ); - loadmodel->flags = phdr->flags; // copy header flags + mod->numframes = Mod_StudioBodyVariations( mod ); + mod->radius = RadiusFromBounds( mod->mins, mod->maxs ); + mod->flags = phdr->flags; // copy header flags if( loaded ) *loaded = true; } diff --git a/engine/common/model.c b/engine/common/model.c index 9b5f02d5..3a25cc97 100644 --- a/engine/common/model.c +++ b/engine/common/model.c @@ -31,7 +31,6 @@ poolhandle_t com_studiocache; // cache for submodels CVAR_DEFINE( mod_studiocache, "r_studiocache", "1", FCVAR_ARCHIVE, "enables studio cache for speedup tracing hitboxes" ); CVAR_DEFINE_AUTO( r_wadtextures, "0", 0, "completely ignore textures in the bsp-file if enabled" ); CVAR_DEFINE_AUTO( r_showhull, "0", 0, "draw collision hulls 1-3" ); -model_t *loadmodel; /* =============================================================================== @@ -287,7 +286,6 @@ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) Con_Reportf( "loading %s\n", mod->name ); mod->needload = NL_PRESENT; mod->type = mod_bad; - loadmodel = mod; // call the apropriate loader switch( *(uint *)buf ) diff --git a/engine/common/net_ws.c b/engine/common/net_ws.c index df34be20..49738d3e 100644 --- a/engine/common/net_ws.c +++ b/engine/common/net_ws.c @@ -274,8 +274,8 @@ static void NET_NetadrToSockadr( netadr_t *a, struct sockaddr_storage *s ) else if( a->type == NA_IP ) { ((struct sockaddr_in *)s)->sin_family = AF_INET; - ((struct sockaddr_in *)s)->sin_addr.s_addr = *(uint32_t *)&a->ip; ((struct sockaddr_in *)s)->sin_port = a->port; + ((struct sockaddr_in *)s)->sin_addr.s_addr = a->ip4; } else if( a->type6 == NA_IP6 ) { @@ -314,7 +314,7 @@ static void NET_SockadrToNetadr( const struct sockaddr_storage *s, netadr_t *a ) if( s->ss_family == AF_INET ) { a->type = NA_IP; - *(int *)&a->ip = ((struct sockaddr_in *)s)->sin_addr.s_addr; + a->ip4 = ((struct sockaddr_in *)s)->sin_addr.s_addr; a->port = ((struct sockaddr_in *)s)->sin_port; } else if( s->ss_family == AF_INET6 ) @@ -444,6 +444,7 @@ static void NET_InitializeCriticalSections( void ) void NET_ResolveThread( void ) { struct sockaddr_storage addr; + qboolean res; RESOLVE_DBG( "[resolve thread] starting resolve for " ); RESOLVE_DBG( nsthread.hostname ); @@ -453,13 +454,14 @@ void NET_ResolveThread( void ) RESOLVE_DBG( " with gethostbyname\n" ); #endif - if( NET_GetHostByName( nsthread.hostname, nsthread.family, &addr )) + if(( res = NET_GetHostByName( nsthread.hostname, nsthread.family, &addr ))) RESOLVE_DBG( "[resolve thread] success\n" ); else RESOLVE_DBG( "[resolve thread] failed\n" ); mutex_lock( &nsthread.mutexres ); nsthread.addr = addr; nsthread.busy = false; + nsthread.result = res ? NET_EAI_OK : NET_EAI_NONAME; RESOLVE_DBG( "[resolve thread] returning result\n" ); mutex_unlock( &nsthread.mutexres ); RESOLVE_DBG( "[resolve thread] exiting thread\n" ); @@ -544,6 +546,7 @@ static net_gai_state_t NET_StringToSockaddr( const char *s, struct sockaddr_stor memset( &nsthread.addr, 0, sizeof( nsthread.addr )); detach_thread( nsthread.thread ); + asyncfailed = false; } else { diff --git a/engine/common/netchan.h b/engine/common/netchan.h index a1c3cbd3..fd69fd7a 100644 --- a/engine/common/netchan.h +++ b/engine/common/netchan.h @@ -70,6 +70,7 @@ GNU General Public License for more details. #define NET_MAX_MESSAGE PAD_NUMBER(( NET_MAX_PAYLOAD + HEADER_BYTES ), 16 ) #define MASTERSERVER_ADR "mentality.rip:27010" +#define MASTERSERVER_ADR_TEST "mentality.rip:27011" #define MS_SCAN_REQUEST "1\xFF" "0.0.0.0:0\0" #define PORT_MASTER 27010 diff --git a/engine/common/sys_con.c b/engine/common/sys_con.c index 30c19d68..f6c78505 100644 --- a/engine/common/sys_con.c +++ b/engine/common/sys_con.c @@ -149,7 +149,7 @@ void Sys_InitLog( void ) s_ld.logfileno = fileno( s_ld.logfile ); fprintf( s_ld.logfile, "=================================================================================\n" ); - fprintf( s_ld.logfile, "\t%s (build %i) started at %s\n", s_ld.title, Q_buildnum(), Q_timestamp( TIME_FULL ) ); + fprintf( s_ld.logfile, "\t%s (build %i commit %s (%s-%s)) started at %s\n", s_ld.title, Q_buildnum(), Q_buildcommit(), Q_buildos(), Q_buildarch(), Q_timestamp( TIME_FULL ) ); fprintf( s_ld.logfile, "=================================================================================\n" ); } } diff --git a/engine/common/system.c b/engine/common/system.c index 08ed09fe..717861f2 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -649,3 +649,31 @@ qboolean Sys_NewInstance( const char *gamedir ) return false; } + + +/* +================== +Sys_GetNativeObject + +Get platform-specific native object +================== +*/ +void *Sys_GetNativeObject( const char *obj ) +{ + void *ptr; + + if( !COM_CheckString( obj )) + return NULL; + + ptr = FS_GetNativeObject( obj ); + + if( ptr ) + return ptr; + + // Backend should consider that obj is case-sensitive +#if XASH_ANDROID + ptr = Android_GetNativeObject( obj ); +#endif // XASH_ANDROID + + return ptr; +} diff --git a/engine/common/system.h b/engine/common/system.h index 8260c38e..a14a1a07 100644 --- a/engine/common/system.h +++ b/engine/common/system.h @@ -70,6 +70,7 @@ void Sys_InitLog( void ); void Sys_CloseLog( void ); void Sys_Quit( void ) NORETURN; qboolean Sys_NewInstance( const char *gamedir ); +void *Sys_GetNativeObject( const char *obj ); // // sys_con.c diff --git a/engine/platform/platform.h b/engine/platform/platform.h index ce889442..657b283d 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -38,6 +38,11 @@ void Platform_MessageBox( const char *title, const char *message, qboolean paren qboolean Sys_DebuggerPresent( void ); // optional, see Sys_DebugBreak void Platform_SetStatus( const char *status ); +// legacy iOS port functions +#if TARGET_OS_IOS +const char *IOS_GetDocsDir( void ); +#endif // TARGET_OS_IOS + #if XASH_WIN32 || XASH_LINUX #define XASH_PLATFORM_HAVE_STATUS 1 #else @@ -135,17 +140,6 @@ static inline void Platform_Shutdown( void ) #endif } -static inline void *Platform_GetNativeObject( const char *name ) -{ - void *ptr = NULL; - -#if XASH_ANDROID - ptr = Android_GetNativeObject( name ); -#endif - - return ptr; -} - /* ============================================================================== @@ -154,7 +148,6 @@ static inline void *Platform_GetNativeObject( const char *name ) ============================================================================== */ void Platform_Vibrate( float life, char flags ); -void*Platform_GetNativeObject( const char *name ); /* ============================================================================== diff --git a/engine/platform/sdl/events.c b/engine/platform/sdl/events.c index 7bff17cb..2e9531a1 100644 --- a/engine/platform/sdl/events.c +++ b/engine/platform/sdl/events.c @@ -128,7 +128,7 @@ static qboolean SDLash_IsInstanceIDAGameController( SDL_JoystickID joyId ) return true; return false; #else - if( SDL_GameControllerFromInstanceID( joyId ) ) + if( SDL_GameControllerFromInstanceID( joyId ) != NULL ) return true; return false; #endif @@ -148,7 +148,7 @@ static void SDLash_KeyEvent( SDL_KeyboardEvent key ) #else int keynum = key.keysym.sym; #endif - qboolean numLock = SDL_GetModState() & KMOD_NUM; + qboolean numLock = FBitSet( SDL_GetModState(), KMOD_NUM ); #if XASH_ANDROID if( keynum == SDL_SCANCODE_VOLUMEUP || keynum == SDL_SCANCODE_VOLUMEDOWN ) @@ -159,7 +159,7 @@ static void SDLash_KeyEvent( SDL_KeyboardEvent key ) if( SDL_IsTextInputActive() && down && cls.key_dest != key_game ) { - if( SDL_GetModState() & KMOD_CTRL ) + if( FBitSet( SDL_GetModState(), KMOD_CTRL )) { if( keynum >= SDL_SCANCODE_A && keynum <= SDL_SCANCODE_Z ) { @@ -176,7 +176,7 @@ static void SDLash_KeyEvent( SDL_KeyboardEvent key ) if( isprint( keynum ) ) { - if( SDL_GetModState() & KMOD_SHIFT ) + if( FBitSet( SDL_GetModState(), KMOD_SHIFT )) { keynum = Key_ToUpper( keynum ); } diff --git a/engine/platform/sdl/vid_sdl.c b/engine/platform/sdl/vid_sdl.c index 2d40abd9..3fa71436 100644 --- a/engine/platform/sdl/vid_sdl.c +++ b/engine/platform/sdl/vid_sdl.c @@ -159,8 +159,8 @@ void *SW_LockBuffer( void ) void *pixels; int stride; - if( SDL_LockTexture(sw.tex, NULL, &pixels, &stride ) ) - Sys_Error("%s", SDL_GetError()); + if( SDL_LockTexture(sw.tex, NULL, &pixels, &stride ) < 0 ) + Sys_Error( "%s: %s", __func__, SDL_GetError( )); return pixels; } @@ -273,7 +273,7 @@ static void R_InitVideoModes( void ) int j; SDL_DisplayMode mode; - if( SDL_GetDisplayMode( displayIndex, i, &mode ) ) + if( SDL_GetDisplayMode( displayIndex, i, &mode ) < 0 ) { Msg( "SDL_GetDisplayMode: %s\n", SDL_GetError() ); continue; @@ -420,12 +420,16 @@ static qboolean WIN_SetWindowIcon( HICON ico ) { SDL_SysWMinfo wminfo; - if( SDL_GetWindowWMInfo( host.hWnd, &wminfo )) + SDL_VERSION( &wminfo.version ); + + if( SDL_GetWindowWMInfo( host.hWnd, &wminfo ) == SDL_TRUE ) { SendMessage( wminfo.info.win.window, WM_SETICON, ICON_SMALL, (LONG_PTR)ico ); SendMessage( wminfo.info.win.window, WM_SETICON, ICON_BIG, (LONG_PTR)ico ); return true; } + + Con_Reportf( S_ERROR "%s: %s", __func__, SDL_GetError( )); return false; } #endif @@ -439,6 +443,25 @@ GL_GetProcAddress void *GL_GetProcAddress( const char *name ) { void *func = SDL_GL_GetProcAddress( name ); +#if !SDL_VERSION_ATLEAST( 2, 0, 6 ) && XASH_POSIX + if( !func && Sys_CheckParm( "-egl" )) + { + /* + * SDL2 has broken SDL_GL_GetProcAddress until this commit if using egl: + * https://github.com/libsdl-org/SDL/commit/466ba57d42d244e80357e9ad3011c50af30ed225 + * so call eglGetProcAddress directly + * */ + static void *(*peglGetProcAddress)( const char * ); + if( !peglGetProcAddress ) + { + void *lib = dlopen( "libEGL.so", RTLD_NOW ); + if( lib ) + *(void**)&peglGetProcAddress = dlsym( lib, "eglGetProcAddress" ); + } + if( peglGetProcAddress ) + func = peglGetProcAddress( name ); + } +#endif #if XASH_PSVITA // try to find in main module @@ -474,8 +497,8 @@ void GL_UpdateSwapInterval( void ) { ClearBits( gl_vsync.flags, FCVAR_CHANGED ); - if( SDL_GL_SetSwapInterval( gl_vsync.value ) ) - Con_Reportf( S_ERROR "SDL_GL_SetSwapInterval: %s\n", SDL_GetError( ) ); + if( SDL_GL_SetSwapInterval( gl_vsync.value ) < 0 ) + Con_Reportf( S_ERROR "SDL_GL_SetSwapInterval: %s\n", SDL_GetError( )); } #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } @@ -524,7 +547,7 @@ GL_UpdateContext static qboolean GL_UpdateContext( void ) { #if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( SDL_GL_MakeCurrent( host.hWnd, glw_state.context )) + if( SDL_GL_MakeCurrent( host.hWnd, glw_state.context ) < 0 ) { Con_Reportf( S_ERROR "GL_UpdateContext: %s\n", SDL_GetError()); return GL_DeleteContext(); @@ -581,7 +604,7 @@ static qboolean VID_SetScreenResolution( int width, int height, window_mode_t wi want.w = width; want.h = height; - if( SDL_GetClosestDisplayMode( 0, &want, &got ) < 0 ) + if( SDL_GetClosestDisplayMode( 0, &want, &got ) == NULL ) { Con_Printf( S_ERROR "%s: SDL_GetClosestDisplayMode: %s", __func__, SDL_GetError( )); return false; @@ -627,9 +650,9 @@ void VID_RestoreScreenResolution( void ) #endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } +#if SDL_VERSION_ATLEAST( 2, 0, 0 ) static void VID_SetWindowIcon( SDL_Window *hWnd ) { -#if SDL_VERSION_ATLEAST( 2, 0, 0 ) rgbdata_t *icon = NULL; char iconpath[MAX_STRING]; const char *localIcoPath; @@ -667,8 +690,8 @@ static void VID_SetWindowIcon( SDL_Window *hWnd ) #if XASH_WIN32 // ICO support only for Win32 WIN_SetWindowIcon( LoadIcon( host.hInst, MAKEINTRESOURCE( 101 ))); #endif -#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) } +#endif // SDL_VERSION_ATLEAST( 2, 0, 0 ) static qboolean VID_CreateWindowWithSafeGL( const char *wndname, int xpos, int ypos, int w, int h, uint32_t flags ) { @@ -794,6 +817,7 @@ qboolean VID_CreateWindow( int width, int height, window_mode_t window_mode ) else VID_RestoreScreenResolution(); #endif + VID_SetWindowIcon( host.hWnd ); SDL_ShowWindow( host.hWnd ); if( glw_state.context_type == REF_SOFTWARE ) @@ -1040,8 +1064,12 @@ qboolean R_Init_Video( const int type ) refState.desktopBitsPixel = 16; #endif +#ifdef SDL_HINT_QTWAYLAND_WINDOW_FLAGS SDL_SetHint( SDL_HINT_QTWAYLAND_WINDOW_FLAGS, "OverridesSystemGestures" ); +#endif +#ifdef SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION SDL_SetHint( SDL_HINT_QTWAYLAND_CONTENT_ORIENTATION, "landscape" ); +#endif #if SDL_VERSION_ATLEAST( 2, 0, 0 ) && !XASH_WIN32 SDL_SetHint( "SDL_VIDEO_X11_XRANDR", "1" ); @@ -1067,7 +1095,7 @@ qboolean R_Init_Video( const int type ) // refdll can request some attributes GL_SetupAttributes( ); - if( SDL_GL_LoadLibrary( EGL_LIB ) ) + if( SDL_GL_LoadLibrary( EGL_LIB ) < 0 ) { Con_Reportf( S_ERROR "Couldn't initialize OpenGL: %s\n", SDL_GetError()); return false; @@ -1108,7 +1136,11 @@ rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mod #if SDL_VERSION_ATLEAST( 2, 0, 0 ) SDL_DisplayMode displayMode; - SDL_GetCurrentDisplayMode( 0, &displayMode ); + if( SDL_GetCurrentDisplayMode( 0, &displayMode ) < 0 ) + { + Con_Printf( S_ERROR "SDL_GetCurrentDisplayMode: %s", SDL_GetError( )); + return rserr_invalid_mode; + } // check our desktop attributes refState.desktopBitsPixel = SDL_BITSPERPIXEL( displayMode.format ); @@ -1137,8 +1169,11 @@ rserr_t R_ChangeDisplaySettings( int width, int height, window_mode_t window_mod VID_RestoreScreenResolution(); #if SDL_VERSION_ATLEAST( 2, 0, 0 ) - if( SDL_SetWindowFullscreen( host.hWnd, 0 )) + if( SDL_SetWindowFullscreen( host.hWnd, 0 ) < 0 ) + { + Con_Printf( S_ERROR "SDL_SetWindowFullscreen: %s", SDL_GetError( )); return rserr_invalid_fullscreen; + } #if SDL_VERSION_ATLEAST( 2, 0, 5 ) SDL_SetWindowResizable( host.hWnd, SDL_TRUE ); #endif diff --git a/engine/ref_api.h b/engine/ref_api.h index 44ebecfc..a90b5799 100644 --- a/engine/ref_api.h +++ b/engine/ref_api.h @@ -333,8 +333,6 @@ typedef struct ref_api_s model_t *(*Mod_ForName)( const char *name, qboolean crash, qboolean trackCRC ); void *(*Mod_Extradata)( int type, model_t *model ); struct model_s *(*pfnGetModelByIndex)( int index ); // CL_ModelHandle - struct model_s *(*Mod_GetCurrentLoadingModel)( void ); // loadmodel - void (*Mod_SetCurrentLoadingModel)( struct model_s* ); // loadmodel // remap struct remap_info_s *(*CL_GetRemapInfoForEntity)( cl_entity_t *e ); @@ -513,7 +511,7 @@ typedef struct ref_interface_s // bmodel void (*R_InitSkyClouds)( struct mip_s *mt, struct texture_s *tx, qboolean custom_palette ); - void (*GL_SubdivideSurface)( msurface_t *fa ); + void (*GL_SubdivideSurface)( model_t *mod, msurface_t *fa ); void (*CL_RunLightStyles)( void ); // sprites diff --git a/engine/server/server.h b/engine/server/server.h index 20770534..7667381a 100644 --- a/engine/server/server.h +++ b/engine/server/server.h @@ -360,7 +360,6 @@ typedef struct typedef struct { qboolean initialized; // sv_init has completed - qboolean game_library_loaded; // is game library loaded in SV_InitGame double timestart; // just for profiling int maxclients; // server max clients diff --git a/engine/server/sv_custom.c b/engine/server/sv_custom.c index cb034efb..f18ad6a3 100644 --- a/engine/server/sv_custom.c +++ b/engine/server/sv_custom.c @@ -121,8 +121,10 @@ void SV_ParseConsistencyResponse( sv_client_t *cl, sizebuf_t *msg ) { value = MSG_ReadUBitLong( msg, 32 ); + LittleLongSW( value ); + // will be compare only first 4 bytes - if( value != *(int *)r->rgucMD5_hash ) + if( memcmp( &value, r->rgucMD5_hash, 4 )) badresindex = idx + 1; } else diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index 0231198f..7462ac95 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -3031,7 +3031,7 @@ void SV_SetStringArrayMode( qboolean dynamic ) #endif } -#if XASH_64BIT && !XASH_WIN32 && !XASH_APPLE && !XASH_NSWITCH +#if XASH_64BIT && !XASH_WIN32 && !XASH_APPLE && !XASH_NSWITCH && !XASH_ANDROID #define USE_MMAP #include #endif @@ -5130,7 +5130,6 @@ void SV_UnloadProgs( void ) Mod_ResetStudioAPI (); - svs.game_library_loaded = false; COM_FreeLibrary( svgame.hInstance ); Mem_FreePool( &svgame.mempool ); memset( &svgame, 0, sizeof( svgame )); @@ -5148,7 +5147,14 @@ qboolean SV_LoadProgs( const char *name ) static playermove_t gpMove; edict_t *e; - if( svgame.hInstance ) SV_UnloadProgs(); + if( svgame.hInstance ) + { +#if XASH_WIN32 + SV_UnloadProgs(); +#else // XASH_WIN32 + return true; +#endif // XASH_WIN32 + } // fill it in svgame.pmove = &gpMove; diff --git a/engine/server/sv_init.c b/engine/server/sv_init.c index ce2c7ac3..781d5793 100644 --- a/engine/server/sv_init.c +++ b/engine/server/sv_init.c @@ -706,7 +706,7 @@ qboolean SV_InitGame( void ) { string dllpath; - if( svs.game_library_loaded ) + if( svgame.hInstance ) return true; // first initialize? @@ -721,7 +721,6 @@ qboolean SV_InitGame( void ) } // client frames will be allocated in SV_ClientConnect - svs.game_library_loaded = true; return true; } @@ -1042,8 +1041,9 @@ qboolean SV_SpawnServer( const char *mapname, const char *startspot, qboolean ba // force normal player collisions for single player if( svs.maxclients == 1 ) Cvar_SetValue( "sv_clienttrace", 1 ); - // make sure what server name doesn't contain path and extension - COM_FileBase( mapname, sv.name, sizeof( sv.name )); + // allow loading maps from subdirectories, strip extension anyway + Q_strncpy( sv.name, mapname, sizeof( sv.name )); + COM_StripExtension( sv.name ); // precache and static commands can be issued during map initialization Host_SetServerState( ss_loading ); @@ -1115,28 +1115,6 @@ int SV_GetMaxClients( void ) return svs.maxclients; } -qboolean SV_InitGameProgs( void ) -{ - string dllpath; - - if( svgame.hInstance ) return true; // already loaded - - COM_GetCommonLibraryPath( LIBRARY_SERVER, dllpath, sizeof( dllpath )); - - // just try to initialize - SV_LoadProgs( dllpath ); - - return false; -} - -void SV_FreeGameProgs( void ) -{ - if( svs.initialized ) return; // server is active - - // unload progs (free cvars and commands) - SV_UnloadProgs(); -} - /* ================ SV_ExecLoadLevel diff --git a/engine/server/sv_main.c b/engine/server/sv_main.c index 64176adc..9b30ab17 100644 --- a/engine/server/sv_main.c +++ b/engine/server/sv_main.c @@ -48,7 +48,8 @@ CVAR_DEFINE_AUTO( sv_send_logos, "1", 0, "send custom decal logo to other player CVAR_DEFINE_AUTO( sv_send_resources, "1", 0, "allow to download missed resources for players" ); CVAR_DEFINE_AUTO( sv_logbans, "0", 0, "print into the server log info about player bans" ); CVAR_DEFINE_AUTO( sv_allow_upload, "1", FCVAR_SERVER, "allow uploading custom resources on a server" ); -CVAR_DEFINE_AUTO( sv_allow_download, "1", FCVAR_SERVER, "allow downloading custom resources to the client" ); +CVAR_DEFINE( sv_allow_download, "sv_allowdownload", "1", FCVAR_SERVER, "allow downloading custom resources to the client" ); +static CVAR_DEFINE_AUTO( sv_allow_dlfile, "1", 0, "compatibility cvar, does nothing" ); CVAR_DEFINE_AUTO( sv_uploadmax, "0.5", FCVAR_SERVER, "max size to upload custom resources (500 kB as default)" ); CVAR_DEFINE_AUTO( sv_downloadurl, "", FCVAR_PROTECTED, "location from which clients can download missing files" ); CVAR_DEFINE( sv_consistency, "mp_consistency", "1", FCVAR_SERVER, "enbale consistency check in multiplayer" ); @@ -925,6 +926,7 @@ void SV_Init( void ) Cvar_RegisterVariable( &sv_unlagsamples ); Cvar_RegisterVariable( &sv_allow_upload ); Cvar_RegisterVariable( &sv_allow_download ); + Cvar_RegisterVariable( &sv_allow_dlfile ); Cvar_RegisterVariable( &sv_send_logos ); Cvar_RegisterVariable( &sv_send_resources ); Cvar_RegisterVariable( &sv_uploadmax ); @@ -1089,7 +1091,9 @@ void SV_Shutdown( const char *finalmsg ) if( CL_IsPlaybackDemo( )) CL_Drop(); - SV_UnloadProgs (); +#if XASH_WIN32 + SV_UnloadProgs(); +#endif // XASH_WIN32 return; } @@ -1106,7 +1110,10 @@ void SV_Shutdown( const char *finalmsg ) NET_MasterShutdown(); NET_Config( false, false ); - SV_UnloadProgs (); + SV_DeactivateServer(); +#if XASH_WIN32 + SV_UnloadProgs(); +#endif // XASH_WIN32 CL_Drop(); // free current level diff --git a/engine/server/sv_pmove.c b/engine/server/sv_pmove.c index b519c244..c47c74fd 100644 --- a/engine/server/sv_pmove.c +++ b/engine/server/sv_pmove.c @@ -80,8 +80,8 @@ static qboolean SV_CopyEdictToPhysEnt( physent_t *pe, edict_t *ed ) } else { - // otherwise copy the classname - Q_strncpy( pe->name, STRING( ed->v.classname ), sizeof( pe->name )); + // otherwise copy the modelname + Q_strncpy( pe->name, mod->name, sizeof( pe->name )); } pe->model = pe->studiomodel = NULL; diff --git a/filesystem/VFileSystem009.cpp b/filesystem/VFileSystem009.cpp index 7bd53f69..28582089 100644 --- a/filesystem/VFileSystem009.cpp +++ b/filesystem/VFileSystem009.cpp @@ -150,7 +150,10 @@ public: void RemoveFile( const char *path, const char *id ) override { - FS_Delete( path ); // FS_Delete is aware of slashes + char dir[MAX_VA_STRING], fullpath[MAX_VA_STRING]; + + Q_snprintf( fullpath, sizeof( fullpath ), "%s/%s", IdToDir( dir, sizeof( dir ), id ), path ); + FS_Delete( fullpath ); // FS_Delete is aware of slashes } void CreateDirHierarchy( const char *path, const char *id ) override diff --git a/filesystem/VFileSystem009.h b/filesystem/VFileSystem009.h index 60437154..e48f4b7a 100644 --- a/filesystem/VFileSystem009.h +++ b/filesystem/VFileSystem009.h @@ -150,6 +150,4 @@ public: virtual void AddSearchPathNoWrite(const char *, const char *) = 0; /* linkage=_ZN11IFileSystem20AddSearchPathNoWriteEPKcS1_ */ }; -#define FILESYSTEM_INTERFACE_VERSION "VFileSystem009" // never change this! - #endif // VFILESYSTEM009_H diff --git a/filesystem/filesystem.c b/filesystem/filesystem.c index f22f4fed..7b622350 100644 --- a/filesystem/filesystem.c +++ b/filesystem/filesystem.c @@ -770,13 +770,13 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool else if( !Q_stricmp( token, "secure" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->secure = Q_atoi( token ); + GameInfo->secure = Q_atoi( token ) ? true : false; } // valid for both else if( !Q_stricmp( token, "nomodels" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->nomodels = Q_atoi( token ); + GameInfo->nomodels = Q_atoi( token ) ? true : false; } else if( !Q_stricmp( token, isGameInfo ? "max_edicts" : "edicts" )) { @@ -844,17 +844,17 @@ void FS_ParseGenericGameInfo( gameinfo_t *GameInfo, const char *buf, const qbool else if( !Q_stricmp( token, "noskills" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->noskills = Q_atoi( token ); + GameInfo->noskills = Q_atoi( token ) ? true : false; } else if( !Q_stricmp( token, "render_picbutton_text" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->render_picbutton_text = Q_atoi( token ); + GameInfo->render_picbutton_text = Q_atoi( token ) ? true : false; } else if( !Q_stricmp( token, "internal_vgui_support" )) { pfile = COM_ParseFile( pfile, token, sizeof( token )); - GameInfo->internal_vgui_support = Q_atoi( token ); + GameInfo->internal_vgui_support = Q_atoi( token ) ? true : false; } else if( !Q_stricmp( token, "quicksave_aged_count" )) { @@ -1386,7 +1386,7 @@ static void _Sys_Error( const char *fmt, ... ) exit( 1 ); } -static void *_Platform_GetNativeObject_stub( const char *object ) +static void *Sys_GetNativeObject_stub( const char *object ) { return NULL; } @@ -2841,7 +2841,7 @@ fs_interface_t g_engfuncs = _Mem_Alloc, _Mem_Realloc, _Mem_Free, - _Platform_GetNativeObject_stub + Sys_GetNativeObject_stub }; static qboolean FS_InitInterface( int version, fs_interface_t *engfuncs ) @@ -2883,9 +2883,9 @@ static qboolean FS_InitInterface( int version, fs_interface_t *engfuncs ) Con_Reportf( "filesystem_stdio: custom memory allocation functions found\n" ); } - if( engfuncs->_Platform_GetNativeObject ) + if( engfuncs->_Sys_GetNativeObject ) { - g_engfuncs._Platform_GetNativeObject = engfuncs->_Platform_GetNativeObject; + g_engfuncs._Sys_GetNativeObject = engfuncs->_Sys_GetNativeObject; Con_Reportf( "filesystem_stdio: custom platform-specific functions found\n" ); } diff --git a/filesystem/filesystem.h b/filesystem/filesystem.h index d2316087..031a6e0f 100644 --- a/filesystem/filesystem.h +++ b/filesystem/filesystem.h @@ -32,7 +32,8 @@ extern "C" #endif // __cplusplus #define FS_API_VERSION 2 // not stable yet! -#define FS_API_CREATEINTERFACE_TAG "XashFileSystem002" // follow FS_API_VERSION!!! +#define FS_API_CREATEINTERFACE_TAG "XashFileSystem002" // follow FS_API_VERSION!!! +#define FILESYSTEM_INTERFACE_VERSION "VFileSystem009" // never change this! // search path flags enum @@ -210,7 +211,7 @@ typedef struct fs_interface_t void (*_Mem_Free)( void *data, const char *filename, int fileline ); // platform - void *(*_Platform_GetNativeObject)( const char *object ); + void *(*_Sys_GetNativeObject)( const char *object ); } fs_interface_t; typedef int (*FSAPI)( int version, fs_api_t *api, fs_globals_t **globals, fs_interface_t *interface ); diff --git a/filesystem/filesystem_internal.h b/filesystem/filesystem_internal.h index 29e78f2a..acaa9fa3 100644 --- a/filesystem/filesystem_internal.h +++ b/filesystem/filesystem_internal.h @@ -127,7 +127,7 @@ extern const fs_archive_t g_archives[]; #define Con_DPrintf (*g_engfuncs._Con_DPrintf) #define Con_Reportf (*g_engfuncs._Con_Reportf) #define Sys_Error (*g_engfuncs._Sys_Error) -#define Platform_GetNativeObject (*g_engfuncs._Platform_GetNativeObject) +#define Sys_GetNativeObject (*g_engfuncs._Sys_GetNativeObject) // // filesystem.c diff --git a/filesystem/tests/interface.cpp b/filesystem/tests/interface.cpp index 5bdbe001..833127e0 100644 --- a/filesystem/tests/interface.cpp +++ b/filesystem/tests/interface.cpp @@ -18,7 +18,6 @@ typedef void *HMODULE; HMODULE g_hModule; FSAPI g_pfnGetFSAPI; -typedef void *(*pfnCreateInterface_t)( const char *, int * ); pfnCreateInterface_t g_pfnCreateInterface; fs_api_t g_fs; fs_globals_t *g_nullglobals; diff --git a/public/crtlib.c b/public/crtlib.c index a5565122..bf6ab578 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -645,10 +645,10 @@ void COM_ExtractFilePath( const char *path, char *dest ) const char *src = path + Q_strlen( path ) - 1; // back up until a \ or the start - while( src != path && !(*(src - 1) == '\\' || *(src - 1) == '/' )) + while( src > path && !(*(src - 1) == '\\' || *(src - 1) == '/' )) src--; - if( src != path ) + if( src > path ) { memcpy( dest, path, src - path ); dest[src - path - 1] = 0; // cutoff backslash diff --git a/public/tests/test_efp.c b/public/tests/test_efp.c new file mode 100644 index 00000000..c7aafddf --- /dev/null +++ b/public/tests/test_efp.c @@ -0,0 +1,40 @@ +#include +#include "crtlib.h" +#include + +int Test_ExtractFilePath( void ) +{ + char dst[64]; + const char *strings[] = + { + "dir/file", "dir", + "bark\\meow", "bark", + "nopath", "", + "knee/deep/in/paths", "knee/deep/in", + // yes, it removes the behavior/ even if it might be technically a directory + "keep/the/original/func/behavior/", "keep/the/original/func", + "backslashes\\are\\annoying\\af", "backslashes\\are\\annoying", + "", "" + }; + size_t i; + + for( i = 0; i < sizeof( strings ) / sizeof( strings[0] ); i += 2 ) + { + COM_ExtractFilePath( strings[i], dst ); + if( Q_strcmp( dst, strings[i+1] )) + { + printf( "%s %s %s\n", strings[i], strings[i+1], dst ); + return (i >> 1) + 1; + } + } + + return 0; +} + +int main( void ) +{ + if( Test_ExtractFilePath( )) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/public/wscript b/public/wscript index c1002e78..4f7b24f4 100644 --- a/public/wscript +++ b/public/wscript @@ -12,8 +12,7 @@ def options(opt): return def configure(conf): - # stub - return + conf.define('XASH_BUILD_COMMIT', conf.env.GIT_VERSION if conf.env.GIT_VERSION else 'notset') def build(bld): bld(name = 'sdk_includes', export_includes = '. ../common ../pm_shared ../engine') @@ -28,6 +27,7 @@ def build(bld): 'strings': 'tests/test_strings.c', 'build': 'tests/test_build.c', 'filebase': 'tests/test_filebase.c', + 'efp': 'tests/test_efp.c', } for i in tests: diff --git a/ref/gl/gl2_shim/gl2_shim.c b/ref/gl/gl2_shim/gl2_shim.c index ecb0c4a2..63b2c9e1 100644 --- a/ref/gl/gl2_shim/gl2_shim.c +++ b/ref/gl/gl2_shim/gl2_shim.c @@ -1014,7 +1014,7 @@ static void APIENTRY GL2_TexImage2D( GLenum target, GLint level, GLint internalf } internalformat = format; } - if( internalformat == GL_LUMINANCE8_ALPHA8 || internalformat == GL_RGB ) + if( internalformat == GL_LUMINANCE8_ALPHA8 || internalformat == GL_RGB || internalformat == GL_RGB8 || internalformat == GL_RGB5 ) internalformat = GL_RGBA; rpglTexImage2D( target, level, internalformat, width, height, border, format, type, data ); if( data != pixels ) @@ -1309,9 +1309,9 @@ static void GL2_Mul4x4( const GLfloat *in0, const GLfloat *in1, GLfloat *out ) static void GL2_UpdateMVP( gl2wrap_prog_t *prog ) { // use bitset to determine if need update matrix for this prog - if( FBitSet( gl2wrap_matrix.update, BIT( prog->flags ))) + if( FBitSet( gl2wrap_matrix.update, BIT64( prog->flags ))) { - ClearBits( gl2wrap_matrix.update, BIT( prog->flags )); + ClearBits( gl2wrap_matrix.update, BIT64( prog->flags )); GL2_Mul4x4( gl2wrap_matrix.mv, gl2wrap_matrix.pr, gl2wrap_matrix.mvp ); pglUniformMatrix4fvARB( prog->uMVP, 1, false, (void *)gl2wrap_matrix.mvp ); } diff --git a/ref/gl/gl_alias.c b/ref/gl/gl_alias.c index 538cb16c..286938af 100644 --- a/ref/gl/gl_alias.c +++ b/ref/gl/gl_alias.c @@ -418,7 +418,6 @@ rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) static rgbdata_t skin; char name[MAX_QPATH]; int i; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); skin.width = width; skin.height = height; @@ -443,7 +442,7 @@ rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) } } - COM_FileBase( loadmodel->name, name, sizeof( name )); + COM_FileBase( mod->name, name, sizeof( name )); // for alias models only player can have remap textures if( mod != NULL && !Q_stricmp( name, "player" )) @@ -476,12 +475,11 @@ rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) return gEngfuncs.FS_CopyImage( &skin ); } -void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) +void *Mod_LoadSingleSkin( model_t *loadmodel, daliasskintype_t *pskintype, int skinnum, int size ) { string name, lumaname; string checkname; rgbdata_t *pic; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); Q_snprintf( name, sizeof( name ), "%s:frame%i", loadmodel->name, skinnum ); Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); @@ -508,14 +506,13 @@ void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) return ((byte *)(pskintype + 1) + size); } -void *Mod_LoadGroupSkin( daliasskintype_t *pskintype, int skinnum, int size ) +void *Mod_LoadGroupSkin( model_t *loadmodel, daliasskintype_t *pskintype, int skinnum, int size ) { daliasskininterval_t *pinskinintervals; daliasskingroup_t *pinskingroup; string name, lumaname; rgbdata_t *pic; int i, j; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); // animating skin group. yuck. pskintype++; @@ -555,7 +552,7 @@ void *Mod_LoadGroupSkin( daliasskintype_t *pskintype, int skinnum, int size ) Mod_LoadAllSkins =============== */ -void *Mod_LoadAllSkins( int numskins, daliasskintype_t *pskintype ) +void *Mod_LoadAllSkins( model_t *mod, int numskins, daliasskintype_t *pskintype ) { int i, size; @@ -568,11 +565,11 @@ void *Mod_LoadAllSkins( int numskins, daliasskintype_t *pskintype ) { if( pskintype->type == ALIAS_SKIN_SINGLE ) { - pskintype = (daliasskintype_t *)Mod_LoadSingleSkin( pskintype, i, size ); + pskintype = (daliasskintype_t *)Mod_LoadSingleSkin( mod, pskintype, i, size ); } else { - pskintype = (daliasskintype_t *)Mod_LoadGroupSkin( pskintype, i, size ); + pskintype = (daliasskintype_t *)Mod_LoadGroupSkin( mod, pskintype, i, size ); } } @@ -680,7 +677,7 @@ void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ) // load the skins pskintype = (daliasskintype_t *)&pinmodel[1]; - pskintype = Mod_LoadAllSkins( m_pAliasHeader->numskins, pskintype ); + pskintype = Mod_LoadAllSkins( mod, m_pAliasHeader->numskins, pskintype ); // load base s and t vertices pinstverts = (stvert_t *)pskintype; @@ -725,7 +722,7 @@ void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ) GL_MakeAliasModelDisplayLists( mod ); // move the complete, relocatable alias model to the cache - gEngfuncs.Mod_GetCurrentLoadingModel()->cache.data = m_pAliasHeader; + mod->cache.data = m_pAliasHeader; if( loaded ) *loaded = true; // done } diff --git a/ref/gl/gl_image.c b/ref/gl/gl_image.c index f0ff212c..9c2fcc61 100644 --- a/ref/gl/gl_image.c +++ b/ref/gl/gl_image.c @@ -166,7 +166,7 @@ void GL_ApplyTextureParams( gl_texture_t *tex ) } else if( FBitSet( tex->flags, TF_NOMIPMAP ) || tex->numMips <= 1 ) { - if( FBitSet( tex->flags, TF_NEAREST ) || ( IsLightMap( tex ) && gl_lightmap_nearest.value )) + if( FBitSet( tex->flags, TF_NEAREST ) || ( IsLightMap( tex ) && gl_lightmap_nearest.value ) || ( tex->flags == TF_SKYSIDE && gl_texture_nearest.value )) { pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); @@ -342,6 +342,8 @@ void R_SetTextureParameters( void ) // change all the existing mipmapped texture objects for( i = 0; i < gl_numTextures; i++ ) GL_UpdateTextureParams( i ); + + R_UpdateRippleTexParams(); } /* @@ -2317,6 +2319,7 @@ void R_InitImages( void ) // validate cvars R_SetTextureParameters(); GL_CreateInternalTextures(); + R_InitRipples(); gEngfuncs.Cmd_AddCommand( "texturelist", R_TextureList_f, "display loaded textures list" ); } diff --git a/ref/gl/gl_local.h b/ref/gl/gl_local.h index 92d23098..a0df7c55 100644 --- a/ref/gl/gl_local.h +++ b/ref/gl/gl_local.h @@ -426,7 +426,7 @@ void R_MarkLeaves( void ); void R_DrawWorld( void ); void R_DrawWaterSurfaces( void ); void R_DrawBrushModel( cl_entity_t *e ); -void GL_SubdivideSurface( msurface_t *fa ); +void GL_SubdivideSurface( model_t *mod, msurface_t *fa ); void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ); void DrawGLPoly( glpoly_t *p, float xScale, float yScale ); texture_t *R_TextureAnimation( msurface_t *s ); @@ -489,6 +489,11 @@ void R_ClearSkyBox( void ); void R_DrawSkyBox( void ); void R_DrawClouds( void ); void EmitWaterPolys( msurface_t *warp, qboolean reverse ); +void R_InitRipples( void ); +void R_ResetRipples( void ); +void R_AnimateRipples( void ); +void R_UpdateRippleTexParams( void ); +void R_UploadRipples( texture_t *image ); // // gl_vgui.c @@ -750,6 +755,9 @@ extern convar_t r_vbo; extern convar_t r_vbo_dlightmode; extern convar_t r_studio_sort_textures; extern convar_t r_studio_drawelements; +extern convar_t r_ripple; +extern convar_t r_ripple_updatetime; +extern convar_t r_ripple_spawntime; // // engine shared convars diff --git a/ref/gl/gl_opengl.c b/ref/gl/gl_opengl.c index d973e631..6f31c8a8 100644 --- a/ref/gl/gl_opengl.c +++ b/ref/gl/gl_opengl.c @@ -26,10 +26,14 @@ CVAR_DEFINE_AUTO( r_novis, "0", 0, "ignore vis information (perfomance test)" ); CVAR_DEFINE_AUTO( r_nocull, "0", 0, "ignore frustrum culling (perfomance test)" ); CVAR_DEFINE_AUTO( r_lockpvs, "0", FCVAR_CHEAT, "lockpvs area at current point (pvs test)" ); CVAR_DEFINE_AUTO( r_lockfrustum, "0", FCVAR_CHEAT, "lock frustrum area at current point (cull test)" ); -CVAR_DEFINE_AUTO( r_traceglow, "1", FCVAR_GLCONFIG, "cull flares behind models" ); +CVAR_DEFINE_AUTO( r_traceglow, "0", FCVAR_GLCONFIG, "cull flares behind models" ); CVAR_DEFINE_AUTO( gl_round_down, "2", FCVAR_GLCONFIG|FCVAR_READ_ONLY, "round texture sizes to nearest POT value" ); CVAR_DEFINE( r_vbo, "gl_vbo", "0", FCVAR_ARCHIVE, "draw world using VBO (known to be glitchy)" ); CVAR_DEFINE( r_vbo_dlightmode, "gl_vbo_dlightmode", "1", FCVAR_ARCHIVE, "vbo dlight rendering mode (0-1)" ); +CVAR_DEFINE_AUTO( r_ripple, "0", FCVAR_GLCONFIG, "enable software-like water texture ripple simulation" ); +CVAR_DEFINE_AUTO( r_ripple_updatetime, "0.05", FCVAR_GLCONFIG, "how fast ripple simulation is" ); +CVAR_DEFINE_AUTO( r_ripple_spawntime, "0.1", FCVAR_GLCONFIG, "how fast new ripples spawn" ); + DEFINE_ENGINE_SHARED_CVAR_LIST() @@ -549,24 +553,54 @@ qboolean GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char for( func = funcs; func && func->name; func++ ) { // functions are cleared before all the extensions are evaluated - if((*func->func = (void *)gEngfuncs.GL_GetProcAddress( func->name )) == NULL ) + if(( *func->func = (void *)gEngfuncs.GL_GetProcAddress( func->name )) == NULL ) { + string name; + char *end; + size_t i = 0; +#ifdef XASH_GLES + const char *suffixes[] = { "", "EXT", "OES" }; +#else + const char *suffixes[] = { "", "EXT" }; +#endif + // HACK: fix ARB names - char *str = Q_strstr( func->name, "ARB" ); - if(str) + Q_strncpy( name, func->name, sizeof( name )); + if(( end = Q_strstr( name, "ARB" ))) { - string name; - - Q_strncpy( name, func->name, MAX_STRING ); - name[str - func->name] = '\0'; - *func->func = gEngfuncs.GL_GetProcAddress( name ); - - if( !*func->func ) - GL_SetExtension( r_ext, false ); + *end = '\0'; } - else - // one or more functions are invalid, extension will be disabled + else // I need Q_strstrnul + { + end = name + Q_strlen( name ); + i++; // skip empty suffix + } + + for( ; i < sizeof( suffixes ) / sizeof( suffixes[0] ); i++ ) + { + void *f; + + Q_strncat( name, suffixes[i], sizeof( name )); + + if(( f = gEngfuncs.GL_GetProcAddress( name ))) + { + // GL_GetProcAddress prints errors about missing functions, so tell user that we found it with different name + gEngfuncs.Con_Printf( S_NOTE "found %s\n", name ); + + *func->func = f; + break; + } + else + { + *end = '\0'; // cut suffix, try next + } + } + + // not found... + if( i == sizeof( suffixes ) / sizeof( suffixes[0] )) + { GL_SetExtension( r_ext, false ); + } } } #endif @@ -829,7 +863,9 @@ void GL_InitExtensionsGLES( void ) #endif case GL_DEBUG_OUTPUT: if( glw_state.extended ) - GL_CheckExtension( "GL_KHR_debug", NULL, NULL, extid, 0 ); + GL_CheckExtension( "GL_KHR_debug", debugoutputfuncs, "gl_debug_output", extid, 0 ); + else + GL_SetExtension( extid, false ); break; // case GL_TEXTURE_COMPRESSION_EXT: NOPE // case GL_SHADER_GLSL100_EXT: NOPE @@ -1093,6 +1129,7 @@ void GL_InitExtensions( void ) // force everything to happen in the main thread instead of in a separate driver thread pglEnable( GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB ); + } // enable all the low priority messages @@ -1153,6 +1190,9 @@ void GL_InitCommands( void ) gEngfuncs.Cvar_RegisterVariable( &r_traceglow ); gEngfuncs.Cvar_RegisterVariable( &r_studio_sort_textures ); gEngfuncs.Cvar_RegisterVariable( &r_studio_drawelements ); + gEngfuncs.Cvar_RegisterVariable( &r_ripple ); + gEngfuncs.Cvar_RegisterVariable( &r_ripple_updatetime ); + gEngfuncs.Cvar_RegisterVariable( &r_ripple_spawntime ); gEngfuncs.Cvar_RegisterVariable( &gl_extensions ); gEngfuncs.Cvar_RegisterVariable( &gl_texture_nearest ); @@ -1345,7 +1385,7 @@ void GL_SetupAttributes( int safegl ) #ifdef XASH_NANOGL gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 1 ); gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 1 ); -#elif defined( XASH_WES ) || defined( XASH_REGAL ) +#else gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MAJOR_VERSION, 2 ); gEngfuncs.GL_SetAttribute( REF_GL_CONTEXT_MINOR_VERSION, 0 ); #endif diff --git a/ref/gl/gl_rmain.c b/ref/gl/gl_rmain.c index da815a01..9a5a3702 100644 --- a/ref/gl/gl_rmain.c +++ b/ref/gl/gl_rmain.c @@ -969,6 +969,8 @@ void R_RenderScene( void ) R_MarkLeaves(); R_DrawFog (); + if( RI.drawWorld ) + R_AnimateRipples(); R_CheckGLFog(); R_DrawWorld(); diff --git a/ref/gl/gl_rmisc.c b/ref/gl/gl_rmisc.c index 617d65fa..9395b008 100644 --- a/ref/gl/gl_rmisc.c +++ b/ref/gl/gl_rmisc.c @@ -85,7 +85,7 @@ static void R_ParseDetailTextures( const char *filename ) if( Q_stricmp( tex->name, texname )) continue; - tex->dt_texturenum = GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR ); + tex->dt_texturenum = GL_LoadTexture( detail_path, NULL, 0, TF_FORCE_COLOR|TF_NOFLIP_TGA ); // texture is loaded if( tex->dt_texturenum ) @@ -150,6 +150,7 @@ void R_NewMap( void ) GL_BuildLightmaps (); R_GenerateVBO(); + R_ResetRipples(); if( gEngfuncs.drawFuncs->R_NewMap != NULL ) gEngfuncs.drawFuncs->R_NewMap(); diff --git a/ref/gl/gl_rsurf.c b/ref/gl/gl_rsurf.c index 17cb8bb1..bd64a43c 100644 --- a/ref/gl/gl_rsurf.c +++ b/ref/gl/gl_rsurf.c @@ -78,7 +78,7 @@ static void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs ) } } -static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts ) +static void SubdividePolygon_r( model_t *loadmodel, msurface_t *warpface, int numverts, float *verts ) { vec3_t front[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE]; mextrasurf_t *warpinfo = warpface->info; @@ -88,7 +88,6 @@ static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts float sample_size; vec3_t mins, maxs; glpoly_t *poly; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); if( numverts > ( SUBDIVIDE_SIZE - 4 )) gEngfuncs.Host_Error( "Mod_SubdividePolygon: too many vertexes on face ( %i )\n", numverts ); @@ -143,8 +142,8 @@ static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts } } - SubdividePolygon_r( warpface, f, front[0] ); - SubdividePolygon_r( warpface, b, back[0] ); + SubdividePolygon_r( loadmodel, warpface, f, front[0] ); + SubdividePolygon_r( loadmodel, warpface, b, back[0] ); return; } @@ -205,7 +204,7 @@ void GL_SetupFogColorForSurfaces( void ) vec3_t fogColor; float factor, div; - if( !glState.isFogEnabled) + if( !glState.isFogEnabled ) return; if( RI.currententity && RI.currententity->curstate.rendermode == kRenderTransTexture ) @@ -238,13 +237,12 @@ boundaries so that turbulent and sky warps can be done reasonably. ================ */ -void GL_SubdivideSurface( msurface_t *fa ) +void GL_SubdivideSurface( model_t *loadmodel, msurface_t *fa ) { vec3_t verts[SUBDIVIDE_SIZE]; int numverts; int i, lindex; float *vec; - model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); // convert edges back to a normal polygon numverts = 0; @@ -261,7 +259,7 @@ void GL_SubdivideSurface( msurface_t *fa ) SetBits( fa->flags, SURF_DRAWTURB_QUADS ); // predict state // do subdivide - SubdividePolygon_r( fa, numverts, verts[0] ); + SubdividePolygon_r( loadmodel, fa, numverts, verts[0] ); } /* @@ -877,7 +875,7 @@ void DrawGLPolyChain( glpoly_t *p, float soffset, float toffset ) } } -_inline qboolean R_HasLightmap( void ) +static qboolean R_HasLightmap( void ) { if( r_fullbright->value || !WORLDMODEL->lightdata ) return false; @@ -1137,14 +1135,15 @@ void R_RenderBrushPoly( msurface_t *fa, int cull_type ) t = R_TextureAnimation( fa ); - GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); - if( FBitSet( fa->flags, SURF_DRAWTURB )) { + R_UploadRipples( t ); + // warp texture, no lightmaps EmitWaterPolys( fa, (cull_type == CULL_BACKSIDE)); return; } + else GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); if( t->fb_texturenum ) { @@ -1413,7 +1412,7 @@ void R_DrawWaterSurfaces( void ) continue; // set modulate mode explicitly - GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); + R_UploadRipples( t ); for( ; s; s = s->texturechain ) EmitWaterPolys( s, false ); diff --git a/ref/gl/gl_studio.c b/ref/gl/gl_studio.c index ebeece6f..83796e92 100644 --- a/ref/gl/gl_studio.c +++ b/ref/gl/gl_studio.c @@ -1726,7 +1726,7 @@ static void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], const vec3_t normal, out[2] = Q_min( (int)( finalLight[2] ), 255 ); } -static void R_StudioSetColorArray(short *ptricmds, vec3_t *pstudionorms, byte *color ) +static void R_StudioSetColorArray( short *ptricmds, vec3_t *pstudionorms, byte *color ) { float *lv = (float *)g_studio.lightvalues[ptricmds[1]]; @@ -1896,9 +1896,8 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) { - float *lv; int i; while(( i = *( ptricmds++ ))) @@ -1929,9 +1928,8 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +static void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) { - float *lv; int i; while(( i = *( ptricmds++ ))) @@ -1961,7 +1959,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +static void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) { float *lv, *av; int i, idx; @@ -2006,7 +2004,7 @@ _inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, floa } -_inline int R_StudioBuildIndices( qboolean tri_strip, int vertexState ) +static int R_StudioBuildIndices( qboolean tri_strip, int vertexState ) { // build in indices if( vertexState++ < 3 ) @@ -2049,7 +2047,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioBuildArrayNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +static void R_StudioBuildArrayNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) { float *lv; int i; @@ -2092,7 +2090,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioBuildArrayFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +static void R_StudioBuildArrayFloatMesh( short *ptricmds, vec3_t *pstudionorms ) { float *lv; int i; @@ -2135,7 +2133,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioBuildArrayChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +static void R_StudioBuildArrayChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) { float *lv, *av; int i, idx; @@ -2192,7 +2190,7 @@ _inline void R_StudioBuildArrayChromeMesh( short *ptricmds, vec3_t *pstudionorms } } -_inline void R_StudioDrawArrays( uint startverts, uint startelems ) +static void R_StudioDrawArrays( uint startverts, uint startelems ) { pglEnableClientState( GL_VERTEX_ARRAY ); pglVertexPointer( 3, GL_FLOAT, 12, g_studio.arrayverts ); diff --git a/ref/gl/gl_warp.c b/ref/gl/gl_warp.c index 30dbe1b9..7ba1afd7 100644 --- a/ref/gl/gl_warp.c +++ b/ref/gl/gl_warp.c @@ -62,6 +62,29 @@ static float r_turbsin[] = #include "warpsin.h" }; +#define RIPPLES_CACHEWIDTH_BITS 7 +#define RIPPLES_CACHEWIDTH ( 1 << RIPPLES_CACHEWIDTH_BITS ) +#define RIPPLES_CACHEWIDTH_MASK (( RIPPLES_CACHEWIDTH ) - 1 ) +#define RIPPLES_TEXSIZE ( RIPPLES_CACHEWIDTH * RIPPLES_CACHEWIDTH ) +#define RIPPLES_TEXSIZE_MASK ( RIPPLES_TEXSIZE - 1 ) + +STATIC_ASSERT( RIPPLES_TEXSIZE == 0x4000, "fix the algorithm to work with custom resolution" ); + +static struct +{ + double time; + double oldtime; + + short *curbuf, *oldbuf; + short buf[2][RIPPLES_TEXSIZE]; + qboolean update; + + uint32_t texture[RIPPLES_TEXSIZE]; + int gl_texturenum; + int rippletexturenum; + float texturescale; // not all textures are 128x128, scale the texcoords down +} g_ripple; + static qboolean CheckSkybox( const char *name, char out[6][MAX_STRING] ) { const char *skybox_ext[3] = { "dds", "tga", "bmp" }; @@ -799,10 +822,18 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) os = v[3]; ot = v[4]; - s = os + r_turbsin[(int)((ot * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; - s *= ( 1.0f / SUBDIVIDE_SIZE ); + if( !r_ripple.value ) + { + s = os + r_turbsin[(int)((ot * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; + t = ot + r_turbsin[(int)((os * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; + } + else + { + s = os / g_ripple.texturescale; + t = ot / g_ripple.texturescale; + } - t = ot + r_turbsin[(int)((os * 0.125f + gpGlobals->time) * TURBSCALE) & 255]; + s *= ( 1.0f / SUBDIVIDE_SIZE ); t *= ( 1.0f / SUBDIVIDE_SIZE ); pglTexCoord2f( s, t ); @@ -822,3 +853,193 @@ void EmitWaterPolys( msurface_t *warp, qboolean reverse ) GL_SetupFogColorForSurfaces(); } + +/* +============================================================ + + HALF-LIFE SOFTWARE WATER + +============================================================ +*/ +void R_ResetRipples( void ) +{ + g_ripple.curbuf = g_ripple.buf[0]; + g_ripple.oldbuf = g_ripple.buf[1]; + g_ripple.time = g_ripple.oldtime = gpGlobals->time - 0.1; + memset( g_ripple.buf, 0, sizeof( g_ripple.buf )); +} + +void R_InitRipples( void ) +{ + rgbdata_t pic = { 0 }; + + pic.width = pic.height = RIPPLES_CACHEWIDTH; + pic.depth = 1; + pic.flags = IMAGE_HAS_COLOR; + pic.buffer = (byte *)g_ripple.texture; + pic.type = PF_RGBA_32; + pic.size = sizeof( g_ripple.texture ); + pic.numMips = 1; + memset( pic.buffer, 0, pic.size ); + + g_ripple.rippletexturenum = GL_LoadTextureInternal( "*rippletex", &pic, TF_NOMIPMAP ); +} + +static void R_SwapBufs( void ) +{ + short *tempbufp = g_ripple.curbuf; + g_ripple.curbuf = g_ripple.oldbuf; + g_ripple.oldbuf = tempbufp; +} + +static void R_SpawnNewRipple( int x, int y, short val ) +{ +#define PIXEL( x, y ) ((( x ) & RIPPLES_CACHEWIDTH_MASK ) + ((( y ) & RIPPLES_CACHEWIDTH_MASK) << 7 )) + g_ripple.oldbuf[PIXEL( x, y )] += val; + + val >>= 2; + g_ripple.oldbuf[PIXEL( x + 1, y )] += val; + g_ripple.oldbuf[PIXEL( x - 1, y )] += val; + g_ripple.oldbuf[PIXEL( x, y + 1 )] += val; + g_ripple.oldbuf[PIXEL( x, y - 1 )] += val; +#undef PIXEL +} + +static void R_RunRipplesAnimation( const short *oldbuf, short *pbuf ) +{ + size_t i = 0; + const int w = RIPPLES_CACHEWIDTH; + const int m = RIPPLES_TEXSIZE_MASK; + + for( i = w; i < m + w; i++, pbuf++ ) + { + *pbuf = ( + ( (int)oldbuf[( i - ( w * 2 )) & m] + + (int)oldbuf[( i - ( w + 1 )) & m] + + (int)oldbuf[( i - ( w - 1 )) & m] + + (int)oldbuf[( i ) & m]) >> 1 ) - (int)*pbuf; + + *pbuf -= ( *pbuf >> 6 ); + } +} + +static int MostSignificantBit( unsigned int v ) +{ +#if __GNUC__ + return 31 - __builtin_clz( v ); +#else + int i; + for( i = 0, v >>= 1; v; v >>= 1, i++ ); + return i; +#endif +} + +void R_AnimateRipples( void ) +{ + double frametime = gpGlobals->time - g_ripple.time; + + g_ripple.update = r_ripple.value && frametime >= r_ripple_updatetime.value; + + if( !g_ripple.update ) + return; + + g_ripple.time = gpGlobals->time; + + R_SwapBufs(); + + if( g_ripple.time - g_ripple.oldtime > r_ripple_spawntime.value ) + { + int x, y, val; + + g_ripple.oldtime = g_ripple.time; + + x = rand() & 0x7fff; + y = rand() & 0x7fff; + val = rand() & 0x3ff; + + R_SpawnNewRipple( x, y, val ); + } + + R_RunRipplesAnimation( g_ripple.oldbuf, g_ripple.curbuf ); +} + +void R_UpdateRippleTexParams( void ) +{ + gl_texture_t *tex = R_GetTexture( g_ripple.rippletexturenum ); + + GL_Bind( XASH_TEXTURE0, g_ripple.rippletexturenum ); + + if( gl_texture_nearest.value ) + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + } + else + { + pglTexParameteri( tex->target, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + pglTexParameteri( tex->target, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + } +} + +void R_UploadRipples( texture_t *image ) +{ + gl_texture_t *glt; + uint32_t *pixels; + int wbits, wmask, wshft; + + // discard unuseful textures + if( !r_ripple.value || image->width > RIPPLES_CACHEWIDTH || image->width != image->height ) + { + GL_Bind( XASH_TEXTURE0, image->gl_texturenum ); + return; + } + + glt = R_GetTexture( image->gl_texturenum ); + if( !glt || !glt->original || !glt->original->buffer || !FBitSet( glt->flags, TF_EXPAND_SOURCE )) + { + GL_Bind( XASH_TEXTURE0, image->gl_texturenum ); + return; + } + + GL_Bind( XASH_TEXTURE0, g_ripple.rippletexturenum ); + + // no updates this frame + if( !g_ripple.update && image->gl_texturenum == g_ripple.gl_texturenum ) + return; + + g_ripple.gl_texturenum = image->gl_texturenum; + if( r_ripple.value == 1.0f ) + { + g_ripple.texturescale = Q_max( 1.0f, image->width / 64.0f ); + } + else + { + g_ripple.texturescale = 1.0f; + } + + + pixels = (uint32_t *)glt->original->buffer; + wbits = MostSignificantBit( image->width ); + wshft = 7 - wbits; + wmask = image->width - 1; + + for( int y = 0; y < image->height; y++ ) + { + int ry = y << ( 7 + wshft ); + + for( int x = 0; x < image->width; x++ ) + { + int rx = x << wshft; + int val = g_ripple.curbuf[ry + rx] >> 4; + + int py = (y - val) & wmask; + int px = (x + val) & wmask; + int p = ( py << wbits ) + px; + + g_ripple.texture[(y << wbits) + x] = pixels[p]; + } + } + + pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, image->width, image->width, 0, + GL_RGBA, GL_UNSIGNED_BYTE, g_ripple.texture ); +} diff --git a/ref/soft/r_context.c b/ref/soft/r_context.c index ece7fb64..703e04b9 100644 --- a/ref/soft/r_context.c +++ b/ref/soft/r_context.c @@ -329,7 +329,7 @@ void R_InitSkyClouds(mip_t *mt, texture_t *tx, qboolean custom_palette) } -void GAME_EXPORT GL_SubdivideSurface(msurface_t *fa) +void GAME_EXPORT GL_SubdivideSurface( model_t *mod, msurface_t *fa ) { } diff --git a/ref/soft/r_main.c b/ref/soft/r_main.c index b8770c61..89bf9f2e 100644 --- a/ref/soft/r_main.c +++ b/ref/soft/r_main.c @@ -67,7 +67,7 @@ CVAR_DEFINE_AUTO( sw_surfcacheoverride, "0", 0, ""); static CVAR_DEFINE_AUTO( sw_waterwarp, "1", FCVAR_GLCONFIG, "nothing"); static CVAR_DEFINE_AUTO( sw_notransbrushes, "0", FCVAR_GLCONFIG, "do not apply transparency to water/glasses (faster)"); CVAR_DEFINE_AUTO( sw_noalphabrushes, "0", FCVAR_GLCONFIG, "do not draw brush holes (faster)"); -CVAR_DEFINE_AUTO( r_traceglow, "1", FCVAR_GLCONFIG, "cull flares behind models" ); +CVAR_DEFINE_AUTO( r_traceglow, "0", FCVAR_GLCONFIG, "cull flares behind models" ); CVAR_DEFINE_AUTO( sw_texfilt, "0", FCVAR_GLCONFIG, "texture dither"); static CVAR_DEFINE_AUTO( r_novis, "0", 0, "" ); diff --git a/ref/soft/r_studio.c b/ref/soft/r_studio.c index e7429599..1da13e1f 100644 --- a/ref/soft/r_studio.c +++ b/ref/soft/r_studio.c @@ -1891,7 +1891,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) +static void R_StudioDrawNormalMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t ) { float *lv; int i; @@ -1924,7 +1924,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) +static void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms ) { float *lv; int i; @@ -1956,7 +1956,7 @@ R_StudioDrawNormalMesh generic path =============== */ -_inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) +static void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale ) { float *lv, *av; int i, idx; diff --git a/ref/vk/TODO.md b/ref/vk/TODO.md index a99db2cd..a864bfd6 100644 --- a/ref/vk/TODO.md +++ b/ref/vk/TODO.md @@ -1,3 +1,12 @@ +# 2023-11-02 E323 +- [x] lol meta: read and sort issues +- [x] merge from upstream +- [ ] hevsuit glitches +- [ ] massage shaders: consolidate all bindings explicitly +- [ ] skip sorting-by-texture when loading brush models (=> geometry count explosion; i.e. kusochki count will explode too) +- [ ] kusochki-vs-materials +- [ ] -vkverbose arg for turning all debug logs before detailed cvars are read + # 2023-10-31 E322 - [x] load png blue noise files - [-] translucent animated thing -> needs shader rework diff --git a/ref/vk/common_geometry.c b/ref/vk/common_geometry.c index 85fa522b..bce827a9 100644 --- a/ref/vk/common_geometry.c +++ b/ref/vk/common_geometry.c @@ -31,7 +31,7 @@ static void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs ) } } -static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts ) +static void SubdividePolygon_r( model_t *loadmodel, msurface_t *warpface, int numverts, float *verts ) { vec3_t front[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE]; mextrasurf_t *warpinfo = warpface->info; @@ -41,7 +41,6 @@ static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts float sample_size; vec3_t mins, maxs; glpoly_t *poly; - model_t *loadmodel = gEngine.Mod_GetCurrentLoadingModel(); if( numverts > ( SUBDIVIDE_SIZE - 4 )) gEngine.Host_Error( "Mod_SubdividePolygon: too many vertexes on face ( %i )\n", numverts ); @@ -96,13 +95,13 @@ static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts } } - SubdividePolygon_r( warpface, f, front[0] ); - SubdividePolygon_r( warpface, b, back[0] ); + SubdividePolygon_r( loadmodel, warpface, f, front[0] ); + SubdividePolygon_r( loadmodel, warpface, b, back[0] ); return; } if( numverts != 4 ) - ClearBits( warpface->flags, SURF_DRAWTURB_QUADS ); + ClearBits( warpface->flags, SURF_DRAWTURB_QUADS ); // add a point in the center to help keep warp valid poly = Mem_Calloc( loadmodel->mempool, sizeof( glpoly_t ) + (numverts - 4) * VERTEXSIZE * sizeof( float )); @@ -124,8 +123,8 @@ static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts { s = DotProduct( verts, warpface->texinfo->vecs[0] ) + warpface->texinfo->vecs[0][3]; t = DotProduct( verts, warpface->texinfo->vecs[1] ) + warpface->texinfo->vecs[1][3]; - s /= warpface->texinfo->texture->width; - t /= warpface->texinfo->texture->height; + s /= warpface->texinfo->texture->width; + t /= warpface->texinfo->texture->height; } poly->verts[i][3] = s; @@ -162,13 +161,12 @@ boundaries so that turbulent and sky warps can be done reasonably. ================ */ -void GL_SubdivideSurface( msurface_t *fa ) +void GL_SubdivideSurface( model_t *loadmodel, msurface_t *fa ) { vec3_t verts[SUBDIVIDE_SIZE]; int numverts; int i, lindex; float *vec; - model_t *loadmodel = gEngine.Mod_GetCurrentLoadingModel(); // convert edges back to a normal polygon numverts = 0; @@ -185,5 +183,5 @@ void GL_SubdivideSurface( msurface_t *fa ) SetBits( fa->flags, SURF_DRAWTURB_QUADS ); // predict state // do subdivide - SubdividePolygon_r( fa, numverts, verts[0] ); + SubdividePolygon_r( loadmodel, fa, numverts, verts[0] ); } diff --git a/ref/vk/vk_rmain.c b/ref/vk/vk_rmain.c index 9e295387..c63e6969 100644 --- a/ref/vk/vk_rmain.c +++ b/ref/vk/vk_rmain.c @@ -112,7 +112,7 @@ static void R_InitSkyClouds( struct mip_s *mt, struct texture_s *tx, qboolean cu PRINT_NOT_IMPLEMENTED(); } -extern void GL_SubdivideSurface( msurface_t *fa ); +extern void GL_SubdivideSurface( model_t *loadmodel, msurface_t *fa ); static void Mod_UnloadTextures( model_t *mod ) { diff --git a/scripts/waifulib/compiler_optimizations.py b/scripts/waifulib/compiler_optimizations.py index 64f1e623..f65e7aeb 100644 --- a/scripts/waifulib/compiler_optimizations.py +++ b/scripts/waifulib/compiler_optimizations.py @@ -30,7 +30,7 @@ compiler_optimizations.CFLAGS['gottagofast'] = { } ''' -VALID_BUILD_TYPES = ['fastnative', 'fast', 'release', 'debug', 'sanitize', 'msan', 'none'] +VALID_BUILD_TYPES = ['fastnative', 'fast', 'humanrights', 'debug', 'sanitize', 'msan', 'none'] LINKFLAGS = { 'common': { @@ -75,7 +75,7 @@ CFLAGS = { 'clang': ['-Ofast', '-march=native'], 'default': ['-O3'] }, - 'release': { + 'humanrights': { 'msvc': ['/O2', '/Zi'], 'owcc': ['-O3', '-foptimize-sibling-calls', '-fomit-leaf-frame-pointer', '-fomit-frame-pointer', '-fschedule-insns', '-funsafe-math-optimizations', '-funroll-loops', '-frerun-optimizer', '-finline-functions', '-finline-limit=512', '-fguess-branch-probability', '-fno-strict-aliasing', '-floop-optimize'], 'gcc': ['-O3', '-fno-semantic-interposition'], @@ -119,7 +119,7 @@ POLLY_CFLAGS = { def options(opt): grp = opt.add_option_group('Compiler optimization options') - grp.add_option('-T', '--build-type', action='store', dest='BUILD_TYPE', default='release', + grp.add_option('-T', '--build-type', action='store', dest='BUILD_TYPE', default='humanrights', help = 'build type: debug, release or none(custom flags)') grp.add_option('--enable-lto', action = 'store_true', dest = 'LTO', default = False, @@ -131,6 +131,11 @@ def options(opt): def configure(conf): conf.start_msg('Build type') + # legacy naming for default release build + # https://chaos.social/@karolherbst/111340511652012860 + if conf.options.BUILD_TYPE == 'release': + conf.options.BUILD_TYPE = 'humanrights' + if not conf.options.BUILD_TYPE in VALID_BUILD_TYPES: conf.end_msg(conf.options.BUILD_TYPE, color='RED') conf.fatal('Invalid build type. Valid are: %s' % ', '.join(VALID_BUILD_TYPES)) diff --git a/utils/mdldec/mdldec.c b/utils/mdldec/mdldec.c index 576728b5..fc4fa0e5 100644 --- a/utils/mdldec/mdldec.c +++ b/utils/mdldec/mdldec.c @@ -41,21 +41,17 @@ static void SequenceNameFix( void ) { int i, j, counter; qboolean hasduplicates = false; - mstudioseqdesc_t *seqdesc, *seqdesc1; + mstudioseqdesc_t *seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ), *seqdesc1; - for( i = 0; i < model_hdr->numseq; i++ ) + for( i = 0; i < model_hdr->numseq; ++i, ++seqdesc ) { - seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i; - counter = 1; - for( j = 0; j < model_hdr->numseq; j++ ) - { - seqdesc1 = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + j; + seqdesc1 = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ); + for( j = 0; j < model_hdr->numseq; ++j, ++seqdesc1 ) if( j != i && !Q_strncmp( seqdesc1->label, seqdesc->label, sizeof( seqdesc1->label ) ) ) Q_snprintf( seqdesc1->label, sizeof( seqdesc1->label ), "%s_%i", seqdesc1->label, ++counter ); - } if( counter > 1 ) { @@ -79,15 +75,11 @@ BoneNameFix static void BoneNameFix( void ) { int i, counter = 0; - mstudiobone_t *bone; - - for( i = 0; i < model_hdr->numbones; i++ ) - { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); + for( i = 0; i < model_hdr->numbones; ++i, ++bone ) if( bone->name[0] == '\0' ) Q_snprintf( bone->name, sizeof( bone->name ), "MDLDEC_Bone%i", ++counter ); - } if( counter ) printf( "WARNING: Gived name to %i unnamed bone(s).\n", counter ); diff --git a/utils/mdldec/qc.c b/utils/mdldec/qc.c index b3da410b..9508f540 100644 --- a/utils/mdldec/qc.c +++ b/utils/mdldec/qc.c @@ -249,7 +249,7 @@ WriteSkinFamilyInfo static void WriteSkinFamilyInfo( FILE *fp ) { int i, j, k; - short *skinref, index; + short *skinref, *index; mstudiotexture_t *texture; if( texture_hdr->numskinfamilies < 2 ) @@ -260,23 +260,22 @@ static void WriteSkinFamilyInfo( FILE *fp ) fputs( "$texturegroup \"skinfamilies\"\n{\n", fp ); skinref = (short *)( (byte *)texture_hdr + texture_hdr->skinindex ); + texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ); for( i = 0; i < texture_hdr->numskinfamilies; ++i ) { fputs( "\t{\n", fp ); - for( j = 0; j < texture_hdr->numskinref; ++j ) - { - index = *( skinref + i * texture_hdr->numskinref + j ); + index = skinref + i * texture_hdr->numskinref; + for( j = 0; j < texture_hdr->numskinref; ++j, ++index ) + { for( k = 0; k < texture_hdr->numskinfamilies; ++k ) { - if( index == *( skinref + k * texture_hdr->numskinref + j ) ) + if( *index == *( skinref + k * texture_hdr->numskinref + j ) ) continue; - texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + index; - - fprintf( fp, "\t\t\"%s\"\n", texture->name ); + fprintf( fp, "\t\t\"%s\"\n", texture[*index].name ); break; } } @@ -301,15 +300,13 @@ static void WriteAttachmentInfo( FILE *fp ) if( !model_hdr->numattachments ) return; + attachment = (mstudioattachment_t *)( (byte *)model_hdr + model_hdr->attachmentindex ); + bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); + fprintf( fp, "// %i attachment%s\n", model_hdr->numattachments, model_hdr->numattachments > 1 ? "s" : "" ); - for( i = 0; i < model_hdr->numattachments; ++i ) - { - attachment = (mstudioattachment_t *)( (byte *)model_hdr + model_hdr->attachmentindex ) + i; - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + attachment->bone; - - fprintf( fp, "$attachment %i \"%s\" %f %f %f\n", i, bone->name, attachment->org[0], attachment->org[1], attachment->org[2] ); - } + for( i = 0; i < model_hdr->numattachments; ++i, ++attachment ) + fprintf( fp, "$attachment %i \"%s\" %f %f %f\n", i, bone[attachment->bone].name, attachment->org[0], attachment->org[1], attachment->org[2] ); fputs( "\n", fp ); } @@ -322,20 +319,18 @@ WriteBodyGroupInfo static void WriteBodyGroupInfo( FILE *fp ) { int i, j; - mstudiobodyparts_t *bodypart; + mstudiobodyparts_t *bodypart = (mstudiobodyparts_t *) ( (byte *)model_hdr + model_hdr->bodypartindex ); mstudiomodel_t *model; char modelname[64]; fprintf( fp, "// %i reference mesh%s\n", model_hdr->numbodyparts, model_hdr->numbodyparts > 1 ? "es" : "" ); - for( i = 0; i < model_hdr->numbodyparts; ++i ) + for( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart ) { - bodypart = (mstudiobodyparts_t *) ( (byte *)model_hdr + model_hdr->bodypartindex ) + i; + model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); if( bodypart->nummodels == 1 ) { - model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); - COM_FileBase( model->name, modelname, sizeof( modelname )); fprintf( fp, "$body \"%s\" \"%s\"\n", bodypart->name, modelname ); @@ -346,10 +341,8 @@ static void WriteBodyGroupInfo( FILE *fp ) fputs( "{\n", fp ); - for( j = 0; j < bodypart->nummodels; ++j ) + for( j = 0; j < bodypart->nummodels; ++j, ++model ) { - model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ) + j; - if( !Q_strncmp( model->name, "blank", 5 ) ) { fputs( "\tblank\n", fp ); @@ -382,13 +375,13 @@ static void WriteControllerInfo( FILE *fp ) if( !model_hdr->numbonecontrollers ) return; + bonecontroller = (mstudiobonecontroller_t *)( (byte *)model_hdr + model_hdr->bonecontrollerindex ); + bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); + fprintf( fp, "// %i bone controller%s\n", model_hdr->numbonecontrollers, model_hdr->numbonecontrollers > 1 ? "s" : "" ); - for( i = 0; i < model_hdr->numbonecontrollers; ++i ) + for( i = 0; i < model_hdr->numbonecontrollers; ++i, ++bonecontroller ) { - bonecontroller = (mstudiobonecontroller_t *)( (byte *)model_hdr + model_hdr->bonecontrollerindex ) + i; - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + bonecontroller->bone; - GetMotionTypeString( bonecontroller->type & ~STUDIO_RLOOP, motion_types, sizeof( motion_types ), false ); fputs( "$controller ", fp ); @@ -399,7 +392,7 @@ static void WriteControllerInfo( FILE *fp ) fprintf( fp, "%i", bonecontroller->index ); fprintf( fp, " \"%s\" %s %f %f\n", - bone->name, motion_types, + bone[bonecontroller->bone].name, motion_types, bonecontroller->start, bonecontroller->end ); } @@ -420,18 +413,16 @@ static void WriteHitBoxInfo( FILE *fp ) if( !model_hdr->numhitboxes ) return; + hitbox = (mstudiobbox_t *)( (byte *)model_hdr + model_hdr->hitboxindex ); + bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); + fprintf( fp, "// %i hit box%s\n", model_hdr->numhitboxes, model_hdr->numhitboxes > 1 ? "es" : "" ); - for( i = 0; i < model_hdr->numhitboxes; i++ ) - { - hitbox = (mstudiobbox_t *)( (byte *)model_hdr + model_hdr->hitboxindex ) + i; - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + hitbox->bone; - + for( i = 0; i < model_hdr->numhitboxes; ++i, ++hitbox ) fprintf( fp, "$hbox %i \"%s\" %f %f %f %f %f %f\n", - hitbox->group, bone->name, + hitbox->group, bone[hitbox->bone].name, hitbox->bbmin[0], hitbox->bbmin[1], hitbox->bbmin[2], hitbox->bbmax[0], hitbox->bbmax[1], hitbox->bbmax[2] ); - } fputs( "\n", fp ); } @@ -477,19 +468,18 @@ static void WriteSequenceInfo( FILE *fp ) if( model_hdr->numseq > 0 ) fprintf( fp, "// %i animation sequence%s\n", model_hdr->numseq, model_hdr->numseq > 1 ? "s" : "" ); + else return; - for( i = 0; i < model_hdr->numseq; ++i ) + seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ); + + for( i = 0; i < model_hdr->numseq; ++i, ++seqdesc ) { - seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i; - fprintf( fp, "$sequence \"%s\" {\n", seqdesc->label ); if( seqdesc->numblends > 1 ) { for( j = 0; j < seqdesc->numblends; j++ ) - { fprintf( fp, "\t\"%s_blend%02i\"\n", seqdesc->label, j + 1 ); - } } else { @@ -513,18 +503,21 @@ static void WriteSequenceInfo( FILE *fp ) } } - if( seqdesc->blendtype[0] ) + if( seqdesc->numblends > 1 ) { GetMotionTypeString( seqdesc->blendtype[0], motion_types, sizeof( motion_types ), false ); fprintf( fp, "\tblend %s %.0f %.0f\n", motion_types, seqdesc->blendstart[0], seqdesc->blendend[0] ); + + if( !seqdesc->blendtype[0] ) + printf( "WARNING: Something wrong with blending type for sequence: %s\n", seqdesc->label ); } - for( j = 0; j < seqdesc->numevents; j++ ) - { - event = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex ) + j; + event = (mstudioevent_t *)( (byte *)model_hdr + seqdesc->eventindex ); + for( j = 0; j < seqdesc->numevents; ++j, ++event ) + { fprintf( fp, "\t{ event %i %i", event->event, event->frame ); if( event->options[0] != '\0' ) @@ -613,7 +606,7 @@ void WriteQCScript( void ) WriteBodyGroupInfo( fp ); - fprintf( fp, "$flags %i\n\n", model_hdr->flags ); + fprintf( fp, "$flags %u\n\n", model_hdr->flags &~( STUDIO_HAS_BONEINFO | STUDIO_HAS_BONEWEIGHTS ) ); fprintf( fp, "$eyeposition %f %f %f\n\n", model_hdr->eyeposition[0], model_hdr->eyeposition[1], model_hdr->eyeposition[2] ); if( !model_hdr->numtextures ) @@ -621,6 +614,10 @@ void WriteQCScript( void ) WriteSkinFamilyInfo( fp ); WriteTextureRenderMode( fp ); + + if( model_hdr->flags & ( STUDIO_HAS_BONEINFO | STUDIO_HAS_BONEWEIGHTS ) ) + fputs( "$boneweights\n\n", fp ); + WriteAttachmentInfo( fp ); fprintf( fp, "$bbox %f %f %f", model_hdr->min[0], model_hdr->min[1], model_hdr->min[2] ); diff --git a/utils/mdldec/smd.c b/utils/mdldec/smd.c index 9c3b4a5b..7c7ffa0c 100644 --- a/utils/mdldec/smd.c +++ b/utils/mdldec/smd.c @@ -25,17 +25,18 @@ GNU General Public License for more details. #include "smd.h" static matrix3x4 *bonetransform; +static matrix3x4 *worldtransform; /* ============ CreateBoneTransformMatrices ============ */ -static qboolean CreateBoneTransformMatrices( void ) +static qboolean CreateBoneTransformMatrices( matrix3x4 **matrix ) { - bonetransform = calloc( model_hdr->numbones, sizeof( matrix3x4 ) ); + *matrix = calloc( model_hdr->numbones, sizeof( matrix3x4 ) ); - if( !bonetransform ) + if( !*matrix ) { fputs( "ERROR: Couldn't allocate memory for bone transformation matrices!\n", stderr ); return false; @@ -52,14 +53,12 @@ FillBoneTransformMatrices static void FillBoneTransformMatrices( void ) { int i; - mstudiobone_t *bone; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); matrix3x4 bonematrix; vec4_t q; - for( i = 0; i < model_hdr->numbones; i++ ) + for( i = 0; i < model_hdr->numbones; ++i, ++bone ) { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; - AngleQuaternion( &bone->value[3], q, true ); Matrix3x4_FromOriginQuat( bonematrix, q, bone->value ); @@ -73,14 +72,28 @@ static void FillBoneTransformMatrices( void ) } } +/* +============ +FillWorldTransformMatrices +============ +*/ +static void FillWorldTransformMatrices( void ) +{ + int i; + mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)( (byte *)model_hdr + model_hdr->boneindex + model_hdr->numbones * sizeof( mstudiobone_t ) ); + + for( i = 0; i < model_hdr->numbones; ++i, ++boneinfo ) + Matrix3x4_ConcatTransforms( worldtransform[i], bonetransform[i], boneinfo->poseToBone ); +} + /* ============ RemoveBoneTransformMatrices ============ */ -static void RemoveBoneTransformMatrices( void ) +static void RemoveBoneTransformMatrices( matrix3x4 **matrix ) { - free( bonetransform ); + free( *matrix ); } /* @@ -171,16 +184,12 @@ WriteNodes static void WriteNodes( FILE *fp ) { int i; - mstudiobone_t *bone; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); fputs( "nodes\n", fp ); - for( i = 0; i < model_hdr->numbones; i++ ) - { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; - + for( i = 0; i < model_hdr->numbones; ++i, ++bone ) fprintf( fp, "%3i \"%s\" %i\n", i, bone->name, bone->parent ); - } fputs( "end\n", fp ); } @@ -193,15 +202,13 @@ WriteSkeleton static void WriteSkeleton( FILE *fp ) { int i, j; - mstudiobone_t *bone; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); fputs( "skeleton\n", fp ); fputs( "time 0\n", fp ); - for( i = 0; i < model_hdr->numbones; i++ ) + for( i = 0; i < model_hdr->numbones; ++i, ++bone ) { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; - fprintf( fp, "%3i", i ); for( j = 0; j < 6; j++ ) @@ -220,32 +227,24 @@ WriteTriangleInfo */ static void WriteTriangleInfo( FILE *fp, mstudiomodel_t *model, mstudiotexture_t *texture, mstudiotrivert_t **triverts, qboolean isevenstrip ) { - int i, indices[3]; - int vert_index; - int norm_index; - int bone_index; - float s, t, u, v; - byte *vertbone; - vec3_t *studioverts; - vec3_t *studionorms; - vec3_t vert, norm; - - if( isevenstrip ) - { - indices[0] = 1; - indices[1] = 2; - indices[2] = 0; - } - else - { - indices[0] = 0; - indices[1] = 1; - indices[2] = 2; - } + int i, j, k, l, index; + int vert_index; + int norm_index; + int bone_index; + int valid_bones; + float s, t, u, v; + byte *vertbone; + vec3_t *studioverts; + vec3_t *studionorms; + vec3_t vert, norm; + float weights[MAXSTUDIOBONEWEIGHTS], oldweight, totalweight; + matrix3x4 bonematrix[MAXSTUDIOBONEWEIGHTS], skinmatrix, *pskinmatrix; + mstudioboneweight_t *studioboneweights; vertbone = ( (byte *)model_hdr + model->vertinfoindex ); studioverts = (vec3_t *)( (byte *)model_hdr + model->vertindex ); studionorms = (vec3_t *)( (byte *)model_hdr + model->normindex ); + studioboneweights = (mstudioboneweight_t *)( (byte *)model_hdr + model->blendvertinfoindex ); s = 1.0f / texture->width; t = 1.0f / texture->height; @@ -254,30 +253,76 @@ static void WriteTriangleInfo( FILE *fp, mstudiomodel_t *model, mstudiotexture_t for( i = 0; i < 3; i++ ) { - vert_index = triverts[indices[i]]->vertindex; - norm_index = triverts[indices[i]]->normindex; + index = isevenstrip ? ( i + 1 ) % 3 : i; + vert_index = triverts[index]->vertindex; + norm_index = triverts[index]->normindex; bone_index = vertbone[vert_index]; - Matrix3x4_VectorTransform( bonetransform[bone_index], studioverts[vert_index], vert ); - Matrix3x4_VectorRotate( bonetransform[bone_index], studionorms[norm_index], norm ); + if( model_hdr->flags & STUDIO_HAS_BONEWEIGHTS ) + { + valid_bones = 0, totalweight = 0; + memset( skinmatrix, 0, sizeof( matrix3x4 ) ); + + for( j = 0; j < MAXSTUDIOBONEWEIGHTS; ++j ) + if( studioboneweights[vert_index].bone[j] != -1 ) + valid_bones++; + + for( j = 0; j < valid_bones; ++j ) + { + Matrix3x4_Copy( bonematrix[j], worldtransform[studioboneweights[vert_index].bone[j]] ); + weights[j] = studioboneweights[vert_index].weight[j] / 255.0f; + totalweight += weights[j]; + } + + oldweight = weights[0]; + + if( totalweight < 1.0f ) + weights[0] += 1.0f - totalweight; + + for( j = 0; j < valid_bones; ++j ) + for( k = 0; k < 3; ++k ) + for( l = 0; l < 4; ++l ) + skinmatrix[k][l] += bonematrix[j][k][l] * weights[j]; + + pskinmatrix = &skinmatrix; + } + else + pskinmatrix = &bonetransform[bone_index]; + + Matrix3x4_VectorTransform( *pskinmatrix, studioverts[vert_index], vert ); + Matrix3x4_VectorRotate( *pskinmatrix, studionorms[norm_index], norm ); VectorNormalize( norm ); if( texture->flags & STUDIO_NF_UV_COORDS ) { - u = HalfToFloat( triverts[indices[i]]->s ); - v = -HalfToFloat( triverts[indices[i]]->t ); + u = HalfToFloat( triverts[index]->s ); + v = -HalfToFloat( triverts[index]->t ); } else { - u = ( triverts[indices[i]]->s + 1.0f ) * s; - v = 1.0f - triverts[indices[i]]->t * t; + u = ( triverts[index]->s + 1.0f ) * s; + v = 1.0f - triverts[index]->t * t; } - fprintf( fp, "%3i %f %f %f %f %f %f %f %f\n", + fprintf( fp, "%3i %f %f %f %f %f %f %f %f", bone_index, vert[0], vert[1], vert[2], norm[0], norm[1], norm[2], u, v ); + + if( model_hdr->flags & STUDIO_HAS_BONEWEIGHTS ) + { + fprintf( fp, " %d", valid_bones ); + + weights[0] = oldweight; + + for( j = 0; j < valid_bones; ++j ) + fprintf( fp, " %d %f", + studioboneweights[vert_index].bone[j], + weights[j] ); + } + + fputs( "\n", fp ); } } @@ -289,16 +334,15 @@ WriteTriangles static void WriteTriangles( FILE *fp, mstudiomodel_t *model ) { int i, j, k; - mstudiomesh_t *mesh; + mstudiomesh_t *mesh = (mstudiomesh_t *)( (byte *)model_hdr + model->meshindex ); mstudiotexture_t *texture; mstudiotrivert_t *triverts[3]; short *tricmds; fputs( "triangles\n", fp ); - for( i = 0; i < model->nummesh; i++ ) + for( i = 0; i < model->nummesh; ++i, ++mesh ) { - mesh = (mstudiomesh_t *)( (byte *)model_hdr + model->meshindex ) + i; tricmds = (short *)( (byte *)model_hdr + mesh->triindex ); texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + mesh->skinref; @@ -385,14 +429,12 @@ static void WriteFrameInfo( FILE *fp, mstudioanim_t *anim, mstudioseqdesc_t *seq int i, j; float scale; vec_t motion[6]; // x, y, z, xr, yr, zr - mstudiobone_t *bone; + mstudiobone_t *bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ); fprintf( fp, "time %i\n", frame ); - for( i = 0; i < model_hdr->numbones; i++, anim++ ) + for( i = 0; i < model_hdr->numbones; ++i, ++anim, ++bone ) { - bone = (mstudiobone_t *)( (byte *)model_hdr + model_hdr->boneindex ) + i; - CalcBonePosition( anim, bone, motion, frame ); if( bone->parent == -1 ) @@ -451,19 +493,27 @@ static void WriteReferences( void ) char name[64]; char filename[MAX_SYSPATH]; - if( !CreateBoneTransformMatrices() ) + if( !CreateBoneTransformMatrices( &bonetransform ) ) return; FillBoneTransformMatrices(); - for( i = 0; i < model_hdr->numbodyparts; i++ ) + if( model_hdr->flags & STUDIO_HAS_BONEINFO ) { - bodypart = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex ) + i; + if( !CreateBoneTransformMatrices( &worldtransform ) ) + return; - for( j = 0; j < bodypart->nummodels; j++ ) + FillWorldTransformMatrices(); + } + + bodypart = (mstudiobodyparts_t *)( (byte *)model_hdr + model_hdr->bodypartindex ); + + for( i = 0; i < model_hdr->numbodyparts; ++i, ++bodypart ) + { + model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ); + + for( j = 0; j < bodypart->nummodels; ++j, ++model ) { - model = (mstudiomodel_t *)( (byte *)model_hdr + bodypart->modelindex ) + j; - if( !Q_strncmp( model->name, "blank", 5 ) ) continue; @@ -474,8 +524,7 @@ static void WriteReferences( void ) if( len == -1 ) { fprintf( stderr, "ERROR: Destination path is too long. Can't write %s.smd\n", name ); - RemoveBoneTransformMatrices(); - return; + goto _fail; } fp = fopen( filename, "w" ); @@ -483,8 +532,7 @@ static void WriteReferences( void ) if( !fp ) { fprintf( stderr, "ERROR: Can't write %s\n", filename ); - RemoveBoneTransformMatrices(); - return; + goto _fail; } fputs( "version 1\n", fp ); @@ -499,7 +547,11 @@ static void WriteReferences( void ) } } - RemoveBoneTransformMatrices(); +_fail: + RemoveBoneTransformMatrices( &bonetransform ); + + if( model_hdr->flags & STUDIO_HAS_BONEINFO ) + RemoveBoneTransformMatrices( &worldtransform ); } /* @@ -513,12 +565,10 @@ static void WriteSequences( void ) int len; FILE *fp; char filename[MAX_SYSPATH]; - mstudioseqdesc_t *seqdesc; + mstudioseqdesc_t *seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ); - for( i = 0; i < model_hdr->numseq; i++ ) + for( i = 0; i < model_hdr->numseq; ++i, ++seqdesc ) { - seqdesc = (mstudioseqdesc_t *)( (byte *)model_hdr + model_hdr->seqindex ) + i; - for( j = 0; j < seqdesc->numblends; j++ ) { if( seqdesc->numblends == 1 ) diff --git a/utils/mdldec/texture.c b/utils/mdldec/texture.c index 5643393e..a281b44c 100644 --- a/utils/mdldec/texture.c +++ b/utils/mdldec/texture.c @@ -33,7 +33,7 @@ static void WriteBMP( mstudiotexture_t *texture ) int i, len; FILE *fp; const byte *p; - byte *palette, *pic, *buf; + byte *palette, *pic; char filename[MAX_SYSPATH], texturename[64]; rgba_t rgba_palette[256]; bmp_t bmp_hdr = {0,}; @@ -87,23 +87,17 @@ static void WriteBMP( mstudiotexture_t *texture ) fwrite( rgba_palette, sizeof( rgba_palette ), 1, fp ); - buf = malloc( texture_size ); - p = pic; p += ( bmp_hdr.height - 1 ) * bmp_hdr.width; for( i = 0; i < bmp_hdr.height; i++ ) { - memcpy( buf + bmp_hdr.width * i, p, bmp_hdr.width ); + fwrite( p, bmp_hdr.width, 1, fp ); p -= bmp_hdr.width; } - fwrite( buf, texture_size, 1, fp ); - fclose( fp ); - free( buf ); - printf( "Texture: %s\n", filename ); } @@ -115,13 +109,9 @@ WriteTextures void WriteTextures( void ) { int i; - mstudiotexture_t *texture; - - for( i = 0; i < texture_hdr->numtextures; i++ ) - { - texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ) + i; + mstudiotexture_t *texture = (mstudiotexture_t *)( (byte *)texture_hdr + texture_hdr->textureindex ); + for( i = 0; i < texture_hdr->numtextures; ++i, ++texture ) WriteBMP( texture ); - } } diff --git a/waf b/waf index 22f9c7ff..1a25450d 100755 --- a/waf +++ b/waf @@ -33,12 +33,12 @@ POSSIBILITY OF SUCH DAMAGE. import os, sys, inspect VERSION="2.0.26" -REVISION="e4a9cc0189c9deacafd38334e0382cc0" -GIT="ad7b733fc60852f77eff200b79e8b6f9562494d2" +REVISION="84a26fddc7f40e4e4f00569f36be455a" +GIT="61ff2ac19ccba4a69910ca9b2686c51e8bbc0986" INSTALL='' -C1='#D' -C2='#?' -C3='#%' +C1='#_' +C2='#V' +C3='#2' cwd = os.getcwd() join = os.path.join @@ -171,5 +171,5 @@ if __name__ == '__main__': Scripting.waf_entry_point(cwd, VERSION, wafdir) #==> -#BZh91AY&SY#q?h&_$m~V#%czݏk҅#%#%#%#%#%#%#%#%#%#%#%#%#%P@#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%#%'^﻽jtyoy϶{VS}}hАx>ۦ={Navwwu—f:cv>I4<һUi}۸n3܏9^ܙwwn%;Xknp[>vm==>r=sJ=w}k[fguш]"#%@#D'e7h#%#D#%HJwuA}@wu[^>#%Q#%٪ۃpݤv͝v"QBݫCt#?YR KF.宵ŝNXAEAMAƶʨY8n6VuԷw{>|q#K2Vw]i>M^6Of}QSCAE#%D#%#D"(UY&_w#DRTPsqw8uwwFH+=wk}pu^0#%#%#%#%#%:#%>l4#%8^LB*ʴch9>[#?#%::4&4݊wgrv!tk!(#%A-P#D#%(@9#%hФR#%}+}\Sכ ѣ{rѦvf#?:>+ʤ8F싻)t9ފ+N_{kwyϻK׾7n3g|21.S^^^4@}4uJt()TYzhpkv%.vxd>oi;ޯ#%^F{|%UO=vKs}}>HcޕV'=ju}Ͼw{دپw{w;tz'Ko{ow-[sl{b}TxEZ.mn>x݌ս|#D>a#%6}تVS۽ͽ[V#?ϥ6ұmulqO]yٸk̠><{qケWl]z;cr_nkBv=uվZ]WZw=-uc׷z;&EϽsPU=bۼz 8Onw5l Z;t17q{zui]>BRV&w1jf=žwq#%zmdv4|rk>t}>@j1U*vTA;aϹ#%#D;#?uֵͭG;j}uexQ5O#?y{޽] 9˹Ӄ[jw{+vnxz)Jy{ܑ! 5Gn幷;>#%tP#%>=#%#%㗛)>۾<،qu#%۷g{uuwwMz$*3.צ#%˄#%XH]#ˎW-JRִ Eݜaq}{>h+8n#%[dT&Z3wC#?[SתE}m*>{vC#D%Tـ;Nuelռl0=V*[=oq"۹6k/on=zʝIJUU`W2GWA:#?ڼZ#%( um@tݮkQZt#%#%=>wݻZM#%#%`Abfk&mӥhw]wtbcY.4uqo|<|:d,mz,չm{| B##%@%b”,҈u#%lYvSZM#%@h.F}t#%(#%#%A}^`S-AtTubah#DUTZt'r2#%cUcS7`jiґy(%U#%KL:j-#%TPHڰzxܔՇns#Dna_}hNvRl#%g'jjXen:@=}gtmnkg7Scjõv^=ow{/Im|>L>Χi#% #%F#%L#%!3BJ#?SCȏ)Iiɤ MA BdbTAI7OzM4#%#%#%#%#%#%#%D@ 2ꞧ6ޤm=(iѣF#%#%#%#%#%SML OS 6O51A6'P22b#%d#%#%#%#%#%h#%#DPA#%4@&ЙFS4B4$Q #%h#%#%#%Q #%La04&@hQD46#?#%#%#%#%#%?O#DtU3!W$*OU3j )ЪFA)8*RG ~Aoģz>0~91K(F$8e`{Ikx)[U 8#%pBk!Wv*D#%;Bp@@MES*wDIJPAa9ck#?ݷWMzP2Y7,(N:3Mh$ Jl)Y0r6,64W5j=?4ݤ]y_40a#?~yևd6#?8}zqyמI#?:=ߖL^Tdӄ]8`X;ΰrQ_װpg+6/?Ԩcmcd@ҁ=<#% a 2TY"EԂJv9XgntAyO5ex24,(Ry}Y)2nl$&h~U[)31w[ΥmG~2Ю͟A%9DP u3a,(hS(R"Ѕ ,?T0͆C#3peM{w (uؕxzR #-3 BkP~|!Œ]+>i+lWvsCW,VxUa:%h\1pi?]2)N1Dotnck{SV ?y4>Nc5C=a(Ch:~^:OSʹhz"UAɏr@Z*7\ڏ/oej&JXT|p =TaH#JPNfG (<;1x޻"iu ؟c1x?l/bP\O&#%şv(|/Z(0Yl(`_*::bÄ=5UVV{UR#DAC{(:&H" 'Ej(yf:WT"Cv`HˣGhG6_n aGs)`iT2nl kh6q.yrԾLZQƑ^91w͎*'A#%M;:| A*vԒI4*©;m6 I.Z%׋," )RQ-nCBZHĭF=w yPu?rc'˜٦8ierq:\*Fz Ϯ*{#D\FJ1k 1뎴_ZBPQ,"ּ3;M%٧d(m~;ֿ;Gq)>k8Qx1Ê&=&19Hu,?ƻC A.6@y}c2(>7u.hF4T͂Ml,Tcg1Ycz#hc5Js}?^聒o\I/hpڜ]u^^'㳟1ODoK(Dj5)>EC-`,@Ɯdci%Xi-_~x!'ưz+@YERiR1T#%Ň}KKXb&(H/<`+3-5UY.PZǓQKҜӚd|?٣cX+f`"Nn̲܋`E ̕)1#DnCmϾ=:QthB5ZNAMDT̠V/U*iSr|Hc8$ g&FJ!IF3Č>/]ѨIi'Zq#D$_qF5&-$-@V<2ߛ0ko){AW$9W'3L~\l8g9?r;Ͳ1 $k);"Ti )J)CݰG0m迊?;;'-䉓bb([Ӄx #?Ġq) 6gaMXtEKf7{Ϟk K8vƗ#?id ߙfNdOC8(l;%ƳTZ{1PN(C8\Bv=8|T?B~I~s)L'׊bQqOs-&xvzvt{Էsʧ6Φ1dÿtֺY:tלU}Vh%"t@Cf!7/ߵqS(sxhg|]2Z1֛ð8#he Tj B/pَ\;4C]Fw! s}FV)1*¬\#DR/Oinux޸$ehZŘ1<>:ȉY+q^b_hwD)}$#2~9<\Z<'smBks.M[UM/8 $|L~MulcjE6R&ަdP9p3L'ڬ邸=&)AiBP&Uִ {}mQ_aQ Vuig,q#D/hjFR,~5LQ>#DdFɹ)Xݏ,g#?d&#??bC!< -2vpYsC̓d{MܼIڑ91}no[3zCU}*b*IdF>6V)j!t,"bP?bZ(C)0GJXdJ*gu=qz;LPC(H #?ɣIؓ>;;_oF9~xaՠ h성FGχ/\C;E'oOr}rzߴtݲ=<,/z_tkjď$O$`z^WZӉJ VkhMXgw臹&hT~ Bۓ ".:g0J4d+#?Giݟqd#?)W1#%K]k:5Q( "_.tQ^q. 0c.aC5i9DžQǞ6L;a!s6KLɇqe|_G1;#DS uW½5j~|prqmhogW;"÷~?UIGefb:#8ی_ö#DPR.CNyHtًV&V0`qQvT4`vT3G;<93!D n1Uԃ^ 5,ܦ#ʌ#%]gaKJ#?rK/V/jUKe{[p-fA  D4!1v 6j]#jc,jLqt` R ~-iy,/ b'sXkh3c8ҕ*y+SfDQ?O- k΋Vѐ2#?W„?Uْ?OڔR1|ҬB0#?g#?߮Ҍ+l_Wٍ5+^Y'pQT%ZclߕI䱳<a,jƱ߲zcC |Ff஡F>~,^|(&![絴2;P6I1{M~;'.C]n$To5zy8D{*B!(%0vO'}0wNMМO#h\a9Hp*ŗqc"a%l( Dsןj.}#D- 븡B.y#D#D $]R?昀KވJ9I)sqIO8R %%(fQׇN}r'ZM3Ӧ~&햊KLiM\@<8drl>#Dh`V=NSv|k;K|"Zݳ_XGs\qqBL,TnT\*~Tmt:_sJsrOmWgGWR.9) SmTq,JyUYB~կ魐N} p)E~e~#%q#%(΋YVBZA^qg`ٸ3L"^^bB#@¤OfTik';f]~%xc&oVŷ_3!fQW,PhDm! {*#?0!4SgLv&'I;zf>' C)Զsai.sR33|EX=Ms;8~Ul{#Dٚ( *9bd0*V#D,:Bwu oBt1P+‰ C<ލEB=ELT-{W#?с׶{~MW>NO/:_Íj67}ۯƧ$ϻ+Bt#ʏ&#D?<&u?#?5wMFcnNjs9oѪp aNR2#DS|;pABKzu!]6*v5JN"M~^эTԘDL{4"Wi2*f0x6ۇ'>=d|0*X]}+V s[ʟUY\1M ⃮|gQDrD;I} xD)'Oh ºng^~~x{>0q88#u Ř4*!}P#DLYʠt`a-v+ynb* TH!V<<y!Ӧomf,;ZL4C#?>-ֵﰥf/Cgc3D탒lů3 Wb!"LjxgO0(D:`VN!YbT5UE EbTf@l Uo;?%qǻEdR:X&G{q+8b#-"3jƳp"+=}#?"61JG6;&hGQhB v88ג3UTeL"Pb=2:}ʏaV2tYAݗu+Zھ9#??B$R1N;R(#DGXb0A7wBCNb7kH@,#?=8jx#?v(g)SJ5A6:Yqq7_6I6E4٢ΫqɒYMGˤ+mРnYG!ӊvG66d2E?KTtjaMCFƌpFWT/v|Lq&kFQȣ#?vQ YڽD& E#DFҙ v#%ꜚJK5!1G}e7C2U\}5p>G,E1[vJM} &/F##0.#%oY_zKxZU|"kar*8g, #ir#?霨9DML6iiEʯ _࿙+UH4sZ8}^?;b2rfrB3fV}#Doԭ2?36ֻT3z_mP!gսvio78`LLXq{W",PT|UHbm~dN*=ItQIi?[@wk[ُgݐ$=<ӆ؎pg uEǝP]r`*l:6#D^dC{څ ?IܬLt v,rsjkdotA#?ݻZgaKb!R+f/3|QEy-YJprM#? pQUbFϚ8{~s;I ġgb]7`=*K* #H|؇Il+ł}(alӓyFp\Vo~8s{euӲ#㕤<RmJk=7) K2gݗvm{CƩ{t{YC ?RP6vё.g;MSRamX{:P̵a#%KNJvQewMʫUB˰"ˆ* #?XjVAC~>|bӉ>ʭgn,s#?\Xu'Z0>}pиol+| RCjk:ҴCsX8h.F]2Z˭;f\u׿|&Loٶd@[/4(b;Pk @!6" @y@ )#hyfm*=\^}xr(&AFA݂iôAMh+6[LO:0FDY~]=?Vv:A#D);aeh(dkTOdՁ A@$GmQlݮ2a5^_!;Tuw6`#%|V}#D DBT!;@C$C0(#HPR(\?,#DUG&x=OvåJT`*YW!*dxy#%A j!H$?eF?95σq#DuajyO6fTxG.sSMXL<#Dqd/Pa.FiP@C넦}y(#D*(ׇ8a%a#b:vnB)Pȫh S&G#[@@SH#hFPR(#Q_ܕ4m#?Vi4gg1ˑڔܶŲDi7ϻO יt*xёAj =ÎA#?˹vj@qY]b'ۋ"LO,|ZXHxAzSTxԖZE֜FYmd<[CbY#DLV|Ѯ]Ǹ.A;]wdhzHϏyX;#% OA9ѧ7RUZHC5xfK_Iom2u[2Ue`+.iJh8]l*Z-A'}CyvW#%8@"+Sf&F op4 &齓C Ԑbn4sXwiP,V5>%p=׆b:te.[7 -cDoIzy@)RQ=",?/lOSa8jRii~=zpUM%=3#=׳?l:@u,O_4o~]@($)K|{£yPBmc;#?9Wf{9zW#%'HЭ>D (a;A1QPdJx?_T% P^rRC@yiTZ^p\ L̓FTJJ1X#D2xۨMg+ث`Ak#%T"06<'P1sQ!XCS餉2V)Wh[q#']4\EK< XOFZǘ"~OJ}bM|Ȳ/ݛM`"?ҩV񪙚ʢ%]HZz#?xrx#%p! uoVs@#%۷ Qɻ9_e >pc?7=cDRhy놑)VHfEqo?r@2NWHZO+>yO}H-,C(m׬%a#?#?`'NhE 9h)XkE\/A9)b̖{Pjsscİu#D.=H-̺.Po ww-TiXR^f3ZnDqsQ=lsC{Zi_Wbи_;< #?($Cp:}WW%S{tI~ʌ|y)7H `PlgR#3Ed7D0t=N~OtfijMC^B$6479L@4kDz-W#D؅ #D<{ҙ ؞@ި\0jR@ X$Gyq3Rm| {PjhD29 B6TA6#?9創OcQ|fŢ#߬<{H eHHI>yxU@$=dqw̚u [HpަC5TvHz);ЀU8!@#N8M1K)fiv <{3h{C.G#?$֗o:d'IOąc|D10*+'l* #?#?G\QFZ (LlS![Pc53zjgPbIav7sOL'YnUXBH3Ы47Iߟ윂խ\1'٣UY#? bXXmw&ֵuR z"b$X۩V!7tf+ݖ(G:80viފH3cg]%OH19JG"λFYEY@)2:iu5jQvaBdvgjg6tu(x3a#?RA1\D&j;4!5i2#Dqˑa5JގF 0"cՒANn~<_'.8Ҭƻ2rΔ/?)AQ9'W_0ᕈ:% \V :4`FƊbK^2ad(wR_,[t"^:G1!׼zoa}:gSNXQIT-U,U*#Á9'W OM@a8yh>T0tʴb)a3zg&__JbpЃF}0G#WY-&ұ g#%MOg#%ʅ 9A#= KZ;'5:ٿ˭Cԣ#?^#% A,ad?]V841D|u@CT|:V?1?)A"/8W_UV>I_Ok'Aͣ?Ljh?{fYV#DF+2~^hUZߘqvm#D"iOu됐jc:\ aK"Չ0 袆|4=]9K`u&He#RJ ^߿QH#DܴԺ3>HNO ߰;ܣUziUwTlA1SWD #Dcq#DʉKSu\~juמQDA =C't-L$‹|2xYn_G>q0zN:vq-«|>8OMJy.gXFqxҙr+eƱ)~uu [\៓ 32A8#?_' ';Lˤ2d͝"ѩPxd&m=qBM-C}L=\#?ے(PF(,/SaZA% 5Q[Fuﻧ;HaUv76/oGJ_Wt㷆ꎆpDhڶRkxF3'o5En[U$u=%uHGՃhj.y){ JУOD2TIlxBa`Y[5l#Dj *M6d*6c֊"Io7̻ѷ$2#DK[d%#?eQ<ʍoE#U>բ 6]P'6| 5k}\ɗ*6`:vQlO6ٌ 35‚}&{u_Mxz9IlF2:hڨՈZgWo7Gp0R9#%f̃RtlQiK oWDKC' cڲgjEgU``k8KC7Ʀ> p(*]v_S#D.4#%hYMDk)(F<}HZ -Ү`:HC:8}u$E&F8c a#%igU$sSl?{8+&FB? JӰzo]T}+W\#%6S`, @`G5B+ZoƙچF<衋ͧQ;#N/48(Qvf_F3R7̙#?}>_O;V/uGZ#3Ew h\8]ax!C)itsS{|ݠp7@^E$/m[s!)?g8{t=Eշ}v^=a4qhvp{f}p<Aȱ!#E#3\ro^`)gjߌ)6IZqO?eI*v{kQS߀mDR0O!Ri.z#%򌆏#%"H% $=BaTRd&hK#DxJ!(*$^EӣES&i9zGrߎ'S$z2=بFwXSh#?$0L5,&]XY1پo#?o :3vn̶`laxvkl,z#DX@ְNU(TP`&g64pl=l )ܝEBZoYQt鹊z鋚U8.4%X N3$2Q.h=uUV$W%E #3`b>jT,T0#%DN1 +`b ${"jXv#Sٸw(Z|&AվpHMͰMy(z9˨ər2 \#?15^MD`qfj0FQSB?6g;0|Y8__9j!#?P.E/M-f&%S@પB)?[r#?r2y/g,`p4`3 S39_K!z6\TUUUѧn@խ~ps|fQC-l٢jZӼqGtaV,'GP`ShW.ʈ/W0?QuwJt1/, 4>V#?:Lbj:㝑(ɥ̼Om~s9K#s>S6#`@tX6n;4$Q܎tҫyU&-o]*#DFqUJ9^WMqShkBo g)\B#s5R&9V#?[58U,䦔fRS;8[ Fljm6%G D޲fjssNqTT5h!S,tAΡ𕹭P>k0w  ݷ q[ƧGEI*#^vZ׶k%1s}cze/;`+7&##%Yۏ8)rZ+j܌utz{AZEy#54ڜ-:p[2u(1_}Aw8۲.:n?ח!pEJ-|zIw뵑'|~rQiV#D#%Y{Sn`F3A@\QD"`B_[f$߾hT倨=̓rgJ^[?~54ˬFCz_{i5=ރsi،o?__*bq/Gg hd"G:)>.D[K˩uXu[&{?gwolXP|dm6V޲1m4SNɍ#?G8X8Kj2HH#?U35q֪V u.;Ec<]KGD<`)m*>K6k3!#67Kd HGxZ8[ZaLrqa$]p3xT:ηI.@:-"$CxؿuPv7N!fRAdMHRP gS2@Sy$ո;]Z.]t{\^k^8D YeDd"Qd`AH$h Ѓ(͑5M-Pe 1B0,H(*a1biq)e 3V% EBQvy^PPc% N :}8O>l#p*FrWäW̅8Xlef~UOQ0mcGG߿Lݺr.ɐS%3.HkQfo!Nw;4D3pN+Y]&dmB@Wa, 'YSB&?09,Q2HB,ӶT,b`LWj#%cLu5@Y}I1m>VaasA#Df9 sOk`΁bvmtq{R}D5BQn)5=-dzU8WF4÷JM;;j(ogXA3ۂӆ @05K]#%s+~Q*(Zkhv* UH;nMx#?jp,ZԨfZ3,x0 RB|/رe$TC VE#] i:@0!~d)ؓa'rP}ZG5 9V1#%i°aw\(,Q"#DMn`z۠4>8#DiVhvBĀ}[#Da;7V5#v:޶I fMGFǣ!pPHY85lCgbY?,!,wJ7GPB`"E"6QdQŷD35>YKs$. FlՅؘL BFX1ٷ掲pTrAʮk0P2#D[5><\7\ʟ W=J@x-Տ ĸVݙjv N뗞xf!62hǃy1?G3Иbad™P6"M$8d.c(욢-$+gvF(HB*={nd$Z1&"#D6#?Tq!Ѣ7rʗZf8uXt•8E nVq2#?ё#?bHW%SxL ;QjAF-ke[@Wd_6[c%7؊\Iv#Di#?!(N+sQY]jvjN:{`ZNٸ+l]|E\9K&ײͬq:ϢsVSY)(`&v/ yT\'jݑH,+tsby(iE!{[,aK񎍶Nɰ8#D(coZw[}XZf _:utCV~X+.6-,5vOϿZfw ^iEVOj!s!/K gn{/bq>Nۼw"/s'8IQ j"'z_;)9 nے8-Z8'ipX 6b/f #DB5 6ʊig9`$%|2׶y+7(8#%{Mf-OqԘió3J:jXif/puR#%{r-$I]KzuW9e=9,y4ŧz#?Hf"HŅŶTE#DSwBxtA@2#KCTcLוXWuSjR,A-=uP$"S8j w'wR \w)7FE?5vo陂eOPڛQ#?--{4#%JxTߩt,c9(u|՗ӱe&}pMH9nXuTfDݬ195P+9.wJAJ֥O;JbMKx3CN8'BmSѓ]rD{?#?h0\zxĝl>rɘ})z3jFŅ@݈ 'J!h2#DMtc:m;#?D V)U#DDGG]VFߥ-!cY}OᶟN7IJ.Ů=@m3eq]Jf9[p]<Ǐ X9Y(ۮnV2űF`5ܼ827qSc~/ |+p( T8AЀt/}߱6{ Gf+Cs˃_e5U6,*BڬW&%a i[9L5BVߊJ־8/v`2 ->Nl1p(v)e$kfhxiq>^fc @J>@o9rq#?wڶ'2 <Y^?[E Ըk 3>'xo<7aL=h=%";̊>s֯-I8cU"d!Hb=HOVv4/J@6YNsnvX܀:S)x%ebL&cL60H-X3J4.+Q4&L#Dbg㏏k1~q#D$ dHZrxvu78Kt5 J ]kupWҢ(w6]()H]Nu(kO>رMbH4.{#D4/Ps9A?R\aKH#Dgǥ^w>~;S1CCh*=:diZ0V9;`-(Z!ȱwaLQrą@Xw(!m)Q`{gB/AE+äSZR"xtQАIN=$v6!p3.Ͻ/P2v=Ʉr j.tSݖBQY־+8c=߭n7H3yHB[Of&)8>?֬grO,[+(I6͋ιsB 3#USVu%Ϣ<vp0x(۱G>DĀ[!ƨI 3qpq0l=RYMυF~>u%~@"HsRp*bq" 5(hJΣzvO:_TtqxeU~t&q3Biʩ]Y(犓'l.Uh=P/jiA&#?p@tNryvxGFoBP9TGǃ#DBXJYaiYw(y8l0>|w v#D?xhOI#Dc?ŬyUP$h}u6x\ǧ3.P諸"kM N;*1ZcHqHRhNk6NP#:=,#?R6b9G~'Ss󸤊LsUBV/3cEKĘ=$кGCӿ?xs-)3_YglR50j KHV#%5O ݽZ n1scQ<_=8m @mx|ў1FV@z`+@D̖D.;{Y%xB?U(pqI:kypO.#?Ff㖘M(PQQDڪ8嚘(cIEʔ1B) W?9q惩* ED=5"rH=;}m %Oedxk' r&<דÈ _RN.h./%Y^2sֲ&R~EFꩵ^e /uCe^^l(9V'Vk+9|qבt:Pl)c(LE: Xw6o>pǫHBhדXLᾳ䆗>.zKs*5)*4 坙^Aj8'=t8"۸ʺ %!#%;CO>(L˥i"DҊEF)SuӜyV(#Dk#? doi#?7jD)wg(YB0eI`[韷#%66+ki`XP!݃II}a3BFpՖ6V.\F4@lrB#?&E(n5"=ݷ`t3g;+I RU9#?6llb5:.q~#DɟDq "<#?HUXZwÂ5rΡ\4rp96b>4sOz^lcf/ψ[ Y Wm"u]yV%ŤQKүݝS@ .{VtxafN(4hEKry168!9vdոbh .݄ǥ-9v=#hmӬ.\{St!;8; Vȉm f]7968[$aFP0 "c*emJm`R+y Ď)d;90Dl)1vtF:8b:OkBw/]K#?/n<<>"b3P9fI5A_zYc #?6һ(V"svR[{/lqtm8x<)c1Q#%#g<̀͢ԟz S8Qh6cK&nۚLj#%mJј$1CYjLk) Jrܴv3Jp*Js>#%]wV^U|*h'яMI񡲘rrGP9/7̂2rmJH#DS?lʰZj.IQiol:22GáEdJsBgjweC腌quaF=y3b*;lzYhvdܣ} - ֿ]#D7͘- |#%̼|:DEX;>xHPo138x&b ڋ|CϜ l(_p|#C7Gѓ(q aӥX`!DY|`/$ԭVoaJrȏ:(ao78#DI0MauWG0G@v'[><ᯟhUDu v4#t)O܁#DchnU=T-ptNT1^B1K`א QFhpQL"o:Q(X ~[~ŻÀ\V5º趔2.넽?"8e:D [6QRܯ6sq}5;Y#t_K۳Z3$8t^USq`Z=xNO_H2眯uͮ=#T\f#D\qqZWhKWl xavaUA=;.F6J1ov%]iE/JO3ȥAiZV<{{K#?*B"`rr5ofNYblgF()`#%s|oi Ï͋'VcP,dkeЈG ' b9Gi ^ Py%)0ӿv!u~;ӐMS9z2!os+*'%!<0U}ZG5Xwe#?fʌ#clc S#%/_A{n+ T`#3~JX_#?1)' b2U+2€5YJ "m:W'Ғm8ΐ=?vk7y))h>- xlsE#?yIk{.qG-o"%D40댋ll r*fG(͆#DWjg^Ofp֠b,2{MNsdh[fnbwWONSP_сK|,&>@vOߛG׶v⇡4N;Po~*O w̛oYO`ezh-[R@"b=͵F>y39n4Rfv4YɖK`Ρ"UF 391>,sLMcHHHkJk2 bI%p+XW*7F_:وy9B3{e"&S(}E!Sbf#Da#j7+ ?\WP{O_ZF(:Dg"TGTaI;{u`^4DDMbХLңԹ4U#%4J}#?R*`~Ņw!]N5i)̞+'aS=GMP]$rT>zi'0ዽ 0lߦ?#?v17F˰hf#?_Lm_$_]o#`5y#jqsW{( $7f$Z汔a2PA,f#Ds{Ohb?9 Y)qzm]B[].(+ y{bk88y2e|x)d[(.{m?֓Hؘul =_ xTqxuH^1#Da62t!-<#%ک3!eɑI#%Dqu&#?#-^/z;zG=sGa`GiޕfLсKY&@u0$SkIVgT[UQy[~I%W#% QQʌ̹|L77<.9#99&mV](+)#?GA ~ trOǬػi3ݢ%\ZߏzV>-/'5-L|T-#Dh#%SN@S5Wl#D(,'/i֖*U*J+UPD](ZzqڵsD%,5d>uQrF 9Pa;zf-0RF37O%9Lwv?wFʮ}'Uư)4_ǣ.3f 9.?n;\\m/!ɕk*-B#Dmh1IG(H05@F-|)dQϞ?)b{)Lw?RsAY6e< '{Dpqo=Сo8#?lG.F[ֹFթSpRrNd+ fS>((Es,>wnquM㳦Wv#?#?1#?؊h:%&=jm,M0eBk#?ZάىDip8bCeb9FN Q~r]*l[Y_c@=>(M#/,FcUgD9zwV5C#9Y:;2Ps#^ Y?M#?#D:QcY+8VbBb@`놕eX %;tu8Ǯs*l_#D`@<95rGn[-CqNaT=A#%u^}Lb`I6,ac6Q, sIߔ`2da[C|9z{^1chbvr6&r MV.GgMo-J/ `^&w*C,*rUAP-)0-:HҊXNcp%]鐙1mO|N&-YîHV3xޗLLRbkyFV| 5@b'x**IQ^yx#?>:B ?| (x'_׶:xrƺl?Kӯ[Pm6Րe&C KI>2Aؓ~swZF[;#׮_L@Wf`Iѽd4*؆;cM#?jΖ"R&E47mk/^"0Iih NW)[z 5"Erjt - #9BH.f\$pm۳g#D+=j>_IKm, тN'.@yg2ueӤ ||v(ވQϦ8Ner2>i#D=#?/|,O'F m#S(h*"@ ~MICRwv7>B? @ӛ">?ȗ4?Uth֠q,!x=/won$}Wl'a8,#%PbbN5Hl)a)Wbgo`jLCDnnSxђҗwwyŜ3C ~_HH950'eW~~ñeZbG-pbsU8ꆳۿKWq#ZQDy D Fh6(QdXP<Vk8YZ&8V֯`Sxk7CS[dx#>OP6wukuUȝ+Vj[J[BiOSmXYQ:8|[3LeиHk[V(#%KAM#Db#D:^a^n D6I^ "sqOu0 I*If#%>sAfRUcn8-DpjϋsV"%/mj10y҆%U8Inlvх.> ""GTkj&ЩkEk >`;WdzFEb#%0AXϚ(v+zJS6V*C7Raζ&ߧuv[I%o~=x;$ 4/Bgơ(wa6&QrG+wP҈.޳4tҪ@d#%. !oz]ֱkRUdlco#D6Dm2#?cgj^y:#4&8Us+!=. S'>Hq)ca~rO>6 ^vAg=AH#D"#?CMH6#Dt(Ž$`ɓfYXMMFwV2`ûAi(b|FbW7KJ|+qG &3Ռ[;E~u]f32N/4k#?D5Gjwnx=_$W;vԒvh0ָ۝-$ n֭wу! pe<{cq 9#%`\ccƟ-C Xɖ{}wՐƸ̽0hϡy|4D{)L 뽮^e*`^s'dAa8v||0J^?c,xgnn}ko=oGfq$G>!w.HjA-M(Mq#%Z׎&ꠍWE!a] ˃##?R Io6\)V/gyeegkL82bJ?=ԨQ=gwN4ޝSs՛^m4L6|)n[,ɴC#D-*Fº$HǷw[X}q8*bBtc6Dv`+KY lWNwI7G֠Y*^C$`&l[6RLm6Qu P3k.f躮#%#%X]rMhnLJ\CR`?P +1i]۴wU#D,;yFMG=Y1K, DBO;`Þ֟^ Zs,A!k?"'tBa}1u7XEeF/Vj~Lh_tqnbY`L',8},&KfK3Nx;#L2uMUJṛsp*g& 4W8{:YT)<ʢ]afQ܎śܢ[W\U-jsyPpۙG|9>9?9a孄ecBuXN`5WwIlܿmuݑ<nx0 ֈXadή#dz(+q8x8˓~6?:DXKp0PI\ -qSg6ögUu#.t;k7#({#~o9Mm'Ax|ӌu\G3diu/NY#0sػiY9(7x#k|dLM;k"ƻlx)nMvO8n7|(>~:[ nZS\sJnuQʜ[8V0#m|>X|ՄC:ϣ՞^~Zm]t9Z!,qekIե9tbdJR 11[4arl#%-3&|E1ʱڣ V6^2Wt&Mqfuc+19vZSx>ȘC AI!=qrRD>'px=Dac4s^ʻ9|6 8UbU'pwUnZ]eai4ZHgԘΑJ6uW^ۇ5qo3{˧#9ŒbG͏懏ɬ^=oFnƗz9[wK]m@3#Dv%z*h5zG3R^PkƏa!y҄bݍ&iע)Y|yAN\eƇLaz(r6^#DD\gnFۏޢV;8Yޙ_uط˼xgn=Ge0#һB~3`TDF a_) {2V0xse&8q: DI8NPfĕZDP9uPAPKd+Rk`JNBvB$IΖt?`H 8wf;~kbl^RA[d,O"cBRy}{c{ɗc.2C{^zeP>;,84`>ĐxPcE,.b#DHZWF& -,:n!#%x<%[|LX 2һ1߯gMqzE8j%%AVa}b|J]Im*#'`#Di5Qc@R$1#%6E ,X5Wgo3_ΎE45 pة 1}vE#%cr#?(&̇[Zi~,27C`'Y+.\c:ŒB"TΧ]4޻X3M<0$) :)tr':kTk7:F/N.UBF |*8tIe9u,ي3hJu7+z%gK@uuc`ھ%@[(vvb-͞@5N m(Jy)J|Ksoojb#;ZfBk#?q8.C0$aeLÑ閨 KuZIp1$23X1A,!k) s,c =tj#%(:ޒ9JɒIx2vnu3Z8zOtmRF~RNN!^_quD FMc|?|w2#?RVxJtk-w>_ S; %hv^q"wC;E09 %Ps>*"6`<NJy%7xgkc^`2`mYWM>>}ۈ ɩx4耍,AKcHyhqI.-`PprI7/kLQ B&d!p\zwײrM gI `#%}#췇hMo>϶&#%j8zنKSv}+bh80洀` N?& B-s@SGĭD%K#%%eΦ .+1:nnU-ogF@٠=A){3ޞ#DFۄ4bmlr/lDgV#D$Ք))ɾ%F Qo!e<_ 1XE7Z+X#?สuD$|6A0ov=CcK.d@v2QuX0L{p{#?C!jXXb4#?=߷.ٟ8kU:tŨbf{Z;sz6sfJ(0ۡ؁#DE>S#Dz???5s>ɘ$QT݂" 4QPQ6ohU\͏#%@ ?P!o{7UԮǡ'ADV۞ZF @Wc#%)@1#%4|z<Ҧ.-|Рt)]?x"xB|m#%^مdp?gէŬ/Dh/t0#E!LIfSiG{;n4»\mE~ VѶ'Ӥ8xS}@/lWmͺς!(!!PDT=2RErC6>ѢHۦ(pGx7DOB2#DHDza2#%])0Xa#?<( v!I%٭0!(P! ti%t[űT@ݟBYҊDJ>U׌gTe$4+3u|HR X)$)%R%(IJ"(D=Q?SO#D-:YC!-VR @1I(߱ᴁñXr}@!p"F>OJtpM~{}q?OS$T+j4JH~K G:F?9^9xX>'oe|q:Z'I_“ nPw6Bǀ鏫:Ya(0On߹^_N{2R:ui+IL Lo6dHibRpfgB])E<;<.y.6ZqF] v#DUU$=X;aɦ261_끛&FPD:DKLF7I/Q>0hi;'8hc#?w#Tɀ>|F)U9B2 dZ#?:3pqQ#?B#QqwF:zL* wګArϥ,>rsRZhh֊< T=!}#>Z3N*K;VbC˼mdՄKYYUAu>m|hmz`J㌃c @s`R1#ML䟳Oۡ8PD,1%QA2JDDULpD)jIRK@~ߎ45CEJVT˰1AϮڄ=Z #?/o^3#?d_4)OjB"!?X1=u51`Q?L!`ٿ.xh< "6GR#D*WX0_4%P؜ %>tOz$1uLn#Dr?BV*UTc{k%Qg߼̐ɫU1 pv &%bQ?M`T74\.e%Q8ddX*;*Ǜz#? o¼3ge4T<f'8`|or:`\rA[+/ l„Xa %(#D.#?PvW;~|P?*0QܚoX@QQֈQvPW &K_́/:ƛ^sD2} ]+#tTa7/?Ez#D !0rqd5‡<񟑿_fPPykOޭ_1{`b5lZכŕ[4Σurz=tX!-[6*+}3>!H`XչEVZ408ً<#DvO jnEVlsSfz6~?moVy7ߐugRx.NuDfAݴ?n'%fy{tLep#%~$]6܃8[_|!>m<+"{c#?Xܕ8=obJ]O6/xWҢF#%UO T#%D.PCro%#%]kl],cWL_R"W&8XEW]*j+?aDjB&<)#%NnA m?]}h0tND'}eob#%2`RTZI~Fk$5#5liWtt(Z?6`߭>{SboWY/$//<{Κ'$H֌hpS5Da޼5=cDDɂaS`u>}OkGFF*7ؤs)F*JX#?'Yڳ=QP }gH9 mO)%Bf,+/4@}q.5f !2Ah{slM?$fќ>|w33oz<_ueͯ,[}:%ק0O{Zb۩#?x vԉxy~q{Z.m$F#% M[:8Lgl!I ?ZPFirqMtůz#%zwrۻӳM\C~K*=9t'1~rv ?^莟n_ŸWuǷ\G^Ԝ kGfw6NƱMuty& 4#?Cq5!-h>Cj5;6h1\b*!?q #DT)LrVu"T*f"A!OO)(;pZ ?*Mek| UF%{@^s7R4'̅dЄC#Dn+f,#w'8E#%,P>K^k~c}g_7=z> ~8j7ϱ2|Nפ} ro=?tׇÿ?F UÎ-ioO>MOA?PGfZo~-7w?Cw_Da<;Fy*;͇-s~_5:yq@W[f/A7D[N<-¶ +6\9.<m~n;5tvc{#?!&?R|8#!C֕GO(l&Opg;rs1PőDze.}+C#D!<ߌWc0K6* 0Xe [^oMͭ//bRCF :ȇn\46EDznJy|3|쏏͖osGﱫ/ ugsO:M@ju<#?ݗ_YÛLa%}{.#Ҽ9sirC[ռoX^c`Aq_⻓{e.54q|yV{:ۯmҍo#?cѨso%EYS|ё{*[,] k1?]Db]h7Fef ߌ-QiѢ0=(^YnڹI7*{RgB{^Ji)ڟ;{Z^M0=3eպ>nVY%_nTl}siB>?Pkw-.>o϶F'yr/ϳXFP{i<\F';M_~V۵S>]|ޚ) 8 -U/̓~/K{1e#>-28j+H#?p ra2z}˔z/5vDr?OFW\=;#DoyrB5w>#?Wu'?K 9՞?(PP2 ) mAp}CfSvy{gkYsTN{dT& luՎ/tsִ6\?0[x_/f:6jeH$uU} Lw1=2߷zO{%(G/곻nGp{ud8wCO)߼mtMW}-=¬~ݾ֛[wWO>Q}~"W^G׷yr'z{f jx>AWU~`cM|@GċǺUuOROW-OLLP]9* Sp)#DޣP1Ki4$W0(8pGyagx[GҜe SG^\ht|חL}#g.3;;@"c#?́6#?UΖy>Dl lJGv ķA8i㖿g#%>}8|w~DAn޺stD0 #D{=BމڰM5W=ok֥'a([Ega^gkdDeŒ+o/ !ؙMA7-`z%L~1QϞXn#?o@6 SZm#?RavФ$#%'-*Y7_!Jݦ[7#D?مP{ _?fp#gXSnṓȷNk 6os|`jk~| F`"Gq֩˦90 9]j#?w| 59w0N8+9 ,pr2!G.@-HNM 1K4A9ˮgnۏl&_e:<\ve6hsmW9G)x5pL#&Ǐ_yTFԜZg\`{AS4M_^>{+KY#DRy!|M`"io7HdP|bJ'lom vXFP E*APSSKk8[JQLFcp//V`5:RO!AOZ'UKrk oEoG(n(8~OFǗ@pQ\ڞ\`H P*q?9јUbilN;qKh".lG1$*\hhpprm4ծ$Q #?TuMiLTL,1T&45@#?:ULXC(lJB27i#DUjD_:j(WM,l|F.-r? q"X] ۵nQd): M(;nE_Fsl2mo C5ˏ,M'.eC.#D (1)*/~7;$N* I$@ {ĐCHh՛b,kJcvW&z=T|cOpWdJRC!xz@Ns[ߎ᳷q廍z1UCWwH;5~qv&/팋4S7|޹QHO 뻔!ɼ#D(1Y7p)J`)uMTΗT""G~=-՟5Hv/o6vvy,pQa|u _儼^=)hm]OKU#R=.[H:g#Dт;|㬦‰NBnÇa*V%`HOF&gۦ\Dv>z%prE7ˤtJlaqXF9*qr\^+탱N)Jq p#Ha؈dt%sS}.J#?(g-چ' VThn8ƈK@,hQmhӐމ>+wCQ {#D6Mƴy>Dx<9W78#Dk7r&DJm KǮ 4V #DڃA{4汅ш4`2Kb lQw1%vڪhE1[4#?1 Qt'Tf`Y4#RRMr Z1&I5Z-ftlFۚp˧Xy%ޛ(6KI8j7Z]Cai=O>w#?-ڴp5LY/É֢*?(wzt30EG k{ta j #D-@$K "sBtɝhd[*d(AHM\)("`;u[gb5R|J@_vX1C`4n~ߡE:oGqJa!=[cdڞ CmUBC(-o՟WI0aí&m#?m+:LEt!BSI gl'EH2"a2MV0NbB&ܹdbT'qpxlPtLѯ,<>ž5s~5D4"Z)sAj1D ΙYi7ju;pajq1%p>r\rC"{.0ڱIp"ֳ!&-98指Jb]ÓTߕvQ ~uۨfI 'yg2A>Q>:g3#% `zg& @V3q2!@mzWs89ݎMFIeiLߌuWFB<t^xlor>{YVb`R;:Ua0jbַI[)\z펗GP`o-#?1&w5Jegaڃ tz]0_F،vc42%rqΧj(:jiev7}k۶Yͨ#D clg/y&rB5XaXﻢƛ#DICKWXcҐ-Y8D9ćbs#D.Oj/9donDR1/ƞ!PLCfRMzj¹4OSc8X0Mx{-¸rsr#DTZEw Ui$Lq5+>{L+,L'`5FAr>OXPR8~5#DQ&”s_ɴмEvu\-YXc,@`D YMTDG"1J$@" <@6%rlxx@<5b1—[6 _J9AJ#-Ip_C $9D|sB4%V<윔blEJ#?V)Y9&s,Rv$Y졌9%.`Խ,$p`Q3&^CXSAWβ2+NY'C>ӼC`_"$O#%0wDwpOF`>o&Wng;~hgjioq/Y鐦'_4QkȒ4[X1Ƙ8[0kr6}uK\0[o hVhbJS'\MMu -~M#D~,=~m|Ì,zfwS (5)0(kpMmU-f弉\t`71M3X'+୨a{/63t]7MΈNcfFIoBttK|߶_H_cۓ#_+#Jc#?z(,"bb[B**-HAI;Wd};w4H[61#Ze},L?NDO<7'Ma#%?$%0q 챇êgT_#+5 QxR3Ef[:8%AsY@UǩG^U .nsn-hL%-pv#Da`oC t\佌RNo{-&Q/f)!hB76'Kx)^*eE+s@]o{)cf:ˊ.y+i]4L=%#?j!bO(.5#?*t'0Pʧb~5'1o JRdQѰ8V翔#?8Hċ Ļ_)Q+JE{_,RЁ`B61/z\Z|&1io-5i᭤m:4v찏 ٿ$Yv;yZooiGs(#`]ԷeÜNr<.͆^BwnCThE]1w>S`s=};G)җv"QziIaN27_ʣOMБ["L͡R8C&LR.j@EµؾÉ%uQ#? v(6p@%:2L_Jn$~9TÈY"\Hyy$u fqV MnwJaZ,h Iݎe-ZzŦ=t7l&6odc"4vtqٯ^bvt.4fr0sK6RQ+K"tO_,Ό)`e?=0|>/{y()׏TyD$΄/-XLNhLSKSW%[RX$]>yR귙E8zUYd`zoaMpߟ/::lu^r"eл Df-Pưog7]7es`u\\ ]#$\}h .52AG׻:xR^1m_m3#±<&WKI΁Y[?BumӞW|=-[AZ-967%EuhmDXZnBcߤuEg+ydrʜjZMkʳSc%+^DrZ]L: wT`*8[m -QӎɴktDfF4@#?q&_4VmWi5 }V&\yJWrFYvwF4"iɑ0"ƒJiK,n$pLi,|)Hr#D:FqMUȄ6xL qUq.rM"#t5;^#_l]OU/dN0w ey.kn#?/X֮Z8VUv` eLX+DO#Du/_? ctV#?aH ?#%>/k أ<`3:E-oϔ~Љ&W_²k|%S#fYb9k?ٟuCKh=aۄ΅BzqYAHG_}q,PxwggbsEg;ذNQJSoR՜R7%MS >#( @RMv(ׁ#DuFѳ\#Dk,ئۃ5x0Ͻ#l;=Z]{q tƴ2?glckdsH|#7ӆň{lO*nCwR#D0<"~V|5%@HhJ*jX<`Ꮂn`vxk aZ3(fR(J,Ä9}"iƦRȏ.M-x]2mFasCdZʂRlZ1 `!Q.ebPHVkE-zEoW5ODb(0w.u`4paW2ӣWeУ33j-54K Ф~]mnFوa&J Vg}`c\b#2[8RVm+JrlÆedE#D!= }~N*k!6NDa~ޘojz mH#DdXuW;!NfY#?`rs5OdeoCDv6`* c̰n!ɻ|uUETCqC&6q˔~G^hj`d(>tE2Q6BZw~K\JY-[YV:.BL:mhf0SR|'ūddƻ#w_ -r":ȋ ]t=])ddFqnY<;z,~{a0mڗ#D>Msi<T,Q(j)tWnj\鏓f2;+͛,'X<5o~?>XWPRyoBBC[,6V 8#%w*9s7qc;:(dB}FÀU헾 Kh@z4&PA??#?EI=]0t٧CzJCMYM;X8(H|#?i6H(9 0#D[Rɵ0ABo#?+۵Ct?mТ(S\疚Aʱ):J`E8fW#?0P8jNJ;|~KoCm #aYiWh;g2 vM4 <o>x>iE/#%mtUAM i@(椐NC+cOeT&n1{._)xzvr"^-aD !%c)b__2e#?>~i{g."Ȳs41E#%-o{V|ԣV7#?}GPXI{y(GMR#D+j/ܣ`v#%2*!h|t6PWm8!L ͯkQ&wm 'Kh7{M÷2j8UaJu7s̏#D/>}I俣=qn/h#?[o5ۅVuQѷoCьj%BtWmwQG';GbP$pwsYd"9HD?bhRpnu5dy#?nex^8\@:cjE|M4L7=iHn.mL\:Vn# P dZqJP=F-ef@(AcfkNf~F4 [p<ڲ-S x<:+?#%f>3yކa {:>i,**=7|ԅ4S@ CHEH逑G *&d^(*TPv?#MT/xk?m*$3;Y^^k-vI~cPY>€rTrZ)4N|>|zl91H6.mpUm] ^#l' ֔kQ+.e—J\|;]w&-#DR5?^i^qNұ48߷#%&(H"sr%#?! ֺdY:,=Yb%#%ħ#%@p-b֒.zj>uBbQʼnǡjbjT3~tcaQ#% !p~uQw՘JgY]y8l5q<+6qPܾr׈jEg8m]k%HK_0 1"pӺ# de'Q>c.#DMj^mJU)mmQjɧ쫴$v D@*蹑!Gi#D"*4نFTUݮss.9җQǭp:E>k^WDtI!8+aXJGqm(Z]uDk]uia%F < D"[⎠qRu"m}]Fio:4?'"b?ovdvksV2OSUJO?Nr="4F燓oMoONAo|I-#?J.ʶ)/lr.&U$R~2?ut;"#%?*eXN|cERQxTR'w_$<ƃь_gcgbI#'m(RQؽdV4X" a; d?ObCPH3#AQYPLA#Df1oH%W-)b#[q!FG>qRE gq,\NwRH !ޮu#?p'Oi.9IA %շ#p#D,/hb뽆/N|Nl$5%!}t۷#%'rc#%Q@_s#D7C'?!Fi.y'z##!< >iMQ #%]dGS^`y>#?eycÆ@1Z0ƱgMvC8LG o]Cq K'?ӛRjJgCWsSwi b2`O6^ї>5/o]j t>ڧηZ/Q(Ќ7w2gL}54fy#uw/@{&ʧ{z-`eb.±T2Sjl8t2][Xp#V\kqROwli)tovYJIQ{ڤH OqD=IȔF` vPƑWui쏑GrODMtJ:҅֗lk4>–rEȪnx<7fSѽON#?\yc[01Hv"|PHߵ07؝"il=YCCjG@ƷWq IoQiD9D?b3h\#%BDw۴[-[^65w;;=JVZ^#?ؼY@(=e"cyT#%z9#%u(7Q@#Dϙur|!axof al,T0p&VaA goYg6{D;|1)v|zecd5+٩M Szt*FJI$3(a, Į[5mw#DA)߻E XYadKfKr&D&MN8L06pEH֦do}m\6J 4`^ftQ#?qٷ$HR]@Vh6_GQ$,]B#%OeFV*cr.Q"kT Q^Rv|9¡,?Jz_mܝF8S5S4L}\lz@UƟ#?aJ`%A,g<HAr qZvG yrۗ,8 dLRBxnXjav+?g"1֖:e"8d~gABdBrT`"uzd6:u^bcͭ5,P!)OǪI 2\ejEW/0rhjl:m-A6#;B+G$#BDGl:"9s Hځ=\Oʷag^~kp6rJ##%\P,(<kl6 ї[/TJoⷮay!g,#t}:xk?[xL[KrC{IXKW+ &b@$#%M[,F Vu'W#DˁG@R;Ah"XloUH57b|H^n1XDT BwЇw\l~g =o:A dNzBc*>/L0fN#?s 3hQHCuWc~.kTp C՗VƷ!NkpȒL0}*p>[mh;zkB,dt{I d%Hˎ7Ѿ +&nuMOCKP1 uk2kH,5*xF&xeh:`_8wxj(ߎɧO“F#%[-[Gu@Q6]\1PeW0#DrZf,T`CZ9r1u{^"SpvgQ;xq\P⒆1SvJH66 }7o}#DM>%k3<8xc[\ )BNZqrwEo 2T1*e%tՍInaՕ?2"AWog9_ bĠN>D#%D&Ղ:;sdt1#%%|q#`+{a ?(0/!#un,!.NZ#DLy)w= uJC l#%hxbz~ $uFsΒ#DZbӿ;hdƍ?&CRiip6Fd?O0t(C^#?#D-8 !=80S@ߝ߽O6%ϐ~8lj9Nu?!mJ7U"(PG43&Nң%/6r #DqJfC!BOKa8k#%Ud#D]l2OE}ƾ0&Eϗd=gjN=Lp)U.^Axswfy?.$PO|l9yk`Qlz䅶NK!I3C~N#s,kc^- ?#}#DE=hw;~Rl*47=3X[5J;{YzZSE$*p1on&[w!s_h91/xOcKZ!q Re0B(K9yKmF%`5$C8|+ 0;VT>מ'b-1 lrZH/gDС;"0]S>ݑ4 ';꼺|ϺK2A o_g( f#?dҥd喹h>O3:StR'sŗdZ-;& ~ԾW?2GG&RuGb>[I(IS|q^NfJWg#D+BlGήm *]C4--tF@s.MODזE䕕ڒW140>F<nocle2#?4}^p̧7݄L1EƏ}VDv`{њU tU9JbQ_v.db%F;# df\%$\'<;l֒:/>HOk8S3i;Ig!ݿ)f!G"9)3Sd?wƣ6#?BPleb fH&#;mH#DG0U|G<%#D*Z?3N㌎Z.;ꨨPĠ(+&%>*쌤vr:u#D/ ] C0:@4Cą%27`[HjWٱ1f*W.x?T*(0d;C·g/ޯ3"""2u|Lu٬<-:1 qk; &)rItl3v|z0e\'䐛|{g^@ru(*D!܀*>=vId#9AEղ=-mxP3R!o_e7څ.G5 ԒN_e.pbŴbh@SDL5n Mh@;̭ځ @К=AQ,FmhȰykh!ԡJZဎu=q8gGv'3#mӟ]̄r?.aRMCB'.@:^a*Q̊qz:C _#Dr\&@)T%5A1ACQDMNhԒoRꨝ')30{@Ic9]=s{& 4DMik iϹԼH-~ÏJ9}5XC };K_VKSXG٢y8v#%y1JZJF9-=h)SvLjSu pY=Gy󦱑=`".tk&4R\'DxWݡaCvQwX<-+oyLl~Q_&wmj)d361 LI(RbQ< b"GtTRPt #%rD:_Νg`(Ť))׳LA3Rr_ )3)]_e\`CɛrQqhB!KfFR87D^陖AfSqz$4P?nau C6Ff)67o^1qqiAJ5 wء{խ]a#%,A=ܟqYyӞ5o)[bFS)0,ӭx@, Lc}_<EgSD\'xU7 SlATaSE@i l?h@l|5yGؙY8`F}\dC]Mdi>ˢ=!AV/yDZM uy,zZ(#DRld;Nx<Ϲ G`8erRkI#LC[byv3ܠi3[c6w9vdš2P #%5剿vgOZ^ kcOWQYēxI"x8:sw"0}\)#dT=⊞0(EܘyͧPlXb*[Bݘvgs8L!9RHQ)sm#?&,X*#%P!GuXkxn̞(sK+i妮R򪫹!x܁MF*#?0#߶TnN#?#%VThh1.vCs)>G8uvDN ~A{n#% oN5/#O$e/1z~}TiJ#?\cUi8-6)@qx#?` լ HczNGA{#Dq\MitB6-n{偐?Qo :I= ;6:CN7=((c"Vwsnsb'* wZW.졈PעLy`VMHY#DN4L:nxAhAd:#%4ĪT̴{kҠv'd؆vƔ)$=bn!ϣ,oᴊߨȒ#?z?5ճ|"iiz/{d ҵ*pQI@ _֝Eaΰ&>kP` ؆0{+SS0THD6(; /Vp#DJtBYN50N'ڤ;8?qppmw-z!5)]܊nM!9㦳m"$:'N~**ǡfzWXNр8B/s:?9n"MUdqC8VwU3P`hV¢m]+MRd@Njp9Dmpi!.]Ɛ'YXJ "`)"i}Ovݟnܲ(q!"9%C#?+$B]~'0~4=|ʖ3Pᢚo8KZb9+r us @C_sa{>NT:k(.M79PȈ(ł(Pn(#%Au޸kLO6n͡qJWΌ#%)_[m/K-8oog~8qf0W88 #?W F%~mw:wC= L';9t?y!ч>՞pM)g>i@?~7a{;ةU8sj6+;Hdf=l #?T,XzfS]C H! KчuT:,h~jk}-gF*6yB8k8@3>\w3$?<)ݷN?,pCCI$3KgA};[+kףI-U6{ YׁYZ֢#?Co#%Ta>#%pe"۝[$G8C#D<_^ײL>ʨP% :0Q6 MPꀃm @3}w?܂fͶnC?on D0cQ>54k|^N}D"dpQЎ 2$@|$ث +ÀRꁺ.?̂" a|?2Y") CEvd-Gk )ե;t#?H GQ/^ #%#Dт@+(pUn(&\;1ݚgLJ=ckY{um>zIѓ ! &U[|<D61AzwIL7faDAJiFzt&+4#%#%64#?g@l$bB/,ޭ=Z|/k#9a=>~oˣ9E 0KSm11?_ҧg3iԀc,#?̵sDz-khƪ>S/ÚKD*&LEAYUi5:2e5!ýgŦhx&Ov91K1k [7TuZj#F$\uc TU E#%#D?hKd}&ʺqoԢ=cQcA,bp Qw#D/-iuHsM{1#Db}Kf7Įw(G8DSGx@|WxL-w\mS0  #Tr˜ff("!@}?'Lj#%6QDDw<]#%#%BXJ5#%KMuU.3qA?(u֥ӿ}zapyi#%vܯ~p1Z2!JvP0$Jǃz4OyOP7x^&!Jt)2>.#?u B@R=@XYCD\laMn~zIsmq8_wQF?)#%#Dtt7O8 [|#?+ 9=o|p#%FvjYϏG/QJQ{D,K[#DT9$3t"󧆇|=Q^= HG[ZPo+S2X2Յ56w0T'kآ$ ZH&#И΁"Ju};$T'ŻiBT/|Z{NBmrMGhx뛒򔔹DC0MWݟ !ܯ@Q6#%GIуǖ菸ln(H,GIzu4u*|HN 4pB?c*&S&KTct?ٕ8%d#%̚\Vv¢! 2c#D.Io[VDJ1 22fPOR8ťY F꧕ݼK)CoC+#?}:j~s!>x/ǜbA`e.#?DQaq9=A 2 (p!|vҬú7^A#D:VzV1!%ަ5/5DH[^A[ռ7-<ٻ!k_㚠4){#?2פ'Jo᪱" lΝעAF$Y#? FWKd& ̊L 2 OX7#@o},A8v=E|zT#v ٙ!ZY]2 l؋\}bFy$5~z@#DR2#% 7 #{uBp߇td^79Ǘ~ѿE+y{Sܶ":eNIV]W&]W#;|Mp'_;S~A']Kqz4!)م+\ޱ;h5-Qp_+:OWJ_Ga|w;Ok2: F/|0I8䪅}VӞ݇wWι1o#7^WWYY:u8i.dT0gU4C<ƦCmvf,BMRL\ v51 -16`-P͋qc4ivg0(C]9C~y1UVML׆,'?P; _No^" >_wZeb MEl(<~Sx+HcEV$^Zl&H^yMb9n'P^GTJ6 _jr88;Qooˆ"\^Gj9NE9PG-xyLqŒ*KP5#D;HY<^roL-TG_U<=;~Z˱'bHCoЗE1ڝq۷js#I8BjF3P|5$5_ .ٿE=кf\V??nk9Qhsd *( OUx㮊앻xd5޼tpvQ/;#%5a#%#?1#?+q|jC /mD^4B ;ѕx%7\W45IFX0Ö0#ձyPe;Ya'mUR6Qԭd"QĚd;%X8BDɺiTnu[UIH.E>)~t_ی`715%-%je6n#eh.lX!f7ʍ>o\G|#Dyj^ErI,`=̪3 9E%L;~HR cIt:N;l]_y59D~:1Mh?BOۯ+}}T#?R`de@7{Z v٢LYI~LeLj?;y7k Oaٟc˞rDeF}Y:,i05c?xLdK>fe4cDy0._Ll`ݡ\Cqf[ )*6 q|b>bG3w"UY^#[3&|65-ʣs4XeUl<;(wj` P4c9w;Yzf:R+?fYZn⿔yC~mjRÎ$[N;rib\$`(^6@\&bX=77o"sc:T1W yń5OCB%\䫻0&E((JY z*1k;?KNj7 J@z Fgks8fʏͤ~>Zs#??].-m \d4Y803Hs£_~vu|RLKR&d s#DKyL@?u^)}>io'K*n>_pԔլm־]KNPɛlC;Β(>,`C/go"*6Y,!$sC 8ZӃB7ŒМdQC .b|~6%}m?5SQ]ع߾Ԑ!5qOUTڨ#%˜,O˚gyr!CڐhR18~0f"wh-˾-> {LS5b|ۚ#sK}}Y,#D߿mPɓsck U)M=DpJ:e` G)#%<2ϑ1Ou^5ǁeF((E1幼g OFvIVd#?yrHfΥGP`ctnR3pI5J$z28h#%`O08RD0%?3l_wl1n}1M&:ʆT]*x0Kt,6rKq~֚ɹzvFӠM(h*"%q`Zlpm`汿7 9hO[ [G՛ JVnc5[})3q= ^z5'L"FA4_6ɘa3KW|bH=1?\b84vgIsq+D@1%{@rV7tB6a*;)᳘˺P1xSy'Yb@m ,9za)(J9BBlY(WVJ+UJkւplk4K幯Kt˭Atpr+_z=c2PBLEٻQ*n6e&SjJ A!#X>4s|ޘLUJq <{糿]^hd_q>ZFT,VqLqñ,]`A#K E7ïں<5A<_!9&mjk瑱57X$R./qDK~?te8#%(#%#Bwh)Pne*Օ;#%)]O4Q?tրDH:9 xNtp?6s8.ȑAр<:5yZP@hADzv$yw=[IDj{PȓPN@{7kP[f2O#D#%#G8#%P)Dzy. M#%WZT(4$I*/zDjm#DP*z/So}_bx{q6#O}="Rfd@ ʨ*4snmM(';CO #L!x~#%|D <N?<#%@%OUmq'P>Ct[)\#Dd)cί,{<7~#dXym{Fx((#0FhEZ1hu9 rBdFЖ,AzHr Q#? %rӌg!H|4#%}hҘG80W0-HӨˑ&Af$6%dN9!)_mݚ9dHQĪbʍ\5i#E#?1&H=~lsG>8sbF4jCɏVz_o,< +gݚ_v#?}LADDzw:M[`{ݍXmg'IA茔?wxwF*"4khﵻQD݉ܒq"&"< @hh1k]9dĔhDF4hח#D" 6dA0oX8ۜVV6~s)ZP#?~2bdX !@ M9l%qUS{$9Js%؎*RzɦT_L"j:xH܌ׄ_o25A P#%>/tP0EYr@B;e$(ݵ#%r*C?2:͓#%_H% )T E751s(S啥Lvb"!Oi5Ҟ{;#D]#DWwzE*6Њ; ҫB]/L aN`p7~HAv>t?GRXAnÇd`h[+>#?!t?CD(@ q? Qy# [2&qj>x) Gi-5kdh/_";su;Qoh#?)PUȈ5peGzS uO^KW]WJu ?'Db :a7U_ 5C_ueƎ4) fip}V–t !aɉ:솑7t΃^0kx9<555Ă2 8> >KxGI 6>ow-Em'dLk= 3Fhb%QUm  v%@@}Nz;6[_$Gb%z{7GO}^zVB?q@~+Ⱦt?J'7jΩ28^:YD xe#? ;#7{bH#?a^F?[q#}T똀62Ei%U-ah3keSoO2M@pe/\wp[#?Z}0vn;.~CT`m P(hQP:Ȇ>C/Z* 0(A&"8ߓw|>_Z੅ȟy_b#%&,@8b,cq5Z_l=dG#vs?QL5$<b@ʆR83#%=yP= y)_a;yNw~i4(%LUBFVIrMtwI>U&6Ȉ( #%xyAhϯ6%|!w|tZh`zAt?If2~6>m:~q&#?q3<s'a6!Vׯt3fm'29Ƕ7Cy'p< =\UD?xgԺ`mFA獤{#3N.LAT`w}rv&~_, AFE[L I/C#?n}c4ĶR!'-m0֫5P)oK.DJ_*Xë9ABu4EBsd~  y#%c&㝞X3(J()} !)?~? hW hr#%v&F^t2#DG 5b}54Ƒ}G6)s^&et9[bpꞎl0vW>0mo~#Lb0aP|3SX['4%PX(0CZ$/yzSУIv'oٞN0*+^M}`Q| P *'gj]3ZƫM/4#DDp~UgȼQ$7:@@oDkԝ: ]S=@3%g4:')U3 )Tq5#?0BJ;(c[4m0= xƠ⸦(h}_C穸(8GU%]A8R4lrSF4eP]<_|-?8tc;(\sjym14ÏA*tq0AܘmKOWQ_.@M0~_.ATP`QCDv \ɶ$0V|kU.A,0&8&ͰWo+$hp88y9V/q &%4%5k:.i-I i'挘9$3R 5ơ.hc~瘹LѰ)^x<#ܛ6H1+T<}rttoG&"IV7dGc2@gD4]'wO{/%ɔ]#ZاżeaFUh_CǶ߷s{i5%ڶ*z}{'֙9'<p5\Jn N/3}8a:~.㮸F= ۿöO"PkGcF;iPbmh{o#%;HSȄ8c{#%lJIbDqWQ#?HfZ]:i)5W6Ak"g<[ډ#DRxZg S#D-<Ļ6I!d.0][I$˼;2k$Ôy4~Cs y d.B1vhȽ#%dn)CQJzM?n2^_367ٶAوFm*A $0quEAY|߲C qؔ#DR"((xEj$@Yc?o&^G ]CS$ 㾦lW_b=$J6~'b|It*7saħyn5kl37I-c-#?2 AN?pr*gC=.+n -=.Ip6{*yY353F,4,H@T#->1 A:Ra!0}#%}?_|}+ ך#%Ý mt֛N#?Uv2Wo5igH\y|6Q{- 7@\="" i!UXwlq#%2a20 @ ̀XwOg`'m ΠP,{}#%N]TtzZ$bW4T׬n$_yMG{uךGO-p֋=#(=q5dj2t 0[#?,8h63%"!QHzJaO3o/% ̢֌`ע9R2%~nj,0?L]B'1制1@ޙ]&K74bu|dњN-XZ]vkelr1!0ҝȝ#DeH!&NbUYgW^:8XIRr)ڮm}娩bᢡk)b\(U=ŵmҵҡovvavCK$K#D#%>|s]}W/` uld]2/8-#DS%PWok<#%ذ"™ *B#?hEW_67|,dā}1U߀u8uf̑)W :Y#D֍3#MK?Y<âBq?t繩j!{R|hb rgmhiPg5ŢbyGC&HR@d?Xw?wJ>}~PEMt:krOLu+XӐ*B@AMl?2h@w{{9;=C;P\?X"]}cп|>a}UN_1sJ)^|ښ~aIJd)Q:@AEtȾq#ZICuWv :F ?{%EBRMO?_)`qG2IĄ2Ku{e긄r$!H}8{tJl }!6gF1b#D |4 )Q(S(D'T;3 ZRĢP`$x1pDw{Ohil1D5I&o:%N!:@x~#?{5SE<~ISJJ##GX&?,-:Oq?jNg`?'f%Hc]c@z_7.A}ߥ?^Gt $;]>'.9ӞT4ib%$DHT$5nxO@$><H!;WoJ' ;?'3ZMTCD51f#%U,_E'./pغT!*GF9PT}ֆshui:@?G$qE7L:`TFvAo^.Eɒso;I"$NsY_6 _1‹2{P0-#%3B_;i.a壑Wv_ }3cQu>c踱ك6HkjnEul8` /#DG԰qPM^_ ڢ""UDs՘ض*ci#%Q#DDdED0 k^]L!H,#D-1"x>pQ{gW#DG]Y=~Wos99f1},>k5u}ش55x #DPHP&>!a#%0t#ʫ i ̺ `o70`{*B/N72~F>~Gm`AyTzmSg$O(^k?(.sd[y?cʿ?z#%zG,hcOʆ/)TY!#>?FBB##O; /jxUc:|6Àq(ICT%#_h[+kHK ym <P #%s( [IϞiwԜ}((0%:_5,СWQ@>CD A9zX"KT ,A rU#?Z Oe?{;fёHXF|o]kO,dԘ`7q?=$×QoJKU`=QrN)R ׫#?8_ܛB? 8|~渌d91$I{?Q^TA c&%u%OtD[=0`PyIXh ^h#?%}>$#%k#%Ty;ՍvAܼG g.XHQg~?fΎh(FDQ ~$N6aBh(3 GWt9R@>ȩ`9ws3ty' ~p#%N8pVRi0P ZXS'V;SQ)ЫKNf7Kr6I n>z ӇݒZ##& O-ʱhhq7'>1/:'N Y AzU`5nl,f+<$=:N!7'{$Opן8zGh:yЏhz% y%=70P5 K#"`#D*9;-(χ~8{4}-A}怃ڇJW40ݤt '@`AG|Fp,DCEdWgs/77wtLG`SkQT#%=RɅ#%P/Zu{s~04r`s$jBOGdK:@Z%/2P<,_Bfu &&q\#>q}Ͽ\I*9Id5Ui?CaÂ1 4E}aM_sq\JI*Ĝ a]dx#D:~JfT#?&63l)22 ހ#U8eoܡqb^\u;O(oOx=IвH)Z)chyөpv a>K|;;#?Dq7܏I*T\eV4#?O|CPChB##%ܞ w=Dz#?]1iN T#DwbB;݊6w#%toNi{yH ŰC#jO6̞W 8 *?$ )ښo0nWO5#D)r&Ӑ"d\?B~;߷lKͻxAOlIQ|`0a\WT #%6% 1sAX1!F#?; 9 Lƿ&cJLd[.7`P3ۼwD( 97tb'_o[X,`~st|'$۽W.4@M#D;qRߵ5IPb#?%+0͚1Xk9)Y[g>[5t"6GSt#~<G1Voim%nCˤmq@{TBm O/#%#?2(qOɲ:ÎA[8xH1xuԢT-!;DI"BdO?;G}l %L"C=59ofo#?FҡgB?(-d#?Aa<'~ٰ#?V{> 0Sn*JQ s8!5` @'6&Aw I`]b0D$pcI&-Z=hׁЎQH=δ݃ы` ^ߦVg:"\Inj>tsb#%Ǻ =B#?:/o'uafu^ dd>gKңs>d>VZg-eDQ? `1[.Av:P!5Wy~I2ՖjpxtꞥAՃVIRI =9wMUO>I#~f}mve)4s#DZm*qCHKa:QC2/ְ#?$1=nM-D9WW8Z!S~Y#"|cNC!~#DG׻?|ЦPy#%@RmRhJWI>ZLzBfIk+lR*PWH9S5?LHՀrcb?XS"b6poOFDl z"\!7:g˛9?&? RJ Q@+ߣ~Tu9Ƭ%({Bd{Z汸ڟH%m1+U`_̼PO yw~k6IA@* #%1HdC\d>HF~ʩ%-t"4y,&YJ-l=qK#%Sꂌ蹺fg@ϛGW7U؁|j#46"ѵC[ղl+c#Ds~X?wS>**66B`o3b~˙cQTX#DE#?z~  h(-'_TXi[vqFNP젇CizVr^RoSL3MՁZ!n9R͈S2kU*w/++n{c9vD[4T tPU@j,9/VFN#%>}V/Ͽ5;dκ3Sێo>UcX9ڪ@Ev|_g?iחBNptr.z<[#?He^ sD;"3xȋ!|Rr9rc\@='ܳ\qO9#?duĞW ]#D‸CL+#DR)Y$kzY+_Vύ!;;_z:͸slkQHH|C;d2#?3)q@3(^)́ڟ 91 ۮ#DzYXڹ"z/zS/_xU¶S^j)#%D&h٢'IxIxQD`׀#%cFh#%W"Nhz GRNH YDwq J&:-%&ĐT+C%I'/F~x* g#D D!%g/$V&Hr%TH-_<\ϳlwi8hڵ^S$=~IJ' j%#%p݊$!Μ94bĔ? @tIl-DH#?b")ao lK#4S COﻘU#DI8Y8.$Iu'>=ꢭwmr@z='9UDUU[[3P2L69 ;[}*X'iUDl)̪} q=2 f4A#@͙ Pň`Cm,؍fF4j&*HH|OxlKIQ)12-pHZ|#dh:9`|DG%8IClHOL!Pd Xqv#?֮נ_HQ6cM`stZ#**d]$LbhX^x{>xf2:w#Dq?OX2a !SܑF8ۜ[m y k^&d~U2灂x8/ŝ|$9;t%O00hEnFh!;#%y6s 孽qOSm}a~+*F^I/ÎjL5aXrdYJ,;'a\`Y>92\G[fg5BpWkk9k!a\Û;>axJ5hfI#? TZӠmgNu U-giȻ7ΐ $`yDpu~ adEJLOy͠0'i9C!{ I<83)d-fBΒm? BC>j>Z{B0ǿ6ZܐTݑ&Os<*H&9ʶ v#6;6Qmw)Q}1Z(1]oCW hA)1HB[I"GH'vAbAn/ODIН/ I B<.{jJ2%Zg*|-.,nPV5#ָ; S}E|bcoc&XsO|piA TǬldxwl˗!UR(C%WL4= Y\3N5N)߲|tD#?||+ɂ>ً|4B@Aws~swN@nFǃZ-K.6Rk޾A41ķC8"JIT2h73IC:T} V50Ks#?\^ɿ3Y`STS̢xr*@`i>/̢OwfL@Suz< "#?΁' 15{8y&$1I:-@|ɕl*U-:>kB#/˙$qK=)j|ѝF=ӿcS¡#%89=T+yn#?OjuWqfc*L70ȶ3#Dxvtm҈Cʼb<1ankH2?#D#%]PJaIS <_D61ۃ,Jg$:!< ~%Q8U!Ee?niWP0Ol9`sA"$*C8b3x B#⪚-ry}s18#,B̅TaS^ #?q/#?M=S_Q{}.@Cf2 }G'_ QIT>2eK$ A2v_H! 2踝wG4@2;4ݏOFoS~#06 6HʩAEUT8h#%!JۄZEuJWRҥijI'}KpxCI]☩觟_S8r*Xn}# D,T@IH@#D :Ͼ#%zW|9+]oWPbFfp.@vhG% 2#?d~?#?04r4ͭVƁ΂Dn$Lݖ0I(d6#?tؕ#? vT:Sx>??=;#䬴0' ]ʛh0rQ9t#%#?#?% D#%y`{@`ׁv.^6ex̒A$&!bwt#D!i@\Wߛ_{I!)HI2{if2did„T&&o.gmI oͫ˗yƚF6Gjfәt7S$jDbt0Vmmnq&o$kB') 2+lBZS:;BɎ8a#?u:#%׉ R#%rs=>Rlm3?%J'燈kx/x8QZgL'̓ 3hAQ+(ADiEQ7,K D<8:#DhI$F%PK#%P9x#%I8B#)TVv̜J gbXsL[UW*ё5%A2Dn#?EcUpN\㝟4NF#?wJǤte{$#%(D%X(]QOP0#%4Ạ@!&X~>#?>gLn*#Dk`AEQE@ T=&(DDΑ JZC0@dB"Qx@ W?8ʯ>9 Jֆ^a>V[{>qQH1 #S J}\|#?& ǯ&i5Z\Ec.M8sOb\6)0cd%f" $&A#?S1?`;R#D$ O"h П."bG475%)HR͐ѠtIJfD#%#ҏ!)C2v*R<,z%T8ox8ȁQ:yj刈j*J;4C}Av'#DB;0x *Α~N+ѿP%ā=$QUD7os6tXVc-ļdLH h"W-?F`9Ya^V`hngF&I%IԚ#D|CtE컶2#Dk|70z 7i5f#`b39D-GPyMW#DT"CXDb˩Xh o8SF{1Nq jCq?쭍 Q#%Z577< bI?nWń0_Al9Ksy݃p7P4fR%3?p6k#D>'_AyrfH8|rfTXpo}_gM;|:/O ~voL(QBiY0*bzVs 2,hv!%ϙdQ&DbT#Dl)ʼn#DaT|Y|gb~^X{D/zRD@ ^^#?bRD p(?L{>/ߩ̍ڄ >t@@ S#%\&hS^ᆸ<";1 P#=)?{áy'0ҡC00tqvw690&~9Ä|^KJdސvpΎYز`L~}ao98 A0Lk#?u?zƊ#?%#QcF-.ɣoGz_2hhVe@;SK#%!Sr'BC#%OBZJ k^}@EO$ I$6L1qi013[f\ I5A#?xMC ZaJPg%[p=rr<.(#D @) R*#?L#%Q0ة[!w~{ֻa(ݘJۙr4C! ri J ]i(1rOu*Ox#D1b_|Rؓ`J780rDKHzyt#?n xq.${#%76Dy.ZR#Dԁ8N"H[}0φFxNs T*{#嫠ᚮFA麸"laڙ>#QHv)KLJi  *(B&QbM(]x>f|9eN՚=:~D4RvO8#?݁i!C!4߀;zNdLQ5?#.hҪm/ʠ|dDR|{DDSbJ/U#7>v&PjNѷUv֩y;|A-zp8+㻝D]#Deи#D,HCa`6#? #%F$f4r~G2SLZ0ixhP'zm3UW@ϛ Nf7baK09+BZS?K 27=ؚNKWR.'hzxO.dĜAN@ANߟ a>{-LB /,GJ;ØAuFh4}UqcV S[AO>k~LVJ4;AALƨi/44pb=Ry<6-L`"0%5NCzOz:>>`$10=M(#%?di!#mˬj<?#?`L4՟C('\=R+u>! g8X/eEtzXߵ1X=ovGܯlJH:w?8~O8#?8-θDɇ煚]#D`3x!Bix9:?r@uz8Bx7m@i(D#?%tuم󢣲i1ZƐHUK*#DW>[54a-<rNX#?J2ΉeNSTN.Ahh&x(>q"  E"V#?vG#?"ֈ("AB@$@D% JұD#D@JCz@́wp3 %C?p\gh0spS>h?^ȑC{m@`q8efNFVtK1/!%(z8XNٺ⇶B92÷f%7ʙ9$KdyK(cpԟ>NAz#p%hFK)#?ك`dR/LAO_imtǷpݲf(`hQ1LZVD AR8Onk; \Js'>y5zzu{b[b(9`AXŭ֏0 4ۡ؋^.*#?S J (J$:I#%bJ*Rk\nQӑpk>âJvJ D"Fםcnsދ+nH!j˔0U(UvG} h)dU*o+bH`vƨ!*ñ{4]d6%)*)!RG~Ȱϲ1~oK;::>]p% ~b?9SAL0NɘE^vպZgSfSzz-#?|V#?uji4hYmr^lgۙB{$h8h#?8dž!rg/.\%$*ɇQ >=m775MxR`d"$u kdTeUp-4#?JZ h hd$h&b@Ё#iG鄐' Liɥ@Rp I'H8h@|6PX*K8c3a@Os 1j,uPґ*/ahL ;QzCt@&B.4b EފagN+gza"& 6NY~kl ? ϊ2X앉 #%'#:RDb(OxlBZYިXͥ/"hlZ!EJn-jFº3p P0{+K9@- 8%wv#?eT(fN6[CA`)J@(@1p:0:cO Qw#%ffuh@iR $&(5:Z*آ,LUU&AcDO#?oWiQ:#D#m[—s>d3/~saW0UAO0K4_& #?#DLzs=B}LL;j`e,Ήryh.sg  հ@@>#?5 5dARцcKx'*R%>|rσt_K솑9s_^!B4rx1q+r{`EXy5-$:bh #?fc]8`oRf>!SSY]"=zlt`{K(|Z& L;:s7)m^uIz6y|ۋ{nr4-G}_y&#?HiFv2#D/>U^-#?:0M)5$LDX}I<DH=xNq(`Q%#D:<#?`n'L" #XxDg?l#D6ZvLJHـPm8(lo-U 59["Oĵ)"USJc{RH@r#?PК*4H2X"% ga76ى8gF{`69ĒDO\S1dF< Dd!?W*胬(t#?9rOYrDDP4D!4D)qT*cB~4\萌`4yI#DD s4P@& oPS E"긙sbn Lb!D!q:MxnĘ]&qFscSObH#%uh"eGc&iv1} }z<9+7_Mu87@}N!$K~_#?QdaɄTJO,9|pT1~#?E쇐j Mk&1fi[~#?s8#Ƀ3M@P2P#D҆5F3qF=6{fPvN*9QV!v#DĪƔk̡ #.izɗqtLS,~/VW(K4a8JDü|&3k0 \;"`ֻcK3E{ NO>(j&]"i%V) 08hPˆ̒4/ķ9jE d2d-"npR"W4M#POa$du q#?@84GyԤѣh-}g]N\l.fVjR>4³Hd*ӌb#D1@ZQt3y[m#ryEck˼=:Nnƹy4d "i71 .ot`1`d2\4ga+.F[䭮cDM9#??IN^N??gP)C4$ @CFN=pQÀ䧄wb[H#?Rah-wd8PǺloo+8wK6B7PSKZQijeQΩFRlZgG髞|ES +@ 4rt@D|!D5.XmC6 au{a#ϗ2N2VqPIۡYzȸ8l>xt].#@zD=Yuq .]Ks#7L1280m)Rz6өw1 Eb4%4[$Ip{#\{1d˜q 2)2z2A׎&*J#D#%ʤJؕ󄮑@ZH4PH8L#%kABSP)@hU0h D f$&X1b(]!FBN٭Y1d.)() JҔ"hhQ+#%4"*R)Jԭ h!\C8ĸ% CJOe>1FOԢ=/OCXL#%Cǻ#?ޑ{CݖmMn]f(qV\Z^fT9,CS|UGϺiB"d(f& a bUK጑$ R13@RDL#%b) ! &gt?';7KQ!bd] Lb:M#D&w<4t;]䄐mh}gs4#?<_#?D#%]&_UDQ,q#%`:dtWw.zӢNk0cwc#_RĠ> \v䛃UOٙfacCrTaHի0X7&)ʺtu=Ղ;`ONf064p4@}h,TC0*B#D#DGĔl%qgZpM!$EI̲JL0C)IE,@"D=ˇh1L>hōژӜЀ݀ނ_て0hdܿ6TZ 8&Hxk:Kb15#D>͓ލ|KGJz?025Hj<>lO{*sP5cʢ)I`$dOhf`-PىC~1#?w4P]t9?&XDeRHg5u#D#DR1.hѾ a*C`KWQ;QoS(dvoXVR+I Ya ]kRhf. tU1j/xI-jG׳L9g4MN{3E(|Ov*vލ@ĻgG,2R-ՊUmm3E$K u^=!,=sĖW+yQ!Qg=nu`U-#%P! #DVC#B|cFJ2wMz752k, tSiY˅@@8dece4@LD>M5.p~l9#%=OӔ$#ǹ@9]#%%[#%AѺʞlA#?s`$uba_y }ٿ39^w(!c)Ot>oϞmu袽)UluT^#De? ^Ƀ҆pV=;o_:4#%HP#% mٺB}(&zfW:d?-4UFǺJT^/7VfDFӵ 18`d{Q1_|ݬ(#D{tT)÷cy×6w[')#?+HmcCRq#?D62IH@;uw$Ρt9΋eu &|SP "z2Ʋ3ܾ;#%A{1#DʊGk5cjL Fڗ4_|x#ß\J%i$i8+ro8,|E9,I'R,?ȋ480q \bIZƩlul!̆nvo:cpx&5_Hp5▊AupDύ<]T(4@ѕJܻ=˛1UvW9' ug{;REm$јR LkFk]77KboW|&4֎I;9X&K"Jxd#%S~ȗ^e%z6\dܾdeP&ᝆȈSsψCu3< AK9q!]K4xiB@#?u#?߃. ņܠ!Tlb;/n]p_mJr#4,P>b;rJ,Fzcˍ#?w䓮.";s'nZBnIn5:I#D:!%P̣g7)(:-1vD',ήatDH;3T0D=0񞶚vO0t]6lL:#?18(INyLm#?I9L{orKUД].HScߧiL&腰3\#DaI,ڴMmι2a؄$$ӝ&FBsa1N@n#%$#?7OZt(L 7w3{b5U".)< BO{ ?#%QyMh”B8|u;lbўr*k t%_l~݊}^Mۓ\#"2 a#DU9;Hbhǘl;dΔڕe}tpw1և(xIr3 i_mW~)zjY4jY$1fQ6WH8#? Q[80}8VvvJF0F3{NشE6]\H)Ho @Y#WYLb\bNho8׭%q[4f[viIcZF0&cPk}//VcH!mEjNPLr:\:o8q1^L$oyAڎB{5ޠk5tBPgT$F5_ #? =Cޢ*Xv02mqVq?n:<3ƞ-vQJ /h|ޫk:##?YQ$O//+BSI٭"4b)ڨ'5Mٌ[Ot .MTIɹ/"@du5+e2ffSeQTJY2g.Rk#?Vn ~Ro殊޷f33r#?/';dH^bYwuϒ)UML''M#DMGj1A"M![TδYivX:Usδsh;rh{! (ܶ˄QGQ#?u deykXq4qq0a$hbgOOCV,7`\r@){i^A,Ϩ8mp]S^dNYwdQu ۆZ 5#D.k2nwT %i-.5ǭpNC*0#?_1݂r9tEj%4ZPq#HwՈft^eX\qtHrF qف\rtP򨮘r5MMpaӅv:]xZ4KDT$96$b5-"J|Y*r#*WRH6Gd|SNS=Ս=#?ݐһd.ї `E$rf^$3 #DۍZ]'|4SԿ\7ET'_dv֚"+Fm~#%eb@"#%sGs,yWm3r$IHlP"4&յmФfc#?՟1"ur/T0eUaTBw-p' uIjPEЩqa|P8Maw@AB $A:$5F Kˠqyd OV ؟ޤ蜓X0hxL9 ,)Z^8gqœ$AMX5d bifhc4Xۥ#D֤B၆hD6*alD@?74#?Rha"!lФ2=4j*nG^C\e6ҏv/4jɚ$$4e:]S-,:"%s7+J>zX,sҨ;ým PCPC5#%A]abZ>.x@E#?ѐp)*:!-3dw[way{!;L=q=@4+!=c XCaTwŽ=l(v;A|TO< ;3CgWc0z8\ՖyY#?NT#?Bhy#?$_[&moOr qz>1bΐ#%flp?>IÛ"P'ay".J5A>R'lUJ(9 r^aH#DړN8c2H%[{DIe˘*XQ_-i6 I.E-jrʆӎLޜL\RI 4G6Ms"^|#D[Rfpkrhl恫%&#Dqr[Pkʤن 66 *!#%;wǀǦTSc#h:a=pJUD('G< `\Z9@M#%cH)DDJ% P T*P-4%#?%-RI3)25CHAsQ) TLGWgzBry_#D%2TR Cy|؏__Z8l FV^hw hfHBҡT%T.V0͊_ ǕAQcU.XUfBȌ g&4b!TƷ6La(d%|ryN\`B#?`>#?܉{r}2&h.]y[MÞ\{`u.#31>9Qy$UN$:c`7=Y?#D 1)5(ٳύiKD#?܂L~w(HrRXUCEA(f:GPCZi Ce< GG[*f(fz2S.A4H]#%7v*#%R'*#%̇j905E#D2$<.w jfHåh,f<.sVy ϯMYv SV*"0D䢖!1t"不gXHqJcgf׌Z u^]AEz?0k(cٓpud\6KYAMS1HٔP(a$8@PorD=^hPDicyfvĒh\]t#D{?+L::#?Z;Քf D'U3C֝ 3+dN(Μv|!a*O}ۃ0Yⱛ#%>3Dy]&{̰/;),C;d8iHc 8w qO/XUaXPQib_ÒB-1jZFM'#)#?m x1sV32 Nbi0XXrPZq1IRaGYi#Dd#?R`Ns-PBY0XXfBI9ϙj[a8:rP))ˇj x0;[3fEiyK7rn1`8IEOhFB#%}tKrFa)tRG2u`)bfp4#?0ũPw/wwYӋ 4r. fFOA^}F#?Cq26^z~(v hkoT:10R )@&A)P)#%h $!}I!d)bPOԉJ{x)ْ7cNЀ &XđWU\w51hf}Wc.Klc)͂rbH.uNڛD>]F$Sw 83\XL.Չ<#D[KBL#pn)O#?w_'Up9s~#,4(nIXvn}##Dӡw֢/1zN,bcq-F,i5`٪%^GץLJWsDQNa5F`ʹfn#DlPҮ̩#IC]bJs-lU46$$81rG[*iZӡSĜdB9=ˑC SzhLɷ)!e<وJ"j)D!H5v˾ENX4sƹ"sAwRIF\ ԥXkx+<&üJ/ O\-zLҞf94 mη!oI)r0=4i:CF7ɫ^qj2xwGAqsD*DVUËUI]A^Ίj8FH]zw#?yHQa2d|݌%!PHC0&:@[PסELS}B#%8#D@kmg6SQB&H:7%NƠ:GIC݄}9/%9ʬ( ̳#%?!qU@)IqNqXuaXG =ȗ&Ͷ^hW#G: hl ޑ&wwi.ZartK$#?E 4Fס>(.+\MhIJv; v;d`Hz8:3!JRDqd,-\멽 鬺L>sf1GSl#mXFp5FHC64Xۖ>b¢"$Y&#?PJ.#Ds*vnNw8qᱭFmXQc!$hl,b\$$ّp#? 8{>7P#?p>ҒO s9Ѱ{]@ #D V`H ApHa!RH A%Ph^=|AAE5CB#%QB(E>@3?wӦ#D`ie(eMrt8t%DN+p**T#%#D#%HJ&`}/Ί6) #D|!}p.CϹ޸^2PE#%d#D#D#D&*x#5jSȘG`*#DA44({K#" Ö.!9ON`xOt8yqTy-0[RlADz{4q7 0ڹUV%#Ur#?` u&SOd]r#%gàr#?3`#? lJ#%G#%V#?(2eFJ?gJ.IӼpX$bK*ɜ ! ^YR}^k؀z΍Rsr}@99zO/!}N^6zs&~ FN3~ñ1㉡#DZ@ŎJc'Z b~8\D#ѭAs8\qȪɈ$Zb,n#De2tLl।#lA 䌧JCcQ1ع5#D&]qYu7E`}"+3p77B\;4D ,"QU(L"cTbX]dc #?-%4PU1Ta0Q!CcV`+#? hmIgq1r$+GM'),"bcs9"D #%"&BVH:p#+-8\zTSmϛR^O()]#s#%)i)S,@1["b;Ä1\HDHNlkQJZ{`cP&1)mE#?DC( 8 LPDaCI< 1#D'#D3(.H#D:¸_z/q3imxYyO. K+"Us~H퐐}m( _DmHވ(3#%#?3Gv* hCŽm^}3dd OFuACIAE#$JJL1! W=7DN*(Y$Y% E,lsk!WJiiJVߎި (# 1@H] HXi)#DX}pPIBA 2B T0IIK )H*$H*)h" *)jIZIe&*aHXhH*) #D)J )(H*Hdi*#D H)e&"i* & bHB@(()!eJH !)D"*@$M+*EIpSQ$BS%T)Bx(' JHIQ(|@$HܣPuCVX)/ [XJ3NӺxj'=1?͊#Dsڝ9X>a x$am>Yࠝixiy#?lM ~я #%ܧ'#e#%&ߩ_y)=ڇ6zJ5"gmwתNR!$JB|E蹃}͔v\Ji/z8 S~"]rL~.}gp%J5GyWWYZXe}P8!:nM^0dD -\0$xGkF!#%)$7'c&bm k4  3 7Oӹu45^X ]} >w'#?#% UtP rL#%X ޥ83IOZS?~66 AܹznK1;h4jr9#`8L<e&;#%FT,4s[1wXA#?TLKR0JAi#DlgbO^c]7 К#?#?paQTD[ '`ɠ3bZJDwf AjڪtA #%v ۰Hz'FÞ#?.1ɾъ#%i}Z9!#D@p}P0Ԛ" _!9)jE@2pҀHN۷ȅ SĊAj0)u=PqO:I`?BBP|#D$, ۜޏ.糧ԏ f%Ne}Յ.ŠkU,uߔHƍhsje(Wl*|ӎ9ßua).h $l޴"O#?X~ n9"`{ \oT Ŧ*tW =g:14=*TI&ᄿVsv@s4ǐd5M`9C0+%=GOj"*!T _w6p%ZcD&扃nDNqee3M! 9<5p29PRe#D8GYNCJiN+mLِXXeHD@M D8O @r#DYo6OdsC1PCDôN0"³n}jy\s8Up4!&CZ p߉'b1Xe8F2\0#DŨka>y|kjI&Q&t2P@Ox4Xַk#iZ yS̊Y_l-|>Mqw',H̓GVylPi~mF1)/MG1cdjd#?⯷*F7f jͱ?t2%` nY+Ks,|3̽9;k4p Pc5hbÚ>w3#?jz~a<=iӕlA3JteAQ$0f6!%#?Шi0#%U(u>(vQEkrQShTv[[*_t4#&-f ʾA4ĨGZ], 1!C5UvT qS(ى0eqH`g*|"ޫ9ډxGypr$/]}'80D*hq8_D6n<AHҏ]!E$N hbĪCNu!uD8hhF`H)! (83I340T^)AmE6$Qt-4imY,j?iR1bH&K1h(&xWGĦ>j (SF4#D*V% &mb y#D(z>!>O@dW=ĥ{;͵iȚh{x'`!NA#?"?e2qku3iy0^ԅ2]?^/48xvk"IS:3s_^oUKUM' Κe58U1N wBb18NePӹo?03g%eҤTxQE#? YgC__i3*^1:tj%C@;NubjsPh484QP(94̏@#?R,Oljęq438}8D1a#?ja#?)"$-HauѢ+p5-?tv %)A6#DPgok#D.MH#%/ fK$2|9N#?g%@}8S &S*}%h%BtrAe*eҢK8IJD#?4G>dni)SN&"n{ W]xt&*Be'lN`(BL t+b#D(t)@Dj)*!(B*U#DfT(E)F(B#?4HP)YI"ĩ]b#%)b)@$b$:#D(Jbh(!)6D#?4-!@B-DRU- DSIJ4DTM@D IHbtR%PA% lPHg^y~Q}gbcQjqJxcwx;!j{ۯD\$0Ϫ$jd@(-4TQ@8wuYӥNMj}add,Yz`"a?>z4֞ J4.҂@&@QT1*#DA}Uc9#DP B(x#%%vrhŔkHRth+o'ւKyGJ(p ]4-RoI96ŊEBQay=9!\"2|H}a$#? 6!'pҞ.0kwl"dwA䬉(WC@yTvh!pgiᬅVqb"7?Vʟ @GvqÂ{eꁄ~, ?ʀv+ G5bbt'لv nnDD#?/޾eol<4PmLOS4J2<4$$XC;~p>:cO7.SWJF#D J#DJAe9EEыy/?9ˠ~}C,4$)"P1igՈ?qCBl"9mOszP |F5#?a\B[_uT^ؤBoFi! CƅݬAjdQ I!A3.BY0p#?T,PDK!,`C\׀x&V!AW4`_>A:d#Z#?W8蔉U(AZTH) D (D 4)GiRm^xDhAQ*M8JY#%#? b\T@QHRR4mL-YR.̒0$X@:+#D@I(#D5JPR:A4)H."A)Q %_ƅД1)m`#DO'&#%#Q%`ZWPOy,4~/FoxnP:`\Ly'$_}}^KiIw"Hk !D*Ü݇ȇ2P'Ug@l0(dIPe !_0GZH*'Q#%-#O`S?8JkhFS7#% JPW,->cGG0IG IK{ r^&җf>]ށհM6p,#D@Zd!#D95PM`q F:@LV[BC'>KC>o:w>2R)JPa(楈?LN9鲆]#DyV1"ؚQ&ERt 11Uq1ɏ#o@Py5e<`QEC\)ua"]h<Y@TBML@UOx֦`pk#?$UA r]CG'MDBA#DRP} <'bH#%$@PAr6&B}c&(#Df Ķ<>4}@{ًY#%N~xn#%@O)]:I&#C O}oM<(4wG7R3V[o 3/RgPcWr'TmL>4aQ~4vSLG.lgRi=&yn&L;v3S#%d w'r4ftG~G\ῆoT)slHJ,i cPBRXzQۉ&sAHD#D]5z"i7:vW/)ՊZW#?]B ^8f7:1fJdCsYఴcoK8}rr 2ި9䄉8CiTxFU2 uO(pJ9y9qLXk0Ük},>2̱ W4,b{a@g;s)3]Cҙ1$[H.G}-Nb d4#DPTJr{E ª"ehu(w|X*PqfB-Ba&@$:p}WGqp#D#?#DᕄOvJ4?C=#DxaX84pS2~]fI(1 4Kdn7SFқ& Mx^Pk9|,M6)fN#?np$'3Т)ÐĿN{1ߎۻQuدMe""#%gNPx~ԛ_uO8I˝oLIH0#%cJ$ `' bu!0`w7.i(&L3#?sf۟xK2zB]Atl*PwtL|T>w>9rNLΒTJ2#%a:΀n5%RAKT0S$@JT3T!HD1#?#%DU1QMQDĔEBTDLDHU K #?4S2R4+BRUP!@SIKE4IE$Q#(@ԅ13I h*BB)!! $* *U T  )DMA ȑL@UI0RDPD#DD2J$0  QIE AT Q0#?E13AT2RI4 RUCDQAD5R$I(R MT%,Q@h #XLB1$8 噓8#?ӜG~PgT7 #%P{a~YYAަ"#%4B)#%bU#?-(9Jq* (^0"@BS!*P!  {:#Dj'L!6JDZ{^E4KQ#%1)F"o[`1)HM2QR҄KҔHKN-MB$U! DD~i ^@ͧ@QAJRG8d9\`Bia?_ɩ_ݢ e8kݞ`C }Ybri)ң`g_t;/9C@@D8=dzߚ~h)(@_D/6>HkP/j]B4MB4K)IDLQM!PU JRPhZ>`*%x2:'HvtB檻DᾨmgW1tbs3F"]w)5 4OĜ[;2l2xus`-3`>`7恚xv2 ?Ύ4dI`TCzxzHx(t5;jkrBC|N6oX/I$>o؞?>7$~Ȇ5\0woprSh#DH4ehƛ;u C~.HF^d PwqD#5HA,ԅG4i8 bT~/Io;1x~<$ֈv@Ra>6ƵThwB  !Y&Z9Vu#?Apdq76qaF!b) 9iIC{`GF[#%]zlxPlQ${v@zFH0{}{ӯO Tr1* C,,WaO^Iat´R2ut % ɃA SHG$! =!kNF#D&̃<#D"!ӶY" bb p}I:6N09cJ'5>̓[cdcp0wALK# 4r$CSHx !Ύr3@=~֊#?<p ~Q@؁tY^10^hy#%>Hܝ4[>߯tr)xINdQ0G`'vb|-%NTDy»IHJlnDCH_>qRPJlȴQHR3@1/B#?/946|#D?iɋ$8nT)#fi#WI3L*!mr0CSC!+ᮬv:wrm8/з5 `c NLĜ4fM8bi浘'h.#?| 'S0*;-n1`#DDN#?ŏPs5QDmŬƚ{"=TK6iHB&9jTONRv5n#?ܠYamhc#5qbnWn]5O,FbUA#D# hT.5#ۉq)M#?Y gQ]G',"<6$=XzR*gA88Á!)Ld_t?pm}I(]4Bm(pM#?#RҰo `mQja,qPC',HP×L١Mffa?51 K;"e49e7x7ƵD򘊽B9yQ(8viITHBiNhg;!VC+ё8f[(y&*ٿ*FVMW}fgT5V~N#%}6 C':tA_?N=GvLo%PZ΁ fDEDPLdK!NADH~O 'B{j\CLC!u_6qGt@#DT"ս su< ; T #DS#%#?We7t{a2E`ɬlv4@#%VTDJ: =CeDzj.3Ζpo!-#DL@H#M=NZd)G%5; s{TЏݐs'_oVw6hd}sF}N{58DM3?Q8V\ 8F<|X3q#1l16g7 lF˽>9}v"i#ĵM#ps`&{]3'Ә]]Wk!s 5KɈH%Oh"1JP4+`IBGβ0ԏ i/*&^lɾr"'b!řE#%{ʛ?zs܂NeX(|ӎoEA C@ {4_h.;P4 .ҏƀ9#D@ (B!R%I#DQ%ݵT4P,S)KyZi?9Ѕ<" 8v}Ody Yz0I'-Ogr8=G#%If#%/gm.Hts^:q^#HTA@!4ge&Őy7ϸٝyPÉG$ pZfI#?әn^F媀"i'=#?/]>..ō؁G:aѴ- !'6KB9H%G_#??b: #okNz_xAQ@:P0OP$ȯS|ū41V#%eM~N##%i(3A e)*ZZcUol!1=&F!_ΥT!!ɓ#?_Csqĭ(EOmIk#D* ЗaҪߟ*9|(K95|tL:n2P'g0` ܡT VD~,%C-:`{9J^-UR+rQ`|z`J#%P3wQ#%!K4$(6DIS% #? IːH=ڦFq5 #DmV#%cTa@'?" #>N(Xcf)D>~;=v1!!QJ9vsɚ"^3LSyhTSp9˯|icd}'i^G!C#Di-upcC:#%(Y٬v1©70\. l.A$$hWB֤@sԎ񓘊ԁm8ȫ#,ƅ|{')+X0zD#>y#D+Pa3X15JM#Dݦ("O û uR;nBHrɋaMt3sTH#?I`X!HP0HRPj8a87-qyoβ&(Wp6!dQ"2gJRn-b# d<湌#%Dvz`<$A6N09g`#D<^'z&8g RRIS(g=e74w'HPQXbsq_H|fGJL i44#DjLpwˆ]-вaHQ*#%#DP}_oƿ!u;k*֗U 7Tg7cM|`_z<>z~!9aG90=#?"H#?T5=ӄ=;ɰ6,UȚ LXp) 6@$a9#DdX p*PUc|+ଖӓ:E2!!jQ:#éZ=T@QjMы*\#%1ܰ40RB\4 P}{]8@JɃ'U/Ң'oa˴ #?vx˚ICd̆J 98BZ"?7(l6I7Q?Pf!.!Om1! `Q#DUPͲ?YP)=v+=ɆF[ 8Mb*(vwg!'c/=vsYr~/BuN|s#D+$#?CNwYQey%+05$ĖhOFJ1S ӻE5ҧ)NzPq!:4E?WHlwz_gO??4M"bP?\e#%kGzFx.7NlДKB#DQH##?(4 oLJ*M90LX@6'9-!tI\#DOvPrA(JPQ@)(L7$@ҭ"&ATP@%&f@-xI.uA%hO]ҝwwRӌn#?(h #?tڔǜ00At?FGz$@% ! /C6RE!ߏ$2##%ym:4hq|&R~+( 23;B2Nkc))>'BHT!~4.L)#?Wy,~9p2dռ#?!IS=WRI=w iϫC_~^Eyxfg\<%x,,2#?y#D\~;}!Ƨc?[Ȱ#?+aw=s#݇61zDdFi遙tDhy.Jbν:hy+=|a4#?aѧMj8[U.FY7L ŗ|B@kAT!B4EI@E"Ӆ^s nn4 ZJ+aatpS*b)FC02IGFg?t(W|*C8MF5)%Պ/Zɚ;]ᇸg^fa2ʟ'^09LOZ$=!)mHLL-&i8orz04"#?Dž&#)ǥ(ǚUU6@KOiGQcy8'(C}51 4<%٘6#?ℊީQV0ĭ!$ʨƚ@9Y d ^J86&&)3FLwu8+NWK4w*HPE Z6 7 n$zvvnThE#$G)hd]#{f m[hMln+{Mv31l!fy!Ёy 4`4#%d;#DrB+;#D*ۅe^'aaJ@RFLc+TrL&`k*j$*\BeM؊m(FB#% SBHcH(G,C׆#%]+0s"11ܧtBv4@=De4Da2 C*C#D<+D%H`D`NS ,#%'Ox3!A@rHCxM1L,ܨc㏷*d8ˉ#?@)=ܓ0`4% ԡKT *LБUMUPUSL#?DEKb U PCD @E3LAдJU-RR1PҔ 4L-2E,LCPDIDQP%2-@!!T&AZR"a5X9'Ç"}zq8A8E"A3kFߌE4#?4!MB u>S^EƋd,#O'&WApM#(}6]P(Y JD" R #D#%9R@I3OGo#?#%H}|5hU)M ZFH!Z J&cQ y"R@?#D)$`((Cm6&%@CJP4J(Be)( J?dSE4J @2!@8lU vS+~ScLE@ЄBB-Eܝ'pP?=1}<Zt[a׶5 >ZU"PϿDǁO=13~DH$B`_{1-YH`73Jz]^a8vhi'>ws;מ}gp2B Ky:Z{ ҇$;"t]!:Bܾ1,#D"-#?G!&l#Dy61W>#%vJ,4FDR U(&B i(RJD),r#??sK]i AEiM&ٱtsr%۰pns%& A1!4)L22OpvHkM)I J~r78#%R̋ !{2:ͮ{KE X.Ҵ汪h(s#%dNC tD }*%Ѕ#Diק*2sA/$CA8ǜdZ8a$ h(1#D1((X#Cɟq~ހ?4g+G#?'$:\*G5Rhvơ]c`ם dY jJ#%okSNR -Y #?tG2z<M+'&6ƿR^ AE.)8ӚrrX"d{46}rV:M|.Ҫ#? aL#%8-$Q0Go#?ej½#D\ŠRkŠvpZ"#Dǝ|mػw(ZF} z悡| C#G.GAOe;+\F$ }[1Fy*qlZb"vd}vJ89`8D=Km#?\sii"Jj |x&)lj$2rHU tG1J'-&)~T e)I.dy S-Q11(S@QGnT$B63zaQ}Z 4d*$vFN#ӀW@`Y#%ă I&kM݈O!DH;8@,(ĬK?%W~X~FC[\/fbt;l4>#$A0n}?̾p;#̝mQI؀#% 4I%!D@RH(@D0B#DH A U!.b7N~#%<(Z2#?41sHZT))C:Ud$(*BI#D" jH$ZahO)BU|L~8"b<S Ce ?@L S1LZbR LMC#%$fRdP");78*%N/o;ɗl^RyG\(ӉAy`Ir`7'V(^lEj4gQhtvL*j'4ډNQ mr?#%rlFtYllȞv!HO[6#$;xszH&OdZ`0} i=J#% `ftyjZpy2v#%HT0cE) iDPUE0Y()cGGoCX`T *уQC- TATQ)ME)`+F6G`c2d @C^a9RyܳK+$)MpݹÝk1#?R@ ͪtȾ]x%1<؆B!K6R[e9Ŧ)Z yc~8<<P`qV F!FGKF%H1 v$ABGd24!B`7 IRR`ѱ7}n0Z`( [\*l,h#DA(KΈ}XQN[q8nf!b#D*h!ZY>ot۠smStr>Ө!E WH 7sp r1F݁F(08 B,6!E z#DA"Қ0) dU@ &DcGx]h5~\mLù./Pq1(}8)D}zr¨Y ##?('.<]}I_csk `':TpD|d !#DD"( BO0HAt2QTb,h(M*-C8D"U I@ eJ_#D~$`@a!!-ȔLe #?"\#DLE yݖ 1""#D`"yDI{K`{1;r;ST= h0P}XUQ@3@If"0W'ɚ4,E#?RQeJ%$Y!h:Ѹ\PS֥ A.ᛛ#b 1ɹ+T 8}{!&,()AӆIເ|;TnW2Ew)T44~?dH#Dg=h.b+P,ِ:mj|M_%#E/)_,Q0,qO}1rIZq ^sC=XFғ2)2)D0bfWNǝs[&#%qǮf 괦"\JJ^%E4 e-W*$񌰋yfvKH6N4\g'65h-"XpᓗӉ=Ý&e9\up3Ĥs/:#Dk#?9aCA!r'L0j;=4]ؼ.C#~U:Dcz#?mZciiDR'j^G Y /3Pu/N3.qFqiY:m猑6~ڣh0U4?vB`dϽʔǜL/H#X( E̩P87 g#%?;"#%D#%Iɲc@0#?L]\P>#%S"#?^3 .pB Bf%Rg654 6)bv![d`6ߖ#?N9qU;8gtuݹBt)srxDCÚs5j{)µA&zzq;ܡk L!G9 MRu'A 7y#DH!; )0L&rw~~_LFPMH O,\!.Hԥp4|t#%ayFJ#DPp4DSxҵ=wAct?-c>\qŪI3[Ÿ57xQy?f}"<vk}78l.5G)X6>v$XKP@@́k{T$IU_o(W (8lV@xM`s$t؁f==~>p~\>[D%TyCEJ#D'U@(c(21 z< 0y:9bQ924SB$f?ɓ\%0$EChSH|"*TBȫ:j{bWoM⏁>TÿH/>g7>̠{NN]=)0DcϺI3%Tp!} (#? ȅQ"#%4!'keBP[Z4j <8ho;޵aNRnJCqӀo< C<;#?s-z)1WqF2K#D:'h_LjFVH.H5/q,Df3kуt?|#?x: $rZO` RjvDH8bsԾBhlDNN>̇'ttF#?FCxI> =e\J`뱱 >}Kc'ݜp18]Y#?%)>Pb%BH!(4`;V-4cb- m2d_%؁ia0(!x|+o'#D[' gzâ2{QY.S0FYC^pb&A!!%l9S|X1?Myyv$*r"X";j~N#%`s&JT!#?Q]9mpAJ3|zvBr_-]7pT• -]%0Si4t 4nI͐(#%eaI%%!B'~ Ns6#A|ymz1E.="3deh>Lu0ЙE3 Kz sZ(ܨm"Mͤ4`1]f0xga_"@"I$nʜ1>:|q΄ґC?;pT0#D jɣ$Ht~{dL4kL1q"ǿZ[xV"92``@RM&LqNzJ#LZ1}&dFshh$۴0`IRC *pE3o;v[&lUb>ryKT 4f0lh D3F amad"x"<84-~hbjQ(!݋t0lW(4DAFC!6>ff˓uT:`pg [PރbӤffjhjj"B"b(){h1s-o:"oWtpPt~c& 1QxaJW&s͂cZ4g\5b/3#;1Mcu=wXׁi&@j8[m @<#?A<˗ܺ Ph)y{Dt*^`y͡!^ڡ#D#ݜ""8\qFgU|{ǡ'|^Mr#%S&{50A&`cZIcdA>4ФDIJ#5 ?ɂ(0DPDQE Q+m($(0CS}3H0HRLA1#?vܺsC»:=I $ a'@Ќκk;17~@Jb&Cv_,q7"pE~lmEV\ۤ^hae>6zUٖ}CJj=1aflr/spЅ1)Ƣ颶mF\!K=@`PMAH1(@R@%-DH$R#%P- 0C2JC,RJTA2#%P=}Hh5M%b""6_'t4GDbJz|`NCATL}"t%W9uM`S%7o#DD5(81#Dlh(#%'!TҢTM~Ӕi5`+pA%m_Hn RƳ"DfH?f#DtEe:3Yɻjw]#Iβ"\њKr3DAh=|4r g>G}Ū (ї$q;mGcQ0'oVh'o~F'>c#>,#%=\齧V{],y3~E@wf;'΍<@5:[y%0{.A{9#%BM6ώoyL>p`s_\#?,h'm(lBf}7#%ʜ:uvbnAMAޘrT9*@ɜa9oyr4WJ)4&{~\ۑj5&8OT(4(ya]iwy8K RnbzQHJ8z{W%J2O1@8%_? A,7㙱!Ӊ44wr#%)]!JҏЗ@$JP4AM##?̍ _cW#Dڢ/2&"(ubS!JpKHy8/#s*%9RikI1)CBbґ#%XF!QBS^( _@!^cHF'vPCs0A#?lu1C Ϙ#?  .$ Ĥ*J"<}$#%.~#%;Js%(X@i0t]dwnGKقHXVVX@(?{CȦR#+ &v'XKI*$BX׫ma/ݪM%2Plw@Pݸ#?+.#DA3M6F#%]"HOrOᶬ+ LyB`4C7Q)i Va P9#DBT#D#%$/;A; Obu(TI#Vl:L1CB xTB4EIHKM4%(2#DDPB#?!0C@L#D2#%4'Ȋ;s^ߞ©*oogg4=al_Rkvc>RA X,e d)}[1Ä>QZ;{p9KU#@RČ;9tgIb~z~` A CJ@(#D_2uo"'`e "hHbHJHJ"#DP &"#%hZ@J@H iZBR"%T(ZR$R{=?q%[W9KS4r46xFy#NG%MU17l:ay$4W$gZĖtIe S!GZ!Haui 2W$6&5[IaVBiF3р 2^kIk5R#?h\5٫\'*'6>v@3#?,?sEºcWSc $lö֊:#y<=j0}\hjH"6T[#?G +$X;; CtX#?k%ERw8us&5ΎhL=X P5L\,E1LQ3DBRRS,F(BcxK5P3*`wIcOhn#%댚rY2nf'rʲfB};l«x?5iw&*e=>{T8vW֓'5SR#?!!&êxj:WNLg&Prc8;|"ä@6snQ!; #D6P$+>0EZdxT:Rr45Uzr<{ꖹGdB{eUq~_ijY)8s)q1\#Dvܴ⓬ ҝ>_#?yRN]>)UUta!o[8>#?  YMv[RE(;Pph@zŪ'| C3e_Xl|SȪ!|EGf9k~7`t+z\\!KI>%!f0w߀wqprFl]~Ql8rv8bqw2es͎ۗw~ojw5+8}jY̏cϬTQۢaATXIz9ow^{Ϸ\#D+JW?0!k3.%`#%j1)LWYF#?J_?9Mz"0c6t%Bnwq6r0ɦ-ݙquɪG:oܔV'PcX3TK %WQT91pקS%ҁ&T:xCWi%uӟ8|M\b_Z#D%6}\I۞{GAyz L.Q7y>wFM%6˓nYE)uKD]v6b_II4RE@FUSXسsN$OMPR=P+CPd)g>nIz\nk0C ;asHt=^'Pf}vZTXDCa脹&\ Diè:{RfEړŴ"і`&S)TzXxGhM@h~1?iyWH빢߲<}9HuF2N %ae M]e<  9c䃮E͝/v,qԜW3X"P.i'\RJCtӖ+ؗ8$:ytޞjiqCU;ňu2Gl=+Bn&š+Reqf#k:lܽvVp:D0*Rvd!MHwm(ntt-7dh/ciU*VVC:gC] Ř\KMyLִ =Y#?&6u>5#҇OiȞ)8k9;1hBA#D3nRNWX(H[^G;q6Ì#g})z޷@z#? b9.$By#D+b!#%i@4ͥaPejۗCkAZk^ö)D-qt]z)zOis\8PT.>NZ>Ke w_9HцqZ0zxT898XSi2 ڴH6s4H%ph) /ˎ#?k#?FYG$p/=BMG|txekEcdYұxΛc8~\v=#?yr <]}b.,^&4saxlU>*L]z$gmƻ_]sqbLX!Ւ&6~"U…#?w#?ý\};xGKc*ij,qF*I&C]Wkxצ728pklM4@&m[Դ1TvvsZDf0u`ҭN%e3D/,꽹p .&s34FJjVrm@s/] o5"c2c%#?jЯTI6#i6rk1D$zkK2c!'t*t˳;慜s16=EVg]?,gp=.O8Fؾ诊#?UP^^d&M53#DҰJdˀrE6_ sQ aaR`EbInj2d#%g{{LfPuY/3"vcˉު{kstqYz鸮_{/3i8~& wEטzvgBr$tcxU^4g<]tnw9iɅ=B{>90|uX"Df-qcrR0#%6zyYJF]#DTָŨwsng ;;7O1&%G/Yӧu Q#.`0ˍv9}bnj"Ȉ4@zX4\HNf^]]YvJ-2< #?Eκ4h"BJm,p>msGφ5>S7;$OCjPjkM" 1U+x^;$4bC/Vtv\ Ê~J}b$,#%A :Oj~篎ydzs' }=}(Bj$ #D<0M44$q`8t;cy6`m#O%>TIV&2BhǬaOǿ_:MG`:-)+vߪ/)ߤs uI2Fd 0kzh=!C|ԒÃS"],:FẼlz6JR(s:61Cp鉜U*φ#D\V6:DL E8zb,@|n'84-&lsO_Vv9U8YA|lce7(RZ-VohFF'>:Q,*;s)^֐\yqz`B(Xy^Ydȭf#zh˖s1]޸i0N38J#?Wge ıt|&~&S͐8vdh8#Pui*9*p_x-賀u8`1?_,e)ʁ3ܕ\'QLf6: yȉ0wczC˕|ũk=\<#%L ۩ú49?gf"a&8@ğO|!޾~H82I.@ӛ}_;55X{40+#DAN]7D\vj38T1EiU-=_2ctӥ add]9G {DŽ#Dy6p4G2;ߞrP%Z61"B0??䩡%#%vG*]":LT'x2#sx1x&q%L;hi4&B߈g|t-_cA45ԣFyx!SQ5ErB(eO)(M rtI8Iy3^#?:w3A 2K#DElm eF5#?tÂX#?B$r8#?$IPڍ!BTv6 L0sۦTごEISxգjTV$uAӭPHlqDPĔ!Hnc0sHG̜;T˻9y"&fcMtUk#\}\R> z\?#?WkQbR>pO91BvxcI<ؠ5,#?EAdD] sv T6]F5u7TpbXʓE[RF <ݩwo! SJEAN0}#X>4ރE/#? ̣#?1;IS"}2 m%%} Q$\4.],9ES g#F 5uG]p&D.gqW#rh۔bFRtЯgfx| &|Kx#%(: 8C|XEADAUUmLC2&] )Q ur|^0%/4q>2(#%6f%Dlid!#DzdxU/\ϒ-L!ѝ.̟1 X0"BϋMMK)dmeT0+9p#?}-F7 m,g0vbJAKI=ws)>><@9=: |}6w}1ٛb}83TkƤ'iێQ́~d5|WRT"EE'Z6EA͂Zd$p¿{Chh*rhZh#jLK@'9&Fv^3pĦP`bklaCdIҠ~]>:& *[1owi#?@a(d#%lrć>C$>Q'4}jUʕ}$1NG9؅erҙkѡޤY-86a-c^XoLL$ĝBJ]A[le5aU\`lxQJƶoch+WeZ(߃4騵UZA5qF(l#DcX7#?~%A@m .#%X& N[#sgVmhj9%4ϢDv1$C )Z*|)&P8`UR^ Peq!a(>}FFSRMpOSsD@Cpª~#Ds"s }roT*l<ɝD(+O6.>z|\ >RzW{φ1UY&9i8 .|{T]-|V r9Y&!G-fJ|R |FFM I'ukb]bnUO}#%vq uu0`(#Dr9"ʅHQJL*lrK[WpYМB(QoDO~h)WD^S>7@}Z~̧䋨w5?b;!PZBc=*:P_ߡ6!#?X W#D!C* l.i*FN@gE0jj(aDBbH#D LjM/@sOrQĽ9@ڟ6q 0sR=ެv+dͪBPÞrJGDP3}xk3򚙊%* "f`IVq|}VϧٵZ[ֿD.="2p)ݨbu|A#?:+ە Ppِ?]6s#Dd!}q5C%)N Ǔ~>JAtuH#%DAAG.bqT',@Jw;qL %v/fˁI}}_>P'Nh#%`s_ I`$8a0]ㆮK>$~9Ә|ON U&8g@u,?eKm#Dc,)\28 =NsTb4(:>tI JВD$24\buMq0\T4%I&LRX"$#`65#?jaշ,9/qsmdu:Apg3 upȜ+q#yk*EO tþvR;O=LkWc5{$Y.ݕdbafxĖxlE)B> 8)ߑ6xG/k|6 3`b܁j |c\gP؂)#%dd#D`+oGKHG pZoۿkfԘ5Hا+5W=qo9o Vu/֜4Ӎ#D(!yNrӅrCE:*&4?dԠʄԓ Qvٷy1ғd7 PXv\bvm5iytˬ츉hy#e=9UsY=O#hmK; 8L$wqWjݒ]*CVqִ'7t/5D3zܸls8lJY:[EG/5v;4ةV#%h^)zOlrk$Eq/j0Nwm,{q.ݠPZtLCiפl Fne#?8oy#?0N4:7.* hrEU֧I9ib6>ATVGDR*|?4#?ջS:z=JVx[*XaFs$#\=ƕ!H<8aLܙ>9irvabl'l;bc0h]3Fk;.#HjGyp Ho'33ūye!H( sXI(bbbi9Fh} sDǏY[ӳfess(6;#?2;\@aLBk/-ELgO0D+5ܬ!7i8ð͎d<-3-{0 `:9)6Pi^754k*٥u/WuZx لL A}`{eо*)xV 6Ih$@!7uq_wZge-l(FlȉeIFss CZdqƓRD#DsjNCdLzI5k3(wDQ6PA٧v9.z2qd U[⥰E";nПGb=%1JWp;W{wF4+]g*2{`8a fdi2aCO7`EH(#?H1*]b A4hD7C&RO'^ fYHf*{bت 5䄰L֊ &/3 YeK,ǷܻsnO,8l붳< fgA.c $p]C] ?;@)p0yq*#Dx_5|;~['4U?|09#fuTqbB;h]^ ^?>aa<;;ˏ{o4ۇ1uzkSQCqG5Y&qH8LS& qLڎIx|Q֫$Of#%[S%ƻuf.F!hLqs3P38i@hq CH0TRIb{< .Ė#?Gip=\35 c#Q4Dc\Mm9L|dmH\@cGл1 1ig#? ~1q2jm9D(CMG6ͥvzo&;!g5;ѮEmQ~]FH\p]ʚ#?wb%HD )H>OC8[_l@I2N; sLpɹ. m9NZ^1ZJYn3"J#?1M,C%GP,A3IC%wnYG#?A|A&VB"ȐTW2 ؊k#?j;0\^!ףd8)oFy<~#D6Rw{ %XV}ҟPǷǰ`g{Хʇrqf8wJHჁ܄89=&F9`q>Lc|PS%{ҟhl맣={bkVGvUR 2͗th zN@HH$IRĩ1_9x\hs#?!#?`_#zH#?DHzNXANf޿W}b-rLB@QtۦKsoAip#%$BAE={lY>-`CN8HUlKi] L_HM',?tR#DE!)~#%#%6Hsa)dgB:ks(1V1߾Ya{^ C̖,>lK& J+ۉ:~Wlucq#D]{;3@i_:9!|9%չSKA:ID#?ҵ~\Gd$idQMA#%D"}p#?$P@>3pN}}iݼfwiɄy#%=qB)1$u9ѪoЄP8EElNul#?R`Qˉ6PPb#%h^rvA|datZ#%chxno9z؈jتiCEeB3h<󜋸t UD]Ƶjh(g;Shƶ11zC :Dkcsp 31TDLDvv!TS mm2M4f@c7#DW(,btP\d $ IL$®hd78ꇕv0q5&beffKT#DҌkJfNpW%P[DiL#6&(mA&J3#D"@1C.fyId?Etu{UtC ӎz49p)`M May44n('7MeCظHRO_5Ñb#DMDEm#D'92N63qPRJp0%M^Y#DqvZ!;<ΟqBŧiu5!u!"z"@/+ GLR H"P F!zʧEE4!'ZD ˀ+R0!tIU$T`#%)Ev&@(AMY=r#%P]ɠpCD1?PW!꒙rė(F& h(?Oy;S=bxoBAOG'ESС8'&n^Q k&h~๭f^*z@牙w8ņnoF n@nje-{Bju҆'VIJO*6bQ>XʔYZΓ#?ELKĘr6 PJPTKsw)^)TJe.Rlᣞ5lo;0| Nc{cyrE>ʹz|ۦ3[F3O>AR=:d^;*;=saLqAMjc>n5 /vrVdpWXb>aN1%fO8C,e,!$EiN,ah37Աͤ6U&O( I[{hPb9tn"aR߱qhJ0vxl#%D'OpЋaT+%!(;&6VA$=z9Ȇ~W4Hz2}523 &iD#D0m #`j5;\5IqG/ntf8D03fai~j%*\-#%}D(Ab\[\I2Tj0'}H7Cz@A1$l"@000Q!PP/`QEEK2L9#% 76r)=914Lp}S*l^O'd*b 'j쎚?36#%p}#DsT׋Đc B`ow$; IƂ#lVcS"(HIgi[si`nI廴b7 0I  KL0m|#%裁hY^d3dHQ`_f\sx7V)Ϭ]ۻ/ֳ`҂m[#?+Nc ."$~EYA>i-B"ԇ#D(b)j(2o@pUQжkXA֠Z$X][h~nHy#DUhe|*d(Y,hN@-`qcY9o#%ܳ.Qk  2yE?sJ(C6E3$$E>{շ5G#=M4WqC$ %&bsƱeʑ@8 :Kd?g&3Ղ2EI]9h:J4O6pc^Zlnf8IS(lS1:8r;~KXڤw?&v~_ph{(8" ;*M*#Dz.,uXB_y](51U:/lDxN#LH!}EɽX >UD=h#DXȊ6._Hrg0ĒEyɯlqtH)9f#MN(X ?;@(7HR%t? VC=#%tLHF&J $ ĕKDJCp1{~qb#%l51R8AuZΐ'4>b#?T|x36 ށq!πNzMP-RM ~wvʒ6!&@o {51M@ţHBB/[[d"jaTcR$#D7@\h%iLx@y@EƗQ3ԁ>5:rNy J uAMYDY~hP8Dڳ.`Y )a;|#%#? #L}C:c=!#DHbM$m4^h!rxdWC&4sUjWbz>uT@,?ac#"5eywLkf4l:L@B0ȒV3ƬӬ5ng&&I Tە힬6渵0[OzpN&;QF7#?W)YۿgSGc-%MIf6٫4xt0<%@FE5Uh* diwLh&T557EHRBf#D1Ah ʹ~B w WlXQ662xrAC!sM3k{3ChxdQ8`oF(+7FiZpq0I #&(]EѳlhV9s3֝םfsmϮmǍmqg}E֕j|#? ˌ Ƣ#?Nzl!5#_̏#bb,pDq4Hri7H6-%;drvׄKaFǩx> X7MC$K@#UBY$Re0Nw' CɤzC㏖G '21 zX^% )F$ئڡq$'09P<[Յ:GqC$}/n3#x1(2Iz8E}m#DmD1^~~ܛk,hGNW#%Ǥce$<"{*<Ѻe0#%O~:^A!`(NwŸCmE!Z4̂Lf!){dRX#Db@ji7?*)LQ2TR@PHE Ph`PDHbWX/or>Ϸ=۠h' H#%|{#%؁-w^mad>~|QYQ Pmڝ=wrzW}7Ÿ' d%7E7:>c+_`t%#%gm4b&bO)MV_ :2Sz@,LpPj,׸V~]T3Wxѧ#D~pݓGmGJqTE3or9SC:v: г;h%c(1y LO !8#?*9(:zuX?aܣgT+:;?:Ԛ.>L=S$#5Q#%'Zh-Sh>Q`?_+%JC71丧ob"tץ}ƘzIRI.E۟+5?2[nK'rֈb##?Az8-Lb?#Do#%Aޗ_z?ԧ3CNH#DDq +#BZh91AY&SYhf_$m~d#2cs#2#2#2#2#2#2#2#2#2#2#2#2#2(#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2#2URzn=oz jm{Oo׶7fnĴji>Ixu#2@ [1' $aET#_Y4"^,U(HKl[]bY%G@J@WmU&܏+{ҘtyS֑3;uwn)#2u}#2j"va/Je$=4C=#2#2#2#2#2|h*6#2J#_;nӥ'mslp#2ҷv#Z]7bh#2꜅A#_#24UHnhj#2+E#_FUNgUօP wo:]vݗݩTuAOw8#_#VbRϮ9^{}m)P=׼#2'wXm]޽_Mmם3kM{/_vwo.n8Bmw]ٗCeܔ󻘠T_%CzoJ]4lhTD)/@v]́݅ٴ9x/MGq־o^}rwuj6xziom֓YMo{{M{ۼͭ]=oyx{ý}wnw^vн[{ͻ{%}ýlym{Ͷrs۹wsNnwOjr C& VL(wklTޏ:JFbr㝺v{x/urqfci3[6˙Dz1ڷd"=x=tkǹ6\q#_ݺESt#2&)f*Ͼ;W{ݞ@#26UU#G=+#2#VZg#2*PE{glHuݷۭoRp֤Rw}#24tWfF \fþ$ kןvNj=2P ) U[m\FZ9i*s7f@#2#_Vis}zk˱^n6ݪW{koy:JC]ҺEېN#2nk*!.<׵@ܗvPTdx|xε\]#2d7omwon=ss!]tfzz;iV=KFhUi #V]FUۧsg6w+SVwvҽm_i$rdn!U˧x4#V#2#_+Gp%憭)'^ܻa#V jvumvbԢX:xy#2#2tfJ`QCvٽ=v;g&Ds8tk]׽/-.mRuϷ#֤>;;wdǜJ ǡwM催Ŷ:#27X)PSYkힷ*t@uCRXIKݪ7@{e#2#2P#2ӻ#VA#230{nM9veuom-:wnk]ugvvY7}uB]SU8(:4ֳJYJ w#24TPw5@#2ʑBՂn+{Km-mY@cm NC,ݷpv@7yFL˝‡w[ϐe! #V:ٗhNfvNs+YE3wu"ƫ׉/ngHYRy;s:|4#2@#2#2!2)虔mCzhz @#2OdLMSiT Hu@5#2i B'$Go@N(#2@|@06#2#2s0$Qmd3٤l"lG\&9#O=vD#W7PũW10VDg5*]ޡAa>V$27M#2IƐ]>w9$CJҍb&b& Na!Ei#2]JR[rnUS5FܪVUY0JpD#2"!QrE@K/8P84+MP ʩMPD<]$b)J+BIr.y`C Q%x.&XԘ~u#2bY4QJ#2#2Q#_)H#@ BHJҰJ($1D4R"TȒQ$PdILL@I#1FLX0M!fA#_fI52-hț3&l05[Ej#_2Ai#VflmI&ehSe0&lkml-X--IIc(3PQɲ!(&ٕ,LJ#V#2TJFdj2LlXi,I-a"c&ɤH-QlCjQE f4ʦұE&̴m&E1Ef3#V JƋ-0ِ,c(fl564$TiMFBM0l$3#_2jh HEX6M*K!ֶ[[L6Bd&ƘKLE$MYmYlKIEC16̩ͩVR4*bL),j)6 QPQ"FȖHLHQ"hEFPP2&̈*"#VJb#VJ 0I2Q#ILUbRjK J$ 3!#VI5fF$,-RcEF RB4ViK5RdSL%)dčdXX4dj4I"&k$$`I#_lD͂l!$d,DE&l#_H-%6I4PPEFD(%$I"E0eJ!ZRKb0I,bT()e,"RDi$fvJL6cELJYQ",DY6LZ-(I0D6#VTQ6B$FfJh6fT&$6S#V"2PM3i&h4h&XAF&R  i!#BA  l*hb͍bMi2R&M14CH#_T$(Fؓɩ#M-&iI&3iֈ2fe&R&ԩLY#$$ȥ!QDc,e$0Y5I4I&4I"6(Ie6QeR b64a%IJcHMEQ% $A*H2Mhhȕ 5bDBL5%ͦȔM$ԭ)$5$HbaIHd2`ƁLjMAd2B[mhe 41#_JhIJHdXJjRK4YH4Hl4L-E)BFdDʚګk)Ȫ&deFF6efBIcjeeeaa&RD2T #_B13#V53 IBI"̃MLjhɢ b3`Q2X%$@Ib%LHơhCJTb!#V h4&ufTiS&YaIBK]0#_mͳ,$ͣLk&hSiMk1bJ&M j)-,i@&őf`4Q1U1bMB)M6J4bLffK"(E))i jK3dAfZYJjQ,`hEfk(R6IԩSef@4̒m*TfeťFѳIXi3M[WdF1FH1D*I1dEchlRF230FYM Duͩ٤f` E6,6&!1 ȆE"ј͓E,V2ciY (3XKI FfKC#IJ(4 ,ƈ3,*($RɈɄKI# JT4Ci&HL4M1e$Z@U&5$H̤S!&٘6S,ddfBѩMDhę#lJUؖX3QhcFb)L14II2d&6ɨ*5**-ZTk([HVJ4Eeh*DY)Z!))iDHekF&YJ(!E X1Q5eR)Tā6Vi1RlT)Z",6[)SEj5b5"QXhZV6эhZ5 d2#jE`m3FȑF QhXѴ-IҶXZF$$JBlj[d%R A[5JXji,ŵ4*dJSR[5(ؠZic&M2U%YABؤ6@k̒LZDXP0)"D6$ДhLPjJ̌I"ɲQ4M4QIE#eE%CdM)VYMCIe hm#2m5dƆ&#_fHM S1!DQaP0I ɱ$1L$̑QbJdbQš*` kM0Ԧ d S-JF̤Ɍ)F4RDS IDjK#VRlf6X%%@DB PIEJILPkHQ jDjffe%QPh2QB(bKd@RX$2&U Lɪ$2ji,E0D٤ddkeAHfZM(ыfDT̆(E$& j5TZ,b3-$fBi̳##VeQ`)h1`J(&4Y,dHZ"(hXF fb,["fbP&aSjC3BY5F4D(FĚKF6mTE@T3Q$&2i4`H#_$ћ͑2-lj"-A2E^›%d[m$m$me$$(QIRi 5D#VQMI$$KfXY5%"jC&fB6S h!Қ̊jm6HSQm#2I-P(e+,Dme4ldڤ-IEDa33 R#lѨ%*Y[&&jT(֣L4lTmf!XJĶJh٪6R+(EBdĭ4#VEѲjٙ$5AjBLAe#24cF(5Sjl0#_<܌b Fƚim([RiUcmLմa *abT$%Z$LeJ*5ƦU#&fL`lF#VDb$)&DF#&"2T>Y/|rZYT}#٘=C R1 )>^/k/"('Wd4@^N)죉!*# #_Z…6п2ÑՔXy8#x3oe-/m^֠ވ$o؟Ù|ٱ+TG@|&gjF/>>? opXeއgCle|A#_qCфB]+>C+9CW,VxUa:%h\1hqH4Cԋ/٤a#VПwX5CDz 1b&߆ߵC!x;f/Zt(0#V2_n.~~ر/Z(0VYi ͐?cbFyẽϫNUDd9!H>#_۶7:fCUԨ eBCߧ l075̆!3'֟Hn5o3Mcl,Ib\+Qq<}>xelclY#ܺ*zPA) ? 'j2_k(t"fL+!5,,Z:#_jzuV'r#_#_-:IԝDG'e%p fqn8=tPNSb0.3v|>%3kpѝE:fr0E P/N؇Ca/ߍͭ% ݔń vZG)\8]b :19'mJn=+%{]MώzPmK_l_x#V#VpAcMO4X1z>z7)ob"v&AIڿ k?I;p-yr#_JiN{ii@Q -#V%X6Yߏh=Q$##V.(ȡuBcAL3k"Ao"B8./ *}'4k#*E@JJR{Sn#_K^#sɮ],dR­:NR23MLNaQ4 c{3oTRjV_ò J(֧eÿtN=`dZ g.XfȏӀ5ERdܮMoMUw/eOU!x&óՃj3qV=f i#2Y#2w4iDu*މz{4c>lG˼"7|g&006a&E]U DO$V{X!?ȓ,w"8>"cY]{gōrhRǥv9bȰ'I2 Y~S+*nBB#_hE#_{LH{UgaB(U2H2Ur7+$a <J,UBx/꽘ypQFŠX4{VNS텏A,'mۍ gҟz}"͝3w0AI2$2e \Ƴby>eM\`>#_2잌vpA#V$/g1 U_Qڽ<2A/# 84s1/T/p$u7IEb)Ӕ"K}ԥXtƷ i;Tm'e{D;rfqT)(#&C4yO/¯$hc;?2#_EG6#_,7^-薒"U*M9&8Ӷ̦a%[Xg#_]L!FjdwJMtioSONW2(SLω27&j`+eor1%/sWWRyw_[H/*Y__N  wL#LhcK9[nǖ3Og!3nn,eȵ9gok^\$He**ni;ǔvuI$*f̴kG6}ZW\B\a?FnƁ9ÿGGI2#_T| X<,4eludؓ=;;_oF9>8 (諩KUOC=qKkDb)9w\4W[柍A70#__#2qSYP@a{F69ZH@f}gjR$ca4 o:yGXw]uթpmiޒW^虢ҥ]'CbL&I"#_*% a;,[ak[Np8(w`ͤa#VwD ̃mmE#_L}L9(Yds &-w <*mtc-iC7o깒t9 _G;S(dWg5~Tx%. ?#Vc(7K\wGXa=HB<+5}sБfBWKR.ؿ>tb9R5`#_悠#V;Hk *U#Vb ̝u2q9Sma2df3GE,3)rEB#26s<[4UKcs [W̅SXl0Uks}R`PŠM4qZ1wPtl{{$g!!DMJ(x` sN=V{?O",x|suT_X>NuY:#_'зv|܍T.Enp, h//@̓#V:g9S =a#2Xk>4u$Q`tI-WZuآ׭Ԡ `@) &Y3L#2DŽqA+TVs;l|!nN>Gpf@]YoBa*Zb9iXV7۝#VJ= IH 6H&#$l^!D,~KO߶2=Yg].\N!$$w"i\Ьo#_+ѐDlEG7麀k-qĔϥ^E$9_D#VG*®/8v}~ːz%u@}C1B( DC=l #_$X])3!h`FZ)'řL>AD.Ftfn#_iؾ/IυIHV`H~La$K$#mߌU!L;p(2 "#_蒑QOkP֨P#_.Y|aY\PI cКbdxpeA m>KD)j}rѠȘIl$ϥwmd2^5HI'vQ-;WQ|PI$>ע#K϶L0v^󒱟ВPD9x^]9ʸN]ky-{jQhVhBKKɇNZ{.i=MHl:tF!&`TDE3##_%F#e#2 g>4ңniw%^kUlgcVPR*f7U0DpJ?F$zU92;d{T9 9'mWgGW%#_ޥn:DDݾoyqfje~5tԋW@@`> b| #_B?iM;BoÔ?C#2 Vn;e*I3* 8#VQN20 kSvy>1smpl=#tWeu4^xklB̢R(>΃mKyygWUA6_g{`6tX)K [l@#Vq\c$ )`QMem[ Iww7*%JghqS$/3[ՊCҹ,qy/1d]sQp˻WɻaPHP#_^1W){Gwj*uR*fymDR2{pU&\J I/W4Rt }}SowgyW5kBu #_~X*tp#Vaic99'aڐɖ# qaz}cFD u ׫wڡ鱠)6lceV8aeǖȏH(TP^G$>#2(Ɣlr o*yK>#VwO4TR((H)} _~3Uwd #WRM.mgϾd'z?}k{Q7Z+ٌm3NbѪxPS<u3L 'tTprrTX/Nu/\!q|-d~~]_s5&A){n<:iaر</vƽLn*U~Jk)iKq2P뿿9nb* TH!V8::E~i`R.j8Ju<IنN&p=Y Ś޾P`pC4TX U#_=G #2d!/ /F=KHL  cZG&Gm#̈-`@.2XW~ްokrr4ێ?u%llGTfSSCuᆬ|Cr"z(v]ß8X#_@7Oc -)AGUNA,J$^MãSf]C,20@ܪ|c=TNb^|ۉ3Z2ZNU-&B^P?xɝ"Si,LTsQy>30CLe' Hl`~%leԳ,A~2L#_#_Yʱa=)?*KH]ŻppSYMҟ19>,}b[[3#Vk[(>vqeM7πmP&a454kL5dXPP?Zd.^Mae6AX6 rAy6RJ!U̢o/dB+=RCO(/)0Y6Z7{ix@)t uᬙ3Fj*έ u oɀQHN#V`4ˊ?q-Q`f0y?"y24׭Z}9ŷ"1S#ŮQ\x5#V"~Utrʹ3S-g>w&?v~Ot< k0o[H Ӎl`~-(_4>>~4uϤznGlmD8(ʞG^`K*샯ACSb Err~ho 0}Q}GрJ-x3iU!:۱if.°XwX=>#VUY7߸ z.I-7?kbT1;qͼ >?vqۃr#_Js@42X7_ ksaB?]t#2o @ 2$ `#25QM'|_B Hߪ v\2:ӇD讲FXVygPsHaRE#2DZ-YFFiw?ﯡ:t^M-mcbLH*\o'P9})&YF&Q?~ɯxF'ʒUpQ+0?§Ѕ?⣵FEmUU+e]b6N{W]~D(x%U3v>>74NhVĆ"y4nB=οEL!O4Écn_RTP"?OFÏ}{fϷ:WU@8x?#sn%P \5t6;^CWFkϽ{j\4ƼjЅ k-)5nos|pw3Sۻv]ys׋z(3 ѪD.]?)ёa7z#2Ypa*|>>@q]٫}2._[u `QP5WE5cuU.1Xr"ÞH KO)_& ڻOaS\A;]HCh;4?fy)V' Pu_"R?:WɯXQf׊G1/e#25K5͍CyvW|q9ND*>#V׬&@>|1[)LFP5 遘Kir[60 n4$v}+q#I>4]I>htʼn7d/)ǘݚ(saj%Rz >G= zɿ#_2pԤ#V,{=g9GW͊r; íY6ꪹ<($)K=}lqPfP_ yvm #N"X{ 7z4t~Q$Y绫㏞XtC%佣Abbh!GNo:bJK`Kdy\J*%#,QHvt< &;۠knYK#25U ꛡx*~{1rΔ"~aq%3_$e]lf7#txG3'y(sh8AB#_L'Ձ-c^^}J}b~Ȳ/՛- 0AXSTZ}U35Eʺ Z"eH6׏OL}xr#2aKab!&=~ׂmE\\pHz1@ pn h$&Ued.!:@u &WDIK0\q$i*J$`>lȮsC ! (ZGtB 0۔cWۋz@`wY#V'D)?)D_^ᷧQ+:b9~l5p(7oN㥳}$#{dj{ YNY_r D8o͋Ks4^UA/y턠]\gl\auN޾XtkǫU'c.Q.o(VPϱat pl#2i"<.2Vtaco97F7?sS8p ;` 8 1GKTFߧ_ hf*є7iVxd(9C^N_Ո#V`mq@0ox#_BWr-tR"#_?eY> r>`hm&KTR3FT] { %&jL̤9h1CЎM \vq8qYDαqhovfI:7E "#:v4AB0H2ٖk^stB#_AB~qp2i3m RYZ^̡?lPak^'j0a'~!#_6{?**mEc#n]9J)k7:& \IC-;ͭ/!!  r Nf<M4{9\/6fY8ζf P(`h oz:2,Gm0ALMlG;!@n驝As1&zɌN,,yhZa _BhnPPiXah45)#_bB64cKP噁K =iyW~r9iI" VkGnXZfvX렘 ;V`-IErH#2#V*6aiih."QVyQVP#_-]p"*or#VC]`X6ŦrY1oю!Cl21U +5DQ hW#VB ЬB"Bf&&]3a@N9r82[qP[ Į #SKaXuJ9pƕf5ٕ#tyJjP2bm֩#V:%2^*Pε`ãHV:0N4P+g^0е)E#@9Je|:8inqЋ1xeLc8`+om>ab%2u4cRYFSQ&P TT Z\ _ҊƇ~v85xv7~yh>ndy#2:eZ1Jΰ白AD~t ͎&´M,#_{ G#VhR!mEOXxf&û?dB9qzBZR#V.h5[ zd9p ]}Q.Ru:s#VXs,/9m!*%ڸ%u#2{&2чE)vޖz|l[nbۋ+b~_n.tc#2#_Q;#_3:˻vap̃.<~>O~h-1ɞ>QD]Է0#;X͛}8/^7lM;'Z 2vr".{h/0såjᝮf(;z(T[1%:ierXq][Y%TgAU #_X;cxX#E((H`#VeC(Ab}f=$|>20@qiO?FJMlrܧeF&A:'`}䤖#:Ըi__ 9v3O";;?*85dW%Sej7Rkptۭ#dp/S`<#_JѴam3>cٿ2;O9(bGH3}e>J[ ioOlFfOzrhX^ډC:FZGm7v&TeeSf{EvT,$t>vh|8uD՟=o*6#풄dePb3D^?7}/(E Z#2E]u}UoS$L QiQ,1@]vPjl)3h5}?WrO#_,#25N_YlR4r}8)yUóxeન(YͳBbdP|E,9rr"9ڙPˑ=s,Xj&e3e)άD匾3y;0b4PFPr{צk&wR!iB[Q]'qI~uwoQ3my<~.UȂqDF6IL7[#_]yeD;m;MIue~вs|81CL;h=5(G3qp:3.+[/N5Lm5Te-l9˷?#V {s<=೤%r*.#dA9 3?'M"85hOh;|אˆJ-* jQ:#_r`#}icR{֒`IBMRA;mB8%߆)P]t'*Y55.G?mWLyٿNwam3\ִEIBA#2l[8ŧ%o;6hg%T%G1ܷ]U$Qb}%uHȲuŇ$ lmfɵ'i2HFo dIG[7&TPYYln[ckʓh*/6k׈lRtQ!%ovmI6sN;9+n^&[FY M0TlY]4XIS6$H#Vp<'#_9Qk?&rpU!i\#VO>s-@!mb87%g5-vK@KG#V4mTS/ѾWKYWK'|QnHp-6oJ|aٿwEM(*T!7j5#V4Uj7#_ 6njzkpA8, 'b*Awc2> ܈hg" #_(exz֡-(*x%lv)IO/vA$ټfjQBOGWmSj# Qx<\ #ʶdA i @I$`DHu;z;vürC,@H,+c-Kwj3'+)iYmwjqGP#VDzŬ,.K@.`fno,{txF|PgQz,t%:EGǸDNn+M8~6"f{k$Lj-kakzgA6y(qυwǭZQ _<~%qꬵDrطʽߨ2`5O=3ѱx,;V:v~G.-Z/'5Xޢ3pl9Y#2r2!t>;M^׹C}N6ྠG]ɷaMDh6=ɸE#Dր5(89v&Vy);oQp&qs`Si1xI3н`NN.iG MQ.O\sW潽1\hKWsBL8V[y#@cNlĊ⨡r]kD$t::ft<؊PZE@.f@u".E®JWtݐ.nAl+#2Y젍ﻚӰ@л,lOz!:\xF/1Z` Bb+{ER i~x[-Jn8 5us-#V>7n5'˪G,Y"MuPGj `jM) qMPE9%2ptVqrr$HM4m8f϶Ll#Vh#V^C T;'Qj %ǥ|x<NصHճ #VFDtu>mQf6{XYi Sݟ8=a$bB"#V>s~YUKƄ>wJ_2I#GgHM?2=\Q p4IX6d3#Vc{놃=En 8)-#2xk¸Qg=:ǚ ;v.r&ZaᎳF6PDN +`b"Տ0\oK㧌Ͻ{f<Vgԃ1g#2c\uO.%i` cK9h#2p[ +Asux(bD#Veޤjzm;d-ym֊f;dE] 8q!@5gk\/%#V@Dՙxz=wpT`L+y/K8jtn% $ٽՊ,󔟨F,*-CybzW͗O캼^sk='t`S-a?Y,]!={d}^dś{,/Kx0u#Ki$( g޺>9= ]S6iL%}X#VWkFqKKXDeFTc~t_DqpĶ7"sH 94Ja#_ۯpҮ6!=?,#2R"#VYsUC};Oo="sjcmBPxx,G㞽<^$IQsI" p>^_ۛ=Ka’zHPdg #o#28:r?wNy+oF^1UT@@q#2ͱ÷1BQ6~zH$&@#_1߼A@Y&]}`ylvt] aFAx,A!H{#2VلO!+%b&p*BKׯH_Z+"ŇM>?xư%g??2#όaVH,`xII$Q9> OU6gϺpG(+@ժn߇_)~r^<_2E M_ V$~FKͮܽ= 7Dujk[.yAnآ_51( ctJXVhfHxKZ~}cz[p8wbבYM%!~SHl}^$;!Ul_;\Ӵ}2DTBPXԈLDe^1%1er~^x00^Ezy-#V9#2-nL+mggә:s̙%&YpIn#Ǐss`qK#_TzdHٕAZG#h#_7a !##2N9#2&mv`ǛLAt~c~Sy{9 +L>ahO\IV-rm`!W`;`MAX#_Q-R#2&O`Z֫f-ʻxwovw䣨v!GscIE#_%摘c0UɌ[MaNS](ejR3^ǣ*zu;\wKvM5wfKjgdSǯo׉ L *0Gd33"Clgӛy _D>[zp_3}>=sӖ_KkBe"2HX.J={H8d)ߏ!;F;` f372"1^/`clŊKMe~iـ_ǖ=;I|\_xpd.W #2#2N~GL# NjiTFRFk{UAآ xq;.3 \uy^jf$̶ORڥB6ަѵK6Km2D[C^:3|ODThE5dzC"m z!0,)Bcc\ :m.,JW#V;1nְ,ZޗO .@$@3֓ w|0mr5FzD<3F[NF^dSUUi'Yy"k&ߵnq\.el4#V;Wgv)lVLΧ*^R_wգHU_ֻ*#^#V.Wyz̪{6qwǶ#VG&+ﴬMOUu(ɥ̼Oq)ߜm/~(;x=Vn#T][Kcf`0ߕ@,w&-r dVm{?*8wejZDHPn[##_؄-zAkGVcT^_fIňc)V&#V˖F][;&VB_Fϙ 0ހֽ>u > 8Oꐺ4ރL- Iɖ20%MvxOds9|ζ,f1ga܁Q`ijOs9}P61^^೾P{[c\fd`Is|ךvHͶ4½3˸$'u|1E޴/ZØ|Bq=cl036΋2ֈ-Ϳvkaa#2K=窜oGM.7p̂P,q'_KrL: ^'xP6p-}=#V~*6S{n{t)H#9u $|?AA]MK28ӯOP3{b…s#ĩv 4SNɍ#VG8X8Kj2H1Zfq֪VZ`s23td$ξ99n8[JY4:81mM B 3irNBaZe2hhE}g) ʉ#wnNT XQ#2 ~nױo..e"BJb;[F77)#.;OP3nS Mi h#Vv"(jZ$[og_.w.}nqW%qTVn…j1 `AS;{#2*ER/!` J!1ɮ*[z kN~rf=THccLħn3yFg8,1Z.W#24`K~fddnoB(#V9=>Yhhї4i|DŽ\)33Av@%Y^68[{ܘb=@7<.պ%I#SE#_[=CWfޭ?zR8HitQ]?,fCG#2SC-x.+[ϧ坰o<׬HZY7pqj=rt"tHlQEqp(xpH#2/ڸkh u)f5wnBuQ!٦p5RBa-1L$~)!q.uU'#_5\F|D#V.A:  `vP!{mo>U!39'"Zۂe߅ P:&#_U38Ti-fȾhz3E;(!j#2H\Ê `@1tڐ:̓۹Br3JfCnX^'5[YSk@IxSyRRW]pep#;f 287ᇕ̺=q}D̐rm켮%I %iL:O=OQdUQ%ӱ\ǡƙ0kO՗csxCjM<iFӍ폌n"Gyyh轑 Unp/>6*.&Tx aMBj#_{jMs$7[p0WSL*ʊ޲&vo펷PGf~} #V'3/l۞Ưc⇺Рhz.h0q3z+zj' `ISH2H^oiZfO&kP@va`}YPU*b#V= S(h{{B{~KK9sc#2KKa{BMNv2e(BBW GF?60u^WS>y-w3oeoooSlۍ#VA?u䵻H5i"N^׌Ƨ*9:p[ǝpB*`uTrIV>׮!31Gϛxx=Y#V,7LS g߳[kY  Y_K3rbn#2 mJ5=4):3AnljkD#ÀqՕt|Jǥ 5jD=*yȢj<{Tv֣?.G#lWeHH!>]t_wT)_l # lFJ*:C#;l$vNJ]϶QH6QZgFF}de{js,2U#ΐj,1op9ߧL5KT0RQb+V]nr= #2}ޗUDH)%9;Kz"Wt`+Z87xqodq(,|Pl-K|Ţ#2ɶÎޫHDgR2pE3{/^L|7hCC@V˂"E1ejtKd(ƌ2(ێ8 OLI!?5#2y\%lqB`fԮpMC-J}cf|`>\IK~fsäD6E/aZ vИLCR0e(s"TY*Èf0#2MClkRW8I#Va8x 1b9ā-6Y$F!YF:>rte3ڌ<s\r捘0LyWYr~d2\#o;q!aL<*&Q#_$y<7@^*ɜێY(Bh\K_hI.-"pKaBh2-dJ}^#_r(h^#_^@ڰeS;.dgOn\`-3ҎSps)ﵗT5DHko\e,舵Tma_c#2T,T`o0Ei*a#Vhk.ۏmasAZ®+0P2#_#VwOmJzˌy@[/k%kd/8K`=YXv`~yyvbhYacGm6ɉ>VصqD1kp}#V_׳{#VPCLp|AzDI>>}"PblNe D q[n-w #20o_QJ#Va٩n!sW2^x.:bUYJP5z㶵4JƴP ׃##kc>&*G/gӡ[6A)gρ#V톴A8<.HQ~8ΦW8\#_z6tdd1upooz (L7.6dd9/MQn#2jFhm9nHvߥ-!l#pT5 EX늣ƨG WԠQj%bhjKKoq,Ӕm7+V@ldbF$C?4ai[gdI;p7;g鷍& pʞ"r`b句i#VWBƹ8(#_nCoks#V#V.G܂zkc}-yXIID ԏ۰+gGݣcY(Tow䫞||pM- N !C}h'~迱ӛ-saX&pusSn(yrJ+uXFDSfUqGR*c&A.e%XF~m$i0·O$paT 46FfbN oKd_4z߄]s"kB;Uº# m4D`ғB@[rύY&0-OfyFBGe9ܿOVk9~M's<]C@lC!,=<ʗJ4֣H{ͳxkd+M)_VRӖr/u&!c>w5#xpR煈.PHkqv0~nS R=te9{~;IUa*9]XئQ\} {zuwL9^,~.M$kO>رMc$#uϨyWi-#Vˁۄ%mRZDS8\ό#_?z ^҂M/7 ,%s"&dĠw/eq WS_)+,?}75MC͹SHGl$`yaf]"Jy#4c9s\(J(:"kM N;xFL#ѐxHi{!C(&)ťC^Oozqv>3{^8:<bjmU#_9EW DtR'\Eo|;!|lH#h|"# ֠`E 턗,r{6UR4`PXaZ"I+1ʹT6\#_ilB)]1Wدl3Z:﹠rX4fb9 \^S㳑'Y92Z"saLXl/4XMf$тE+KҙRǨ[pd mF,#%"yϻéy>qn9D: /7g#V!gN|2PF*u$Hx?Z> 4E}y}c{8z̑Q~?%UqBg`9_ЅquaF>4U2E2HS0A|?bs㪞_aq t 9Z|`)ACo.#_W] g@D?v0u]!#_#V c#r˔ch[!3'zrŋ[j8^Fb=[/UhV7v8P]X*mzBv[kw=p3ܾ=gǧ @mh c$Y)糚v%Ai(7s"#2 !L 7toζ{8lշ%7A?nF@͔sTg+鍜oCfk"Dn{qKoaɏo]#P$g)~Ih7,㻈rsfv>)r͇g{[>pQ#V{ǖ 9 Qc`_:h=ߦƸ%iR2\ 0yg+ҕg\=_p~N|糰ތ#U7A~{Ӯ|!a+Ht~31PAӦ˵L*J<yj`oYngС2T6&y\6Ɨ&pB,ΌPTPhi[V=1 8)/|Kd{=b#ki}6r91D!:$7 #VMsN/1ytp$#VPja2M0q#V8#2/6* 6[I+֑6sahY0!#VDI5k~6Ɏ|# C#VbCk6Ն 8c9 $ լJtf&'SFtg7ՂBbm3ΑKWuѴ#XXo.Ruְxkf+cT7%#VD#3! e0 SDbCU5=A6kT@ݫ}Hu @vJalj'UIنPAW'#2cX 0s:Rń+\̍Q|AVL|?n.G;nD!o>>;L⇡4N;PgsHw;qfM޻]`:m8e8I^j^lպ#5y92֩ly5rQJ@"Ns4D16DPcbCXVXٗՕ,K8&gMZOķa+cnu6ks.sNr*f#VCTQ{HN>mS Fb0ړp-^^ηzcR>#V5|GV/r@^${r/yZ^>$k&t-5vvOP %wOI=IGw 1\S(#2-={l;I$G֘stk z;=.5P#VO7;˧ӢA&:vSN#2NDm]G.P(6*H^j l"#V"enx!Bs/EѬa䛠zm['[)\G(^GYܔ$FL y^h"Sc":]҈@߯wBevioE6qzCH#2fFUVT$,%?XK"ùg2(pL?\n;&s*j9d4aH<Q,iKݒ8B[w#_L*~=rܼM!w-Mʽ(>mpKk(z*O]XgR[8e`H&'O%9Kڕsșg6Yg#_Y#V}ի<JW.,?;Tݚ䎖w$`Pj4BOYQ#Vˬ)#'tb9{D1!F?zM'!uۑ3q.8:W_<﷿-eU[xzݲ2Ag~'X6JJJ dj!8#Vg"$ƭvaM[XW#_wT6DJN ;{Y<$o I&O캈ᡄCO,n[ `;2Pq#9$VIA_߭nuv3QUU=-*ZA,P3T~æR*jD)L7k8Ph~C+ͫtE')@,( 꼻FxI#,@l/Fݫ+}6 Jݓf(FQ Y KP7.g: H-YX$ Rr!ThhNXu8FUEڠA7:eunejS;үE3#_0(99zm$|X;t93nü[vCv:f=-,#_g}^W.#Vl&P.H#V@uy;W`ĪFYrho D@݉`q!X:EӫXȈ 9-_ EU"fC4|?+ 1{:YYę=iKͻUٍedI6-,|YMl0 nQ/畽ODp'ZF|\[#`,^UDSHvamˎp'qt%("ꄻe=$CA{bjlQYY2wS%8ʸ`BޕdIr7 BdD/K4mGD֬k{-!j7dZ\^F~wݫuEbd(.Q8gV4NWXBRT|QI7-3w7IP]7Cͳt%MYQtv-zкmgF9H#Y}jyh=43?-#V7$CkG@4=#VqxQkqDF`'4a;T$##VzJa5kϻB3w*7|JUvwc{m/3m#Nm;Ř1>Kܗ4?ѣZİ_[}tҮ 70xI R">u::Y `ޡg9J3<@ 7dt7X=Ri&b!(T? $0C9BFѮ_I7ȧ [ݎ*l|8ov3;2Q3!%|hN!ϯ*d}CB/~u|n$r@}7T &},3ԩ^+H9`eX2LiFG |dݬ@"(6Ǜ|qb #i.PُH6R"礻_\xnrEҐ I!Ρ5Yap#5X#_1Ŀ;eiUhs1X^犾#_lbRhBlHcs#_XDt"!/W4!8F߫S*VT#_nVțDcLQ+B⣐ 5g`?6#_Y,ѧ@/cX5/zji}s i#_#2lBWsC Gu|6s(|899=5$FJjm<##V,ȃ 2~1*z)n[Dw iK+(SS+48ѯh6 #~+QWcmC$c'C.?2JgsΟguilOIB':t:vA9oۊ9 .@59F ҆;G<{~M|sY/t Bd=>>X!$N'ۣ IJ8F&V,FfOXs,#Vpa'L|E8xplqՌd3㰝 *R%X PatS|zN(lY 5 rO{]m L\`>74<|><06"8 j4s^ع#;?ԃ|)=p`-)G ݄隐oe#_H*Ⱉf\>47g+S|NtԻyzN&J%'J?w=TQ}u]&ac7Tȝ/eaۤBK}N{`'qe aXΆPɯz":`ރ/?f<MqH{Û3cˉA#Vd~ߏXͧq RV8sFsk,o!'{a흰a\kO[- ֏C]G)CJhzz6Ѥ.ch%ֱZŌy>htv%31\]TU^D˕>"aCV5#mSifkWnO8~*MvX4N/7WVevJ*ָI.=;۝:ƞ]yj*'~v]MG3uY4vYלCʤoIg*vGc$`eknn#6#<+\ZZT9GlXK~Nx9m~3mjWDo{-Ӈ;f2-4S pVk5i#2iZ!a~X!%ں(+q877˓}r~$",w io|V_y95cJēpߌnvۻiyQZ/LoxI1[=g6v*y99oXwOœmmDw<-Xc/ű>|uv/W 2Qv}vhӼg$<-qg}B~'X{V1{e^QyALsmNPSX- -k;mXҊ>ƻ^:AԍkϑbXR}0g>\g,1WhRww-~8=zr8v( !| A3!c= X;U/cdu_#%4#Vp)TZNd*Xr촧7^7>ءny6PaRnx^S[ef֎瘝o,oF#bt.'w[Dl8Ub㨪OvUnZ<-FF7|u}~Y9N{ɱݥkiđg1"#V(lꮼ7w5ƶtg8QHP5c525':Ѭz-f3>8UhTȕAt:4UvGm.-LN|ڋƗj8[o;mx)q McCS)ٹ1JXU_jn1GkJ&gsezjAC=ye=L|Lοƽ|9>< ۻ0DfRFവw݊ȕ.1+Jv#_0Y2%.fbG&@} Hݡ 7f𣀹 ~+Y BIMne_ZNu;X?#2JKB򸳨(cs˱zv!^^foD:Ichʡi}:Ykqh4 [ bG;1񾳎`oJj N"z圙Sᨤ_ӵ4S: [8#gd"jy#27`#_7FK&#_hRg?O#_|C:[S)DQbR~i#Kp0Ԫ}apzȅ#Vz[Kؒ)|ߗ4s: eZSBxuhnE)#QkqB#2`)| ~m o#2S!$rwpn͔bAt1T!Y՛Dpf# -Pay0Y~?0x=,Cqc^[ă#_/<՟#2Rd z.aX!rǺg-ɋ䉷Gxh<.oK<-9:B"TΧ#_a&,#_'SCCa߾{漷_[N\ȕ4TmTS_p:gmL %0A}HڙPp l9wRĒ{413O;ӔO):AH4>\#rzßy: b7fبɩ悝q7/ڝ ;$^LXil*ZYq }l&Hy_attx=zd&HH;w S0A#_%@Mӟ%:цICG2BKjҜ/2kV7ż3+YQ!޸bv0'$&T3t#2}2#_$,[>DUgu9ԭʠPk#2<#y#2J>-2.2acs׈˔̄x{Hn_$ypvӘAOe|#)1ִ]5G`Cpά_ #_Ou1BIiy#_"OgnfryQbL#VDGT#V=ÃCx6[o <SWvovfb#V'K-IA"Nih#2E%E ;3S9/xH"Xk}kbFh\YVqL %#2H$*#2;fWzt8|Bkxq f,5- W\nW){,@`_ I7-*k>%h-7z@sb`fZo*:b}!$uA)zg<&Y~uX1yLD$9=DZ(Q6Dq2:W<ojX$*Z=l|gӽ^T<#$]X?kD="#VH>i4z@ 5Ȉ6QV/ pe$mEfDοD/s`1^/CH#2:58SJJLnФHeѼ 澷@Z.#20dRl h l@4ń MLL b)LW s˾|H"4ƺ#VҌVWVh~lш1[bc#Vǘ{0fdV%fgeUD:ێ^vse<{1L ,Ot23-b#_l ABPd__)_%<^Q zTvƃ]'*!?#2M,#2:|w1J+l}@˒ @#Vc𳵛*cφv?CMTwV]gP##2?y/a6a2BKSxyuHȼHT󓤢"zB}<#2(8X#2a&I az7-{%LGA;@+/#_d"/>IͷzJ|#_hǎ2&4Dl~¯M jL6*W_+orY}̗_vq`QO9<Qsy6;^~OdmFu D#_4Т¢B*Llti(͛@[z^P)|UH(s*P2@% 휍2)6Ldq.墇$7#_$;^TMP0F#2#dۻs#_NR7w ⫪#j^Rw2I 9IrCOZ7˚[q5w\2liati7⃤ҽt a7 ~YmH&#% pUQEA[$F"Qͣ42Zm7Q=APE" 2DT@BЍEž.s߳,ɧIYf<8SΜiE~yLR{XDVxsT#2t᏿(Ow$Vmد00דl'T{ϯ_O什NG#T#2p#y5!} l-.#VG5*$ES/#H(s()?U~GyAV#VBq&3T10c1 )"5,k38ZMJ4lqh F*ҚFkN#И˱R]$l#_K@z6 %#_,|iGOT@|W>쳟Y/'3:@eLTF¡IlkgE3jM& 3iRD}ERK@~#_is pyIG}.ÜI>!f7`KݢyLvy!B;#2ބ~T *CͶa>A4mƲSn$_ѶSWV#_.hz{|˄r(vQB,qġv`o)TĆTJać#VnNY ތ:W|(ݞt,jlF'!DQC΅bUO7][~]GՆ誇nt1hmNS'Q"ӲIS,6tE#VW$~5m|#_~GlU`aҼŕʹ~3*S3N.70Wa)h,{Ř149Ʉ_DeAU&#_~?< J a&c կ=042Zdg YY+bTXuzYC ȢZdKG{@Ѝ?٘B7~o t ǒ{~vtz=-g`##MkJr_1v!2s*}qz|PcA^}|gxݎ9vfE=?#2O6U3㳯o7>jxvfδ|7q9!_nq:l?uj뱻@9#2!mIxp{uqYZ,o976:/^e*K.-?1/Ho+yDX6NO_dFޞiu׫|ߏhթ,2wu.P<#Vift-?膯c\\2??#_Ln#_qzF:Fܵ5}?Yᧅ.? ̶lN;lA߼o#7۩.} t>]H#0ki\znEW:씗&ӭ_L ~9=K#VX|備^{_ztas!#_#V_^7߀WyvwxC#ĸX,3#2!}0hB8o@#צFe^!/}[ZW,~_3ǙwU>0nZf#2'1}^{:4HK$:1lVa%?I9F0w7^#_{?ְ'jߧ`@=SxyݗMVѿ^lXlmjQ\n5E֟N#2s~=yr&?NՀ:wG|u'M+,;= y!Z^#_e*zܷOS!ecצa2~~hUDj|?0 82{S cʪ7avM>H{xjڕQN@$ecDK*T1O5QF >Kf$ *E`JGY:CxW׀JEI=K>5bhR6`Y\0#Vn<`5"[++$k5)$v$k* }rCuv17^8=5QP}zy&vwU#25Φ|/,C]W*YFI9^qs[6'.}ua4~kˌ#VdHzM/ПFs&'Af15pyXĻЙ._c튌;6v?|ٓ~ʋ&tRML=nQaH#2jPiIdwFnwtl,?փ]zkٷw9;P@Fmn4ė闭پ!uQC @P q:z:ZO_(/=b#f[{>prB #2=Á$IV𨇯qQ#Vy2CY,Nȋ9[tY3Ud=bCG8RE*tI'sɗ2e:xV2ܦ[I3w+6SGpNx5M46Ïٯpn <狼<O}:'j׮= .Х3M_Npd);IE59#V* VvrsEE]</Ht$J(E;hOi$^4)z~n^ %4>>, hQe|0qYkDʼ'fMQ;[H(^8u}Vl-n@~r"|A}#_ʪ?wp]1o{itROwfȗiPۮ]AQD 4C5ծ$Q #VTuDqt(M3&reDcLFЛQƮa ;k׮q -)J#@|?"Ewds--(P?u.#_wY?Y#_{N{#p#_%oMB^_eԺ6!"iJee'8{;LO-k( Po|D0H)"I"uzzhM&Sh릕]6m5sR$YiJcv^?½<\3{n~ɏ+Skui>,Vt9tb.;,%)^gᯯp廅z}>7~v)= ^;Gwv7 `!\c.?!giݰ{ڕ;|eqpF ?!*?Q|2X|.)G=gS!wb2an:#V-TZ(?|'\gK?/MCp^p敂(Np?.Bf:%c6iW+gmivjGaiP?l&&Ε/lM,JtB"5#G"*ܺr1 A¦*ʇ%I,i*8ItN+[F(`FG,*{rcLlcy#R;:s;8yFI#_TҰlA `7i ɝA;;ΠEmPk[7*[p~2+:#_.¿W^P p#2?G{]ro\Kz!j`ƛU֊ lά0%:B'vpYDQQ4cII.}%Q6(Ff1cCA*ZJPn^;{\{ݭl c떥U4F(241OR L5e30?\xȆ10۽ DC1(7ȕk}y)" 2y2hcUqHa6t"ܡ)#V5BQ4h؎ vD\M)]װ{uP<ۧHG92fԐ)a1eXDɝhnngJ ‰bhCѢe"k9efu5LE'9]^7WK66llI#VCL&#_,XŌ80cG;0dPP* HhQ@RȨ)m ;fr#snO#Vq4IK8>#2AȢo2rLJb^ @zOLv5׈6֪H&qJ4h"\8X?|GBH QxdZکcיBhT2EWf$V2lOض[#V!Px2q+}5S!PYD}m#Vm+:LEtfmx"Bdn!qdaVJwv0'6L!9odMF5\;w;8ͨrMgie;s~5 #X}z6" EƑ`(bЭ3U7Mrݓ|y7?m a77 8n8@bȧbi#VQ9 Ǚd"Fj"g4dǺa-e}ϕXєmOQ傛Ëi1X翈*;?f'˃%2=`\۷Aܬ<.;66mcEBƪXجA["P&\~DZh̞LU/VN.E՜412PN5)}-f=bxJfcsW,7 rR֍mFv0FSo^l3+F]3ZQ7N"0TX:uæyu7}9}ָ#\z~޽ \ۉ!5e;{Oۧ%$-(<_yG7[ya=N @P΁``-;ZSז2S&ⷚR8{X\gVuXCgjM8;o =A|M2Êz:~O#V7ʃ=AAH_CW2j#=5{^cH%Cs|\.YWk<`@ 9IDDG0 @Hqq" :#2LZ{DR̸^'GPV,}yfaBc? OGa B){cl}0#2(pG`Ț@ˀs9 RT<;,>W#_t/SM+tg& jrA6 |M+tJ6K(0I>8A.~C۱UrX nM;3c77^ދle v5.e*BCBq}vقᱪ#_J\K#Ovz>Sžr0ymD_FΝΊl#Vw$Iڹ'%> CFw17?y`Y1,>dԵfc*dIBc29_HׂMfO3}n?&)I1SQ-@Cwl4H!׻52QW~TZµYEK&Ll`Uyo;W&>A,Y!+ 3eFzk͐fY#J_UlGRqWA+$P͕`m3z^)jmbvYNҗ0zj^RK1 ??!|Lɗ-Q0)+β2+N˳|ҲNG6>L!İJtCH#2``bϛɡ>7z)ښ~{썓IzL[~ӼyGU3S"Hmcúi#2pa'"LEL9!;?b]M5ևGlRYPlnt=L8/C|ݹ%k۱?,oAvnZ8*Wz1Rc14NE@okRwv73t )YiP{'[JY#_Ƶ}y矟Oۭ_3)B#_dW#Vcfgf^K1A,3OU8>ܙ#9 ZO"]*6S;{wr\ uaG+୨a{/63t[Π#ƈNctffQ-Рm >.s鷗+z?Yf "~#qfRHT[Lc&h*)ԧB**-Id};g[{zFsȌ2V& F'֞O7-C~K cYT9IMQd< &_o^S$"#h#/q΍sFl$\ܖ٪ӔY@Uǘ%`ѐHcA^̪Ƒ#Vxf hDS3ʣVd>_N_>5Ygfﴻ\Bq{#)Fkɽȃa-!W%tWEcx&;g'=)s9Ѕ#-ٮ`L0N.S*eE+sofJ~\kicq&bWDnj|\'Ngoec(BWeSBE=M(1d̸C#_N#_1e j? KǕGBGtn'ԑMD)НfT{ք!ћG=y~?[nx;q#VTԣ_.ۓvZsW !an.:kEc'kF:aTZ$b{e}<9Td͡~jB6NbW|Ϗ|f^`ĒmֽvX8x7EȴiCxotDxki5vgF7N],#o]{,֛٦E`llv2R=Kt74Ɵ63py"KH[Nv~(9͏b uDGg0(:Rmb/#_5y/M9),)Xy!oVt܉san2:gmZ~]='dÉUݚ{ [+]-"w+ cF5^gSA/ے ]<>*wq $K /$aЗڍ1XAX36uJaZ,h Iݎ%-ZzŦ=M,ǦA,M1F$؏VWqMqwR:6Pcr-6) h?ѥ)(kaR;9Տ4R:勰ٲxga7z*L M2ԁC-B!ggR̍-վ !fDθnX4(J$v&jCll?r]xjcؘn/SÕ &YGJ!bD81LDXΞVk6y.rx*ery;Qgm9ɶtaH-lvcy` #PwL{'S92NQyC:<>`w191O-H#2e#_(Vy%,o1gwcA\靂%Y)em n2}pYh`2w{ ~SGR38gˌ|h(E˙s!b+~ s;AA]a{\ mp/=M7n#yKʌq/v[}#.pri'V5Y{ގ䳗4)-_{vvi=ڈ~N;nk>ZÓ={C0[z3d>MDzXXm{{>킔Tݿ[3nQIF'[-7dn#V">紩#_>e/:rXJmp:s$[d%#_%A*դȾ.5IL#o?]Ĺ9eqx\yI[@p4euy0$кn]Kk"(,һ?''YYbgEZ1 6VPpi+rXW/]%[,`_jG#V/%k8JMi9PmO(\8X#_cU?'K-[$ō&SմÜ yVi mxޏObm ta[`W.`UU h궔=JݗY -#_w0bߎ;%ץEج1xMdbD/}2fmnsS /?~5}#Vb(fJ[%#_|#_04eg/i\dzA [; &U^7㎚J-{cMX j/P|(#|'H(ȰT0j$5j[%*0bU^,[:N{Wk٣߄i`*.Utz+̜ҘPTx]!7Ef#_ 5"*25dR@X" ފ265)g7*# Ye`%ӹso-ߟ˘v c,bQў֜ի.K δ{"+riE/"tvUX~K~#onÕ䈿Th(G[mS$Pn!Y y_#u(KLq2.m\YS|S{}^l8O<fLt F7Ǵ$H?u=]Ը?¾4˶bQnڈ6%pN-))l\&簲soa›UJ-xPwrl ,OqdҐ#_w\q[^gl 3;'6Pyj;gRԔl@4MrKLZ= qjVLeMV,q{Or y8 \I#m=k6\Ҥ:WzgrsSG{xۢ=AǚLz#_#_4#_9Ql#wӓ'WtD  ϖ8#_K--n+|FZ+M#2<;OU aAs%,7\tWx{+PG6͜ΛQ-J."S}WKp15#g.>8qbΑ;$`TA cH{"eDsbp\cѩVX'$d=:9oEs[8i%dKSk9Lps#VK5dQG:⫌Eq%}VvNw;G*́(Ac]J6}Z){gN.ݟ9O9-b[["0Ys%\ɪ70yH2Ya _37HssD#|UQj"2D7 DM^i_zkHCWj܅90#2NB(Ss[ς;#2=€;R9"\|x+;u){H.^#_#s+Z^7NaXusb:5K7*I:I ){Я&c>V(ثt#V2TԬ#2u}:[$lD|\k`V6loP޼>bpxjEyf\svw|3 jH4L|G~Ί(.Mb]m^Rfٝ@b3Gm?<{o̰Nm?0jc#_,e[9dmnx)6[E.3K6p:E>kvncYMP@vHWh{Xskɲmu8 Լƻj(/ypB8PG;t/c˓Ws Q([[ڎ`qYE|uhE|ES!V~~.[MZD#Vrt\)='ۙNQ5̡7窈;ea|b)su-IHc{]*QMݓF*E)⥸Uj>ʹFqU)M`\DPYS0Jd#_}$ӎ<#95"Ҹ`ZG1:f%Rv $OACЀel#{o1VDY`aXD6\e2kIŪ^RY-|*9KB60w6αVVdtlY;k;$'Kok}յ+EWzUO3ԡ;7A+>ߨﺉ;zK`T|%gnh:Zo}zHFxxL.}{^#_x;qZi4."E[you,P'N|\h7!_A6}azDZ.j#V{&"6ܖ}KmDSjj#VqnC5 xFCB>EBVr^RMR-Fݪ(~ 9s‘+q&%<߇+=z%-,AE΃EPVkSwܒ{{ .OBtipmOJ@$)X}m$)?(id#`#_e#bKxv-v2hp8xw}DAKظ؁qU#V@]~zh53$^<@ǚ/fev#O`[AEs-nt΋$SO#VFe[yEIcp!-W=s)/_Ȁ|rw@x\u@u'Sl!5y#_8'0ERGY~w<L=NƁE6_KnJjba!; Kșd:no~Ŝ|2ϼktww"5N$+~N61kӬxlL2{ωPQpѐAҁfM{<3<|Ԕ?&18$Qlk[9R&[&i(B >}olzkf xbkv|nMf51=e#19V&rH"e؟g4,1yJfݟCƟWrU"Q-#V'd #_!b` pA(}B<{DBF I84v:a#_QkKY~*X`!#22J1!w֑k&`>f$r|~4Bc_oXMɉlFвZܟ ^^Ilv#2\$MV6] 8Q'f=Kn,A,BNC *aJ zC35.@RIOddyCOg#o)Y̐#VA #2e/&l]EQl$7 HvwdT޻X${~\9v;y7(<9>N?;=Hcx}(/BGƥ0ē2<Cשk,EhLi!CІU\Xpv3#V\klM2LN{&KgL-ѝK|h!4 ${&A%Q<ΐC6t/Eu{aZ>#2#j#2.R3C% vw[Fڻ 2(6y@ㇺ]ekrA@K=#Vn!*P5*+9ACwD&468DzRbMjS&@ J0KX S_U'Q;BN<i[脙dUuP lMႇ59{zqp>@ +ns?w@3 )#_ bZH-4z499/=_=)HٿIkCw<*}J8ߺC$,[?]gR@VBڊt d #2mQ9O8O &0C'$Rqvn[rS)Q3[*Ol@l1SS̱GD;-H4ǜl:qOz-3HQBPPJ2 ƘfǮ49Oyv#+kexu81:E𐽤&Mף.NNv[*ړ0L[)z-GDy|)Q"XX\żQEU./F,^XZ6ꃮōnD/pPu\Qg.^p `.℀I|9`hm~cXv^#z7ːfaB6@F{ғxƭyAm_ejj<̂#\@@K6æuȎK>0\ qK_'vBh&@sv^MAֱ˙?t(>B W{nKf#F9`|6xy0alЊ M#2.db7;S_˖@X80l -z &=\|rss:2D%O>뷅]\>(ogt; [|?,],ox ;>aPo mxF昔I9]k'ndPMwԚEkzV*7{oSn) )X%l9"8C#ٟXq;1~W UtO#V9X[ռlD3Z4鍔p/i ͉:DŽ;"nGh^|.v f˝m#_sS@ 7k2_o6PKBns_Y'?rF~#2J$4칁;*7lcs16ǺɵY)X1P-mitGALudrs+IpvgQ6;q\Pr!]:szy^n^<ܷ>&nkfgwr,p8+cÛ"#_}"e7H%pHkUZ6u=0sWPHxo-E}#2F#V`A$#2iKUwnL'. *7ZABN˙3gv?u1Yܗ*(Ez?KrR;Y7"EŢ9ydQ~vnʀB=ِ#V&_^B}ЪT, &>cS%ƿ~ ULR&.w@< o/ACp#_ ^\R$uW/9e~r*dL}4>`@S#V?% ""!CB#_?/BzIA"  #D0PiN?֠s|PSiqR#_^&*VaYH~opB<́oI1s/:H^F0NpNOaqgy&:%:r8?#2jʆ*r5X3!? 1~9m#V$ȰNQ#xm&Ö271|Jҫ({%P "Bgξ|W#2&{B=Ӱr5(,7א`QCxh`6N{#_W͊ ߄$7T@Ju+(gRQ$A< THe G3)4`vV of!'UY ќXi=*Y8'vaC͛LdwkkE$n`Am{on^(F<115F?)1U#_ݗ26(ؿVkn֮I(zڌRdco76]0\WpkcB a"V3r'W!7J-qpL{vG[ :lʧRf';dEv7ww~__7W:7x{t_cWW~#2 #2~g_?$K#VֻHhm拋A$]ﺝ<3jmjp&V"&\%ƃ1g臉/?OPO^Z<*t>x"OSA#22*@Έa@ˈ1 Tb :ApGۚmv{bbl"x2=DWfȁ@{†т7kw<ф<L'TMtz0\dHG`9taAI'#T>Z3.Djylp &%1wIwRHky-8R\)}" DCA@`|~2ϑkc?,̎q3/hc5LTӣRܲGaW>E{+,"i!5#2g?J_;nk#szi`aEdȧeI%],PBb) iZ#VYl.CLyYlЛ{iҠeH5T6X-xvm1Rw^rdq2|8/yy5`2%ee_$F #w7i{RF`7ݱPyLU=Á!m/qv)ѥ2vc#$6{mFPyT)#VF#_WlvGM6!^c:]CsS\%!9|ҞLQj/qR=0J j{ߔtR{#;~G4QAu&~~Cȍ=ϫ퉠g*3EVJfK*A47N8fF`ParWdws$*X?2NtN:*Zv9Sfӗ3lr;îtv79,Ձ#KbBm/Ɔa#Vli xCce2nґԔWױ1f*DéDq8uJHL0d;C·g/oOy'" }y}2MyfVe@qB8a&=tPYAyM[1oy^c>`uE%tS=Q^dN]<v`v#_pwLa%kyhykNyGRҦNH&@:!v M#2 TR`! Թ) #VB [Bjv"Er0aACQ0Qwqs}$_lQvH/Zz8iȶwM)d솩x=+>{CSUm:5gb>B@Hh5|4U0P(zĵ>ȕUNĞۇSiXZYh'Ü0 =Tߠ)m@*wΡn#_@=QJ0DTc΍dzDR\Fc4 X71F_M ȃ?ҵx]0 [zf(t3c]a^\0JZ)#_i 1Su1#;*(i#_9 B  *D gKY|`1i#_Ju#5G2#V&n= v'HԶrC?-6d}>w#_I0m֤0oa$G-ġIAfSs{H<`~,ff0Gsr$`#h{vpE#2=6N:M<)Y&s!{14?'PamZ)e6'+8>~>۳{#2!؄C!q:~oTvN]>'<ӳ_MowWeg!ԇP3$gpa,VNIԆ^܌<FHP-A ⴀ#-h=-|^#2L/{≶pFf?i CGGenZc!?:WTW`E{Q(*is$WW]nt71#_t w{W >O#_{J#VA.W3r]t!*C]ur4̃>ˢ=c!RvJuCDbQb`3|kC/f`v)> !9_,g1nPkyMd.-It}; iv9:׏3hj͘$Fee%k#VC^RDFM榓c!}eG8Z$A#2!j7=#2.RJw?l"8*Z8BG n eoaygVdZ Af)Xù|W][8 8#25=)]jBPAud(9c]SAS2@la#V۰{J#V4X96JAI1dC>/ȹW/JnH8r6}Gz[arS I$#_gLL#ҲSbO';=\G\b(Rg#2C-s<1NRS7vR{|F׾Ij.847 iDh`rGL#ĈlBj#_ `v.E?w#2 D7Xc\gv"sLbCKZAZg8h3!J)<'Xe0#_=n]s0C%.A)8;8hus{JE@#V#_٭dPT"PY)w:2p<.Uhd6+Uptg\26UbNWU~N(šl{d-f#"ҜR6dۡX2B *%h#V=<XPv#2o!4D:(g(৘pE.'Q0V{/A1^:#6&rUv>Z8dM\yCvxxӵNb$p6$!WbU-aOӯk"Aruk' |ϗ PQ:lRRClxH445*ZCmǟ+}R#mUOu~o='NE@C_}#V 3a{*p2u`:Rq3#24(PzP\,=,,=?j^$%ѻj6 Ay@DDU+ڟRtedJl&7>+m;^G%6fy}QGۏ89Q<Þ @r%>~#V"zSs>q_oi1/`#2#5AA#2#2x}~_vxĹ~Y@77>>LW> %gifb.n9d,5ap5pO.(%TS#@Q `ob."xUA?K xW}ϟ:yTpBH{xcHzlmNvmP- xE߅FKu2t(o6\?;% ^AQr`s]4i-n#2#2{iA:6/rԨ{E#_ݛ8o=ٸ#23:x8*;}Ǧmu?#Vd#Vհ~ONd^&k{;lQN\c-?'#jICkY377P#2jz2}%:_u" #_ ם#2;.J:ؕ֏@/ߋfᓀk;#2R?I2uim*36FF`{)Z\= (;^U㔐~p} tYP&j]Z[n$1Đ5\} 5;2 #VLòaZ`g*R2 37g7ƾBD;a#u-Ȯ`rm 6$vϞjtdfn;|vIlc#2nF|>#_ C_~>>S5Bi=Q)vy~_5&xb"&Sa]#_nI$;>;~)˚@h( % .s SXڻX& 4Apʟhk)Nb+PN\jy>x곘WɖspcݟiT>>Tc9v2Vς@΄h,P4#FZ-U#$͞(#V5ޟ#Zr],POҢ!@ a^b-ჲҍ*G#V(W[]>P9G>R}QD):0$JQŸŌ}$W~YD, BoHٿV #21,X~!~7D  s{G$x>u€p#_c|ifs>}=%E, _ >`㞡ΦeDdTY{깆"t!O'pB?GJP?'~Q^S祦?[9.p$$"'L6r83?#2P@WbGJ$×1AK3 4;%$37Tkn6X}< }>M$(%B~mٛ#Vh*=TLܩݒ,X{#2 ݽ cڮ|qfZ#յI̓_Îkm@<.7l# oxa& ͩ#V4D a`m'LS$4叱'%Myt+bK 6 0yFI93#V]݉ʷ|-[Lˑwt2]Ө!kN9kO=Že!#2{#V='??r!UuAV֢%i~#2H6`[K'.(##2,ئbD_<_^P32#bg3#VMi**B.a-~7jX? W?[a05䧬fum >Crn}ft=$k3܄FuB㞄EO ИB[ຎ99W8#Vz6j +PQ,YOz.ID9=RPq#2ߕ9 ơFY|h =:փ)3z#_b-OT2F4b/az,EqPHw[>&X$nr,~Hdr+2H+d~J.[2g<j|t#s\WC[L?ۿVDN 0yO7uRKSNV,k9>+K>e@z8Dw<}YϳTRzuo#׹G Л84ŃsQhg֝-}sMX`^枦W ų!c[=#'Le(pBi ~K#uu b_nuXTsEWx5ÕwBP$!#Vpr^gΗY!Qv!w+Y!Z1~pm$l!U#_+=:#pc ߮G'n}"ɉQ z;\'G xE#V&p!VeıCCraus 1h82M*JWiY#_|>9";k[q#V읽nuXJ#_d#_I-tUZy7}3Wk^1ЁA2<{mhkϫ/ػϓ Bی< u(rfk5 WF^H+˂.Xd}A#'b8t=\C9D|0o8]ݸ٪_UlОz>qeؓHCmЗE1:߮@1I9MH~j0oH)τ:?%hbma3]W_˦4Esd *( 㮊앻xdo^R ɇ18(#ʌ˨o4myɚ4/۸=&Š3u%##2x#P<#_^Si^BTh"l8{=זj02hxŠFzM&#(q)CT_H汝Uΰ)6iEȱ{3̔7(TwSWxsd45jsQ&, d9Q:h7`?ƪ-"-?%.SX<-68%MRc4Jsv(e)#20y#EI9&* ז2rA7tcjKl?+ުOۯ+} *7aTL =?ӢA~on~K?֏}ܲ}d4T#2/*u)"]7k NC>_8 t@[5#4><EpL1~3P< clyFdd( L(G/@"/9aoa/.D4!Tؾ& .6vyMmd;{VWE~' s#V楸T}nflq70mvrb0_wͻHqgi]qxikRq""vX1ÝcSiq8F#?/& ;Wk_MP_,\-1dn}ZلI9:ߗg^uvcVI[sIK3S?AM>ze <1Q 깝㨟b}8Zff;1.8IKGF#VLppr.qW;' <a~kb=."267J&db 2{#_Hd|a NNPJD8uR<#_GZl[;Pȃ Q1 bRW/dPl54YBI98}eoq'aDx[Ƭ;Ѣ$#VO4όϣ@_ҠKq̩ }!It)VjΫ6 S9sL"`6DzHZJϛCPp=luj|rtY'JPef8q׏eGGsٓ[ߥ[>!F$x4(E_#QT#V3&̚#VmG~=XL! {jޢ)=p)#2EhVT yڶ6￶r`)nb{ul/7'QDPu*ր`jZH/ja[Vh٣ FY&8IBӽqc6\s[mA0Hdžٟ?Z3'Z22͓49 'OWyiձ_9|VM##V%57$#V }El$WKV~ؾzG%`XѹfL*RRAn+elA|Iݘ{Z\!nr+/ Ʊk&oNj,}n2qm KNQA>ȆkxEq.z$~[`"#d!EXƻbID?s`FoYYw1-t?$l4Iq!Փƛg,q0yK|F柋N.6fJD#_"WoV 6rB6a*:SrJM%SYA;*#_UQ5(Ǧ팜JdS$0qÕ6+FKԂp54AKQi:KA3o#2q+_zv=#"SѰ*֩9͡O?|'ƞNbχuHT#2?vl!7bb#_k;8u僺Ne^ GV&3SL{".XX䌢wa]ڪo^1׮g x/en<'s.n3?>,('b 'mk~K|O#_Z/MAu~b\`FW?{q\N>0>m.Fќg#_kIm#VFx( 3IiAo-(N˭l $umOͽiޣ^Z[c=jr$b#_Wو=>T`m_jwOGC9@-4~Wjklm-C$~6}#V7P""#VGÕ7x ϛ#VćNV=n)#"oUG};ِW|:(n@ONoP۸|w!Z'@;J!P"mo }=;ޘkSc֋tBzh0)BY9;߅ay&DXUǿ&Mۊ ؇m0D#2wM"*9C4;'I_@&Ж#_[|DvƧ{Da_?uE W.mt~e([y+u#N.GpLDSL:>-՚9. fsT #ʍ\C5Z44ę 9Bw@P[],$n0p! j 4B[F)疉`mdr@v) N+16S~/W.nmYpE`,wJp~dJ6-+*.:|n/_/ݧ#_%=t k">rf`6 t48dsVU*-V6fQרkIgS*`Q24пjL3~84ekeWNA5`d9T:ې;"׿4>ѦѭT{#Hh+-4%rSAj?b%kJ* H(:~#F۵#2'#='3kc'a"t9‡`3fߪ.a(ģx4#V[L՟~xHai['Ax*܊_8c#_3Fҝ!\?cY]͟;R))ĺ4b&KW\JupbkCp%vy ̍!U˫}t~'O]jGy}JndK>8 rXF O#VK !aɉl9H#20  d$`p@! %z}#V< o,σ)݋cۓ䭤&6ggDWwӉMLaq }]xغAtCh< [JA#_z{???}>y6`1~Qobj}&ΓD5ލQ`"acg/D zóŽx޻"Ԍm)p4Fyϵ=]|||2< ך+t4#V#2<8:Y6{C/Z* 1] ^?$>/TX^}ޟ,}?DOQ>P0@F +hkBc)i\5C3H7+.e#VjctwX|?#2:{RC(;{8 e!9 ̇a D!=F]Q=8t160l.JU5jd|4(PP[OV nAp :A?}d#V k3$N U`#TK-Ș/ڜ#耑{(=#_R1hy!&Ϩtoª'}A˄D}}ؠ{( {tNi:Q K`$ m<&3! Ty?FUk^J%~I8C)_]dd}!xa{4`|S!9Ok( 'O+5oϙC'a6!Vׯt3f潦.AD&=.>zV9#V#_{/zqzl^U};`=Q}L Jj 5`m:0zz@npJ5Ki)1[!?wqyqPS-&<7>~Òm?N/|߄ި=`c']N-S\{w2$hg*prV" 3L Td}7L Ȣr>d`#y@w~cڧajNca,#eR#_?oa9b@?GڀϏZB`x_Pk8.3/dCq4O#2a@}`%u^7Auj#VD p#VM>y&8}i+=\:N=O߁`h=KG,#_NNUL4R=ӲT{Dvv9:}j$n@G^OPȓc=':sy |ϵ;e{p9t;Aɾ.E&|o8ZF?.@M0~_.ATP`YCDv p>gFP! ӳX͍;T8 o #2u@*I|?#(#V5EB~3u8|czgbSBR1QY$xJk3 0ȈmCLX(A~O[j\%EKLjY˶YXXRDi(9XxE~@z(d:K7^5M#_4#Ve^hDJ&mlh|ց5_e}0qrhW|t2QԆI^}PYҘ88|PsRG_{$9ן&kIs/ c^B;1JVk@s'd D7Qm6EY Yk)gbL#&@-7  #_~ {6=@}md#2ȟ~!MCH-U.M1ȉ)+~,aaAR*Na,EaA[ƭu%_8tf,U7,3#Vhֵ[M#V>T c,.WmSeDFM:$IX6)ZcۚX֪pJH9IPe5 ,CC0&3T2=ܫ2b!ucQ j6ӧ} :筂A=%A6#2POC#py$ljx}E#2(|'#],PB(4z#2$?!/6\#2(LsrX!J (QEX\U9?L _߰9'bjBWOoxhDZ`<ߐ!!5#_$I>>ˁwʒ:,%qoA,]vke:f 6&喝DU|cX0c "93s#VTcka$l$9nwDcm=2zb/ܟȡPrߗcw&w IU,(3䛚=3畀-9W@/XnF0Ao^Gߤx_=#2".RCR\)R|k)f&$SV]dCwVh Qe*8(E!K.h}TCZsvod+C?4`d vm\؄>owgE~# ɟ~BѶ5R* &LR_>GY=OAx :#VC9?O{nnIee([BlC] c;6@?P<GswhAN#V?4xG>ϳGyl/wQ+3#2Pza+"38?Ulz 47X#2wΔG#V#_#2|-?Gd:kI ݾ&(*Mcz"^/#2gDhB~^JHdy XdxM''x?igfI?8"!#V. ݹ;Ӝ?q@'w@#ډA ]g`יӞv#2pgT1~A6 IVw crh>쓻>X tÆ#eoȔE#2l7Pn!V [%b؊҉R~W!P @?r 9#2!P$'۽l7|ݬ#22H} uO#2q!]$ #22~p6EQD1EA7)$t`M:'|/#2.#GDI>f|p_QuS<$U4̯#2X~b+R_m3^׎E_yZ]V .T+#E`#VӫC9D}dLvOcD܇Q?8;G*#_ҁ(/_} OKXߗ֯sP~>h3;jFx?x=eD |F~Ѱ)O@,Stmlчf+OplZ6@B߂Ox#2_vO&'diS饭 so;I"$NsY_R _1BaE>({>;y){r>׹'t"|h*@ SX#V)h7MCQQAN'*J-@`,^F; F#C ,-CDih2hjڈ&`o70`{(%^o)F!͓#r;Ishsْ*< xOϬEqDqp|Ƶ/)?x-O҆/%`owow#VG"3 #_-HC&y$c,Wo~LcM^ODů#1WzWl~IbA#2"Nn#V'>xo߉ץ'bpY\JA@ðTz_5 ,0#_-#_'3#0.dz: /覛oMPw4_ކG=~#Vk94Mh񥦍3+%:z' }l?d&#_iAkfC~Kސ4#2]ѻ.mD1-mDHKyL#2B#2I+#V:BO^:_xnrAtddi(eU/vs9g6f,>Q5+QbpQ#_J G#_b0R$ytv{CF(G@\N{у@Q%p6! gߝ]5&hDO,d#VR@4Vĉ;vQI[)%#28Dq}N!G{ {t#_P^GJJAJ:Rj(5I]@r&7(g;c\HRKF ӟ#2#V&kN^x=_\`Og\tOô#Vr{֏|gpڎȯGv!2%{GJ^jXL%V8=٤1笳BPD#̇$!zb?=Oi+v8X^p.U _Ezâ=~s3M`yܾ?Sp#Ђz{pq >Hflhfd|ڹV$f"Z"4?1Qۡ.4_#2#Vq&+\ct_w֪#u&mg66̘7Zs>pr$%l#_UZelRT4qDu@^ʁZ=kًlbB0}xmlkK_-ί˟kKwAEby$JH [0f;00gǀ壴AmI,xw."7X4'&ID;ϻ !=y_& 5}G{Je$%؊ީviD%wIM@8+0?-=0} Egx#8#2w葨)#5niv0Z_H:f5Sڊ']vƋW< ;@`yMU5PY`V(~2v![@CJRn{5_Gy|xKc#V`8 #V60,ā^teǼQf?9}*mYI<ΞCN2jQ$ #_h#V!ڇêg$5iۧu#2(Nd#V#_XRcH"g /Q2so듮Ӝ ; v mmY!l30WM$^=wd|SgPHOHCK)X`dn`GsJ~b{,` w@tV;#2(od>vDNko7"`!܇lO6̞WBm}s8U{;@"Hȿ,700O$}%{;QgX((:yԿ>C2.m~ls|ڟ78q^PS>DTGW5I#2 b\ c0R]G#4nG|$4C1zjGiILo-LͲF""iLT6>I/MX&5Ne_UeNBoFqϣ#_edB(&nk@aiôKvWa4bsgm8{:k"ӭG3t cm}Tq_m ٻͫNZts Q3aǶ_O^@X{s#2T'ߘL$ @7%=##V=#6~'r+X -!4Ǭ~#2ǘే9/=ϻm,#nлB!K=ҡOՇI|<@nО@k7_/z?cK &^ZC9ɱZ};nw%L1M0 hA4>p? @kK$ ,xqlOPє/(IA삩]IJ:pi#V1HjFJs#2 VsBHCԃy?/ĮT#V"M1-Z=h#V#1F9,dR)4ZwA݃`rD:),hUDXv%\7rL&[Wyi{ܹ_>n||W;%N+{{9bQ`X&JR$;#d.#_5*U"н/o q'…[fǠ#_n¯v ?wuC#Vŭ5ڻΒJaE'wPM+S]Fw+X0UﻓbP?V&#?9(~#2~X6Y8Wo۾^_DlTlcg>=Ϗǒgv&xoUU~}T_'JN#Vy#2(e gPG̅N,KPEv#1h61WvaёU.$Lvc#2$/:#283?~7t=a4Hj$UBe@f&-(ACGBqt4X?[F%J9fFdJ\ٹ#2P=$$#XD)lcUI?jOC:P|5+fRSL89r|M ~ۭ Ѱ)˕ʺ[#2V?Z * ۇf~?>O#_!,"j><e#2VYӟzUVh?b;`߻_?UfaH9O:I̶Wo $2Z-7Ms#O[fSb?!X_a1}@#C?=~H3E}ۯ5|1b)Y2A,:$d~Q$m8l""77T)BdKNq]J.2m,$XߟqfBXk8P+¯4^#2:[2Q Bxڨob2{iCc~#2{v=ڻx4 ~ױ#2aw1E+F>S=d?Vϭ=ѓpհgUsR"MJ %~K՛j 9#_eB *zD`9iZLzBfIJb$T֓jAB_#25`ČRlF /ݞ#_'JQEE*%;asPl!~SZҜCg'iŘ.YwaYY.ībtL̴0Yؿo>rs^UZ^YaªZ=vڢw2ǟ\s~0u8I1$K@EK{: :wκ_7kV^'L'gϼB٤;2ZG:7mW9_vⵊd8*k5rj()*bQ OWgL|6 +t~$_V??֕q HYs! qbCN?#Vd̝"r#_7#Vi/NDX,B9i|(B=|gMq,a{;ÃxPo7#2|Ki>1hSKI _}*jiiaԈlۦ] p{5>6eآ10 ;t7A8#2xg4 PZ9/ݝDС=>FF011`h@јB\i<'Goda4:z3U2!:^ʦ,P#|KLtyosqUQZC,xח=eBp pL0d&#V8f, Jlfl7#2W'lA\4O.d6誨&0ybz!zKy%N7{\WvhE~E'(x( >1 U?F/0`%?&}xSX{otX4_ͼG9:N>6yL{Z-'(%NLJNPمeHkcF&Z@9(#V?l~Jm%~H&nK#4S CO<]VYFki}i?HWcL ꪱ#VkVT׵m{*V:4"$XMy](M;#_#V`̯AKq#_O3*&X`NAmHh hj-8)PBBC|S-bZH?bOT#_uOtu4"nTx>n'II$~Q,=Ϳ#2MwI(k.^#2k||dh+9;j5#_4ALT$9Pː&gΎbuXD"uG>Sө;#Vf甲&63`C[Ԧ"p8i=O܆#_Jax#_a-VT#_9`^.Kgh`_;t%Oa .a/ 1hIPL9p7!Yit#23oWcʛVU?㗼2Pó!C:aؖ823CQ4|89nU`˰yzn"(?eːCBG&#V#_#8CaYvճ#VtR)U'dhd1(h+=FBl"m"L!4*l!3lp4[ũe*Mr~F {_K;b'3ԮJCH]0扃y34frN}CYLCg!:gyaq$`H${7dzI4s,NWSXɈh1Uj+ 0v/gw``55`*-%eyrls e$xL^fvpB8g8)k$YH<(Ǐ8^zQ9nvYcB[zK|25B.yXVK6Sg\iHE&=vCc}oN"&7u۵g:ZϜޱ#VV%Gz/ym`MtgfXVP4g"^Pv< 62!|:ET%ϊ10.qNwB@ 3KyZmU (Գf!ڴUcӑ#<ջ{O58҄/A|{7~wNttvi#C*o߂>`uk>0Mی{;SlKq2^r Dӊzp΀gTxV`z1#V#Vv,9ydXesV>Q(M' N5,rdq 鷺*c1  <#2tJ5 ɄWeg$q75~ww.EbcE@h#V"InɮHL.XHÁa'xXhr TT0y%Bi{gDk2ZZjd3ܜh^{)#_`ڑ֢= 3uup|G<& 3Bxg<@;`,V9{22ŦN/n$d/Mv}e@'SrUF,Y,tn!TM#SalHYdY `4ɝ(pSH#."|#V+44ALymrLgPHDam]AZ}>aΦ"47:qʑ:ƨ؃8#VTIΡP C#2 <##_Áw/p|I> uNªz;(+YmO3{~q87:M}u,O=nwFNCz{葶0Gn8K>[10=e65oUz]Ɯ*jQP$;3la1(kQS4"R !.ʗ̎MZopCra4 l$0&A=%3X~󵛲,93atNWyYWrDBD OӒ%&bv693|;|[;}85n '#yvyrʹYO&w;8z4ùzT6IѮ*o2ENZex8oΆM}#_n+.Wns;"R6CG>\6v)r6w^CJ㡷Ͼ_g'f%މ/pmP \54b'(Huzy2"#vV{M3TJ ?0(?vĚRb#2#2I@iR0Of޶yYaf }17wXy#_̌lv3P J%d6Li1PI` zi"Xo<|E W#/9l{Ra+^6q 샴j_~?&#Vs,~yOw_Lу;ob OP9#_fa$͇/Cm2C wt#V̜e8dDa!DpA@7"xc~V0*rj|sǽjSkQRP(ӆ(cQzH(:Jtgm\S)~]M.ٔ[3P.?엻q}_:)B>t:ȞuHsC}⤤!BDXp;(q L^Fn,bT fGl|1` }kn%FTLa Htczt Ý24REUTUlSOï!]$f~*4J0T='Věܺ#2@7bݥ=}˧RPψ/~?+AQ"I0A1MIIɑ2XrD#2|t~m`(c5{.9#2m!L?F00Ӏtv&JP$a }0?%\sOmF?#_ O3W΍Qġ)9q9EhM@"vIQF#_ |=<< 䬴0'mlXjٲv0i~#_XJ&G&/RЂd#2zϘ>c/<]Я젡Ūm3C d 0 &cVmϫHs_\r;%&ԄB(Ɓ0/cbՄT&&o.gf@%HX[c&Mge<4%([uNfNPEZ)SXuƁq6| Z:Ha]crr/!Lw:nv3o8z'ꓟ>Dlf ~:?=CNEoib8QZO25H,NJX&_t+=h.nQ7,Kܻ=o$`{N_\"C#Bw(%#2o#2^skNA~GUbr0 3 +*=١|MHOK36,Q9:nCʼ<}_8f(jjdn#VE.>W?IFv$hMߟhàHPB#2Д.gs} (&РJ(th!)RaWqQ栦ߎJ?:**#_VM\IA*f%h_W+aiE1#2QdU T$`p+/gJ-$!q+Z}Y2\ 8>&rdVD*66iL6%L-V3#V7܆fRyFjWX8@mtDTRƺ#V,sQLhiqM3 L &A@D$J"vv9AF#VP- !-lM$uOլ)#_Zu+rQXmv&$22(Q"G5&똣%(c#F Rh} ;0U6Cψ{Ü(a;SQMEIUԧ|Pp=(sGq#V"z>#2#ES#^;($)"&=,efDz?>$>oNF#V#_{/tڀzd0j}n4(,QM$ǒM@|CtE컶)A8IDm#2yy0%Awv@c=.E F-n#V>kv*h6/y3G.?,r.|ֻ:$*"PJ$lpp|* _a~W"&iHQj3Uœ#_!I#2 Cp7PQ3hDՒ#_z8&&jg]8Fqӈz3&#ܰӏTzMLʼL$#)!0ǭ,1@v*6L=Ҵ:h.5sXt9!ؐ@?Kik%:RDx#*Ј_n}9l-#VX}4\Ĕn0Ğ(~}!e *C!ddz~LUndl>z_)3X!}(VѴmL I*EH&1{l8԰ 3=gمGSu#2O0ҡC0{}Vpy2H|' /m]\pY7 F\5԰;\Iϴ]4sM''bZ;Lm#V(~?EsF:{.I4#_c~pŢ~!\#2|;,Ncl !B!@x쐡V!h.SM3mvRP3/͏$&+b#CZI9&cY6D♵tmclr?>Z|w#V/#VNLu>0>D"3ZJ8#qdvcrrf"\G_"[6^YT-7D3z8]*Iyo~rǏ${4ᘊc%YsG䯒o3e۹:N`VE#AeHV =ctmITjL#VNjQMmZXصL5EdѨƊmFբِ#6hMJl[VԛI-X6[5%E6USj,Z1llQlh2!?Gq{%w~gZ0۞7\~QC0dH2/2Oyd6dӖ; sEÃpY#V1#[hHָ u6%x6(qU4$CRjF)#P@c=f8N*dHl*y~c9:C 籇d 7WiI ?+G}nP7$œq١ɢ؊LqNlo=,]ʹcm]621mðppLx)o> 5o] rͭF#Vm2!%ͺz,S2EkX4fFfmuOM$c-$֩t4"8s#V6>A jqcJX52ݐrOC=E$U~"A"JQ#_"U^<1D#V@#2ryz=d2ӻ1zNHlrVdhB2IA5ŶuVQkySBݣL5\eXk@)CC&P&u[^œm˨j-!L6aT%V4;#@q7al`w^"VFa,NăxʚB??g#V]N T#2!%]1QBbQ#V>}g}Fz hjH4UR,lQ&֮mͮm\jJ-j*JcI sGgz j#2(zi#_R&&*RMRQ5m,H)€|Q|6gW^{PBj#Vx#_5#_PpyS#ybTә!C!JqH0'pئIX#2C@xD@$LP<\ӓ9k#Vzڴt0_wzdƈ̧4L_o Fo./%#V-^9Uϖe*X-c3#pvH}%S\H8F#_jb#_1Vv/] #2Ą:ov Ta#2@ϴѶ`Ѽ;$0e8k_B#2@T'gm:}Z(݉,`sV~ OdK>rx//N'|==|?bN KC `Hwn١=ޏ#VYlUrbIxIe?>BUwnWōXh'g=R#_y+]0+[[ץSʠ΂ ;00ܦ#V{nEh3q5lЗ OԔ"'jga>ς?vOCv~}@I b|\@#2Ѩ#2kX<\Ï.Ҵ>;Ц)o{.~Oi;0TPQ"f >5;'u`I^7޻NSH#W6%l#VTFBO$cyQ{ڇpIc1 q}K^FxM0vzr9>GOU?#2 `L@r{sbMUbDAR?s{#20wId֐h&Ȣm!h%97[©祗]52"ZFb|I BĠi1TLP,V2хso!Ze)Aq]*W'PsX"t#_8d֩%=*#VՄdJN _l`Vm\4ZfJEDJ T7Pִ|4Ao1J?$9.A#2t0uEpS'^?БC|n#2,.?k{m b27_xDX49 wb26Kc[BXp-(#_ZIH`GC{BΉR(,O |#2+lN@C#_|-J;+Tnq^xbh|xÕ3ӕYB}9x2I(;0zzzb#2>^-'d.KAQԎB#_@4H Uͬ[QyJt".!#_R^7uy.F~2 *)7xfu!Šf>@)6 !vEȑyoP=k|[gv랦N(1;d8aҘNkc Ml;y]#VXȼ6<}4 fͦaȷ\W/S^r v.#5[) iJQx5gK-ȇ9T!(ZV7gsc6'_Yå.r&V߃#__'#2w9-qo'z꣝Üqf dIƧxkaz[57@ Xs˭#V&B3rz6nrȎ{ScYdecOill_K|!ôudu,8%_ݸl/+"y#VwXhWm7'}i~lj6 ]#2A PP3ڦ4pyǕw/bl ;efNVtK1/c{2u#3Ûvˠ72?ہUBY&T:5폍βH<}w 4f#V-9F;:HHTA) 0xm/t7f#VHE#vA/~]{68rD>|7¶N?&]p%{s~lvXy)32h)G)_ݴΧQo?ݕ4dl3U 1HWWF;Xp$Rtm+L#/=n&vOVi B`ӵH_(xes=åt1UV"&1<ۃ05>Lq2ӎ6dNeD,Rܮ{6o5vKrHJ\1ǔ`Ȉ޲^k6cm.s43d\Guv&TdK@–;ZY9Ys4C[0*0#_!Vֵ'.aR{י鋁4 Aq#_raH='.!#V#2AHQr%*9퍈1ִ,q6;PئQQXbM4LQ(#_er%a v{ck>ݡN@Æ4 hjjOo:#=60UFVeV8Vri]%(y8VJO)>5=dP靇ՏW|v>>ߠ#2i!~mlp#_vdw0]z#VX]]9\R^z:kpYfr$0D]-q!FH4UkeG4.ߤFU4pb_s"A/{=͐ߒB_g-B &2#V00:||mwvM^^4Tt]\stڹ}zh`enO@ mhby3'3V\bTAB8l(58TM0,8Y*T2i<$v#_Wo8櫧_#ih6ѲjQ2byfΡ}&5Q#_#8}نVN#_\Dz/NASS0 #ԇnM:!Zwqu[cє|hB@ىD]"bzO2|.T!b;kw-;ԿHA}q`>axUHJ/8ưN0 bEC!͈7Z[9ق& T]-Ir6Y.aXR@9p#V5&Pҕ#?bЦBVv>m)yw;Fngl:Ց,U:53b&#VD$l\L,*CZ [QF* l~qy")Ɂ;؏[Sbl?Q#UUX=!j(l&(͗1MDó#2RI{3uJXA2#V5PƆ5աuwNAH͢?.#_Lt2c10!`(A ABn B!]=V01WDv x H `0qZs/k'(|v.-hayBiaW~:q[J4=L4B3(A>v_I lPGDGQ-JNHLQd 5:݁lTQ:;N0O[W,dX[R#5LLqI.Ѧ(8(HP9D ʸ¤T3*h#Vl?3SKo#V5EO.`_&VEF1(AI^*i(5Z G5#_ LÆ4+ndu!&>EҌd#Vw])c_f{` c#_\KM9i(Hd%4D\f$Ir̈FJ(1,Xf#V9*7d 2iA2-B+Gw`D8i#OzoEyabH+Ǯь>JIfжzuH/Ρ˽: kkc0~;L1xBC2 VYwx q 3< Q1+NgSn}dnB $NJ>SVww]\,KĒ(5 ^r(A22[]#BtC4ʘ.F'ˣ_v|;b@ ,'#2&g2MFpɪ @qZ $6dh49L 9ۃ")K>!ZSZ(SpF Jjhj@Lb#VmA#_`[4-݁г"@0BK/Q 4fWm?N8y]I8o2reCzҹ-_nwwѵo^rVj2KrVcPB'>3&C4o\6#_YP$qz#2& `@o2 r\"DC Y ZG#7}ef #9{ٵl#m &PֶQFbUng3n*Cd%QFd1hk 䡄MR>l LP7 34#r_i/ېH!3eԢ gh35wR_Tfu |ؗN9"l}%@Ӛjfkl`RoѼVMc%L}AAnRc#_sxD톢AӍ'a&i0hD{ar33yk˳wP "}͚!}=:kP6!2b #2#"p- iB@=\##V, ( "BZG%o%#VI#2ĩPr~M@41kISB8@ 7)1@aP;j5SDv@%Bi!]ε/.C3SeMu#2|vt+atBpC9L=ݿqSh˪Y/!$:(zej21._f`LB&(PPR^1!CۛKbAkن&BEW6MmVǶ}E+ ]E#2Aj@$ߎ!#_Hb2(Ƃ(s҆S&殒"Kc1Mo7]ѩsku#kpQvswv8Jz(ҒRrR Tv2i@3lD#2T#_TRIZ02#2WZRjMJ͵C~gҚS+yHeV+WF|DrT #_#2DKozZd(d,{4_0Cu0ZUDQ,s ~#wH,!@J'{fBI~[`)(y<+ c.h8980!$#YPg&|AU c|oh{tG4H`~X]fhn[]uJZǔR5j6#VXƘlt#V+.+fb#p@ƌp0'i [gC=G*zA-T< BQ4l[5$L#7N(9[jQ(6lF,RRCDC/K˳yt=?ZO_m!U4]zJt K%3&*{3jLuGI8CiB0At.'#VI=J=MSX0+ҘmBfCh5-#fѴRq.02#VvAa0vp7Qٞ)÷cDNC#_Y!5v,]xfjH(|0( "fb߄Ksh+7L@=Kwyڜ"֠|i;lp~SSJw:*vFX]t{ϟT,a`qPZ#_vZ%Əԅ,,֧6GZ,{'t,cV鍻Koy̜kGT`Q[Z #_b [<5sMDV2ΫG2Cw9j%|VW9[tŏw#@2hցxHR>P22Qkѿ-CfT#VGLl8#_R!ece4@Pid,LU#_Ct >Hz>ڨv Kr#2mhzn92(6z@GJ F='?~}܃?fӦne2CKw71EKf-4HPH])xxŅ+gNf_P4OAت F1V9'ӤZTjzs0V!ޣ} ɉƴf$vDěm6gaA #VDkLeQ)Z[Taqq#WwL)p}ɼnվr5:R<}yĩvڝg#_b"#_nn2[*CB:C:D^gJ 9rԨ:6Cc.<^_y3&΅.눪kyRxR$B/#2dT' /7b>:XFDQ>T|}j|#V埻mD5kA(DP/Rge٢HC芮#V!M2Q(pbVST -ō Htx3C9Y&[LV)hPgye†1K[N1Ťx;N~%Z*c%Sδʦ^5ƛc1ȑDF-\sQI6NIp8*Phrپi>&Ǥ-,: jA#V4[9YX|hg/<,JR##_)j8e}#VOaHO6ଐ *uҐ]Ku:5l[D98ݎ>=G-4lCVQ(.8 D w$;O Pid#hx8w.l"ǽscl (PP: FSaò{#_-W9DthL-4F`3et2:,7wv5{#bzQx܆w#]\G |'#_L錹Xh7gvb#58*U8ss~;Cg&+J_ =p@FVS8~#țnx}7يbF4\#duUY"3v+[=@J" %A(LivlyQRD:#V;Q#Vg0DeעvӴ5wM ͵;aMߪ ޛext^fGH#Wg0 t#x$lpN[&~R[ɁĜ.PԍT-#VYCDyDOIvu*)x~d$1Š|]3q/#V6zk!%֩FN58JBi4-=/=53]H&̛9R,'XF)0k"vqqE3Jԩ#2T46#MgNڄȗ #X6<b0l9#VkDmۧa؞ZtV3-&83Z541#Vh79zR`sw#V&6cf0 AM\y>αad&4p㾐#RZIo ߖ'v#7ДyE 8tf (NٴFk]77Kc:KzCNh䓹G#_5:]TVA}״ߡfcA#VcW&#`Y-g|,u۳#_{S1 m%m/P?#2̼#Ix$ #V1)Ah;nwewMD*  G!rnA6! y|oFi  1g Y&8}P>"'#Vy)TcI{-6 6iA5UUWv6uً[Mp6㍾eD!PgD>8Ļwlx"2acAɷB{yquU %6s5$ワR2,R9=@a.ɱòY&8ôjdmNÿ.ԒaHi4QZƬaL6Cn5mpJC\®Fgn!;3i\˺Pb 5wCjv8&7vO[YLdK6Vm::C";'M)I4Ffɑ0piNZ1#7#2t7dܾi;3$$LikAQU2!)O:0w~i0.⼓Z#CmŘU.6ţ=dUfˆ2 l뢻_ekr{KdFPa>6`Rպ@DF;lUؾ1bae[RUJdwyXch{⇄959fҺV4. q^v>$* iԳTIofQ<4Hʠtv:2ISS93RݵNڿ>8qңwь.F3{QXFʗ1Utyxؽ6Sמ5vst.\-pxSl&H:oQȁ=Fju4i=10֣6kՀ[0BMX Ei\c`Ix8Î/za%<#2zdԆB'Řޠk5tBPgT$ 5_ #V ,{ޢtaàɲq4#_$}}'](T3oj K58YTwxٽr6`'T]JZ)`[ޱZɻb[VpmPS2J!ĕ^VX.Z-1Z'5MHr˭(˖wNʒmʚ98;:a.g3tR4V`˸Y^E4쑧:^ZutUfM$"򤷯M*uS3#2vcg\uPוdiÃP+y8IEc!&pBBD!2T\L%;HDY N4 vkᶛYRf jAl4bqkZ4]{*\H)V7VY=ӡsT8MAZN8uޜNg{]G#VY8ddun#h'(ټ#_"`MQx|VҽCbۜ2':ӺK].Xv,I˛"0z::*.T^ hxbFka*4d};x_ 1Tbp6c N;$>Y>vR%Eْ0֒i3#Vzfsj!P FR^l+֞BaW#_8UA$[վk%L%R[:|#_b U r7O1*vxQuf!$1iяa6"5( 3P4pD} "1\u0aGLrWUZKFr.\LT%W umwτM#;57<(]}9ߎMa8R'ù&&]2qɣX:”üj6 ܃pWPч^&fsOf]w7&MEez,NIHD>_Y/JS)|i=a)8[통0v1;,&#V]pheOyR#u j*Q 6RLEԧYAqbo\$fgmq9$h]kMYFm~t#2eb; 3B`^FK#2g9<+Fݏ7#_ݥ(p1&յlJYHl76|D yӎT0eUaTL&!92!&iA'.C 傁m#VLθxw:'$$9; 'j888I/LY"ɜt.[hL3B,q˕ɇtAMX5dY%!R}ܔRO#WˌF.@&4Cbf̤D Q a7" hI@#VI{L0p`IhSj銀/Z))E]*ӓ%-l6ҏn/4jɚ$1jFM#mN[8Z2s|O\ʹw]ɚa& sBj!YD;mܥ7zvExK(ؒ:) T#n\εfSabTqU3=tarmu]jH;q6ӵ[ebl7[!nXMCRdo->_wde":_XHI&jBPO3Mb$+)"zBu5#2#_gNLN5Ĺʽp8F0hXw9ԶaTD8m*#ɦlAXC.'Й߳p!a7fBjV$H͍.E"t2 `vj#_?g1&#h=iSzw1Oq-.DĨdPQJYd%w!i.S,f:@m {l{lr*$r=n)F?#*0T$V|PU0QS)7;Oz NNRPM#&#V4*Ă6MS11cF0,WYhrR(H#V Bb€@V{4#_nsNJAw.D9#2r7-ܗ@&wEqgb|"6%(懌|(YOFeƸ!)"aIJ)vr<-*Xb7#_HG(|×<ohUy 1G#Ã)3';hhwA) gLAbnEBEF]IqES!:v(~fq=;\;v[פ}tuEy\CDD˹)bH̐*F6mi4L2nHf֝or\%r9ُp;{pxC䶼&aǥV'8nrt6vݭ=&#7S#Ԝ>)AGr@Hw[ue-;Tnpt;#Vao"l7`0#_EV">dl+jM:8c2H#2o[a4D],me"g6AmMd#_Nr,al9ݢxUHN93zq1`. `)#2Y-i\كFSS.R(xܚ1);aT %:¸N]s^AګNB'z,{BGN#2\$( Oף8Xy]|bDl#2d/Ŕ8Pox& $" )vX!D$MQhB&-&h4IE2 D0TsmdjGcwSwHWј'\ǰFo#_%)#2@f6{1&~P5 I{NN^q-UaD;kCAjĊD-*A(u[8Jʭa=GJ *kƫX\l̄1v Li8šRbc8i1&Hʝ;ԏAVc+H@`kF#2wNN) "I dg>W#_(׎E'O|@!DTHVgzi;ڢ3X#V4#V@n{geBV40cRk2QԚ&f*˲E/fCrDQ7!;  RhUCMASGpDQ5  GG[*f(fz2S.A@] B%0UZ5o#_~-_`je毩eWwTj̵ͩk=wEjH5`VڒlChUci-C$d T4&49F"RӳhvRXٿꇮ(V)x@׳dYDҍ0Y4xcٓpud\6KY;XnIF :Hҧ(u&'H8ov`zKlтZ2J)0$Pj9]{{|׷:5t-ܶddG24Pђk#2?<3`#1TQ#P"##_OD-$vi 1|FR)1;DN_/9q3 >aA zZf)"-[6 ccŔ &N{|#_|sQ(Ii*-s''6qa'%,[&3Y:~նC*-v:#V-l...n%!q;˺|8/$3%4з6S{f+L:؎]`ŭ3f"S@DR\FD5BL؜ތ6iv¤ɄXf#2IpjyIuk 풶 [$b!yq1$2+[?2*-;K䐋LFZSI`E! ă5fA@`0.a4hai}ʆHS&jpCmBUnf1a׉5.]]6F쭱3pu2墠SR)#2_\.k`^tlȳpM#/)y0=mO;P$re9-e@qY+)l#_3S#_@֘*֥dETQ0(##_pL'wh#2f@O`?ӝ8)@(hgFd 8n,"M0n;t|fqN«(N A_|9 5T[64C#V25E#Vv}M"oDA)jUh.6E!⣱gq?\J1j*5hQV*$"mc0,,6jFLZR̝Vo&`I3]~\nva,D\U٤>nP>^[ؙ̥Km&B(ƖՊ{ݳ\Jug1u&ItrRYP+eMZ.wZiin:<4\e : a7NyJS^^IRpq93N.Vdpje1/ 1iyt]ck{zN옫xytN@#_(nҬb0]K19,m#{|Un̑Sv60gΡ7,I?0*櫉g{5vܻU%P91$@u.sNڛD>⹍$3w 83[Lȵ\e.biZ+9~*/9x$x߈0d&#Va)0RV]7lg8򜫧l'B;Eqw;\sFY ޷ (ux7jzw״Q戣'5jg{-`ʹfn#_lPҮ̩#IB޸9sL*accĄAHvC#V\uMǨgqe@.G 2U\tҸnl|3+K(t`|{aN*t饅us"sAwRIF\kSOc~\JoJ2\w}Gp>d3Jyߝ՘n&궃[g1Ηۻ x.#V-5Hh*%Ӄ CFqj2wwFA! ⓶;wX8凖:*Ūū:7ҡA/)339&7qJ2eŐd1&b[| EB"#_iha#VzD\1}1,\:Hk$DwК!M{#_X"i|!Gzu(rHÚlnZ@0A)%a! e@7 'X n?[|by +("GJ66mGʹ9CeUoQ< FCiq#2r%?E;n.0AՉ#I#2hhCO9CarI0hm#4 4=MgF6 ʔBcE@c"pE4ELMe`E!6aS$u18mM鉋,#8x˚$IJuE1UmmCDcz%DV)+m#_i-йQ-4nHsE-*ql#_9d>UDM %KD29"߮taqLbUB(`GO@(eZMTi$5)I-5XVljfM$3#_C ($@>F1#2S ( Qj["[$3_9yS#_`ie(eM2p*J74Ъ_IT*Lㇰ.ߠ2dCg0Y*LiJ:_gg#&qR#_v[$0憔PI6($*y } T&!"aeAH&R16\y58G OzO$ӄ9ߤ9Od) Xa-`d_ vl4q7 0ZE[`W 2ZAJ X *H}++RS?f 0#"B G}_3Oٱtᘪ~?\_Ǐĵ@&p.1$:xp_?+`LE>Ö wa)`3t'1P|S!}N^6zsͤ{j'$orPZMf_ԓ {}n HX&a6n#2[Va$EA8NF\Βnͫr{zjvU'2sj{l`f&`Lf9Fƥۜk.]q+lgOw[ݵvtָ,`p 1;DCV3h6.`DJPPw!dU|vcv 807H6)U-qmw]Z.{;cB5ahм`IY,X V \ N3), aFa&kRjLܪuwEv aH"1:mḏ|h!lxma[XAӶcH#"Ŋ1q0ك#V+9|emRfƤt.qqZ*7q2t)BpRd e%qAnfvWI׼RƓLK*f)]B SuͅpW롖_9Uk!G,G_?͹96~ *xo"%[ч8߉Ҁc >soFAF1.GhTTB, *=qҲ2PBT'nXJmY F$"H#_YJwȆQE""(&@lJHG%`BcJd#_%+FqHD$GA b^h[s5YZEi5&ŋJo[-)6KjYR5bѥJmۂ VR$(hlh+$60"̲H6f6,fA0#,J[&ʋ$ld(j4MJQLƂŬEFPTi#V%hPMI65f#2 Д̤S$&mF5E&DmX&IUVY-RL (#2","JҲT2$$Q%)FeP@CKH:`FsjR^ҵrADyqH99Pі#2#_hN #2Oc &$YxXVK= NzbO67(~+8(]Obt]`Nƀ fϖf('o*^^vC[=B_dQ@ r>fy$6 Ѻq܂>S9'sgSYR&yvyMz"bD'_\^1J7iET|g|{CΊkܨeDhOp0B%,-Qu,g2>`S)1AKSWIL$2:Pי$#V`z}GA𾟔n]?ߞJ#_02Zd'|w>]а@{CH*lJxLUQ~ ]']Ll_IUe@0X hp $ք&tߙ޺G;XLT@&JBE#wietFzT#_#VGCq߮L̒n\YcT&4&~z4FTVeEOg VgApmA7IoHlOB$9& dm#_9R~k|Z3$n#2ujeD{<4&c*;L^#V M ǐ"ДH0ՋN#2m @#218Z4vD>#V+Dm#2|Mt4 J2RpXA{wS\էb#_Z1 xz-8c5m3mU0ֳ3': $Jt c H2nA,:Yw3Z>wމ#V2IGo|~#V}ݙ#V徇%0~A2|g2'e=)(O0dR$QynjD~~% )8~@2#23pDU?` jyfT(:(0w 3mN)Z''%~Ǭ=}IK*h>,N)<cYJ"J6#Vd&`{>NӖ ksy%JrQmk*U2%ZSld&f;àp@ZHAѼ$z?(#Vw=pxB޿J?fk4*p -|.)v[A?n֪X8_Cuպ7f1zNuBb!kh b5˼A_c#_N?kU qP,O5M_48aaS2)}w3H)c{[36e6ѐFr/7 Ou>lU #__\sNSāQ4Uغ[miM5܍Һ+́((Y8}.;Wr ||,&C\< Qtң&Z`Y*_n(v\xsJ=rWp ouaN[r@2 \QUb#2v%&L}Y}U-U4:k)#2T8.;&xICO^?PgMSs[Gq#mW;Ǫ;{p}1rq8OÎgï?i_ktQ|fU|{'ub{8PYޅ&h.ealVOVZ,R+llÛf}b<fjX:)R0a#2@N[w/͗<ӻ\wkoL|]w\W!->RD(,vk!H!GۛuΖȋ5GDdim6")'rҾ.`L18f&`XR ħDsj6XؓP*4liXiӕHs3dV6UuY%{DCu"Cs(u@팑ֱ'{#_#2JF؍Tq-&@#_'Y(q"m#V KKh!k!qI̪Cxe3{pEI0B ԲT{6Hp'ǿSyY ipCBJ4bV"^!HhKFPbX~N(1)^BFGv=s1%]{Y|~)qnQj+kenlhV6D[F5FkE+FM5!ƨFj#s5EبcUmEUƣYYh"U$ڮZ-bi\8nj6Tl"14g]nk9DEX6ɫIbwZJ-EjKcQl&[DlljѠص&֌hب1Y$T&Z4j鹱Zh)MZG*j%T#28ݝ~s߱@1jvy$7z4q0Ap{>zDi%)6ѵ[ #_OhiN^C2#2~^S\ҧATc؟Hk0>9M4L "~Xj*Ik(.F,I36h $#2#VlE㼈wN]|G2&"j0ᇑd^R0Rt݃uniosb nVzGv䢊C*#%b#Fi0bCJ4LIJER+"(mk##2hHB=Gk~yd; #_#2aވ#_fcd@03ש&r)+p#_ΡA{#VyK]ub Slm 0@ }` H~BmBrBi߯7t{6*(%PGT@<^Rڜbo^!($H#V":ߺ3@ӀnGVt +Cpe 8)#_g,ٛJ|MnNd(G]C ~|C{m'qsZ07!C<#ASy *̥}I3-Wu6g֟%MO0%{P@ *@ &H#2&dR IQ\% a;f"5&JB$@(+0SF~ Z@ړ//}D.A\J9Z7S'MCL#_QzIqڽLښjJ*Z{{6OcAGS<1Xf趉U𲾲 øl!X 0Nr',`cvF܃ .I:TG>, s`_U>.%C2e"zRV<0sIs$ZsYs8!6^aF!pUR,MYL#_LZ)܍#_lm!Fo##V.0iSP&H; 5Xc#V#_2?Q*#VP5.F3Sn"`P"5i8y䔊hBqA9\uiW39ͽ #V blR#_2dY>!ǭdtƀUX.k #2|o4GQXƅ8-#V1F1ǬdAQ8ͺ7WW@#VJ#2vCh6&AHL5%y֯-DcZ)oT˄ᤋa#" mPMB.T5,JdՄ-Ғ)n5ܛ}Ҿ-x@mdLJ4VXWȻ, tYjul$&^ھ>#&.|Wyj岽cQ6±=+@VnJ;\剪MDCo12@Յ$dު&mY4tj&J nA8tjֹa9DHrXfbdqpEԁQ܄Ko5Ү'#2װ1AޣX`bֳO a_*)'ewZ\Y$Dp23p>/(F6Ji9Q$ΐO;A?תC9o~8}kqFh62>7~M!9#>s#V#_cYg7O%Cl2 _X)* JL}oZoD>Cz Ϛ7G`6{GC"&)4V0h>O tP=vT*UYs8Q#`ZH*NP -;@Ɖ_j$KB4C7lJPNQrtu;a")Id3J\I6i6KRJ&җ7u68 2BsXBHj5<V jjP͍סj"t. $9X/ַE 8N}ŌjQCnm*uMB$J(==C@Qd3.#Va(⥈gLN8OJ%&1"ME(~j"R6/Ef!5aQGC{gN~q8O|rRPKZf\>1`"o5QÁGN_8#2b~X6N#VCI#_OUn ho̙$wqdZ^o[Y &*RҔh-h0t&&䘱$#2["y^Jb57n߸EkE$Q P-6{B>&4}X@{Y#2N~x)$'ڟn#2ؐݻf33<7GmcT{54]@K,[[#_Įn+l,<\eJJCm^%Ȟ1QΙ|i'sUU)]SÛ|^'-Fתف&l$|#2r4ftF>#V#0g;q#_fm8$x_1TJ2X}穒mg=)AK{`L'zT`;c߃ 3+!rDGO2~G~#2)UREP*PqT L0 }űp~ynrM|1PzUVzB\'ݞFf-4XІČ72K)6o] 2E; I2hTLΨS2~]I"ԙmAF,n"%~gJlMP5ء!"s,s2r N`)(p yfGd{?e|Wְ,E?\C'}k77okbkfu q;pq)% `S46HA1FلOO#?lc$'COiFٶhWvk4IX*J#$kQ#2D4 GH:R &cFB2i}#V>+"rd7䘁oA:B"]؂y*}M[bN\i}l>SfdݘdVV`-3`#2ݍNpƹ#2B#2]㖅@!ckw5M3>8#ZJl"Ѧ@ 8L݌'LB:#2*$k l]q I>:j’d!{0y.>e6}cale.'e,s^j>z$|Ez8^ʘ$zÅIb!49({#2>)R >èR8%#2دQ{si &$0,9{#28KC/W#!.OaLԉ(k#_1s,uYe`05 ɏTK0uc--#VMt*J/7DELm144n,FgC&DqS *>@Y#ol r@ͪ %ևt\vi0sI%)FJ~쬬ՃZ#V4XbP*^K!,$E53ĭ%6L5p&ij,@D(HkK+~^Y5JMy:J1]-QSV@ >PŢ┩9$Ǟ#V;[f+bD\(Ml#_CCeaZ/i1$*RĴ2?>&1&T:~-x-?s,ћMBoh;X|-;N!Ouj1$SOİ՛X4FbcNqNhAA,hrX7wȧwEzvT Y9aZF3HCӒ%v bon5$1_RbL-zM.#VC9q`,fH%bP4'bK1 !eR#oaʈܱR%1@-kR}9}ІĊ0@@"(4&Z9Vu#VApdD\76qaF$!6,aTؓQ3 FE- @;#_L?3@--Pk8v@ޥi"Q N;fjĒI(#Vh><NRj#VWC#_Hb3"#_ADKb`hBLeM)!$IA(U &dBh(94bP)%Rb% +V}=6'HQ1@C`'0_Gԛ=]7zAŔLKG(yl_h)ug\ZBHl@:, T%C@ CP0e{6RP율bvB(#V60#j5SFJ}/>vT%67"C_z'qXYkr pu٭FWcTI~.@dHji#Vf(#V;#V&Ȃ)0e}wgK׳2c1ؓ612*!#C6;=G8T1n>G-fÂ,yla6ɂɧM<ֳ|Ydڸv¡ Ql ~S@eʎ@ی_+SCH9:#2m7J/©.E3c{"z*fͩD-JDkYIո66#rwXEǶLN|<\ѧG.bT D©bAdrA1QPԌzn%ĥ45dEvYE\xl#2S թ3LΏIwg= 8_E60a`"D^mѷ;!$Ew\҃!#_j.)46FZޭ4̍Fq7d5enH/b+ՠ+)ZJ`2ZH9>XeE"Qdl2#_5n)-cmralv GNY#,LF#2r#V:66U:C]q,G̴J0 RƄl&6g7C#UH6+PҮ)i&H11,I38FuO=;TNa0O:)Cma'Ә]]Wk!s 50+L^k0L ݍSC!֣^qfddAr ؞33M#VKD崲0o+j-mB1`KV3#뮎#C:zP,AXnk#Ej#X6[{nHM}_V*+ZlЖp{x0$gPw1iM2v1frW<%a O*G}5>rTLKKbg~7utHΝ;?&|Sr 9`wYNM1Cd#2J@C }4h/(<4 8\G#2j]_JݕS4*-Im2XR%|}<"Fm>Qs7-F|o|koMn^IckZ(6suN\wsOm쳮Vn5-9CJRLrEF6XH1#V^߰#F.&L;gI%@g"FΊ\JrV[r=GO_#2n@E@זQp)>/𝡼. zȱB#_ Q1"83 q>PX~'fuӒso-a?BH4K@3nCLpbݸM`X-<%:^oc~5o5N4Avg#_Ѡ+L{^z׎WûFא"|ɖ^v@8zng)'c6揟fl;~@0hȳy.W򃓥t9ᗅ9( h-OFxgǿKn}zǼFSãi&Bˢ#VJ;tm x{T_˩0рs5lRq'Arkzl(b=8h:#2">ʓ󦬭Gj#_HBJHMЫ')[F#266_&/\+,CAc>+CL8OkU!!ɁTYÊlṁV"}V -('B"vje{kqqхmHzNE/]1,7ora']`#_G\c>: ̫JS#_pTew)88%ܷ$0 u\U"\9`>#23Q#2g!%DDR#VDIc% )ţIѼҾQ;:UEt #2Ǹ.€O.U@#mJCOK>hN#_xc Cw{vs $>2ʈdn8X @vMr1/c#(rvzuzm^r-i^Ж4\XFCq4/=g鉞JFl"+BaD*pB"#VP18&D-\SCtRAdo\J>z3&V!쇌conv-<1P* Y⁣@I'E>S C$|/19"蝹}UMACC#aݡCJupc:,;}ITiK#Vj#_[r`ш,i%(<$^MjO{ zԔuZEO)@ؾ(dUzcI_D>o59"m8pHrgwŠ.8MS/MZ#VDڷ M1̴Mb as%\ppL%%p1C*h$F$L %qq!@4`XY#_JX HqLL:Dy ^xܳ0}lFY.4&xYXl:A4@"p*Vբim&7Z$z>c>xORq#_#c*.APYC #VH$u;7v#Vg%HU4 G4vROFf=ZѢ3_*4VSs5?i\A@QUS!.(2ֵ-pܕ7YӫEpydiE;k)(aGVfae,/fgg³˽48j"G#Vl|b4I$fBa^[𬲆0Ɋ#Vk(2,%)3hSAV;8L4e#_Fx2-@Hll4MXH7p*,`ÚL45JI*j GfS5G3v#VcpL`A͖TKSby`h#2a˫P&]B׽ͫY6W':Ots"m'?G2Q5ߍ?6"ۻtEȄJ 3ۛLj1оM##VtE命Ϧs4:LC-jaϤIa6ʰwHy#VT5=ӄ=;KX6,UȚ LX>) 5ĀS#_mH}k*aa{q2,A͐8A?PB8ݷXܬӓv9E2@BB '{rS>UYs\ztHftQ#=ċx`ts#eH6 ?#9UD@!*Iܯ՘}UMpv9rDc,I~<RJa#2gͺX Ë23]pk#V#_y{ɒsm;pwGS)(`J#2#"f#2N(ɔAT5ccN{䝑1)ål0_] a~0,YQ%NaҀ14|tx4 ܾTz @X2LЦGTU3mT}sCLl\J *(k6AK,|єnρ2)$_LS$M{Hy9%ِEwS)FA=P)!+ƃpĮͳ Y$݁(Ԝ@%)-ԑnrQ5f4-R֫گ!4(4*ȴ& ;ɣ6yDD8MOZ#_K4J*(8BI!fBJ#_#V@1#_)#MTI$73-!jhaB9j~ĸϘ?xpO7,}3tuMu2ᰑ25y*VX@D5 46З,DbMm/ߜR,}\GZЛM;ذ4[|$FQ36TOD>FY4l6 Y<.&^9MFxmLY#2әEdœ$,I]md>|@ ꀁ|oԛk}Pa 9 g OT|5LVC8G7Ceo"p7p|'̌gvΥi03  dlhsL) KCM&P1`otiZ!$~w00jڨϐgsPމ2:x4Xf,5[B!azSE{Qk#VN9V}{қvz(+(Ldbl#V}яkdT)#^rQх#RJHg Ơe#!Rfhd[Exa#}_IH$VSlGHt]*a,p˲I"}̣ 5I m-Iml]X+O߃uf5h.hpͬHi#hj,a'#_q2$7SCɪbPI.#Vah[-(H4R {i0VC#_eTcMOc&XX8&a;-ƌLI6V%F% po(BtGr4.d#_vIX`RbhR1;._|oKۏ^lƎ, `tz"ԍA#p2Eo`Q4#ViPR'jtFܲ2K0` 4&(@rBR.M ;&;&h0"ta4dSKA<&腊7ͶܘZ<6U ;:zfu{F"16Gj%Ѣ"!8wZ;\.FDET+Q<~#VSoC6NIFF&k:3{ZWWh:fV"hq3 X4^yj'#2H7/f1m '=W\؇A i(JDf#V3#V8acMF5V&"OS5]eX$yeN v9O=#n[AAg٣Z; "|@>B#_D3 #V#_tb"  R$@m$w?[4*J`JR"RerQ[]$t+RL&% 0S^Ψ;U0 i@5#V)@1*$]MFfְҹYL*J5o5 PJ?]ưR#Vy " .[/يљcݹ+ ]PΐT2!vER$\$‰tGh=8g($JQHg Q _ȣLY_J^0"2SD`,1V~L4,q_l)*2EeD@f*NXHbV]?n]1=aUq܃t19hAѱ9S8AȆq2r#Vk%ɖQJG0q.A4`lHg;ÖDe)*(RKc3KM,zB,2WY-fd#V݀/Chj_j`sfl4/#_ #Vܑ64~^&MG$4da9/eKAENO7#\Q'=3e1V,<b_rf}F ~۴6D<,F-j#V6h@C%$%YuJa*o'dQ"\2hSVrtl X|Ơ~{F(b8߷l~YZU"Pߤsbi#Vv+0Dk775ILmj0#ŔViV 1mT̑6Q&a2b(59o%zͩ-L0m%Gh\iCRHHlBR1fU\Wh#V1hm286Lfl`cx !τIE(4 U1TxNRQD!2 X"1bZ-R:Y2qSc#2ۃա SwXE"+۔{]OoaէZZ% I@\#_Xm51UvWżbcK7qVlJTmJY2r|\RBVvַUs n؅L(a#VL#2ӈiP!׺9"@xQnB…#_޵4CٱKJMa*xB@4*$=#V#_ڡa$'85@p(Ihb(g,Q,~wr02ނ&)C~w{ k#2FzBEi{3`/휚6~qWfAe$JemvG!Ge26CBcBlɰ5֋)$vU`XX8L``#_K >7Z(q`L(sgsyFo< #V`NK*Zp T1>gx'ʶ8`4DD"J(TK+I 26M{e,\SQhҧKwt#zޮL\s^nwW&.w#]\^WȆQ)* ҍ%JʷYjHz]&9*+1WzUcc3 5#2yIASq1(܀@#_#2W@l#2aSA"(% J:~HbO_q r\0`dCS_ht%pSHI#2C:->!u?:;,(_xs/aa@yBHX@QT/#_<ًt RiSvhsp@g@5{_UE"NfQ#_RfQ(S{PO{S4PJCF0EUkor^X#2l(1<7#Vyu)#2i #_"&Q;:97…(DDKkEjڍXZZT#_A ֊m+!40@pkvd/]dMwἛTԂ^˴c T˵mE&Gvi#_;Pі10BUdaJj6R^^vle9m#&KDh4dq`4D`J'ju RِBi"Jj wЙR.tɨxBZ'NTc;B-A.lIAZ|̐#VJo??7apBq"SݭdeNtL(ږ(#_)m:$(G4a^rd;4#]y(: Yf@@v;i&B"JiE4wgY]0Y#2ă I M)#V7#?"rM'"PH#2;M] +wgߍE,&N.]6籈@.$#Vr " sR.f#_֝oQI ߡUfZJf(ʔFiFjmJZkRکKU!.⇯8Nn(5'>}ҌH-*R*H$)5[kJ)-k")ҳ%-,"s#_R=+xEf;[H^@!i!8A(ϿlvZXƉG04yP?vx 'mkd򌧘 M#2#V#ZJ,66dO;p h M Є#VPz <"Cm/IMq5DRLXQu/#RWtĔw?8s;}ьB^Lφŵ6Yl#2mRB^(He !F@-2U((`22$AF POYf#_F!I>M~]ǪĊQg!n3 6qC)f/<$%"BIسK+$;CP#ׅV"DU +mØD%('\6lTCHHC!J`$%R|ZNkj`".#_ aM(34ZJ4&:vda-"E\rNK0H $1Ij-u]ʿhV0’ڔ+]c0 b+@P8|T+$,YvÌ $8a+SNۙI,P!BG,<# &1A)1[$m#_FTb^PPQx6ptQ`k)ъ>,l`;d2Da*()C'P~ " T #_ ~~pS5OKK1L8z`c N1?E?$:LY&Q1 R#=| ߒu#SvZXwN{SlaOPٹGGAL糍q2ʱ6)ritl0Փ^mJ)m#Vwļ{B5B=&yFt znw˂1xeN b9JL,ȸ>[0>ҧ 솕36X73SZSV@%A%.e"rUye5rNgddjuu:-1 \M~$[ 2s'N&3)#/$(S;C`#&t}Le'AMd_kWOr!#2ƂB N9& FӴY%1k #_My#2R$XC!0V8-1JD#VCKC!bk9>O40#j'lh+ZeVNy$e͟ΛTqfC]N#2sSCd&9g첥8A1 n4ܒ13|?֘Nsx"#2_HhPBd<x @c.<˴+<Ǔ{0wc4>ϯE q$Y!B[BpK=㐂EF{SbSH0b(megP8$-ul#_t#_V*}T3D]h6;SDxDCÚs5j(ש@ӵ w֬/1{}x2:Hf2#21phBt3I;x|ܙiJH!< )L npl'\Lc1ŋ3wej Aצ&9/Â|kREEdĨF*X|$휌& t̻H~7dӣUI`\BC]$K+Q_j#VRNݨʷ>6T7x`=2}اo~*w'neqɰ,~ivB@r,mA-o{*I5j024Jh"/I" \8ۂ#V AF@;@o+̰XL7%lr&ܯ78WXR{z*w&R+c9ɋiX[ٔYIJp㛛\a&H)(-2XmFkdKʙ#Тe&= 'a %ZI!P)R@Q։`v(J pcMsDn_g\9=f>!- 1c`Hm8w:N4Ŵ&8`f֚m[e!,{=sYFzkV\k!Ѹ1H'*\|< ('1ִ1Z/#2 F-/OM<8 L{_>h<#_}I#2&?0JI (>+J,_XàlOG'z;T mhm 4DA:xAӀz!F|8S(WN44|wDhRRt˓A&0@=N:!e_ڵa#V~+Dׂh|'-wNGq&7 ω#2Mh 2 0>!\x6-x|>9\*- KT@`1=IAr$OS0߀;TMxnC "#Vȅ4zS!R"dY!f1&3(%H3`Ye%]lX{,')7%@v!3C`;C%`>WF׻RIc6_ͽ0oZ@7au!TaHB1^oy8*P`'#h|&#V%!HeBdA|#V8)>!΀y4A#!?;#Vs-4Rcү#VbE$_F́\֤iqAP_(W 19\04(Cw`N#2J@yN?%#2Gc`|=:`lnG.dæ٢ .,NX~]#WQ ;T5TJ&$ 4tB#P h!m6 ū0J.c/XaN7n'lb0spmq' ѭhzi@A#Vqe} !:O롽VXiR8NXbs1wBAo#V0hHfV9.z% ,oC1j:r8)U.}n#V&$~٢JRM46.(b\?'nF7| Aغidh`i5C'Q,#V#V4?BAAhv3P ?hc7|PݾǹZƿvq)(`=N̬RLhM`1fX#V#2j4L1H_H j$d$+0qo鿁 t8JB}4 UC c VemB  a`Racd#_J< jORT=>9=Oó%`Zn-s\Nml3Kꦭ F|{J+#_L,) #2C|A}{nruT$./Ûյa| K#_NfDJɤP%&M DqLu $J&oTK~]90#2FeP3L1RMQ-IFhL,2h *[#_3#Vް;@*12&EE>c\'כAh/FRݰ:e(ŤDD.$T MmT1&zKiۆ?P:`NSiNb݊+3|cM2אBe1.XQP DHi+Nc :iSAia#X'!i ז?a17`R!$u<󡇎#CYM\9#2#2 #ƘX"a7Za5+=BWum1ɖ YQa麤 YPgO˭ⅧwLDf nl%JHᡄKYl2J2m4D@EbAA@HAۃέ`QKP- 4f0lh D3F amad"x"<84-~,DZɷYAƂ#qg&#[!@+#V315L}MDNdk>|n.NB/֛l;Hajr#_,M{߃~ff`Ջūi5Hmi)1G#LdkyXW5COqלh}J}T@d(+s#7t;b[{hR!!@-W+tUV]ݝ( 0*d#2*a"SstSz} r|gZE{`) k2(FdQ8`mcVg#pDPX̦m5mFڢm4lRɪ"Jh@f9o'UC%O{\~^6;˂1&"mLFQ)!Rllg)8cPQтv"m@gl7fƉ&2`1bdo @45ZFVlvR#_%A̰+<0N.QJ̰0ZAVAaucie21.y/RuXҰ1&6b'ri"ht4kk3  )#_41PlN֢Ls)w46m JA.G'CNƷH}.TS0llRdabRml/` 3kL$šZ{Õ$aշd$HgkyMQRLii,YUׁPoEJ\ &6r4^OP'QPL1 mo7*"#_( i4>]2<&D]2(N+^TUBӉ0qg?*#V#_wf8wiӱlE/ȏ鴱Fjf2Lar6$w4:4-h##"%ZMd(ٓ)xG<|L:oW. 1USKC9wgGs0Gۚ_^F'=F|#2=V}Jξ\ʓW9`j1ûe˓n||85udd_/GâS$# |kOKNaH29lB<#V-abw5GwrgkEZa.&|2o`sNPle8SPz&*NP2g8Nbޝ3ZW%IGň?emnIf^2n@zB$AQC;<ͦB]JÊF Q,tVs;̪۱ .8Wٸ߻kl̓X7ܐ*cm5ir1DR#_VzBDV11+<ݬmnqhʈq 3)jB%6Ȁ\\$ u4.5HGiZL%C!0birR r25@-dk"g %]!BN:pWe=zK#2cAzan#2?N>GD.#2PN`p2V,AP@aj<}.tЏ#2~~cħd.N9B0CIit'O]5p~;U_Hl-EhHm"=lGD>%2Hs oU#_Hv#2Џ _08e0$#_;m.0yBDԲ7^7 0s$ȑqhb$@D#DT$I"RC-4EjKjɶ+UfmfUc#V(t PUN:@#2/@g4;nÄ3zM"kR9&ATkDI#2o-}O =%!`#_MBzN\%!$# '> M晴)F|:C=Gaf'۹'#2; %[liJ#_jffL*LJ0j&KFcfmF)2JATXZ+lUmZT6mI+eiPiHHmbX~WBU i6㑻[J#+#ʨAaG!;Kw7wݢ6pVXMT^|[yzk/Wv\5ltӏ|]|R1MsABB‘1XQ 2W$68zK'#c-ReB,vd#_<# Z6y&"sֵKkDVVR6ǫ\'ShJD͏.Lt]`ŋcWSc $ö֊BiQuaȐWz'S µdҐ.Ɲn!E,d| `Aݦw=9hZ%Y+OI#Tp2Fi7͗Rвe0c'HddLrѭQZC6ntdQ@,!:]?ƒKWSE'xw&W)<ˑtn#cMe g9fUm= (2*"#Vtn5IMTԼud $ uO{Fy<cfP?x/@\J[˧)0sn7;L8JI'w6  7V@#N[H#_"FrZEDʮc_ j _JPRǭpn5$N#\]_ g98Y&<3J6y2V#~2c m|pvv8mG.\9D=>%Ma$'yPu/'!Y퀆2* ;OaѻqT}ZJ;,^GdB{Tʫ?ߵW/:U(#VO3FAe:ҙA'|z,NcRѻP!16;lsR%&9h8'JCw<@PLJ0*dx3ӻףԋIW*$/9(4!'1TNv…EEJ׌B#rtG[xqxen{@K?'Z3ߓgn=GĂ֋8c< l͝/Y RqMGXnP.׉i'\RJCzNZg.1It鑬;i]=\)!↫!S#=+B6Eqj71hr#_ԙ[وN-n7DlNg o3H(OfB_g/|<ѽr6oޘ)#V4FFVמ#_N>T򲰍6:4BN,PԭIyLy#Us+;grIr:MvxkqYYH./:r<%?"ye)8k9:c%L!zN0cNQ.틗h1"qêuǤJG=`1{"~cu#=dR+Ր*;E;-^Kz<8~<}@ׇmR[kshyL{ltGdJ`w#_& \48jZ,^8cDVl76}]+ z. 2IN $ Tcfl$D&ݾkFxc[!yK4;"xI[o'I͗pBl&%ݨlk.ٷ:n;KkoLKB~Nhp]ʹa<Y8zU䑞76:G[b_(MR6%:Ƀ^^yVxy66j}.2'{@h3-Rc0#\UJM+[Crj:;HƼt M5?^zbT\@;v;`#V-\MU:0iV'3Vf^콼.pG&L<^5)Zɵ̿mhϜ5"c2c~|k"I5<ܘ&SDxz8OQ +B 9ŋYc)1vU#VtupOTyv3kzwqW_t6wbPÑڽ7cŕX<<>|Mk&f>}>Y"QPޑnkITi-^ VٜQ#_)ɍ{jS9{o|R;u;fWm*K{!ɤO&AG+|sE3ۚC[b1(P78,a cMF5`Ғ9elkgjkr7u2,Ç(~t,]ztt!U?͙rcgl DAE!jrŏEi@yX46є೶t3#V۱#25 ‡“ u4h $M~MnuBWWGv5w>7;wOś!ɪsB3S^x8,U܋tpu֨6Pwj/^)[Ѯ]*I]|-mir4=*k\H^ Tʐ@DN Lک߹,oNdxMD\0y`i#_$2hH^p35s8Qh.CYSO%ZSZߠTId#2QH Z.< D6Cy>M$0vgkgd#J^xGG` aޮ?$q$ Qi͇PoQNMY xLϻJdvxA[xr  ^V.EG`i H>$0O}<:{~Z?QT=Ypǂܧ"y(@=?0y|~1Hctm*m63DȒOnrP%Z61#^8ak#2? |"yLT#_$r<7#V<969æ߾Ŀ+#_:Ŭ&1!xĪ#_]Qɬ.5{1VD촋V!BF5|Y+i'=4}g`^2<C#VQ9f W{62ԋytcf6JY`µ0F fvp`UNRR9U%%󫽟>-Ej/+GY6DtK2fj1PJ4A5#2w#26\)UQ-1Ptj4'CGA#V .Di8L%L;h`4&^mu@rbqLb)K̡DF(4 LJX0{ʼ-fҎ#VDdb0vDa ɒoy2Xt3DmFCSQ4EnTcQF-@?#2#_P #jrP`ȓGl[79egO|,t/NV3H_^7LI]ݙ*u'F4PUHmtNZ;{aI#ۍ6\̰ 1' Ҫ2D4i KE.NMi50Gn9ۑ `CnGe: g6ՕE7ɵ 7(B@@뭣K@ffC;\m۷_%Ϸ{\^rRemq+e݌ s*w6ӤLiF(ID4sKP@ uXl&1Xݣ70GXa-9Q\R#_C]ɯ:J/B ZQ5 K+#ViΚ5j   ׎G];p@dݎ:/N`G%Nf*7%OmS{n#YK(\#V1 -cDiDEUJ4Sx1 ;Z.%7q#_HM`5YA<'fךÕa R!H.#2ҩ6lOpRSQQ$dUwu:y{\\SBdѨnY]<5cjs'2PF5g餓9ٙu|eŘbwm) 'OU~KtC} dFi7Aڅ,+6!$*#VO0_u嗮뻢NpMc4EKsx6[!R#V,BP\xcWSuG%AB+-k)sg#__gbC^rǭ}D\#V8YEQ%1v%vuDN|62nŒzf3 3EɄ0x/֟`A Bσ{&&Z%62WKn8q>nr*2Yxa_U|+_4?Ay“c_ L%zl x)SQ#Vֆ"nJٝӇML%?+&cnNi#26$NOE5XEgJL-s#2nJ(oǑ-*W#V[UksEXi4-I*@͢ LP`SoKkWZmΚKZmQU̝ I]:SGK#8 mgXZ#_9w,؜`S<a>:_Z 9Fy<^f6,XY@>֡ @*S&ABLB`b"(4B9[I @*4ƪa=)#?C#_?{)O̜ >RGn5!#2*#_#2IfcDC1 y&eb2ɱZJIiiƺ+ݮj-&#VțCQESbHɨc;]NA[2(#_FLK#20RUW[{!\YFLPM@Elmު}qЪjP%C:.lT}5ZPC܏R#V6+XXW i϶Lo5K}Xc54.8Dd#_64׶$l08;H*&7HVDOSV[7.=)̍Eӎhf54JiM(Ra@^PI@|^.mJ:8D l"P_z8jk{b*]c!r W\%h@; ^{c #= p:Ɓ.50 0j}9_]BB6#_ rȻIRZ}B9C)ˡ8 (PR%z#_~b`8CM\0^jyi6CVdK%G_$}??VSENGlOw>1>XDAwG2̃WAS/ѿV!6X g#VϩGoQ`#VPܒ׃#2"2tK~R>"(E&"~jILm2ؚ_@ԹЩ=9Jh±y@4CP``{@=i^mʦS%P3}i*UvwC2&͐I1Ry r8ٗl>;]NDa$y2JbRzw+%ؾa;$AՇ#8~H3GCoʔRF]*NY`"U?#`a+|.#V.NwxD 8e4#2`MϪRFX$j˯6 p#$ #_ӎ#_emL~Rb)*!uNWMG1@JE,FeD>?M#VBRh#2S[='ؙ>s>7R;7#V Zl$ɓ$=TI'ts0YJYN#VӚwl%n2m0M8T:GRfLb2I8\ֱR(/zx[Rp#M)Kf*w.ݦE8!ĝ+YL,]R/ؒ툼\e4hWa6mޝ}AёĤy>Z1n@5>nr53¨lAfqn)wXx&01kkfԘ5Hا+q5?E9+ lӆzqD#Vp9 -8Q-:Z_ǚ mmw!#_i"LL1Ft5&bvd6PXu&D@Sy 1/W;تpwO@d'LݮJ<8aL;;—LcFw0ӣ&vùF&3&ۍs:h8"braMnq;us#VZ#-.@@d^jƶBI@ɉΘ[q59cLJy,tiً29? r#V1fd9%eqDrt uS/ӧтk"̚nif#_:jG03c7L^e`99]{x#VlkD&U#`5tnpvX*0A*OZ;M'_U6csWswv7 1FRDh쟚ehm[ky,Cr5W P0f2#_*iyqeMbk4#2Lڙ(δ(5%"q eeNS$&^ƞL̘[A.`r<(ftсw[t΂W5eіt3#_hW Zglt:psBQ"%*R9%aJ!¡ A.!0͕Qu18k+DA[Oc'z19;LFjW+C9r]cSk䴅:Os>0KQ*Db#_$hnBJY7tml+f &, #_ZN$U X i6N-mˁl !; ;js#Pbu jLSSLe\5Lds?/j5{x;vP#2#VϝZf#_]ه4q˳mipm#_|x9%Ńx#_fH.΃#,!qAJI@X`,FѴsw3FiWWSq](r9"{zE#)ٱ]ds-ΩS$31 VsCik..|L# MTzV1jHIoa9&#/2H..`L"ӪJ?3e#_;mkaӪCKj*s1 Ο((V駋a^o]dXjޤJK^3͒Ns˥\b=^m` 걺gv(Xr`u::k[ev82,w$J :N,fv9/6yaC u~F4D`lxo86wh s%Ĺ ;gM ^PjN8#$#P>_b鎛X$njA)^;^)߮1QXr+ `:aA ɆƊÍ j>0#2-.8<"6 #_eKh1\CM5#VPɳӻn?yrK2:БG/Ǩl/QSFQb6fxNֈBpP$Lji6ލY%6G}al3Kv#Vs-7͕s1ZUx}'Fg?Lw:``Oie)P%.׌VPɾ% +#VKE}.Z1^EUJ1H\צ&>26$AH.X{~;Rb)b6@@qP PX0Fl1MAk2(끆$,x#2A=yP\4ܝ&1ӢÎMGm!;'a Q )H:G.x5aY2u#2A&:bg.t%.No`r{xȪrآTqP(mɊo1b*:dr#V("V룪l1:AHed) (%#V3UD3@v 6"&gZ;d8$l].70HiItGJCdqcz#V=&#V2mN0%XW ige`ؚYe PBߞٙG#a8+a&0N`)\wAd2S !|b,[`Q#_ժW&S9-[b)%,J¤Kwƒ7Ш7u5f8X=7^XD4:D~c+ >qM>y+ϗTd^dXJ_ױ6`*ıJF7by#2I#VT#2o환+'14WXe%AI??T D_3!4?RP*)#VȹLGx0m@#BBp@3Z`a#V$P CL%~h\$9IhKb%{z}%iLy?5Gc9Ē r@y@;Hd?^#V3VvaB6 #Vb=jPԤa쫏{#xt$cnLSs(@L!H60]aa[m?e$<`9vΚ+CYv$F0B6} I.R^h/i<c_M`dЪGɉw7p}HNtmg&Sɡt+s-&Fi6ޜиŪȤUcDNi=JnaiD[Щb"##2m@#_#2"a YDsj"RvH($i]e%<ᘻReMRZ;"sCiKswoQ&"%-XمLXmu>fI#2VD՛[X|HRO!z##_7FS``(ni61^Vq9ə8\CeA@*mã087yd(>V8J,5M}+Y4iN-a9@mmDӼbBP;|=mيC:cDs^F]_[֢M[ ]w5 ea:XYQj"lhp:nŠC*彿lleM% č_c'h3(\ׇ`S#2PbfBg߀M$jWϥn%f;5}MzJ{iwU,hMUj$U5ZM5)DЩ(nH#2lŠ(lJY#2j(J蔥 b#2H )AP|CT22S3R@,ZQrB4R*2Q #25(DR #2=U氊1alM#%`U$hK ~pS9IFYd#_w@M&BA@`*#p>%z_>L+Gӌ7JO}Ih AM#2%,!HAyr_af:(Fߟpx{?>*|!@{"C =b$LB2`@(˫|רMV)$~={lGf#2.07~h7w!bq;O&2'MuQNz.TL{SQLv|}Xv/TÈoWv=!hgo#2bNP 9fN-$dh۲HVXG+#lNhTiJ1Q w+{ƽPA>wI܎G,++)MB\`loSm|rs80aZEµ\C2ef3NXP1b$L #V0U+l1!Zf6,X$.`lQbt&ġ#_Q0tK"hQBF!,Qoh:ۺ;nh{v:|wF)ֱ=cv+#2A#_68$Ȓl!:tܢ5X1?ݷ0Y<=1y"j|Ӝ(l#g%M':F#o&{})YpC&10ӛ#_ `Z(/I :zCǿf%4™iS%ղSIL0L[vVjڽ D9(@)AShZ 5ЩH#_kk6k6$`򢠾X(&#_k#_;kSuVdqu܇=}vK6/tFk꒙u%(  duQu=yPmCݜ^IB{r:*|6hD)HhXJlLKJR1,VKiRTjmmFA2ʑScw<@d4J)~q?#<j\7}9&=,Vl]|gӜ5=q,֫%OR;36ot\ L֫VVϰPڴ3'VIJO*: Qlal=:LupS:Ok#VELKĘ2%e>.+EҔ-乜#_WxTJe.Rlݣlma[۳ꩪD%!E>ʹz|ۦ"/,D'7-<h|襖zu^;*ym;20saLqAMjc-Ip:M4G0@ҳ#"}@  ?*q+5Wy*:#$ j8C.󾵝ݶt#Vo17@N,¹#V#VZҗw˿=[B #2AWx&bD*DGE[3;@ ۊF>!u$1Df^]P(BgLhHQ~:?Wz?#VLK#_*?rEHΎ[2#=?7۶@G .^l{M.z'$'WA=G3K(h6b#2ЕHzCb#_;.i(~2dFR@ ?#VGs$Q]urQJD%ٝ{_6sdHIv`rU+2$;yt0cqMQsH2Ai\1`J4lK7r#_5%6jżs\L*^ܚcQY5j)a#2h+;a7b"h*/iI` @ qO+l=#g\Gq|SD? 3+Zo@t,`SX>fB#_X"QY D&(PIE(Zgأ#V4Է(g=&n|EW޴#V%VT2+ Y--IRh!"dt"%+Z:*$Mm,hY ٴL%D%TDrCaP5R܁)xT*!'&]|IGRi`M}~ܶ,&HEM̖ƴFŔ)Y(i/[d %H!j|*BvMWX.ψV5@I0m1s\5..dd ګ'%FE}0#2h ,4RCm>r^G]yFօO5 A܇#а30vL#2r233$'ƴA4to1:^IF!a._! !.EFC;9`jꠂ#_2 X %#_d]*#2;dA#2 $q^n^\(.&BE"ƀN$m#VXֆTn+#2 ($*$BM*θ+?9$mOs ڙiL{'AÆ}tН<(AF#Vؤa U, d6(У{#VFfQ2xiZB/os:hr!|x&d9C^P/pQEEK35/)ձ|dd"w*'b^~9Ol^O'Ө, :hSWL ?|#2ci*0 r\9#_Nc!3G\s844n%P{V*#SdXn#!O&;d1zB] D&t+RҍdF_NyWZFYeWe)SnZK`7̶qLʎR2̧OȽ|~vplj3֌k?8VӲaӳ~xlKYiA649,:@rP$AzuZPrCTCTl((2o@pUQжг$A߈jֈEKUHу,`gZ$^jWAH⅙BƄ Q9!f539;lCGièrM$gp'.f66Dd䈰{;{Q(97vciWSJpǮ5.PT: \1(u?Yo:_k~DXztAF"LlyC%E ,Re's0M6[Ss(lS1:8r;#_DrQ e[^6*W3NRG= 2&Ѣ8A#V#m(ٹ:HT~)@ɥTGT@.X01 #l_lH\59Òk?=3EN#lLR"#V<#>GNd,#2Х(/_!!;h#2o 6b?1!M#ĬIqG@9.#_D.Oף{,@$9S"Ib*F)J?mhĈXIlU鮝׊ľc#_c;oei%(9A~'#2#_m­Gghb#{L_YB ;3q+}#2i{y<5(= _}̭i 7$8$_6ҺUFd4a3Ё,p;M#_Y Y>s2Ģ"jbeY#25H684@oNlDoriq^aQzH!(&QE8#VCJ'vxxKJ&ٜ=HiwIK($v.yGyB #_3%2*<4mk 4fK '< 5zUt$ъ.Xmf`i?ac#3ޮ-:D*'}I,΅c9hN"#広+"%#2rħsK\Bַ&Kick.fu65% Xu Hugq~=qÌ[p]&bw+L*]fPU-V,4`B8?u1DhSUS*=뙈h&TX/!Qi@zadZ.]_nrʚV9Z3"VmME&ذǾBQRgHkRRUxɥNi4嘶-JCeZEL=-48ѓ5̤؞ g̅bcov%li[q9ſ-.#2&5PoX&JE#UL+Z;6 $&S9>f=r4S#2)*bZxfI|2U^Q6pOɺĝh9y""5kvD:0و(4#2'yt""~܆e"QT&4]zl#܊)YplO#f#_|#V!C\{ UeP]J B5&:H\-t40)H1%Xupiv˵"#V;o]Cɑƞ(H])A2ys@F"Ӣ]fRe(.F1 wOcjZ#Vc%&vW-QFyE|5"쩨iW8Ng&̳#V#2Pd5+NODK~,6Op\k%>K;88bAq*,#2_Ui2KgΨP,Of${&Ǘ >\êC/:#_,Y@_R dKo&6_Μ{C[e"pchFU`c+svPVJbHg낀*戳>j^YW5C9}p8o4vԥxULj\e|!;hSYh%c(1y L/ lVm5tfPsWDkgjq=jnYѥAZEǻ0`4|">4@(Ix#*D{,_MZPTHPכVx kG.(;N K=8C#2#r>](o|_?z=7U]4w"YfiK'TM؈??ڶnʟ&8˥R'O i`~¤ M#VA2#VфfrE@rE8P #<== diff --git a/wscript b/wscript index 7fac93c3..20244ba0 100644 --- a/wscript +++ b/wscript @@ -169,7 +169,7 @@ def configure(conf): conf.env.MSVC_TARGETS = ['x86' if not conf.options.ALLOW64 else 'x64'] # Load compilers early - conf.load('xshlib xcompile compiler_c compiler_cxx') + conf.load('xshlib xcompile compiler_c compiler_cxx cmake gccdeps msvcdeps') if conf.options.NSWITCH: conf.load('nswitch') @@ -301,6 +301,7 @@ def configure(conf): # '-Werror=format=2', '-Werror=implicit-fallthrough=2', '-Werror=logical-op', + '-Werror=nonnull', '-Werror=packed', '-Werror=packed-not-aligned', '-Werror=parentheses', @@ -475,12 +476,11 @@ int main(int argc, char **argv) { strchrnul(argv[1], 'x'); return 0; }''' if conf.check_cfg(package='opus', uselib_store='opus', args='opus >= 1.4 --cflags --libs', mandatory=False): # now try to link with export that only exists with CUSTOM_MODES defined frag='''#include -int main(void) { return !opus_custom_encoder_init(0, 0, 0); }''' +int main(void) { return !opus_custom_encoder_init((OpusCustomEncoder *)1, (const OpusCustomMode *)1, 1); }''' if conf.check_cc(msg='Checking if opus supports custom modes', defines='CUSTOM_MODES=1', use='opus', fragment=frag, mandatory=False): conf.env.HAVE_SYSTEM_OPUS = True - conf.define('XASH_BUILD_COMMIT', conf.env.GIT_VERSION if conf.env.GIT_VERSION else 'notset') conf.define('XASH_LOW_MEMORY', conf.options.LOW_MEMORY) for i in SUBDIRS: