diff --git a/docs/spec/CodeCharacter-API.yml b/docs/spec/CodeCharacter-API.yml index 6e4b7c9..26afc4c 100644 --- a/docs/spec/CodeCharacter-API.yml +++ b/docs/spec/CodeCharacter-API.yml @@ -305,6 +305,150 @@ paths: description: Get leaderboard parameters: [] + /pvpleaderboard: + get: + summary: Get PvP leaderboard + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PvPLeaderBoardResponse' + examples: + Example: + value: + - user: + username: testUser + name: >- + Test User1 + asdfdsfasdfasdfadsfasdfadsdfasdfasdfasfasdfsfsdfsfadsfasfa + country: IN + college: NIT Trichy + avatarId: 0 + stats: + rating: 500 + wins: 6 + losses: 0 + - user: + username: testUser2 + name: >- + Test User2 + asdfdsfasdfasdfadsfasdfadsdfasdfasdfasfasdfsfsdfsfadsfasfa + country: IN + college: NIT Trichy + avatarId: 1 + stats: + rating: 50 + wins: 3 + losses: 3 + - user: + username: testUser3 + name: >- + Test User3 + asdfdsfasdfasdfadsfasdfadsdfasdfasdfasfasdfsfsdfsfadsfasfa + country: IN + college: NIT Trichy + avatarId: 0 + stats: + rating: 80 + wins: 1 + losses: 0 + - user: + username: testUser4 + name: Test User4 + country: IN + college: NIT Trichy + avatarId: 0 + stats: + rating: 230 + wins: 0 + losses: 0 + - user: + username: testUser5 + name: Test User5 Test Test + country: IN + college: NIT Trichy + avatarId: 0 + stats: + rating: 850 + wins: 0 + losses: 0 + - user: + username: testUser6 + name: >- + Test User6 + asdfdsfasdfasdfadsfasdfadsdfasdfasdfasfasdfsfsdfsfadsfasfa + country: IN + college: NIT Trichy + avatarId: 0 + stats: + rating: 600 + wins: 0 + losses: 0 + - user: + username: testUser7 + name: >- + Test User7 + asdfdsfasdfasdfadsfasdfadsdfasdfasdfasfasdfsfsdfsfadsfasfa + country: IN + college: NIT Trichy + avatarId: 0 + stats: + rating: 57 + wins: 20 + losses: 70 + - user: + username: testUser8 + name: Test User8 + country: IN + college: NIT Trichy + avatarId: 0 + stats: + rating: 850 + wins: 0 + losses: 0 + - user: + username: testUser9 + name: Test User9 + country: IN + college: NIT Trichy + avatarId: 0 + stats: + rating: 78 + wins: 0 + losses: 0 + - user: + username: testUser10 + name: Test User10 + country: IN + college: NIT Trichy + avatarId: 0 + stats: + rating: 10 + wins: 0 + losses: 0 + '401': + description: Unauthorized + operationId: getPvPLeaderboard + tags: + - leaderboard + parameters: + - schema: + type: integer + in: query + name: page + description: Index of the page + - schema: + type: integer + in: query + name: size + description: Size of the page + description: Get PvP leaderboard + parameters: [] + /top-matches: get: summary: Get top matches @@ -318,7 +462,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/Match' + type: object examples: Example: value: @@ -514,6 +658,25 @@ paths: value: aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kUXc0dzlXZ1hjUQ== operationId: getGameLogsByGameId description: Get game logs by game ID + '/pvpgames/{gameId}/logs': + parameters: + - $ref: '#/components/parameters/gameId' + get: + summary: Get pvp game logs by game ID + tags: + - pvp-game + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/PvPGameLog' + examples: + Example: + value: aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kUXc0dzlXZ1hjUQ== + operationId: getPvpGameLogsByGameId + description: Get pvp game logs by game ID /user: parameters: [] get: @@ -997,8 +1160,19 @@ paths: avatarId: 0 '401': description: Unauthorized - operationId: getUserMatches - description: Get matches played by authenticated user + operationId: getUserNormalMatches + description: Get normal matches played by authenticated user + parameters: + - schema: + type: integer + in: query + name: page + description: Index of the page + - schema: + type: integer + in: query + name: size + description: Size of the page parameters: [] post: summary: Create match @@ -1035,6 +1209,60 @@ paths: opponentId: 0a4b34b0-6057-4b82-ae27-a59c36eab667 mapRevisionId: f52ddf9e-e933-471a-9250-41078cc39f80 codeRevisionId: d9eb9923-651b-4ec4-b6f1-b7625b2a9392 + /user/pvpmatches: + get: + summary: Get user pvp matches + tags: + - match + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PvPMatch' + examples: + Example: + value: + - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08 + - game: + - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08 + scorePlayer1: 100 + scorePlayer2: 45 + status: IDLE + - matchMode: PVPSELF + - matchVerdict: PLAYER1 + - createdAt: '2019-08-24T14:15:22Z' + - user1: + username: testUser + name: Test User + country: IN + college: NIT Trichy + avatarId: 0 + - user2: + username: testUser + name: Test User + country: IN + college: NIT Trichy + avatarId: 0 + '401': + description: Unauthorized + operationId: getUserPvPMatches + description: Get pvp matches played by authenticated user + parameters: + - schema: + type: integer + in: query + name: page + description: Index of the page + - schema: + type: integer + in: query + name: size + description: Size of the page + parameters: [] /user/notifications: get: summary: Get all notifications @@ -1292,7 +1520,55 @@ paths: value: "[[0,0,0]]" tags: - Daily Challenges - + /dc/matches: + get: + summary: Get user daily challenge matches + tags: + - Daily Challenges + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Match' + examples: + Example: + value: + - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08 + games: + - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08 + coinsUsed: 100 + destruction: 45 + status: IDLE + matchMode: SELF + matchVerdict: SUCCESS + createdAt: '2019-08-24T14:15:22Z' + user1: + username: testUser + name: Test User + country: IN + college: NIT Trichy + avatarId: 0 + user2: null + '401': + description: Unauthorized + operationId: getUserDCMatches + description: Get daily-challenge matches played by authenticated user + parameters: + - schema: + type: integer + in: query + name: page + description: Index of the page + - schema: + type: integer + in: query + name: size + description: Size of the page + parameters: [] components: schemas: @@ -1436,6 +1712,29 @@ components: - wins - losses - ties + PvPUserStats: + title: PvPUserStats + type: object + description: PvP User stats model + properties: + rating: + type: number + example: 1000 + wins: + type: integer + default: 0 + example: 1 + losses: + type: integer + example: 1 + ties: + type: integer + example: 1 + required: + - rating + - wins + - losses + - ties Code: title: Code type: object @@ -1501,12 +1800,15 @@ components: createdAt: type: string format: date-time + codeType: + $ref: '#/components/schemas/CodeType' required: - id - code - message - language - createdAt + - codeType CreateCodeRevisionRequest: title: CreateCodeRevisionRequest type: object @@ -1872,6 +2174,36 @@ components: - matchVerdict - createdAt - user1 + PvPMatch: + description: PvP Match model + type: object + properties: + id: + type: string + format: uuid + example: 123e4567-e89b-12d3-a456-426614174000 + game: + $ref: '#/components/schemas/PvPGame' + matchMode: + $ref: '#/components/schemas/MatchMode' + matchVerdict: + $ref: '#/components/schemas/Verdict' + createdAt: + type: string + format: date-time + example: '2021-01-01T00:00:00Z' + user1: + $ref: '#/components/schemas/PublicUser' + user2: + $ref: '#/components/schemas/PublicUser' + required: + - id + - game + - matchMode + - matchVerdict + - createdAt + - user1 + - user2 CreateMatchRequest: title: CreateMatchRequest type: object @@ -1900,6 +2232,11 @@ components: format: uuid description: Revision of the code nullable: true + codeRevisionId2: + type: string + format: uuid + description: Revision of the code (for SELF-PVP mode) + nullable: true required: - mode Game: @@ -1927,6 +2264,33 @@ components: type: string description: Game log model example: aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kUXc0dzlXZ1hjUQ== + + PvPGame: + title: PvPGame + type: object + description: PvP Game model + properties: + id: + type: string + format: uuid + example: 123e4567-e89b-12d3-a456-426614174000 + scorePlayer1: + type: integer + example: 69 + scorePlayer2: + type: integer + example: 69 + status: + $ref: '#/components/schemas/PvPGameStatus' + required: + - id + - scorePlayer1 + - scorePlayer2 + - status + PvPGameLog: + type: string + description: PvP Game log model + example: aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1kUXc0dzlXZ1hjUQ== LeaderboardEntry: title: LeaderboardEntry type: object @@ -1939,6 +2303,18 @@ components: - user - stats description: Leaderboard entry model + PvPLeaderBoardResponse: + title: PvPLeaderboardResponse + description: Response model for PvP leaderboard + type: object + properties: + user: + $ref: '#/components/schemas/PublicUser' + stats: + $ref: '#/components/schemas/PvPUserStats' + required: + - user + - stats DailyChallengeGetRequest: title: Get daily challenge @@ -2032,6 +2408,8 @@ components: - MANUAL - AUTO - DAILYCHALLENGE + - PVP + - SELFPVP description: Match Mode Verdict: type: string @@ -2051,6 +2429,14 @@ components: - EXECUTING - EXECUTED - EXECUTE_ERROR + PvPGameStatus: + title: PvPGameStatus + type: string + enum: + - IDLE + - EXECUTING + - EXECUTED + - EXECUTE_ERROR AuthStatusResponse: title: AuthStatusResponse type: object @@ -2081,6 +2467,7 @@ components: enum: - NORMAL - DAILY_CHALLENGE + - PVP default: NORMAL GameMapType: title: GameMapType @@ -2161,6 +2548,7 @@ tags: - name: code - name: current-user - name: game + - name: pvp-game - name: leaderboard - name: map - name: match diff --git a/library/.openapi-generator/FILES b/library/.openapi-generator/FILES index b04269c..354ac19 100644 --- a/library/.openapi-generator/FILES +++ b/library/.openapi-generator/FILES @@ -1,10 +1,4 @@ -.openapi-generator-ignore -README.md -build.gradle.kts -pom.xml -settings.gradle src/main/kotlin/delta/codecharacter/SpringDocConfiguration.kt -src/main/kotlin/delta/codecharacter/core/ApiUtil.kt src/main/kotlin/delta/codecharacter/core/AuthApi.kt src/main/kotlin/delta/codecharacter/core/CodeApi.kt src/main/kotlin/delta/codecharacter/core/CurrentUserApi.kt @@ -14,6 +8,7 @@ src/main/kotlin/delta/codecharacter/core/LeaderboardApi.kt src/main/kotlin/delta/codecharacter/core/MapApi.kt src/main/kotlin/delta/codecharacter/core/MatchApi.kt src/main/kotlin/delta/codecharacter/core/NotificationApi.kt +src/main/kotlin/delta/codecharacter/core/PvpGameApi.kt src/main/kotlin/delta/codecharacter/core/UserApi.kt src/main/kotlin/delta/codecharacter/dtos/ActivateUserRequestDto.kt src/main/kotlin/delta/codecharacter/dtos/AuthStatusResponseDto.kt @@ -46,6 +41,11 @@ src/main/kotlin/delta/codecharacter/dtos/NotificationDto.kt src/main/kotlin/delta/codecharacter/dtos/PasswordLoginRequestDto.kt src/main/kotlin/delta/codecharacter/dtos/PasswordLoginResponseDto.kt src/main/kotlin/delta/codecharacter/dtos/PublicUserDto.kt +src/main/kotlin/delta/codecharacter/dtos/PvPGameDto.kt +src/main/kotlin/delta/codecharacter/dtos/PvPGameStatusDto.kt +src/main/kotlin/delta/codecharacter/dtos/PvPLeaderBoardResponseDto.kt +src/main/kotlin/delta/codecharacter/dtos/PvPMatchDto.kt +src/main/kotlin/delta/codecharacter/dtos/PvPUserStatsDto.kt src/main/kotlin/delta/codecharacter/dtos/RatingHistoryDto.kt src/main/kotlin/delta/codecharacter/dtos/RegisterUserRequestDto.kt src/main/kotlin/delta/codecharacter/dtos/ResetPasswordRequestDto.kt diff --git a/library/src/main/kotlin/delta/codecharacter/SpringDocConfiguration.kt b/library/src/main/kotlin/delta/codecharacter/SpringDocConfiguration.kt index bdd1fb2..9c26f94 100644 --- a/library/src/main/kotlin/delta/codecharacter/SpringDocConfiguration.kt +++ b/library/src/main/kotlin/delta/codecharacter/SpringDocConfiguration.kt @@ -10,7 +10,6 @@ import io.swagger.v3.oas.models.info.License import io.swagger.v3.oas.models.Components import io.swagger.v3.oas.models.security.SecurityScheme -// @jakarta.annotation.Generated(value = ["org.openapitools.codegen.languages.KotlinSpringServerCodegen"]) @Configuration class SpringDocConfiguration { diff --git a/library/src/main/kotlin/delta/codecharacter/core/CodeApi.kt b/library/src/main/kotlin/delta/codecharacter/core/CodeApi.kt index 2c41601..60f758b 100644 --- a/library/src/main/kotlin/delta/codecharacter/core/CodeApi.kt +++ b/library/src/main/kotlin/delta/codecharacter/core/CodeApi.kt @@ -78,7 +78,7 @@ interface CodeApi { value = ["/user/code/revisions"], produces = ["application/json"] ) - fun getCodeRevisions(@Parameter(description = "code type", schema = Schema(allowableValues = ["NORMAL", "DAILY_CHALLENGE"], defaultValue = "NORMAL")) @Valid @RequestParam(value = "type", required = false, defaultValue = "NORMAL") type: CodeTypeDto): ResponseEntity> { + fun getCodeRevisions(@Parameter(description = "code type", schema = Schema(allowableValues = ["NORMAL", "DAILY_CHALLENGE", "PVP"], defaultValue = "NORMAL")) @Valid @RequestParam(value = "type", required = false, defaultValue = "NORMAL") type: CodeTypeDto): ResponseEntity> { return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) } @@ -97,7 +97,7 @@ interface CodeApi { value = ["/user/code/latest"], produces = ["application/json"] ) - fun getLatestCode(@Parameter(description = "code type", schema = Schema(allowableValues = ["NORMAL", "DAILY_CHALLENGE"], defaultValue = "NORMAL")) @Valid @RequestParam(value = "type", required = false, defaultValue = "NORMAL") type: CodeTypeDto): ResponseEntity { + fun getLatestCode(@Parameter(description = "code type", schema = Schema(allowableValues = ["NORMAL", "DAILY_CHALLENGE", "PVP"], defaultValue = "NORMAL")) @Valid @RequestParam(value = "type", required = false, defaultValue = "NORMAL") type: CodeTypeDto): ResponseEntity { return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) } diff --git a/library/src/main/kotlin/delta/codecharacter/core/DailyChallengesApi.kt b/library/src/main/kotlin/delta/codecharacter/core/DailyChallengesApi.kt index 01e519c..383e989 100644 --- a/library/src/main/kotlin/delta/codecharacter/core/DailyChallengesApi.kt +++ b/library/src/main/kotlin/delta/codecharacter/core/DailyChallengesApi.kt @@ -9,6 +9,7 @@ import delta.codecharacter.dtos.DailyChallengeGetRequestDto import delta.codecharacter.dtos.DailyChallengeLeaderBoardResponseDto import delta.codecharacter.dtos.DailyChallengeMatchRequestDto import delta.codecharacter.dtos.GenericErrorDto +import delta.codecharacter.dtos.MatchDto import io.swagger.v3.oas.annotations.* import io.swagger.v3.oas.annotations.enums.* import io.swagger.v3.oas.annotations.media.* @@ -102,4 +103,23 @@ interface DailyChallengesApi { fun getDailyChallengeLeaderBoard(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?): ResponseEntity> { return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) } + + @Operation( + summary = "Get user daily challenge matches", + operationId = "getUserDCMatches", + description = """Get daily-challenge matches played by authenticated user""", + responses = [ + ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = MatchDto::class)))]), + ApiResponse(responseCode = "401", description = "Unauthorized") + ], + security = [ SecurityRequirement(name = "http-bearer") ] + ) + @RequestMapping( + method = [RequestMethod.GET], + value = ["/dc/matches"], + produces = ["application/json"] + ) + fun getUserDCMatches(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?): ResponseEntity> { + return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) + } } diff --git a/library/src/main/kotlin/delta/codecharacter/core/LeaderboardApi.kt b/library/src/main/kotlin/delta/codecharacter/core/LeaderboardApi.kt index 5fa81b2..a17976a 100644 --- a/library/src/main/kotlin/delta/codecharacter/core/LeaderboardApi.kt +++ b/library/src/main/kotlin/delta/codecharacter/core/LeaderboardApi.kt @@ -6,6 +6,7 @@ package delta.codecharacter.core import delta.codecharacter.dtos.LeaderboardEntryDto +import delta.codecharacter.dtos.PvPLeaderBoardResponseDto import delta.codecharacter.dtos.TierTypeDto import io.swagger.v3.oas.annotations.* import io.swagger.v3.oas.annotations.enums.* @@ -56,4 +57,23 @@ interface LeaderboardApi { fun getLeaderboard(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?,@Parameter(description = "Leaderboard Tier", schema = Schema(allowableValues = ["TIER_PRACTICE", "TIER1", "TIER2", "TIER3", "TIER4"])) @Valid @RequestParam(value = "tier", required = false) tier: TierTypeDto?): ResponseEntity> { return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) } + + @Operation( + summary = "Get PvP leaderboard", + operationId = "getPvPLeaderboard", + description = """Get PvP leaderboard""", + responses = [ + ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = PvPLeaderBoardResponseDto::class)))]), + ApiResponse(responseCode = "401", description = "Unauthorized") + ], + security = [ SecurityRequirement(name = "http-bearer") ] + ) + @RequestMapping( + method = [RequestMethod.GET], + value = ["/pvpleaderboard"], + produces = ["application/json"] + ) + fun getPvPLeaderboard(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?): ResponseEntity> { + return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) + } } diff --git a/library/src/main/kotlin/delta/codecharacter/core/MatchApi.kt b/library/src/main/kotlin/delta/codecharacter/core/MatchApi.kt index 88ccea9..033cfff 100644 --- a/library/src/main/kotlin/delta/codecharacter/core/MatchApi.kt +++ b/library/src/main/kotlin/delta/codecharacter/core/MatchApi.kt @@ -8,6 +8,7 @@ package delta.codecharacter.core import delta.codecharacter.dtos.CreateMatchRequestDto import delta.codecharacter.dtos.GenericErrorDto import delta.codecharacter.dtos.MatchDto +import delta.codecharacter.dtos.PvPMatchDto import io.swagger.v3.oas.annotations.* import io.swagger.v3.oas.annotations.enums.* import io.swagger.v3.oas.annotations.media.* @@ -65,7 +66,7 @@ interface MatchApi { operationId = "getTopMatches", description = """Get top matches""", responses = [ - ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = MatchDto::class)))]), + ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = kotlin.Any::class)))]), ApiResponse(responseCode = "401", description = "Unauthorized") ], security = [ SecurityRequirement(name = "http-bearer") ] @@ -75,14 +76,14 @@ interface MatchApi { value = ["/top-matches"], produces = ["application/json"] ) - fun getTopMatches(): ResponseEntity> { + fun getTopMatches(): ResponseEntity> { return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) } @Operation( summary = "Get user matches", - operationId = "getUserMatches", - description = """Get matches played by authenticated user""", + operationId = "getUserNormalMatches", + description = """Get normal matches played by authenticated user""", responses = [ ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = MatchDto::class)))]), ApiResponse(responseCode = "401", description = "Unauthorized") @@ -94,7 +95,26 @@ interface MatchApi { value = ["/user/matches"], produces = ["application/json"] ) - fun getUserMatches(): ResponseEntity> { + fun getUserNormalMatches(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?): ResponseEntity> { + return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) + } + + @Operation( + summary = "Get user pvp matches", + operationId = "getUserPvPMatches", + description = """Get pvp matches played by authenticated user""", + responses = [ + ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = PvPMatchDto::class)))]), + ApiResponse(responseCode = "401", description = "Unauthorized") + ], + security = [ SecurityRequirement(name = "http-bearer") ] + ) + @RequestMapping( + method = [RequestMethod.GET], + value = ["/user/pvpmatches"], + produces = ["application/json"] + ) + fun getUserPvPMatches(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?): ResponseEntity> { return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) } } diff --git a/library/src/main/kotlin/delta/codecharacter/core/PvpGameApi.kt b/library/src/main/kotlin/delta/codecharacter/core/PvpGameApi.kt new file mode 100644 index 0000000..4a4cef1 --- /dev/null +++ b/library/src/main/kotlin/delta/codecharacter/core/PvpGameApi.kt @@ -0,0 +1,56 @@ +/** + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech) (7.1.0). + * https://openapi-generator.tech + * Do not edit the class manually. +*/ +package delta.codecharacter.core + +import io.swagger.v3.oas.annotations.* +import io.swagger.v3.oas.annotations.enums.* +import io.swagger.v3.oas.annotations.media.* +import io.swagger.v3.oas.annotations.responses.* +import io.swagger.v3.oas.annotations.security.* +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity + +import org.springframework.web.bind.annotation.* +import org.springframework.validation.annotation.Validated +import org.springframework.web.context.request.NativeWebRequest +import org.springframework.beans.factory.annotation.Autowired + +import jakarta.validation.constraints.DecimalMax +import jakarta.validation.constraints.DecimalMin +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size +import jakarta.validation.Valid + +import kotlin.collections.List +import kotlin.collections.Map + +@Validated +@RequestMapping("\${api.base-path:}") +interface PvpGameApi { + + @Operation( + summary = "Get pvp game logs by game ID", + operationId = "getPvpGameLogsByGameId", + description = """Get pvp game logs by game ID""", + responses = [ + ApiResponse(responseCode = "200", description = "OK", content = [Content(schema = Schema(implementation = kotlin.String::class))]) + ], + security = [ SecurityRequirement(name = "http-bearer") ] + ) + @RequestMapping( + method = [RequestMethod.GET], + value = ["/pvpgames/{gameId}/logs"], + produces = ["application/json"] + ) + fun getPvpGameLogsByGameId(@Parameter(description = "UUID of the game", required = true) @PathVariable("gameId") gameId: java.util.UUID): ResponseEntity { + return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) + } +} diff --git a/library/src/main/kotlin/delta/codecharacter/dtos/CodeRevisionDto.kt b/library/src/main/kotlin/delta/codecharacter/dtos/CodeRevisionDto.kt index 4bc4821..4edac41 100644 --- a/library/src/main/kotlin/delta/codecharacter/dtos/CodeRevisionDto.kt +++ b/library/src/main/kotlin/delta/codecharacter/dtos/CodeRevisionDto.kt @@ -3,6 +3,7 @@ package delta.codecharacter.dtos import java.util.Objects import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonValue +import delta.codecharacter.dtos.CodeTypeDto import delta.codecharacter.dtos.LanguageDto import jakarta.validation.constraints.DecimalMax import jakarta.validation.constraints.DecimalMin @@ -22,6 +23,7 @@ import io.swagger.v3.oas.annotations.media.Schema * @param message * @param language * @param createdAt + * @param codeType * @param parentRevision */ data class CodeRevisionDto( @@ -42,6 +44,10 @@ data class CodeRevisionDto( @Schema(example = "null", required = true, description = "") @get:JsonProperty("createdAt", required = true) val createdAt: java.time.Instant, + @field:Valid + @Schema(example = "null", required = true, description = "") + @get:JsonProperty("codeType", required = true) val codeType: CodeTypeDto = CodeTypeDto.NORMAL, + @Schema(example = "123e4567-e89b-12d3-a456-426614174111", description = "") @get:JsonProperty("parentRevision") val parentRevision: java.util.UUID? = null ) { diff --git a/library/src/main/kotlin/delta/codecharacter/dtos/CodeTypeDto.kt b/library/src/main/kotlin/delta/codecharacter/dtos/CodeTypeDto.kt index cd1d061..61ba7e8 100644 --- a/library/src/main/kotlin/delta/codecharacter/dtos/CodeTypeDto.kt +++ b/library/src/main/kotlin/delta/codecharacter/dtos/CodeTypeDto.kt @@ -16,11 +16,12 @@ import io.swagger.v3.oas.annotations.media.Schema /** * -* Values: NORMAL,DAILY_CHALLENGE +* Values: NORMAL,DAILY_CHALLENGE,PVP */ enum class CodeTypeDto(val value: kotlin.String) { @JsonProperty("NORMAL") NORMAL("NORMAL"), - @JsonProperty("DAILY_CHALLENGE") DAILY_CHALLENGE("DAILY_CHALLENGE") + @JsonProperty("DAILY_CHALLENGE") DAILY_CHALLENGE("DAILY_CHALLENGE"), + @JsonProperty("PVP") PVP("PVP") } diff --git a/library/src/main/kotlin/delta/codecharacter/dtos/CreateMatchRequestDto.kt b/library/src/main/kotlin/delta/codecharacter/dtos/CreateMatchRequestDto.kt index 8e50f95..8a64198 100644 --- a/library/src/main/kotlin/delta/codecharacter/dtos/CreateMatchRequestDto.kt +++ b/library/src/main/kotlin/delta/codecharacter/dtos/CreateMatchRequestDto.kt @@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.media.Schema * @param opponentUsername Username of the opponent * @param mapRevisionId Revision ID of the map * @param codeRevisionId Revision of the code + * @param codeRevisionId2 Revision of the code (for SELF-PVP mode) */ data class CreateMatchRequestDto( @@ -35,7 +36,10 @@ data class CreateMatchRequestDto( @get:JsonProperty("mapRevisionId") val mapRevisionId: java.util.UUID? = null, @Schema(example = "null", description = "Revision of the code") - @get:JsonProperty("codeRevisionId") val codeRevisionId: java.util.UUID? = null + @get:JsonProperty("codeRevisionId") val codeRevisionId: java.util.UUID? = null, + + @Schema(example = "null", description = "Revision of the code (for SELF-PVP mode)") + @get:JsonProperty("codeRevisionId2") val codeRevisionId2: java.util.UUID? = null ) { } diff --git a/library/src/main/kotlin/delta/codecharacter/dtos/MatchModeDto.kt b/library/src/main/kotlin/delta/codecharacter/dtos/MatchModeDto.kt index d59915e..1904ec6 100644 --- a/library/src/main/kotlin/delta/codecharacter/dtos/MatchModeDto.kt +++ b/library/src/main/kotlin/delta/codecharacter/dtos/MatchModeDto.kt @@ -16,13 +16,15 @@ import io.swagger.v3.oas.annotations.media.Schema /** * Match Mode -* Values: SELF,MANUAL,AUTO,DAILYCHALLENGE +* Values: SELF,MANUAL,AUTO,DAILYCHALLENGE,PVP,SELFPVP */ enum class MatchModeDto(val value: kotlin.String) { @JsonProperty("SELF") SELF("SELF"), @JsonProperty("MANUAL") MANUAL("MANUAL"), @JsonProperty("AUTO") AUTO("AUTO"), - @JsonProperty("DAILYCHALLENGE") DAILYCHALLENGE("DAILYCHALLENGE") + @JsonProperty("DAILYCHALLENGE") DAILYCHALLENGE("DAILYCHALLENGE"), + @JsonProperty("PVP") PVP("PVP"), + @JsonProperty("SELFPVP") SELFPVP("SELFPVP") } diff --git a/library/src/main/kotlin/delta/codecharacter/dtos/PvPGameDto.kt b/library/src/main/kotlin/delta/codecharacter/dtos/PvPGameDto.kt new file mode 100644 index 0000000..3d526e8 --- /dev/null +++ b/library/src/main/kotlin/delta/codecharacter/dtos/PvPGameDto.kt @@ -0,0 +1,42 @@ +package delta.codecharacter.dtos + +import java.util.Objects +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonValue +import delta.codecharacter.dtos.PvPGameStatusDto +import jakarta.validation.constraints.DecimalMax +import jakarta.validation.constraints.DecimalMin +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size +import jakarta.validation.Valid +import io.swagger.v3.oas.annotations.media.Schema + +/** + * PvP Game model + * @param id + * @param scorePlayer1 + * @param scorePlayer2 + * @param status + */ +data class PvPGameDto( + + @Schema(example = "123e4567-e89b-12d3-a456-426614174000", required = true, description = "") + @get:JsonProperty("id", required = true) val id: java.util.UUID, + + @Schema(example = "69", required = true, description = "") + @get:JsonProperty("scorePlayer1", required = true) val scorePlayer1: kotlin.Int, + + @Schema(example = "69", required = true, description = "") + @get:JsonProperty("scorePlayer2", required = true) val scorePlayer2: kotlin.Int, + + @field:Valid + @Schema(example = "null", required = true, description = "") + @get:JsonProperty("status", required = true) val status: PvPGameStatusDto +) { + +} + diff --git a/library/src/main/kotlin/delta/codecharacter/dtos/PvPGameStatusDto.kt b/library/src/main/kotlin/delta/codecharacter/dtos/PvPGameStatusDto.kt new file mode 100644 index 0000000..50775fc --- /dev/null +++ b/library/src/main/kotlin/delta/codecharacter/dtos/PvPGameStatusDto.kt @@ -0,0 +1,28 @@ +package delta.codecharacter.dtos + +import java.util.Objects +import com.fasterxml.jackson.annotation.JsonValue +import com.fasterxml.jackson.annotation.JsonProperty +import jakarta.validation.constraints.DecimalMax +import jakarta.validation.constraints.DecimalMin +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size +import jakarta.validation.Valid +import io.swagger.v3.oas.annotations.media.Schema + +/** +* +* Values: IDLE,EXECUTING,EXECUTED,EXECUTE_ERROR +*/ +enum class PvPGameStatusDto(val value: kotlin.String) { + + @JsonProperty("IDLE") IDLE("IDLE"), + @JsonProperty("EXECUTING") EXECUTING("EXECUTING"), + @JsonProperty("EXECUTED") EXECUTED("EXECUTED"), + @JsonProperty("EXECUTE_ERROR") EXECUTE_ERROR("EXECUTE_ERROR") +} + diff --git a/library/src/main/kotlin/delta/codecharacter/dtos/PvPLeaderBoardResponseDto.kt b/library/src/main/kotlin/delta/codecharacter/dtos/PvPLeaderBoardResponseDto.kt new file mode 100644 index 0000000..820a83e --- /dev/null +++ b/library/src/main/kotlin/delta/codecharacter/dtos/PvPLeaderBoardResponseDto.kt @@ -0,0 +1,35 @@ +package delta.codecharacter.dtos + +import java.util.Objects +import com.fasterxml.jackson.annotation.JsonProperty +import delta.codecharacter.dtos.PublicUserDto +import delta.codecharacter.dtos.PvPUserStatsDto +import jakarta.validation.constraints.DecimalMax +import jakarta.validation.constraints.DecimalMin +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size +import jakarta.validation.Valid +import io.swagger.v3.oas.annotations.media.Schema + +/** + * Response model for PvP leaderboard + * @param user + * @param stats + */ +data class PvPLeaderBoardResponseDto( + + @field:Valid + @Schema(example = "null", required = true, description = "") + @get:JsonProperty("user", required = true) val user: PublicUserDto, + + @field:Valid + @Schema(example = "null", required = true, description = "") + @get:JsonProperty("stats", required = true) val stats: PvPUserStatsDto +) { + +} + diff --git a/library/src/main/kotlin/delta/codecharacter/dtos/PvPMatchDto.kt b/library/src/main/kotlin/delta/codecharacter/dtos/PvPMatchDto.kt new file mode 100644 index 0000000..2041aa6 --- /dev/null +++ b/library/src/main/kotlin/delta/codecharacter/dtos/PvPMatchDto.kt @@ -0,0 +1,61 @@ +package delta.codecharacter.dtos + +import java.util.Objects +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.annotation.JsonValue +import delta.codecharacter.dtos.MatchModeDto +import delta.codecharacter.dtos.PublicUserDto +import delta.codecharacter.dtos.PvPGameDto +import delta.codecharacter.dtos.VerdictDto +import jakarta.validation.constraints.DecimalMax +import jakarta.validation.constraints.DecimalMin +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size +import jakarta.validation.Valid +import io.swagger.v3.oas.annotations.media.Schema + +/** + * PvP Match model + * @param id + * @param game + * @param matchMode + * @param matchVerdict + * @param createdAt + * @param user1 + * @param user2 + */ +data class PvPMatchDto( + + @Schema(example = "123e4567-e89b-12d3-a456-426614174000", required = true, description = "") + @get:JsonProperty("id", required = true) val id: java.util.UUID, + + @field:Valid + @Schema(example = "null", required = true, description = "") + @get:JsonProperty("game", required = true) val game: PvPGameDto, + + @field:Valid + @Schema(example = "null", required = true, description = "") + @get:JsonProperty("matchMode", required = true) val matchMode: MatchModeDto, + + @field:Valid + @Schema(example = "null", required = true, description = "") + @get:JsonProperty("matchVerdict", required = true) val matchVerdict: VerdictDto, + + @Schema(example = "2021-01-01T00:00Z", required = true, description = "") + @get:JsonProperty("createdAt", required = true) val createdAt: java.time.Instant, + + @field:Valid + @Schema(example = "null", required = true, description = "") + @get:JsonProperty("user1", required = true) val user1: PublicUserDto, + + @field:Valid + @Schema(example = "null", required = true, description = "") + @get:JsonProperty("user2", required = true) val user2: PublicUserDto +) { + +} + diff --git a/library/src/main/kotlin/delta/codecharacter/dtos/PvPUserStatsDto.kt b/library/src/main/kotlin/delta/codecharacter/dtos/PvPUserStatsDto.kt new file mode 100644 index 0000000..7abca17 --- /dev/null +++ b/library/src/main/kotlin/delta/codecharacter/dtos/PvPUserStatsDto.kt @@ -0,0 +1,39 @@ +package delta.codecharacter.dtos + +import java.util.Objects +import com.fasterxml.jackson.annotation.JsonProperty +import jakarta.validation.constraints.DecimalMax +import jakarta.validation.constraints.DecimalMin +import jakarta.validation.constraints.Email +import jakarta.validation.constraints.Max +import jakarta.validation.constraints.Min +import jakarta.validation.constraints.NotNull +import jakarta.validation.constraints.Pattern +import jakarta.validation.constraints.Size +import jakarta.validation.Valid +import io.swagger.v3.oas.annotations.media.Schema + +/** + * PvP User stats model + * @param rating + * @param wins + * @param losses + * @param ties + */ +data class PvPUserStatsDto( + + @Schema(example = "1000", required = true, description = "") + @get:JsonProperty("rating", required = true) val rating: java.math.BigDecimal, + + @Schema(example = "1", required = true, description = "") + @get:JsonProperty("wins", required = true) val wins: kotlin.Int = 0, + + @Schema(example = "1", required = true, description = "") + @get:JsonProperty("losses", required = true) val losses: kotlin.Int, + + @Schema(example = "1", required = true, description = "") + @get:JsonProperty("ties", required = true) val ties: kotlin.Int +) { + +} + diff --git a/server/src/main/kotlin/delta/codecharacter/server/code/code_revision/CodeRevisionService.kt b/server/src/main/kotlin/delta/codecharacter/server/code/code_revision/CodeRevisionService.kt index c39c9e5..13ee845 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/code/code_revision/CodeRevisionService.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/code/code_revision/CodeRevisionService.kt @@ -15,6 +15,7 @@ import java.util.UUID class CodeRevisionService(@Autowired private val codeRevisionRepository: CodeRevisionRepository) { fun createCodeRevision(userId: UUID, createCodeRevisionRequestDto: CreateCodeRevisionRequestDto) { + println(createCodeRevisionRequestDto) val (code, message, language) = createCodeRevisionRequestDto val parentCodeRevision = codeRevisionRepository @@ -54,7 +55,8 @@ class CodeRevisionService(@Autowired private val codeRevisionRepository: CodeRev message = it.message, language = LanguageDto.valueOf(it.language.name), createdAt = it.createdAt, - parentRevision = it.parentRevision?.id + parentRevision = it.parentRevision?.id, + codeType = codeTypeDto ) } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/code/latest_code/LatestCodeService.kt b/server/src/main/kotlin/delta/codecharacter/server/code/latest_code/LatestCodeService.kt index c0d5185..e1febf3 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/code/latest_code/LatestCodeService.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/code/latest_code/LatestCodeService.kt @@ -19,9 +19,14 @@ class LatestCodeService( @Autowired private val defaultCodeMapConfiguration: DefaultCodeMapConfiguration ) { - fun getLatestCode(userId: UUID, codeType: CodeTypeDto = CodeTypeDto.NORMAL): CodeDto { + fun getLatestCode(userId: UUID, codeType: CodeTypeDto): CodeDto { val latestCode = HashMap() - latestCode[codeType] = defaultCodeMapConfiguration.defaultLatestCode + if(codeType == CodeTypeDto.NORMAL) { + latestCode[codeType] = defaultCodeMapConfiguration.defaultLatestCode + } + else if(codeType == CodeTypeDto.PVP) { + latestCode[codeType] = defaultCodeMapConfiguration.defaultPvPLatestCode + } val code: CodeDto = latestCodeRepository .findById(userId) @@ -33,7 +38,7 @@ class LatestCodeService( ) .let { code -> CodeDto( - code = code.latestCode[codeType]?.code ?: defaultCodeMapConfiguration.defaultCode, + code = code.latestCode[codeType]?.code ?: if(codeType==CodeTypeDto.NORMAL) defaultCodeMapConfiguration.defaultCode else defaultCodeMapConfiguration.defaultPvPCode , language = LanguageDto.valueOf( code.latestCode[codeType]?.language?.name diff --git a/server/src/main/kotlin/delta/codecharacter/server/code/locked_code/LockedCodeService.kt b/server/src/main/kotlin/delta/codecharacter/server/code/locked_code/LockedCodeService.kt index 0ccc71a..3c282b9 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/code/locked_code/LockedCodeService.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/code/locked_code/LockedCodeService.kt @@ -18,7 +18,7 @@ class LockedCodeService( fun getLockedCode( userId: UUID, - codeType: CodeTypeDto = CodeTypeDto.NORMAL + codeType: CodeTypeDto ): Pair { val lockedCode = HashMap() lockedCode[codeType] = defaultCodeMapConfiguration.defaultLockedCode diff --git a/server/src/main/kotlin/delta/codecharacter/server/config/DefaultCodeMapConfiguration.kt b/server/src/main/kotlin/delta/codecharacter/server/config/DefaultCodeMapConfiguration.kt index 00f50ea..8c21ca8 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/config/DefaultCodeMapConfiguration.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/config/DefaultCodeMapConfiguration.kt @@ -15,6 +15,13 @@ data class DefaultCodeMapConfiguration( .getResource("player_code/cpp/run.cpp") ?.readText() ?: "", + val defaultPvPCode: String = + DefaultCodeMapConfiguration::class + .java + .classLoader + .getResource("player_code/cpp/runpvp.cpp") + ?.readText() + ?: "", val defaultLanguage: LanguageEnum = LanguageEnum.CPP, val defaultMap: String = "[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]", @@ -22,7 +29,10 @@ data class DefaultCodeMapConfiguration( "iVBORw0KGgoAAAANSUhEUgAAAnsAAAGJCAYAAADyhvUYAAEAAElEQVR4Xux9B7yeRbH+nH5Oeu+dNEgCCR2lCCpWVCxgRbFd21Wv93qtf/Xa+7323rAginptF0Ep0ktoIUACIb3XU3J6+88z38xm3v3e75yTAJrk7PcjnK/suzs7Ozvz7OzsLFF6JQ4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA4kDiQOJA5s+O6l30tcSBxIHEgcGGwcKBtsHU79TRxIHBhcHHjvC5dNuvRpC3977IJJZ1BXD1FNJW3esOehq+/d+IE3fuu6PwwubqTeJg4kDgxGDiSwNxhHPfU5cWAQcOBfnrW45j9ffPK9oyeOPPau8mo6vq2F7uwoo+cMr6BdVVU0rqeHOppbmz/x67vO+/yVy+8cBCxJXUwcSBwYpBxIYG+QDnzqduLA0cqB97/oxMUXnz3/l0sXTl5EXd10TSvRs+rG0y9ad9Mr22rp2ro2eldrBR07ZST9qnk3UV01bdu0r/PPd697/Zu+df3Pjla+pH4lDiQODF4OJLA3eMc+9Txx4KjiwJufvbji/ReetG70tLHT63p7aX9HN2O9Hjrnnp20+pRFNKKMPXkPrKeaCaOoYcwImj26jr7XupemdXfR2HKikZXl1NjYsvkr/3vvOZ+44o61RxVzUmcSBxIHBjUHEtgb1MOfOp84cORz4P0vXDbvFece+4vjF0w8uaulg56xs5dGz55AL26op29XDKEVj+6g/XOnEO1vo2Hrt1FLfTP1LJxOY6eMoXnrt9JxcybQ6e2tdEX1EPobNRJVV9DO7Y1rf3vLI//y1u/d+Lcjn0OpB4kDiQODnQMJ7A12CUj9Txw4QjnwlmctKfvPC5dtGTV93OTh1Est3b3UyN686Vv5EMasyTT6unto35RxRE0tRKfMJ4K26+klWrON6JjJROz9o2vvI1o4jcbub6U9y+bSjfXbaDp106whldRUW0M7dzTc+cu/rLjowz+7bcMRyqZEduJA4kDigKi/9EocSBxIHDhiOPDvFyyddun5i36zaO7EU/e3dtIze4fTJ8pa6Uv13Qzmeuj68lpqHz2caNMu/reTqKGZ6BnLiCoriNo6iXY3EA2tJRpWR7RjH4O/rUSzGfwtmkm0cx8dx2Dv11Ut9PKyEXTV0E6ayp8b61se+fbVDzz3fZfd+tgRw6hEaOJA4kDigHIggb0kCokDiQNHDAfu+cZr7jtu6pgTasp6qbu9k65rIzp/9BSa0tNNW1duJJo4mqiRPXnHziBav51oxBCiRzYTPXURgzzeot2CAxk1RLyFS+UcqAegN3Usl91BtHg20cYdVMuA8QNDiT46bhK9sq6Hvtmyj4ay57Crtpo2bdt72/9ev/ri//zJzZuOGKYlQhMHEgcGPQdY26VX4kDiQOLA4cuBf33e8XWPfvOSW3v/+M7e/ZPHnfDCnqF0T1sPXdpWQ5esZPC2bz9t7eQt2aEM4rbtZY8de+1aGQW2tjOg4/Us/yTevY6uwjZu/X6J3xNPX00V0RAuP3YEUXsHFyyjNgaHH72LsVxnJ93a1EUXNFbSrvJK+kJzGd08bvwZ773olI3tv37bjg9efNqsw5dribLEgcSBxIEDHEievSQNiQOJA4clB955wdLy1z/n+DuOmzH2ZIC01s4uev6a/XTT8fPpfxp20bu7GKTBM7eEt18B3LBl29RO5c2ca2X+NOpBAmWAvkb+PG4kAz4GeaPYZbeOvXjL5jDQY3C4gndlEde3i7d28d2mPcwLBoQPrCN65on8PG/7MoBcNpbj9/a20vj5k+n2+i1UzV7BsuG1tHPjnq2/vO2xi971gxtvOSyZmIhKHEgcSBxgDiTPXhKDxIHEgcOOA2u/87pbvvLmc7qvmDDh5L+UVdMNXeX00+5q2lhVTcTpVN7dzn/vfZSBGIMzeOwA9ibyduziWVS9v4Wq4OEbw3F7E3hbF969NVvYc8fAbSJv3+JgBh/mID6UQVWVha3dfU2F7d/RwxgYspePt2zF+8fePerooHu3NtGWMSNpO4cFXtRaSx/orKGHG9rp9xMmTHnni068ufPKdzS/96Un895xeiUOJA4kDhx+HEievcNvTBJFiQODkgP//sITh77tGQsfnDN7Arvqeml7YzstaK6lmbPGUdmdj1DL3Km0Ze0Oal06l2gPx9/hsMX9j1HVPPbiTR5NvQ9toF4Gd70zJhZAHLFnr4xVHP+hVt6iXcUHajsZrfEhjGqOz+vu7qbuKgaJ7NWrwO9zplA3NOJWBpDsRazifHxdvBXci23h9ew1RJzfNPYCrt5C4+dNojfvr6dPjZ1Me3t20WjO1YfkzJs37ln75+Xr3/mW79zw50E5iKnTiQOJA4clBxLYOyyHJRGVODC4OLD+e6+7a+KEkSf/vqeSntLTSTc2d9EXVu2m+xcdUzg5e8cqohkTiB5lD91pC/g7PngBoMZgb9L9a6n5lAXUBJBXyzF4OHiBPQt48OyF7/BqYO/duu00g4FfA5/GbZg6XryBo//3Fupmb17jUxcXnuN2pq7eSLuPmULtnJKF9rDnj5Muw4M4/L411MmHPNrZo9h77lI6bX8jvaOtiV49rIxWVNfS4s4O6u3s6nn/L25f9sXfLF8xuEYy9TZxIHHgcORAAnuH46gkmhIHBgEH3n/hibNffc7CaxfNmwg3Gz3Ahy6OrxpPP6L9dGk9M2ADx9YN5/Qox/HuKOLs2PtWxmlTqjlNStmQaqq551FqOJ7j7MZyPB4OWuDVy268HhzK4L/YvhUNh/+pqpM/DOba2BO3ZjNNWrme2hZMo3rOyyepWKoY0MEbCJwIz+HDG2jB1l20jT2CzSccQz0MGod0dFIXA712HAA5mfP38QERpHz5S3cDXVoxgl7E8X3faN5DZXxgZPOG3duvvmfjv77xW9ddOQiGNHUxcSBx4DDlQAJ7h+nAJLISB45mDrAnbwXfYLFkP4OqYbxluqO3jC69YzMfvlhANaPqZNu2h0/IdiDubhT/W75agFXdDffTwse20qrnnErt40dRD9KoVDBA49QrB14K7syzB+Amjj1+w+0Qp20R7x+/r9jTQL3VldQznA9uyO8MEn0Zju2r4ti+bm5z9KYd1MK/tU5nb+BCBqB3sbfxtGMl9m84g8L2SWOoY/JYOndIBb2xo4me0d1OjRUVNIv3kTs6uvZ8/Je3n/S5K5en5MxHs2CnviUOHKYcSGDvMB2YRFbiwNHGgQ+9+KS5F509/3fHz5+0GIcqXrShjR6bP50+27yX/oOG0J4Ne2gXAyaAt9rVm6iHPXkd2GbF9i28fABsyJs3kg9R8O0WAtoAzOQFRGfbtubJ089ItyKAj/8Hr528tEzmcfyuzwAoCujjf/AQogLE9WEbeSsf6JjCh0EQC8jgjuqbaHhjM+3nWL/e0xbSlJpKGsFxgc+bM5Yqm9tozZA6upK9fthi3rhp733XLV//kUu/ff0fj7bxTf1JHEgcOHw5kMDe4Ts2ibLEgaOGA+u/87qVo6aPXQTcVc1btk0MlJatb6Ptc6fT0OvvpWbceAEw9RT2lAFgtfH7jXwDBnLi4UQttlhxQnYke+AA+njb9MBLQZmBPQGAHvzho6o6eO7KdKtW0Jq9zBsIzx6woG0BR3F/+IiTuxv5wAZO8+IUMGjHwQ0AQdzCgfi+5Y/wlW0TaUhXNw1ZOpuu3LmJpvEW8THs9dtQVUN19fvXfu23y5/zySvYhZleiQOJA4kDTzIHEth7khmcqk8cGKwc+NCFJx37snMWXHbCgkknU3sXndNWS68bWkbXbG6ktZzj7hHeea2fwJ48XFkG8LSznujpywqHLLbxdwB4SHSMLVZ48wTDGfjKAWpFjC5RxoAivHjm6RNsqOpQPHnalnn65LOBQIBFfo9ULUjbwl484oMc1MjAdDKDvhaO5eP0L7Sab+44diaVz5tMPVv20NkM9D7bUU+vHDqeHq7cT7WVZbRje8MDf7htzbve/N2/Xz9Y5ST1O3EgceDJ50ACe08+j1MLiQODjgMPfPOSFeMnj14ykR1fzS0d9FhHD50wfDKN5vQk+5Zzfjxsf+5lDxifhJVrzZDb7p41haTHM/lULU7NIhEyDmhI+hQfk6dgC166kCo0B9jJNq8F7EWePqA1fGX1ViBVS04ZA3n4DR5B7w2UuD/+Cp4+eCABWEEzwB73U65uwxVt86YScVqYidUV9MK6MvrutGn0bxWt9J7WJppW1kO7OHdg+57Gx3501YoXf+Tnt6XTu4NutqQOJw48+RxIYO/J53FqIXFgUHDgvS9YOuy1z1z8+0Wzx523tqOXXtw9lC6raKGP7i+jex/ZThumcPwdYvDWbyt4xcYzMJrO8XgAezhRC8CEF64uG8WePMFeBsDcdqo/XRs468EevrS4Pc96Vwb7yQCKwVuIN3qCt/DlAU+eneQVz15OO7ItzL8BvMLLBw8lkjNXMoDEXz4AQpvZA4jEztXstXzmMlrU0MSZXMronqFt9IbuIfSyym56bmUnNexp3vSdvz30/Pf9+OYE+gbFrEmdTBz4x3Aggb1/DJ9TK4kDRzUHHvrOa++bNGXsCSMZMO3mE6xvfngv/X7RfPpkRwN9uIXde5zbjuazh2sMg59VvL2JWLe9DO5O5Zx5OHyBJMhIlHwcx7zxAQfiWLfCy0CbgSxJoKf/LDavvzJWDx5DTJ566TJbtKoK4emTnHwWB4h2+2oHdWsZfw/vXu4LwCu8ksP5UImlhuGDJ3T+Sbx13cBXu7XQ2ZxC8L7OMjp75hj6yd4tNAa5Azlly95t9fu+f92q577vsltuP6oFJ3UucSBx4B/CgQT2/iFsTo0kDhydHFj1jdf8YcGscRd8ubGXungL9kWt++mynmr6+6Z9dPPs6YWrzO7kFCXYzjyDD1/M5i3aR/g+Wxy24G1Nictby54+eMXqm4lO5zJ8c0Uhlk5caQq8HP/M22c/y0/qXcNbcQIqILQy5snD90jVImUiL52cvuWvcZWatS8pW4wW147VK1vJjlQBic7Tx9u3hHx8E7lPnB+Q7ubzGMvmFQ53YPsX/JkzmeYNq6Jx7BW8cEQFndHRRiuGDKW3lbVSy/6OXZ/5/b0nfvLy2xkhp1fiQOJA4sChcSCBvUPjW3oqcWBQc2DzN1+zcur0MYuQlqSBrzVbUl9BPQun04ibV9LeY6ZSM18vth/xeDhhi+TED64nOn52Yev2oY2F3Hk7+P5anF7FAQ3Ol1e2bhtVMQjs5GvRenFIQwCX9+QZy6PYuYznzQFEA3NwEkqC5YDQtF4FfHYwQ7ShlrFdYxzmqDA1KciPXwYS7Xsr7D2M/F7a5H/7GMTybRzEOfdwr28tg+J2vne3dxIDwAcZDCJpNPIFbtpFC2aPo2VNzfSnKRNpW/tOGgZPI/+2bfOezb+65bFXvvuHN940qAUvdT5xIHHgkDhg2uuQHk4PJQ4kDgwuDqz5zmuv6v3Tu3v/MH78olU95fTDHW10/qONtIkPOGxp7aKHW7tpR30r7Uf6ET6BKyBmJG9jDuG/LW28PctAqZ3BHzQPvHq8lQkAhPQlwGLDVqyliu0M/gDQ5EBE3su2YeFBc2V8WB88eXKTBv/DNWf+1K0BNp9LL+TfA95D/fjH77HdDGyHusQ76L2B2qDQ4NowzAigiL6N4v7DY3kMe/Y4VnH0ijVUxulnJI0Mp2eRbV4+1DGKY/0eu3sd/XLtHtrP8X4XNFXSD9q4/bZ2WjV14rR3Xbjsxq4r3978ny87hQMd0ytxIHEgcWDgHEievYHzKpVMHBiUHHjPBUvL3nL+omvnzR5/LoDQZgZ108vH0efrOulTO9qpAbF3yIt3+kL2YPEhBIClR/jveUsLXjH2YhHfQEEz+HAGkiHDk8dAqJwBYTmfvK1jb9oovrZs00l89dgYPpwBcCYHJACmsOWq4Ctw32/xahkDYYbHwlVpeMjUnG25OrUX3kZlMgc8jBYAPu/pM0+eR5kAmMjlZ8S6tkBTA3v5AHxXrOPcfMwPHFRB/6aMoSHb9lI7A+FufD6BvaDb62kcb3V/uXEXvX3UJHrPsB76YEs9VXNM35YNux/9w13r3/K2795w3aAUytTpxIHEgYPiQPLsHRS7UuHEgcHFgXXffd3fP/vW83raZk0+t4fvh72ju5zecx8ftmCg9r7K4dTGiY/rAE6mcn45eO2wNQtQN2k051xpo3KOw5t4/xqqBsDDC0AH25nHzqBa9vQNZy/X/ppq2vL0EwvpVwD07BCFPACgp9uhYVtXxyBgLIAxFFXwhZi8sCVr6M+2Z11dsq3r/wEXqrcwxAziQIfiRdQJ2tEO/omnT/8JLfpePH1oR/9KH/irRo7TQ3oZxATiphCczgWtDN7g4WzhLe9upKTBaV7mNWL9urn8JfsrqYkPsGzuLqOvVwyhjuZ22jph7Ly3Pv+Ea7t/96/7/uOlJzOz0ytxIHEgcaA0B5BcKr0SBxIHEgcCB973wmWTX3PusZcvmjfhHGxDvmHVPrpq/hz6Ts9eekdXHXVMmSQnZnsZnPQwOCvDViwAH+6xhbcKt0fAQ9fKHj0GKZ28lduL7UzErg3jrUt8z1eLtSydSy0j+DOfVO0F8MLWLcr5ZMdGlXeeBfClHjf5DHDlB9G8fwYCHWALxVyZ0I41ZF47POcb13oMAYJeeC/jMob97C+qQ/wd+ARvHjx8E0Zy/OL6Qq5BxDIivhHev4kMBNEk5xrswu0cqzjeb9pY+uveZrqayzRPH0Ermjtp3LAh9K3e+lFfePM5e996/uKbb7pnwwdf9+3rU0xfmsuJA4kDRRzIqMfEn8SBxIHBzYF1fK3ZeD54sYMqaHpXB60rq6DXrG2mO/lkbcVtD1I3UoPgwAXfAUvDOG8IgAtO0SJ9Crx5OHgBIKNbtZJcmL14sq2LcohRw3ND8Y9j1WS71sfBqUoCiJKt2BIHNMz7J2lSPLLCewfUAK4kJYp54Wx81cPX1xVrln5FtpFRB56NTvBacmarJ0687E8TI/3MRuYT7vdF4uW5fOuGxBTyP3j77lpdSMrcybGOZy4qnNhF//AcDres4VPLa3g7nIFhBXtIj1s4mT63dSMtHFpNs2sr6L6Kmv0LW5r3f/Ty2xd8/srljYNbklPvEwcSBzwHEthL8pA4MMg58OELT5x44dkLfnbiwsnPQCqQCxoraOm4obT30W10zegx1NTUSjuQDBlgbTPfV4tUKWcdz6CDvXe4NQLeORzGAIjBoQscagCYQa45eK5msicQqVYAukZwnj3ciiH4zDxmMdhTtSTXmolLrPCsPROuNfNAz3nfwunaWL2VaCcz/v2VyQGN5lIEvQIuQbb1AUBR6cR3zQzgkGAZyZdnMV8A6JBqBgAPPEOqFvDwHOYvtnORf/Bh9uwtmcll+XfeGqdHOAsLe0WrZ4yjDj71/IqxNfSShj30obGT6SH2vpbXVtKGjXtvvAGevm9df8sgF+/U/cSBxAHVpIkRiQOJA4OUAw9/65L7yyaMOn5BTRmnwmunfexRO7luInXyDRatd/J2LIAI8sHxHa+0ib1SOEEKsIf4vPnTCgAFV5wBxGErM9w7q145HNyAl2881wMvn2A3D6g0vk6An22r+m1T9dJ1MRjC23IGPyHWzgYNbRUwYSHGL88bqJ68kl46bUeuWEM9BhTzwJ/f6s0Bf3jEvJWI8fOpWkLiZT7UgtyCAH4AyfDgIZ4RyZd3NRDhsApi+nDHLng7mw/gbmBgDY8nDrlgTGbwd3z/7ty6CjpmSBVdPXsGfa6rkS7oaKFjy7rpkYrqzgn792/94m/uPulTv7pzzyAV8dTtxIHEgQT2kgwkDgw+DnzwRSeOe+k5C36xbP7EZza2d9OSzmF0RV0HfW5fNz2wjdN/VDDgWDSrsGWIdCm4NxaHLtjDJ1473G4BEDcB27YMRARjGeiJgJolGJY0JwrGbMtTHnRbrmEoHMAKJ3LtR23HPH1WpXjSvCcvZ7s1G9TnQJgDjf2WyTvT5trCoQ3z5JnnMsQgKpiF51Ho53+4ZQOpZgCkAfTmcyJqeE85Z6EAwU7eMkcam1MZ/PHBDGpj0Duct8AxFvCsPsheP85PSCN5HJ6xlJZu3kHlDB7vrmyiZ9EI+mRlO51S3UPbt9Q//Ls71r7+bd+5Id3IMfimfOpx4gClAxpJCBIHBhEHVn33dStrp4zhVMY9tJZxw+ce2UcbjxlDv2SQciPnktvLV3cRkv3WMfDAgQIAulV8xdcUPiWKk6PIkYfvcII0eOkM4JlHDQy19Ck4lBCDqah8L9eJr8rhBdOcdb36nMXkZQ5AcBmJ6QPI5P8JcPJgU1yADswZQIu9dHEZ1NFfmRxPn2wbK5gVcKteSiMLtGQ8nvyDeP7472j2iiIP4WQ+lIGt3b3s1UN/4A3l/HryPVLbIBcfDrjgd3j7sL2LO3cn8ljgYMw2dtzxvbv3VVRTHXton8Ik3F3bQz+ZMIwm7t5OM8YMO/atLzrxtlefPX/nf//p/qUfvfwORojplTiQODBYOJC3TB0sfU/9TBwYNBy47yuv/G3vX97Tu3zI0EWfqhhGjR099KmuWqofwl4iBipfKRtCe3Fn7WO8dbibAYVsRTLoQKwe0qpUIUUIx5Lhyi8croDXymLUMlwE2InUSgB7CsgsCTGeY9DzH0OW0AvG84GPHkaf3Qz84B1D27IF6r112hDathQoGcBpDeW0U3SwwohWcOn7kKEXJOSUkfLKA3kPetF3o9d7OIE9lSfi4TTA6J7H9vgC9uZNYlCNR7cweIMXtYZBNb6oR/JpBsDw/iG9jeBZrhOHXbD9i4MyuJaNPa6tnNfwtl0t1MHjdBOD92d1DKMfldfRFQ1d9PNRYyd85NVnbN192ZtWfen1Z3Gm5/RKHEgcGAwcSJ69wTDKqY+DlgPbvnXJ2klTR89G/rrGxjb6Un0vPTZtOP3tng20e1oN9bQoMIIXb8lsSakStmbBtUb2JiHnG0AhYu6whSsvO3QQe/UUqBVcdQWgEvCavvEnYxmwnL+tml48djo92rWJGkdxwuDm+gLAElCEOtz2p+KqA4c21IsWTu862gKosi1eD8AEJTq5cGWETCMahy7MRec8hpnbN5SGEHeoHsP4GrZwKpjrAVC2/IFyopfrAJgby146eOqwZYsr1laulftzR27dTY38ey9SsXDaGomZRP042IHnpjMox9jhrt0xvKWLgzP82wq+zYT2tdC3q4dRTyfn6+PfXt3WQmNH1i54z0WnPnTJOfO3fv9vD7/oAz+99a5BO0lSxxMHBgEHkmdvEAxy6uLg48DKr736D71Xvaf3l6NGz761vJp+uGU/vXhjG93b0UuNfHJ2XVs3NTGgaN7JXjwAhCoGcjgxC68dUqfYtWaIDatmNQFvH4BFAEEAcQw2LAmxx1HhlC3KqKfPJxi24YBniuPRzt/2B3rKzl9S9fI19P8a2btV7kGYnnAFsAngSCsIMXH82Tx9cq2ZuCXdoOv7cK0ZgGQsE84bKDQb+EM5V1i8mfiNvW7ek2cA1mITM/Tq83I4RFkYkjMDc+J33daV+vkfxuHkBQzAj2EAOILGrFxH5TiggXFZMqcQs9e4n0ZxSpsqpGzBFjDy9E1lz2AdP4sYSwBgTpMzeudeuvOedbR8Sz2trqqll3Eo4A86eazZC3jzhPFT3n/xqXe2XP6W5o+96owTBt9MST1OHBgcHMjZIxkcHU+9TBw42jjwtmcvKXvfBSc8PGPG2AUAFbs58e4UGkNvHlVOf9vSRKvb2PhjC/CsxYXrywBq+JoyOpM/YzsQMWHsQZITt8iZt5bzuw2ppvL1O6iKtxfbASQAQjJAyIAZvFMAMrH3TL1e3tNn258tDE5Wczzg2s309lln00tGLKLf7LiNvvEUbr+SwYjlsBMt5T2I+OwBoa1ZFbDJ7RaI6QNARNkYuKnXMfdaM6PXntN2ijx5+D2OBTQemGSZd9PTm+NlFKzH7SA5s7xcGfQBYBDe1QfX0zz29K3nQxwVnKi6jMesh8t28vZ31cqNVMMneBv5QMeJtz9E944bRTW85d7Oz1czsOzY30y9OMV7LIPpTXto8dBKunjXDvrEtJn03+WtdElHE2d8qeKDHPvWXX7TI699z49uTsmZjzYFkfozqDmQPHuDevhT548WDqz+5iXXfeOdz+xZM2PyAnh0ruqupP9atYc6N+2mb1QMpcc27aXhyHs3ndN14LWDPXVIqzKTP8Ozx56iKSseo2E42QnvEYL/kUCZAUNlVRUNf3hDwZtkTqgDb5SFkacvACGH0+QQA/8DfgJ42cP0oL7TFtGKju10yapf0DdO4Jg0gFI51arevMwgKfjKeAyNKPPg8QMW7yfADxVomYBF87yOB7oSDnwIfgQdzAt4IjNgFhWrRw4HRQK92k+pLqbXtoStjHr7EJ9nbckj6p2Edw4/YDwYzK159qlyI8moVRto9J2rqJ3b7Jk+kUbwVu602x6SU7n3XvAU6uVUN6MfXEdlD6yjdq5Dtn93M7/Z+1e2r5G23/EI/b/WSurgPu1mXn+6nAE2g+8VY0fP/reXnHxj55XvaH/fRafwKiC9EgcSB44GDiTP3tEwiqkPg5ID73zuCcPe8ezFN8ybPe4kOIPe/0g9fWPWbPom7af/ai2n/bxNuwN56aaMoYr71xJfSkZt+/hk57NOJrrxgUKePIAvvpsWz4/k5L1tDPzaGUzQcTPZ48beP+TQY7An158BkEguPeAP9aL5FCjhBKz3Xim6EqzF/zPABGDUyocxEBOIPHPb2KPYwQBwHtMkhxkMlZmKyvPk4Tu/XnVexpCzz3Ae2lYAKdISe9jglcPvjl68zVzDFtGSicmzOk0U82hBhZEXMqbF4v4ynj4UUp6Ip48/Ykudx2vc+m3UzgC+CQdnJKE1lzNvIEA8b/8Kj5EiB549TtdSxvF8Qxk87n+Mgf3zTqPpDPBbWQbeP6GarmA8/7zhFfTRTgaGHKO5duPeddfd8dgH3/S9G385KCdZ6nTiwFHCgQT2jpKBTN0YXBxY8+1Lbp85Y9xp95VV0smd7XQHx+V9ZX0DXT5lKtEDawvJjnGYYhnHfGFLFrnaOhhcwWuHZLzX31cAMgAVS7kM3uO33fWF67mO4au8cDMGTnryHawCvoquNdPnBTcZQAuuswJAkYMIOjbxVWKyHcxlkEsOwARgpSjezrx7BsKcZywM+QDLCD4VBKfpWjyAVPAn17BpfyQ/HvqNQynonx0W0Xr6uoatvyvW5Po1BZ/SNU+LgjvZOtYywitXRuMPy/bweOHuYTlVDdod/y3HIZI348AGbt9A/6bx+OMWE6R3WTyb6L7HCl4/HMDhFDvPmT6SLt2yhU4fVUvTOV7zlooaemrbfnrvz24/7Yu/WX7n4JppqbeJA0cHBxLYOzrGMfViEHDgfS9YOuPi8467fNm8ibxP10OX7u6ldjbOsx5cSz+YOJmqeFt0Cww5cuEhIS+2XnETw2y+lovj7gphczzlR7OXB6k6cFMDDmPAizeOt/lwAADgTtx8AHoan+cPXGQ8bqo+QsJkjZPDWMh2rYKw4P1Tb6CNlfeMhbtnY+BjHrh4gHOAWpEMlCgjqV3ACwBR3wdsxaL7+l1ZDy3dV0FfGnUavWbog7S1iU/BMrguSi2Te6rXExP1OzeRdFzGeSyFl/wPmWjsZcmbwzVyynO/zSzbz+gnQDc/jwUAbj9p4bGfOKYgAwD+SOwMmcFvJ8+nugkjqHXdLvqPSTU0lg/m/GHyRLqhfTdVi6dvz6M33b3+46/79g0/GwRTLnUxceCo4UACe0fNUKaOHM0cePRbl9y9d/yoE0+tLaPNDe3UwR6wp1eNoU0TxlL38kcLnhp4buCR21VfSLpbzx4dxOYdN6MAYATcsWFHao8tDAZPnFfw4mFLEPn08MJv8LBl8sHhBwVC3nNUdKSVy4RrzbDlK6jPDYuCPzl9aqAqBwgFrxcez/PkeVpMheUAu8zVZznt4CuknMELt4SExMxKMp+2PX99Gz1zZyX9z7xeOmf0LPpL9V7a28I8DPn0FFCJNxDeOjzbHy0lymSAdI6nz7yeaCNcw2b80TESDyxoEQZnaRFPJPMOV60hXhKHcfikr9zgsYC3z6UK/h0evke30BK+Qq+Ht+8fmjWNftS+h47r7KBTyrtpOQPeZR1t9IFf3H78F65czvEA6ZU4kDhwuHMggb3DfYQSfYOWAx94wdKqlzzt2J+dtGDiRV2cLmVB2zD68she+vWeNrq7vp1W7WXPzOmcjJjTb8iBB3hu5vE2LgAXl5cYu2ZcqzWqsCULoIYtPRh5eHVQFtunyNM2bmThlgbBRAaMIqBWlODYASh/WtVGDFuGmWvNzGPm1U4MavI8eQPxjA2kHldGAKeCxiBh/J2BJPzFzRWPcaJpvpnishkvoNfcX08nn9VAd1fgYIn1IY/eHKBWJMX9lYn67K9hE6zIv5vzT/Ce8dZAno5j5kCJAkLbfoZXd9veAvCbyDICWYAc4FQwrsqDpxcnsM9aQies5gMhU0bTNT31dFbFaLqsvJnm88Jjw4a9D15z7/oPvPmb1/9x0E7U1PHEgSOAAwnsHQGDlEgcfBxY853XLW+eOm7J8dRVvaK7nH7z6C76+MRpdNGoCrp3VzM9uo29dPDCLWavHTx7uMKM/1aw0e7GFl0HpzXh9BsC8uBpMm8aDH03QB8b+P0MBFFWtm5dGWF3fEgiZ5tRHEd6rZnFhwW3krqXxBulaiYcvPAg0sf6GXrJ2361Z/I8fQa4SpVxgExi8gCUsCcat4Ov9DvkFURiYz7R+syOEfSVk15FT9v8K9pz4kzqxi0W0pc+vIr9eelij2eeNzDk7uN2BDQbL7WfAHh27Zrd0FGUkiYG7DaOWic+4gAHYjohE5ATbPPjrl14AHErx6mc72/1FhpfXUFTW5ppxYQJ9JlhXfS8Pbtp0XCWHQaHHc0dXR/71Z3zPnPl8vWDb7amHicOHP4cSGDv8B+jROEg4sBdX375z05eNPVV1+5ooS+MnUD/17aHXkrDadKOPfStEexlgSG+7WGOydvFOdMY6C2ZVYi5GsXAb80Wqqxvpq7j+DuccLVUJ3nbrSHWC9t+MZADw2OPVY6nyQAhkgsLpojKyHcAJFqdgKx4MKN2/IlYKToQT5+BU193H54x8WyhbORds0TISHeC+2fhBQWIGlVHT/nrY3TrUzlHHTxfAIros7wO0qMYSIwZEdUjuI7ptNPAMb3ekydlFdQVafQo9q+Upw90YZsf1+Uhxc4cPt0LD98uBoALkZuPvXwYCywQ+KaVZWXdtJtv5vjq6DJa1dxBk4fX0mvL2mjLtoZ7/nTXure95dvX3zGIpm3qauLAYc+BBPYO+yFKBA4GDmz99iVrJk8ZdQxi7fbvb6eXbe+k646bS6PvfoTq2TtXxka2Dd47XIXVwECED2BU7qynIRxr1djCsXp8U4IctMChCnh7xJNnKMuAmIGiHC9dBqh5b5txX7+Tuvk7SaFiv6G+CPwYcPLeKMl557cf/TPmscN3HviUoiX2ysUeLPN6gQ38m3geA+rM0huAk/OeAWTBA4ptcFxPVsMAbwYfdMH3QpLVZc+AFwZM8zyT9p0xrRS9AN8G8vz4GX/c7xkQr99Lomh9PqSfsbZi+vxn9fSBTziZyyld0McyHq8h7MlsRlwnbuW4f10hKTcO/HD95wytoIf59pVFk4bTn1t2Ux14zOED++tbGr74h/vO+a9f3nH/YJi/qY+JA4c7BxLYO9xHKNF31HLg3y5YWvHq84791YnHTX7xV3fxbRejh1AbJ0H+aXc1/W1PC/UgHo+T30quu/V8kAIeu8m87YpYK/a6VN+wgkZzUuQdCxjkIajeruPK8+R5XCagJwdsGJ6QsnmeJq0EQC/XkweQYaDHACZAiOIgn9tOSADwyzTqAJO2ldsnBSkl6VVQifZQpqQnzwPAEvTKHb0Ai6gTQMrzzQM8L6YlvH390Zvx5EVjlBeTJ6lWtF3huwJwgNRAWkyv9/SB3TmyIDePAPQ1UPmtD9KoIVW0d+woolkTCylc5vAhIAZ7ozjtS30jg+Fj2At4wjF0wcOP0cWTh9KretvoFzXD6JXURvt2Ne247KZHLnr392+88aidyKljiQNHAAcS2DsCBimReHRx4I3PXFT30QtPfGDatDHHALTtZk/eyd0j6bhxQ6l5yx66sZdjwnCTxZmLePuMt2sBrvgmBDk9i0D6HfUFI494ORhaePUsnYiBMMFD5rkx8KbTXcBXDPaMx/g+8qYBPGQ8eYYkFChkgIj3eFm7VneO98+2bQOtppIMMBnYium1diJ6JU0JumBATkGQkKB1Cr0ANB7oxNuynneun6WuNQsiys8N6Bo2q5MftBQp0nXrj+ed0ZLHA4/2HJq0r4OnL6A/bcMIxjN5nlR8je1sBnOI6XxgfQHkbWCP3vRxIm9V7FHuxCGW8SMLN7HwQuUZwytp2rZd9NO5x9AvOvfSed3tNK6mnPbuatz/7asffPqHfnZbytN3dKmz1JsjhANFETRHCN2JzMSBI44D//q84yvu/8orb/zee57Vcue0ycfAmP66s5K+trGJNqzeSleV19CN2xppNG6u4Px54pHa2VC4jxYpVZALjQ8NTHtoPY29lTNeIMYK8VVIngyQAPCCZ8TzBtDBn8XoG8hRgCEeJEx9lDMV4IGPbgNLHfxPbs4wIGLl3LZkfK2ZbHHq75lrzSLwJ/QClOGqMPPGuWdlhNEvpVe8bHkqCwAPfRU3XiEtSWYZC1oEAbotUuuPiVFMrwOdlm4GX6Fu40vYKg/ISulV3gq9GXde4XfLmycgj8uIJ02/F3JAiwPNIc2LlTEeOb74LdsAoCN6gyzYGCrdJgvyV/mCmzjgxXtoQ4FGXNf2yEbezuaFCP7xKd7OSv4OHmUsSDjOsXrTDrr/1tX0445K6mb693A3PlnGaXx4MXL9sFHDPviK0+9ouvwtuz7xmqfwqiW9EgcSB/6RHEByqfRKHEgceBI58ObzF1d/+AVLV06fMXYePHlfXr2H3j9xOn1sSBldua+T1o/hWLCRDNg4BUoZx+y1jGUDuooN6zwGeDD6SJMylg9gwKDz8/s4RUo3DmAgdg9ePRwWqGUD7F+4GkxsuQIhcQ45BGSnOA2LyG9q/IMnTz+bJ8yAQJEnDz8475MHWgFg2O8AFPE2Jx5XcCMpRrQjmXrkFELhNykKIKr1CI5TwCSPOlrsszyDug1E4QdPizYWDqsY8AEYjegNwJffgFeS7NgAnw0CtljxrNGLvzYmRqI9oyA1tBMx0Kd5CR7ZPHr1u0xiZe2mkSVjq3w0RodTv9Zn/h3jgBtVAG5xUAWHVnCXMmL2ELs3bkQhPyMODGExwguXLgZ/zZN4kYLDHItn0ice2UMtE8fRLPYMfrG7lnbXdNO/DOke9+FXn/HIJecd+9Af/r76Lf/645tvysht+pA4kDjwpHAggb0nha2p0sSBAgce+tqrbzr2mAlnXl1WTdM7WuiaMjZ6fEdpJwO0D62uL9wJC88JjOacSdTLNxq0V7GBHc5xembzOcGtGOhFM7nGGmqeyeCwDWlB2OuCmzJgeHGfrYE7Ax4GSnB6FMbaQFAAA86zY9eECaCMAZl6xQTTKIDJHOhQT1HwFtromwfJgSG7Ii185coIGFOgZilbPAgN/WIa/O9FYMsAHTxrBnY8LQYcFWTl0WIHSwRUOtCWubIMQE/b8OVDGWsHZTS+LhwU8Tyy9+C78iBzNZ2j17Z8A4h24FJi9kCvB4sRUIWXFrJgLArAFfXYOAPE8nuAOeRvRLwogN9cXnycxE459JkPB9HWvYUTuzihzHLbwzF9LXgO2778/bY2fs85IP8NjfGJ3rvKqqh2Vxs9b1wZTR4/4rjjX37mDZ3PO77zI1cun/2ZK+7kuIX0ShxIHHiyOJC3J/JktZXqTRwYFBx497OXTF35tVfd3fvHd/ceO2fsmR/Y3k7/VT6UPr2ugV66r5wu461b8TAdw6ANKS5aeCt2KBtIGOp9HAPVwjny5C5TBnST2VMyZUxhq7aVv8fWWQN7WuBx4aB4ue6Kbzk44Fgyr5ZjtQAet0UnwE9f+A1JlQHyUMYOM3hHlW0J45GAI0q0k3FwedDh2vO4K7vfeoAuo1e8Ya5SvJWDEkqv91b6Pnmnmbw/RFqsnkBCrDIBzlC9fi9AytELWu3krsU9ZnhUwHcHXgBr+JSjmotoKdEnz4e43yYLxl8Ze/cyetEfpPY5cW7hhDcAHsIGIJ8IHUCi7vl86wYSeQPcgRR8j2TeHHcqsjuBQeB4ls02llve+v1lWQ29rmcYJ2SupUt3ddF/l9WW91ZW1Hz69WdtXfv9S2/97hvPPmtQKIjUycSBfwIHcrTFP4GK1GTiwFHAgX85f3Hlv79w6SNbJoyd/TS2jxub2qmTDf2r2cDdPpVBHQ5Z4AWDOo2D3HG9GTwnSO/B3j549mgle+vgfUHC5GkM8u5YXfCc7OeyO/fxPbfIf8aGF8mS5VYMJDX2aMG8UB7tGLjDdBdrXwCPAlIUNMVbqzD2T+i1Zga4MkivQIuQZa6m+HcFleEaNgXK8WliOS2rfQ7eQM8X/j1cw2aAynu9tJ3MFWsyWFkgZgc7AoDModcAKf5iLHNz8elQiAbG/yIPnPAFQFfHSNrLoSVzHLcE7zJlctoBQLW2wjVsSpcBWbliTW9fQQgBwgdwMhyyW8fCjq1deJoxjhNH84EN/nzLgxyKwIAQN3Tg+j682BO9tKKHtk6bSDv5359ad3EEQxedydew3cyevzM7Wnv/8xd3TPzClXdxIGB6JQ4kDjxRHEhg74niZKpn0HLgPc9ZMvI1z1ryp6XHjD+zi8HbaS119OoxVbRxTzP9iTHamjV8gvFpS/iieeQmY2OIe2lxK0EnG1kAPtxgAE8LTtauWF8IgEfuvONns7eE4/JwMAO/4y+AH2KkABAO9lozAQz8P9mtc56jzAlTFDHg44b0kJId53j/Mtu/BnRiNaSARLCLglMrYqAnvoYtgCajOQY1ebQcSpm8zRBXj8U7GhlFp40B4AA8re/ax7xkx0UzKgeoZcrEYA8/OnoFOEZlJO+hljGQiq3ZgL2V8fgOzeN0Lq7bw3VqODQEMAv5AcBbyzuxWIggxu/c4/l+XS6DcAQAdcg0DnPcs6ZwRdt8vqrvpPm08IE1tOyYCfS11r30nJqx9PuyJppcW0FrN+y+5693b/h3Ts58w6BVLKnjiQNPIAdSzN4TyMxU1eDjwNpvXbJ228zJs5f2tNPtPXyV2aZ6uqe2moaMHUbtHc20hgEfTWJDOIxPJWLLDEHuSFcBUIAtMNzUAAOKhMl44RTkaDaQfCK3mo1jB27GgBduHMf0TeY67Lqv4OWJwUfsJVKPlm3dwmMXYtzEpVYw0OFkqW7lxqAsnNS0MdZnMx42T4v9nueNiuvwZVAH6NHtRbnWzAEUwR6OXomBsz6bJw+fUY89Z3TFtFgZA16CdpwQ+3qsTE5//Inb4BnT/ssfT6+ORzhxCwCWR28M3ECrB8XxOCtfMlMwKmPxkMETzL8L0EOfQJfKgm2do18C/LBA0DIjeKExnLd3cRBjH3vs4PGD7OAfZBzxpmgWnmqEHeB7bP0C3OE1n2/jGMmeQZRlwL6qupY66lvp1PoeWju7hq4r66AFvF188qSRJ/7L84+/nr3l9J8/v3XmF35z98bBp11SjxMHnjgO5C1Tn7jaU02JA0cpB+780st/3ft/7+ltGj5k9rv5pGFzRzd9uqOKHhnCQI0B282cRuWurQ0coL65cJ0ZEiED0OGyeRg7AD2AP9xWgEMW2OqCfUcM3hA2hMhbBpCHgxuzeHt3KH8n6UViEGAuGPNcGeBRxgMcyjYdQBwMsQcMWlaqgLEHcOL3AgyjejIR/QoAisoYIPJteAEw2o3WHOEAyJOYPIBSQaXFL3H2gVZVXwYMQ0nfTh7Q81X2QUtm+zOvT9xOiHlT/mVocGz08XqZwxd4IAap/jurMB7nUoyx/kT0ShMOtAWQ7Os3UhQEyi0iPBbhFLGCOtCPRN+Qy1kIK+Bxwl3CWDQgLIHngqQMwglxkAHPNWSXF0HE9+tSNX+/nb17OADCi521u5tpbRMDQ359vrOant87kq7hg0hvaq6i39cMoc+/8ZwN6773uge/97bzzj9K1UnqVuLAk86BUlr5SW84NZA4cCRyYMM3L3lsxrRRcxCD1Myei3/d2k4/njeHhjKoa4WhY8PXje0qxDDtZq8Hb8OWrVxPY9izsQe5yrZx8HptLY3u6KR9Y0cUtmU57qn64Y1UtmAGtWP7FluUiM3DCVsAmeCJ8Z4ocE/cRg4s4DstY96ZomvCDAzYVilAitVjIIM/+xQouTdmHAQtYaBL0QtAwYXQppwENmAjqE6fRhlVV8GTZ/UpKAkpRWKA54GwB015XkcPcvFcnifPbTPLdrKV8d43oxf89eDThgyA1ngf8+VQ6bXxM4Y7WZCv0J795vgQtuh9u252Cvn8P/k5KiP9539YzODULp/SLWc5H8dxfTshN5NHF9K1LMVBD5ZnLHwQ5zeMPYRTOW4VcayYB3xql87mrd97+MaYkcPpab3t9PcxY+hlY2rocr6GTRyQ/L8unnP/71d3Lfzsr+/iYNb0ShxIHBgoB5Jnb6CcSuUGNQdu/+LFP+29+j291wwfMecLfLL2t+vr6QV7y+nyrft5Z7WXd2I7qZsNXzfimXBqEbYcnozRw6mMU6NU4LspDABPPU4OX5TXs3HkuKdybOEycCxjD0pZPYPDubzddezMgvcDXqPM4QvvASqxTvMJeyW5cGzAuQ4BgmI9HdDzoJHfB8+ZAqkiUBl5mjJ05gCKUqdu5fSn0iiHGaxf1lf1LAlQyQOm2pZ4Afl3Sc4ceyZjTx/KxeLsyxjPckQ+pHyB57GwBXpm5SR6egXHr5Vh61mBa6DXAzBtA9UHevFIDNj9OMfbt5630RgUdUnHWmixsY7oCQdjfDtGj9HLz2cSdbvfwQ8sSHBYCIDulAVUxqd3Kzbz+Yod+6iihufAbPYA7q6X+NSJ9zKYW8EAD/cNczqXsukTqALebqQPYiBYxt7nYbwguuHBrdTLh5J+VVFHL9rcQX/klEV7OMb150NG0GfecNaqbd+/dOd33nrucwa1UkqdTxw4CA6UsBgHUUMqmjhwFHNgwzde/eCM6WOPQ+xSPV9r9pyOoVQ/aQwdt30X/baHPRW4MB7XmCF2CYHoG3fR6FbeomXQ1sHAr4UBXy/utBUvnYIQ85St2UKz2ZOx/tRjqXcqA0F4/kKqDmaq2PIY4Hlg4KZvrifPD4zWI4cDvCeplBfJgZ/wVuuQ9Cw53ijDHsEz54EF3gNwGNhUkJPx5KGMq7fIk2e/90W/lpGDElpdwSWlDDWa+MfMoZMYZOozoNdO+aJ/mbt8y2hkZxndtnEBPTJvDL17/+20n0+a7q4xRsgDOggxmIzQJrZL42THmXkFnvMzmaTQnr+ujz4mr6i8eyYjC9aYb8f4HcmRpOLJ80waf/h3DlOo4Jte5j24jlYtmk2jecu2meNVe/lwUSd7/8Y8to16OWdfG7+f++B6epCvYhvOW8MNVVVUPryOerDNO5Pv4p04kufUHnr5sDLatKOBbjt2Dl3TvIMW8B0d0xjfNze2tnz+f+89+eO/vOPho1gNpa4lDjxuDiSw97hZmCo4Gjlwxxdf/qtTF01+2U85Du81fKH75b3VtHFbPb3/MQZ15y+Tu0LH7thDe2awQWLvhJxExDYVg7rJN66gMr7ubOsZ7MVj41WItYu9TeoZsoMTRaddFRwZcwPwi0ACqrU4PAFhsZfIG+wDgKZSPX5dSN0SwJAfSb996WgxUJkBPfac2yjIpRdeNwAWpSM3RYq1FZCjgjTf7zzPW96WrAIbOXHqwZ7xJALL8jFqx+gNsY6uHf6ubl8rHXvzWrpn2Wh64dpK+uBZF9L5tcupoZU9tuGZvA2UvO1hA+QKVGOwV+QdjcYoAHAD9KggHkcPfK3/pXhnBOTIAh41ECzsjOQSV+CB54jl4xs1JnOYQwNuhzntWI5pLaOp199HPZPH0bZTFkpsasXqjTSWF0c7u7iesxcX8kzCK8i3yIy6aSWVc9jDXsQIcnLmH7XsofvLq+jLPU30i94aelVdD+3c0bjl17c8+sp3fPfvNx6N+ij1KXHg8XIggb3Hy8H0/FHFgQ1ff9Wj7MmbS1Xl9LN19fTaugn0uvHVfP97I91WVUstTRxTN5+37B7aSFW1ldS5mmONYLDwwm0YI3k7axLHItWwl86AUWZr0oyi9/gYnjFviQdP3iMUgRGfNiNzgEKfKbrWzLxVPfTp7VOo8bjx9Nk997DR5u/LcTBfkKP+i4FCHDvnjHvQIoVtTRcYVnhroMDoDcAgiq0LKV9iL1gftGSSDxso8YAGQFtBJq6QC9vaBvhcnw1w29am4E0Ap3jMdKt2H2/F72AP1NqN9NXR59KLpy6jD+38G/38pOHUhbYCLw1o5nn6jA5fht8Xefpy6hDvmsqKgFKN0ZPvIh6I6KAvJlulaPFjonUULURy4ijhkTbPpPcYWsoWbNWuXEeTtuyi7Qv4RC5ugUE6FiMVf5FMnD2BcniJvXtyYpf/VSEOkGlob2QAfd4JNObWldTBp3o/2rOf/l/1KPpVdQtd0MPzksHkzt1Nmy77y8qXv/fnt916VCmm1JnEgcfJgQT2HicD0+NHBwfu+tLLrz752Mnn/5pjg17Wvp9+Wz2Utm/dR2+vGVM4TYi7apEYlj12dBLfXMFbShKztZpP28Jo4dTsaI47QsJZwSrRzQQhebDnV+QxgbHG9qOAC93yjFOb2J22MMAhbYav0wy0x1zeG4WquR+rNzEwraNzGqpo7hmn0g92PqBgQeuyk64B08UeQ20nHOQwUOQ9PNyHEOOmwCnP01QESiPvmj9BLD/l0DKQMpabMJd3TL9sT6qnMxxsyfEoouvIeYj4TMRe8gnTp+4qo+ns9P3laZCDkYWTz5mr1KIxkgTPOkbSRI73zLZj+6LX5CMcWollwaOpEu0YjrS/ebQUTfMcekUWtFN5nj7UgYMcIxjESf9z5B/f44Q6g0K5kQNxqwtnFFIUYf5ha/dGllUASxyI4hs+PlTTSdO376FLxtfSXo6F3FhVTYt37+35xjUPPv0DP7nlhqNDQ6VeJA48Pg4ksPf4+JeePoI58OanHzvu3y886fr5M8YuRrqUr+1opy9OnERvX7uePjJsHI1saaGdoxjA4eTg9n3sdVjPKSc4790p8yX4XMCW3WaBNBMIug9bWzEgydvKK1VGAYZ4whyDLWFvADbR8xnQ5Ke2lrPDG+u2U9l9j9KbTn0BvYYx7EMjW+lflrA3JbNN2B+9zgMYYsRg7JVeaYs/iLcnfKlAzcpoHUVxZf1tK8agxdGSkccS9QSPK4CvkcT0mkfKtpcFtziwFydDRowmEgSjSBXTAPC3cHbBK+W3OIWmg+2T3xIHL/mfz0QTZMGBOe/pw9cxvULH45FLGzfwpcQ2v4FPeCbh1bOXOFAhDxorGdPhZdfKwMOH1EVWDw4t4WYZePja2QuIQ088F+v499btDfRDvnP36009dNrE4fQNTtJcxnNy8/aGTb+7ftXb3vmTW/50BKuqRHriwOPmQAJ7j5uFqYIjkQP3/c/Ll++ZOvGkczgsaBvf48nnAOmDHJ/3u+m8xYTbAeDNg3FBWggAFhiWUey1Wbm2kBjW7gvF72I/cXIWBs2Mbzy11DiGMvDc4bnwRQEQyEdzscDTp9eaoXCeJ6/UtWa+WvPS4eAI+sF9q+STj9/uWkR/rW2iK2bhflPefkafcCLWe5pK0msA0vqMfoNeBkBGq7Ag8oyhb3nXmnneidcHwFG9X3kAxXgXyuSAqQx/Y3pRN2jRcZOTwDEQ0jEU0IT6bZtaJR4/w4OH7UekE0EaHembjaOThTx6c+XFvvRb0vgOXlIAUq07XMNmsqL8N3kKANrTomWE/Dy56+P3zCMuBCEAyj5kV5Iz+y1+kwkDfl7m0UeVKdlCZ/7uZU8fb+XKiV54zpGXEmlacIgDN3fgRhpO3Lykq4PWHDuLWjmf5Y0tO2Xr96SyLrqTc16e0dxEH/vt3ed8+pd3pJi+I1FhJ5ofNwcS2HvcLEwVHCkceCt78t7xwhOvPm7W2BO72XBe0FjF2VCG0YI99fTVnlraxvnAup/GRgRpITAzEI+Ha82QQgVXQOFELZIfT+Ot3Gp+j9suggenlKfDuJMHJOLpFwGSjPcGGAIASA2hGF99PrOFyXQUzWo1zngeefzggeK7eYfv2k+V7CXZN5VBCqeIkQdLeeFyD3E4egPIcI2H3GzKgxDL5SUm5pvv4MHwbiD1OMBtMW4ZXikIse+KrjUDi3LasZQwA5YF9Ks/eXFlBD/y/8K2sH42zCV4z2QBzxlIhbx4sO3BXKkxOAT+CziLFjPmyTMs6mP6SsluEU+UXiy28Dzuh8bBDbt+DfwYxuBP5iqHJQAQLuP0L/Om0vQVa+nVC8fThY376D3DxtL/9TbScL6Gbd2mfZv/eNuad7zr+zf+/kjRW4nOxIEnggMJ7D0RXEx1HPYcePBrr75nx7xpy87taqMb+Vqz3Xyy9iUttTTvuMk0neODrtul8UFn8gnaVWw4cGMFPAY4Acj5wIhjgsTbhO8X8OXuiBeSmx7MgEYerFxj3lcZNbIhjgnAS6yiGm/9W5TsOAaRti8Z3CNah342egFa29ijh+vbglcr8hJlbnbIaQfF5QYJBYlF9FrMHooYkOyLXkMveVuexjujMealpz0u4wCM3LbBn4u8pFpGYgw9vaVoUW+bFO6P3jxgNxBZMF4Z7wRB6XjiD7+3GM7c+MCwR63zM493eWUOlV7n3QsxhNamyoKk3uHvhN5SsmDqxNOLMdO5hjRHuIINHj9OgyReeNxOw1cMysl4xNXyAarFo+toW30L7Zk/g/7StouGs/f1KcM57g/V8i0f7/v57cs+/5vl9x32yisRmDjwBHAgLzDnCag2VZE4cHhw4J7/ecXPev/07t5hY4cve1drJW3v7KWvtVTQdXW8bcl57R4tqywAvcfYi4dEsPAeAAgA4Al4UYOKez7X7ShsHTVysLj+dKCXBihKrZ88+IrKiM3j9gJwwhavL6PAQKrAlNW27FqzjPPG2tFyGYMKQ6v/kMR2Guf2Q9B7ePmKPL3RWIqniUGTHCbhdiQBcx69/Lv9Jtu3ABGxp8nTi5/j373h74u3NiA5ZVCnJag2eovEU/ni+zKga81iYDQQeg20ORCaoUfplS6V4q320+iVa81iWqJ2ctPyeL4dquwyveLVNBCNeowWk12VFUvkLbIOnvuOx3PEg0FtA+3AC41FGIAdDkchNAGLL4RdwGuNa9gY2K3c1kB7MG85tOCdnbX0wvJR9NfuSnpmWx3dUl1Hn3vT2fc+9v1LV3zrX5723MNDWyUqEgeePA6Umt1PXoup5sSBfwAHHv36q++bO2vsCTCW+xm4fXpLM31mxkyq4K2enq17qRexdm18onLRjMI9njCWG/i6J8QC4S9SRcCA4EL30bxV1MReMN62rbxjFVVNH0+tczn9CrZ3QyoPb2hjb4l12Fs2GHEYdecdDHFW3uiZwTNgEIEh+ahl7JaDTOxZDMK858/oOggPkN14ERIAW/tGn3p3wpaw90YB8MHoa7sWS5jxjB0ELUGOPLhw4MXAD0iQIH/jK/46L2LuNWxauYFPoTnmnR8TP2Z9efqMvqiMgGGX81Das7Eu1Y6XBQAn7WM4vWt97q+eQ5RdO2ktY+1lPKI3N3mz4kG7Sq5f2TW+qazDM3jfWqI5fK0gvHwYayzU4OkbxfP1Ufby4dpCHKw6g3P7/Z1P8PK906c31NPts6bTW4b10rf4EEdhMcJ181z/z1/cfvIXrlx+9z9APaUmEgf+4RxInr1/OMtTg08mB+740ssv6/2/f+u9f9SoE97SPZSu3tJI5+8rp6+vZ0AHbMWHMXqRrHUTe/HgAfBJfvfi3k42GA3suYNNQeA9vHjswapkA4orzXo5bq+Xk8BKYL54/WCTIwAWwIsZvVLeJvV2IAg9gEarC8bbAIR58xywM0An26P4nf9JthfvDYmBmG2dxSMQg4GcEbKbPdAWYhUzsX3aTojbs99dX6xKfw1bn54+5V2upy/2AOXQa15SjBHoPXBMWAsDlGo9mS3miIfmSbVcfRlXlAfivg1PTwzWS6yvxUsLvsWePMdbYWcpWUAXVRbk0I7JZQQIhTTQAFmwvsb0WhmT34i/4ilVWgK9Xu5K8cW+17+yYDDZNXmP6fVzKJpnVTxvGMyVcRjCxHvW8Gn5DbxQY+87whKQow/FcZKe53MZewArOFzj9rUFz9+3e+vo2Zs76dayarqvq4x+WjOUPv+ms5dv/t7rtnz3bee9MEei0leJA0c0B/wezhHdkUT84ObA2q+9asXsmeOWwIPTyteaXdZZTX+vG06tw9vptk6kbBjFnjwGdxP5L/J34aACVv4CXJh38PJNGMnbmvwZW5swFhyzN4o9fA1jhtFkvvppG28bdZ21hLqxdWSnOMVeAvQBuHkjCUtjHhM1WOKAUqMmaShszGKwqHWGU7mlwI15btR+u49Sc961ZiG/mxX2XhhHr3cK2eGAQK7RY18o6A1xWp5eL5euUmmW/+d3GsMhEAccAAYCbz1/I29UOOXLzwZ6la/yx/cT9AJoePCRB9aUXhlXjC9AFP8N4+brdH0OdXtgF8sC+u7okxs+8PIMMbkArca7vraAtXwgS9tAP4MX1erEdx7M+THLkV1hAWRXWVkku074Mp48347nRyy7KkNGXqkr+WQouexaTh/E83xsWwM18FWDu885gcMs2JsHzx76irQtOKmLPIg8z3s5NrUbizxs9XJeRKSxuXroCJrW3ENXN/TQlvnj6dimrTRq8pgpb5o86n/fcN6x9KEr7jz+s7++i12C6ZU4cORzIHn2jvwxHNQ9uPXzF/2898/v7r1++tQl9WwoftNRSV9pLac/3L2BGnj79rI9HTSeL18vA4iDkt/Dwd1t7LHDIQu+ggneu4kcrzfvnlWFk7bwsADsIQ3JvGmSvqGME8FuOu9E6kLmf2wfIb1IiH9SkGceCvH2ObAio6MAz+KakI+v6NRrsHJAac64O+N8ICeFWtyAvgptCCABMFDgUsq7E4w8gJRHda4+xOPZjRZygEP7ETyHOeClpNfLgwovrgBOnl4PvhwtQq96gAK9UT24Zku2E7kc+Jt5GXgxOlDO+mMFhVnK1xgEuTGQgx0KFoUFfqzdGJgHrohelQXw1+gNV7n1N9b+9xj0GZByPATIk5Qn6sWTx2N68Z3x19fh+G8gD+VQX2YR4uvTejKg37fp+F8kT5HsivzlzCPQgK6PGMpzuoLKd9XzYYwNnPmIXY0Iu8DhDWzn4kQ94nDh/cNnHLbiG2+onecuEqKzh3/qvavpV7evpc3IbsRJsu/tLqcf8LWIe1mWfsbJ1T/zxrNXbPz+pQ+zp++ZkUClj4kDRxwH/FLriCM+ETx4ObDua69aOWvm2EVQ5jdsbqRzO4bS2bPH0MSNO+g3Q0ZSD1b0xzKge2wrVTLI67qX43uevpRzdbEx2LmPc+Xxb/DwTR5Nlas2UwXH37VvYwNx9hKih/k0LrZ/zGM3iwPBsXVrtjaTAiW4UbJgwRLcir2CIYORtAriZ1DGAIgfUw/E8toxQ21GMadMaF9tevBwGVhwZNuWaewZK/K4qRcmACZTI/3Ra+UioBgAiAFlBQjBG4XnInqBYSxmTHZAwT8PmPNARswnlOmDlrD169oOw6NgzTx98tGrUwccZfwBXozXnlYPLD0tDmzl8T8z9T1I9X3M0Q8Dkl1Pr8mu8bMv2TU6fLt90BaK55XROSHziNuU8QbfVBYQRoHHOLly2cr1NGHrLtpRyWBuDsfS4o7qJp7/AKY76onmTi28X8MJmnGaHv/YO1/FIRu9TS3UxQe16MR5NPSmFdSz9Bh6X9M++q9R4+nusnpaRrwg5Lb2N7V1ffl3y0//6BV3pZi+wWt2juieJ7B3RA/f4CP+ls9d9JunLJry4ssqhtAl7U30yyo+VcseuVd08V+87n6ksD2LAxbnn1iIu5MTe6z0kZIBsXrsCZBL1sePJDp+NtF6jvOBNw+/nbmIY382cp28+sd32BaawadWM7FjurVo7A+ejPBFNpav5LVmKO+noN+axG95jvf+ysTeEDSh249CngENA0bajqSRUVqK+mN0xmDG15FHb0yLBzYezPh6tIwdrrD7XjNeQ3hXmV5hj4HkuA4FC5lDLzntZKbQQOgtwX/zQgm9vgwArAerefyPwbKV98Tl8a4/WcDzUZmMLIB9cVsm2wrAZDs1bsfTazQeAi1F8p0ju+KVdADWAL1tlUu8K9OAQxo4ibufF3CIxUUYBnvmcR+vxPBN4wUbvPtIyIwr21DncTMLp+uRDBsHtm5eKSf0RbZOWUBf6GmiWtYt7xhfQ3eUV9FQvu96wuZdDb+5/bFXvO07N1w1+LRv6vGRzIEE9o7k0RsktL/+3IVV73/JyffOmwFPXjn9Zmc7XTJqAn145zb6SHcdDWVd3lDN4A3pRLCSxxVLOCkL4Laek7BiKwdpU+DNg/FqYYOAhMmz+STfPP63kUEePB5IzHoWPwPQB+/Ag/x5Nt/FyQmIBfiFuCoDYeat4b/uqwMxY26AfNxZf9eahcfywF5sEA+mTESvYR/xTsHzqODJPFEBjxkINNBXCqj1B07zAEsMSFyZALDhXQJ4A/0OkIbtRNThgFwmZqwUmD5IWjJzLQJzBtgtrY1VHWLclC9Gb8ajZaA05m0ffAm0PE5ZkCac7IbPNo7KOy8Pj0t2+wGEsdxJP6O5FidnNq+uXbGGW2JwAw4AKhZ3ALe85SuADyeyJbG4XsMGYIcDWbiCDQnTEd6B39jbRyfNo2quq4MPd/x0RA+9s7uG3jW6ij7ajkUg5+nc29zww6tWvIRz9V07SNRw6uYRzoEE9o7wATyayX/Ls5YMee0zjv1r1+zJT5nLGforeMW+iZX/D1gXf3P6DL7nbF/hwnRsy+A+UlylBEVvVyjh9gt47RDHM5k9dPDkYauWA7vl/ZptRIt5dc/B3sTXh9Fovg6NL1ZHQlbZxt3HHgDcgcspG+RgxxCc8IOBZUMi2MK8NPCcqSHD93Y1lMVrySwr4Q0MwfzesEVgKoADjyht6rp2rR15PDx0gN4AjuAZw7VmAHj8kIBY9Mn6g645b6CP0QrVBkRTqEP4oeAjj14pE4OTyJgXEF3hX9xOuIYNnpwSZTKHZOK28ugtAdos9tHoLeqz528OLajWnpX4wXiMvGfMKleZChrZjXXgry/jwSrAsONvLr0mUzmya+l/+pPdzDZ1Hr1e5vqQhcz2uBfVEvIt1ZpsqFfXFiZBdgvTsSDPzPPdDMrgvZdTuQjBcDJlJ4ihFzC/EasLsIfwjqedwIu9wg0z9DDrAQaAc9va6LGTF1LtyDq6pnk31fF4ToKTsLKKFu7Z2/y9ax8+670/uvneo1kXp74d+RxIYO/IH8OjrgeXnjN/xPteeuptC2aM4essiF6/r4xWTxhDb6nfQ+8uG0ZNdz1CnU9lDxy2Y2CYkXLhhDm8FTOSlTwrbih2eOdOnV9Q2NjWRRzeWYsLFgFbPVDuuAJtJgM5gDsofaz+T+ecXEjECgOBIHEkV8bKH569okMVaqgynjG17eWCfgqv3KussG0aD13sGSsFWPxzB1HGAJl4Q5wnT7wlrs48z1iR1yXPM3YQtITm8uoxcKQGWkA1/im/BYzF9NrnCAQPpJ2DKtOH9xLdz5MFXCXmgVLRNWzoj5MXoac/L+mhlnFjJG/5fxJmoK/Y03fI17Dlbf3GAt9PGQF5DiiHIdZ6JB0O0iCpbNhcswVMOFhSoh25ho0bwb3GWBRikYbY3KG80ANI7OR4vdW8S4AtYt4lqGA9MJJv1/ncgjFU07Cffj1yDP2hp55BZSVt2tbw2OXXP3zJ+35yy63xrE6fEwcOBw4ksHc4jEKiIXDgri+//K6mxXNOPqujlW7j03GdrGifvr2Hxi6bTads3Ul/2c2KGdss5x5fAHTwSuC0HadHkftqcQLP7qw9eR4ra/b0weu3lr14T2Eghy3bTby1ixQM2Oo9lj2EOKGLrd4bVxSStI7kbR8YjNG8LRw8A2oYZcaoJ8+8CyHuydAIvCxieQp/w1VW+GxABQbWvB8KZoLHEEZO25Eq8YzVaZ6ZnDKlPGN4NnM7hxl85y3BtpcYV6PFaHPtyChJIUMG2Y/iKTTQAnrjOhTY+birUt7AzLVmMYhUL6nxxfgfSDMAqbT05Rk7aHq9l07b8R7LELMXkKmCfZUF8SqBfXleOk9vCd4Fr6OrA29NXmyMcvs8UNl1chmuNYvkP8iCNd6fXJag1+S7L68uZEHG2MuXyaLzqqMOf7I5yIM+J+y1cbHxMJnn+b+3oZBXcwPrB/Qb3n/c1HEnn9RfMruwc8CLx5PGDqGVTR3Uzif6b2rcJid5n4pYEm67vam951P/e8+iT/ySs6+nV+LAYcSBlGfvMBqMwUzKg1995a3HzRp3RmN7N52zr4e+VVdOl+3vpdZa9qrV8v2WbCT/0sgr7fUM2pA+Aduu8EgguSvidMoYmMGA4rstvFWLmy/4XkyaNLqwMudTe4SkyTDMU3mLBvffjmKAiLqg2BHUzReoy9YPexHlUIfYJ9mTOzA0eBvihuK1kpYLeEc9DnIC0hmXYCgN/DhDmlulAT0lI1MG9Rq4cRIUbJpuzxZdaWbG0to2Q8oPinfHgIfVaZZTGzfQWkRv/IXa10Ca0RtJuxn9XtteBj05dQnZCn6lCvBGAVgo7gBn6GZcRts3z5UnZ6B9QrvBSxqPQSwLDtxnTsQage55eTTmvxHYjyzYyV0nsgHfoAk7iFPEW08v5FaZEG548eM4UFnQ8SnJ2z5kV1ggzC2EFGRCBVyF5pm0+E47uJMZjj5kzuQJbY1hHQKdgAUeFpJI0IzPWBjidD9SNTGwu3sD6xcO8wDIe3l7HbVWDqef8mGxD1cMo18PLS//+KVPffj15y+++/e3PPLud//gppsjSU8fEwf+KRwooU3/KbSkRgchB+7/n1csP37exJNgNBvYKl22dT+9czynTwD4WrGeV9Z8QAJbsydzLN1O/gsP3Iq17NlbWjhhi3gbADOcvsWpWaRWGTmEyq6/n+p4Zd6C07RQ2rw1O3NINW3m/HndnGAVOblG3f8Y7WfPXhfAHmKrpjAI5DJiEIPBVU+MeZoADsKulzd66g3JpMnwRskZcAkmt88OmEh1HqiIdXaVKGCwWWunPI0MKanEGb3iqbMCnl7zUJlht3a1TAZwWIetj9YOQJfhkoAunbfP0Sv1aX+K6AW/YZBBvoEiq8/aVtAq1RhfXBkPUP1VaMITs/zxGHneujKZPuV44LDFL8MHWbDCNij21+g1/rtOh9O54B/653h3sPQGL6mJiUd5keyGk8sme6X44uswoKS8s5QnmXx92mfphh/ciBa//W7xrP7gko2b8BeVeU9ezlzrNS9pJLsYVuNLmGsl5lGQS/QT46FjinHhqxXlhC+DulretsWdvG04tQsvOZKw4yT/X++Rg1+LduymBxfPpQ9XtdMnWvcV9Alkg599/y/vPP5zKTmz02Pp7T+DA8Fs/TMaT20OXg7c89+v+G3vH9/V2zBp3Ennt9bRjbtb6KzdZfSRtaxgESuHHHjDedsEqRIQO4PYOSjPDla02Gpp5JU2rjWDMkfsHm7EYAVcBV2NGBxWyl1YkU/mE3mnc+jfwhnUxAakl5Ot1uxqpDJesXdwDE4PlDJycyEXF7Z15UJ5B2rw3l9lFU7kOgMTYoPMcJsxtXrUiIRkx2pYMulEtExIHmx1eBnRNiX/nwMXAfypsRXvDdMSvHkBvaCA9g+0mvfM0VlAMbqdq/2RZNCOJ1ZGvGoKGMyzgt+MHtvyzXgeAX60T6gShlN+V8Cd+RHlbDzwnP4LfHNjIOOifSo6COJBnvbJ45DQHwUUsac09EfpNW+TJVj2z4ssaD0WZ5ihF7zFP6NVxyyWBanT+uQJMHnQPgVZyCkTy24m5lTHWeiN24nGW/qh9PaV7NjqKeKtk+WM7IIPTr4D7yCXHsi5cQ5hBG4RkJFNLusTdcvc9UDPaHGya88LLfw85g+ewzYutm9nTaZuDgXpZu9eFeuUasT64rQ+Tv6rF/DBbQ0SP/jJ9io6e1svPUKV9OvOCvo170x8tpCcedc3/+VpF7repreJA/9QDuRpkX8oAamxwcWBh7/yyuULj5lwkvSaT7pdyocvfjxjOl3KiUx/1MSKFiAOq+fhHCSN07aIz4NFWHYMb8OyF6+Bgdx23kY5hQ9f4HfcirFxN43ZXU+NDNjG8Ym6ek610IYcWwBv4uFCFQpK1m2nGctX0TbOo9XJ15/JoQwxCGqgxfuhCt8OKgTPmB8rM0BabzA4/nMfYxu8O1wmczWUN0agu1Q7BgbQnnnG1ChLUHs8tR1QDCcbXZ8zYMPAof2u7JEm42ccfaG4LxN5VKRq9ZZlkjcbvVHbUpX2sTCQ+s8jCv8MyjAQyHj64jHSMplDJ1bG6NXPJgsCbHWsAq9KPJMBd94ravJg9LrPmUMyGQR0oM+B3vh3x5eDkd3MQZxSspAjw8J6BYTiOfP9cTIjjyqYzMic0Wt1Y76Bt+bVzWlTqoJ8u7YHJAv8QMbTh3GM5SXiZ6Yd/g3zEx7NXQ008cF1VMUZAPbOmEg1vKOwbxgvSEdxjC8WpTjZD33CeumNlZ30k65KqpkzkW5p2ErDOO3LHHgs+QDI+35+x9zPX3nXYyV6mb5OHHhSOBBbhCelkVRp4sBtX7r4+6fPm/SGn3RX07KyLtrJCvWGjjL61N8fJXrGMomnG3f/GqqfPpG6prI3DgAMVxwhFx7y5nEC1DEcrzd11UZaOXwo9Z7JJ2v5yiPR97VVNOKu1dTMXrzuY1Xh4lRd0QlHLgxPHraIsXUrRiBa9YvnAR4HGBabHrFnwIOHUmVip7kaxyAK+nvGI+YBjAISZw8P3OWqX1qVQoIapSKvljesRmtMi/XHy2kJesXzAcNsRtc/4/osNj+nHYlXUzqKAIDV5bdyvXG33/PozduS1fLhhGkMEJ36C9uJURl4ssRTVGqc8/gby0veBkqJLWRjmT/NLd3or88Khh637B6ELAxYdjHmOfJSdNI3lpe8fpeai14OS/DfPIdh693JUwB4ns6c+Qgwx/HCtZyTs4q3eZtQJ27duW+N6KoyPsk7/9q7aUNtLbUdP4fGTR5F72tv5LC/cnpGdwfd21NOr+IwwHUb9j76t3s3vOfN37zuT8k6JA78IziQwN4/gsuDuI1HvvLK2+fNGX8awNVje1po7o4ymrl4Kp27ZiP9eASDOuTDWjSDY2P2yuK55/51hS1VnIiFJw8rZo67q+LUJ91stCvY49d196PUy0lPBXQgEepQ3lIZP6qQHkW8RupdEFBiCtsBNFvYwwKZRyUYLh8n5EGd1lNkFOIypaaUNRp7UPRz8JaoXc+NM1NDZF0KnjEzUDm0BEeL97j0Q0sAFx5NRs8EO2iG3NqOvIr42nIQ4ifJQZgzJn7LN4BEDzwij1uRJxVE91EmjC/AG7xRNindeEiwP8CsG5MMMPBeuhLjGDyqpbyr/cmLyS5oURrlcELOmJmnb0CyizpMtvqQl0z8oMnyAGTXwLIUNXBcSn50qDIhEX7elOKtV6SPo4yfa+CxXMMWg1H9zngrukT5YNvEjbzLsGpDIVUTxwmXczqnXl5MVvDvPay3eqaMoTJO7VR5y0NUyzsRl+7ZRV+dOJm2d++miZqOp5cPiH348tsXfPrK5Xz1T3olDjx5HEhg78nj7aCu+abPveynZy6c/OofVg+ll7Y3058r6qiGY+le0sDaFbF4rADlaiIcuIBnD3F48Ljh5Cxi8Eby8hcePnzmbdsqjrPrnDBS4uvKcPE5cufhoMUo3vJF/IxsF5XyqOA3GDpBHm5c1Ija/Zv4Jc8bGMCPPXqwnhs81593R8sEww1jY8bS2oU30kCEgpaieCRvoAdKb9xOHr1xGQUt/dGLmDzL45bHf7Gx1lcPKox2D/jsuxK0ZGZcH/TiJ0vGG54xWcC2v8Yy5nkmD4nex+uNKkVvQewL64JYtkWYtXdezT9JshuScCtBReAJAFsRtvxWit7YJPVHr45zkKE+ZDcjZxhv6AUsVlT+DFAbfeIRjkCor8PubkZy5s07qXzbXurlA2C9C6fz4pVDTaax/kIoCbKwVHH8MHsFyzkd1FdbuBzrwneMrqRfltfR2ay+ujbs3HzNPRve+qbk6cvM4vThieNAAntPHC8HfU18rdnw/7zwpLsWzBq3ANea3bG3nU6vHkv/r6ORPrWjg6rGDqd2pDpBdnqOf5FTsvDencEHKABikN4AAc+43oi3ZgNIgEJG0mOOt5MrkAD0eCtXTuEaPgjc996daEjEDqnBwZ/4mrCwRWng0IBI3tBqPQONGyoiNAY2OVPRth+tqYwnzwMffZ/ZFotpfrLo1X6Yt0Q+qsEP/I0HycYARjb+LY8vfZXJU2H2XZ4seHpVHsToK2Cy2E5ZPBhe8vQaX40mD1oOlhZfF973Ibs+2XEp2c3QCzDj8F5GHJ5kWZCuAES5RgPALiGXGW/gwchuPA8E+UIIcyat7zd+VgYJXvT06vOB/rgjxlv9nterkikAemk96yjoMOg35OXDghW7F1jA4pDZ0jlUzr/18PvvVrXRW2pH09eHdtFbO3lRy17vlv3t+77w27tP/9gVdyZPX57aTd8dMgcS2Dtk1qUHPQdu/OxLrx4xb+r5nRwrt4C3KJZzbMrNzd30kam8ym3kk7KPcl47KEHkscPVZQ+s55QFvF3L6U+CFw+nZ7F9K8HwOKVpxoo/Y4+X0xjIqVtcayYGGlu2AAxW0MTZPqvxDGU0cN8Mee61ZlpHsAtqzHM9BzAQqvgz142ZwfEcyqFFfvYWGf3Bd9YnpheeMQMg/lozFBRS1WOSmcngjdWhZTLtiHVzZdz2pO+PkOdoydAb16HthGvN4B3T/uV5VOQ3M8qeFmesM7nVvPGO6VVaArs9/42/Mb0YN/5nB3jkWjMr4+RO6jQ6VRYyUz+P3j7kUvqUQ0tARo9TduVxLwyeFpOFPLkrNc4oe6iyy88ZKeLdVVq87PYplyVk1xYTRYeFrLG8dnyfS8mu9lNOsau8CT+9fvHzFXKr4nHzSk4PxdczYr6uXF8AekgbhQXpHat5QbuQE75zjlCkgkLqKGbMpNZW2n7mEprJC+Nvt++jYzhpaAPruTo+dDZ26+6tv79j7Wvf8u3r/5YRt/QhceAQOZDA3iEyLj1G9Nqz59e9/yWn3LJw1thl2O54/+5u+tGECfR9vj/y4t6R1Ll8NXXhGjMYH7mgnG+3mMK58ODZQ2oVSYrMyhHbutiKRfCzxEs5AxOMrRox1FWyTJ44m6GCreG6BeDpy7ZwzAsQgJYz8BKr44GGN5heCqLt4dzTsL6ePEBoBkb/xldvydcG8vBW++u7HYyTpy3eQu4LHAXmFNoKr1L0wuApWBO2ATTYo45WIR2/hR9RWAsOsJ0+aYn5b2Po+WBGnv/KuBog9V2N+OtBqdB/sFuyeXwDTf3JiysDsvNk13cx77DDgGRhILQchLzYUOKv3wLNJJIG4FVZyQN7fsiKwh8GQkveGGWYdWDAbc4X/axyYOMfMF482fgH9BOLV8QNI98nvHjw5nHMnhwww3WMuOEHYSrIALBjLx86Y12Im3/OPYGQvr2Ckzj/Yc4Q+ltjF+3hlFDf72JPIYe47NzbvO7bf77/go/+4vYHM2xJHxIHDpIDCewdJMNS8QIHbvrsS68tO3H+eXM72mgjcoy2dNDT1+ynMg5EPnfXHrp6O29LQOE9fWlB+SGuBQrzfl7VchJS8eAhZm/86ILiDdd1mdZVIBCUrQNtwQBbGTUe4box56XInBhE+djTgWcVrATjpB62uB0zYOZRzHgoQDcMREwLjBp+U2BhXgnhorVj05D/ytVQCs4sDst7xgzwCZv0sIMZWPFA6HdBUF2fPb3COqPXQJDRavRqxf5wQMbocSUSkwd+W7vKS09LANU6XhkQ4ujNeL2MFgVLB32tWdwnjAv+qac0XGvmZ7TJAthoALmELAj/tXxmy9fAnYFGlDlE2bWbV8TT25fsMi3hSr4+6LU4uSAvkVweiuwKbTpn7XaO4Jn2smDzQvkt6Ybwu82bfuaIqYWieTRA2S2KF8Rcg+cx4q20o/Mo6CTz9Cm9Nl2NFuw2PLCu0DEsXJHcfQV/5tO4sojlu7xp/IhCwvcTOYUU4pARk3wr4zeEsOD16BY6ma9hW85nPUYcM5H+tG8r1fDcOpVvEsLY7t29f9tPblh1wXt+eNPdXmLT+8SBgXIgiO1AH0jlBjcHVn71Vdcsmjn2mfA0nN1SQ28cVUkP726me4YMpRvX75V0A3Kp+IPrC1eRIS0BFB7AHhQhYu8mjC6APSj8kOPOAw0zmJ7XB+lRgb7OeENM1F09sWfMjKD3ngkJbtvQgEoAPf73Q6TXDF7wNhmtasisWqEXxslAmBkm364DG/g6A6ysXH+ePpSLx8DREoy0GsqwbZhDr/fk5dLi6VUAkDkkkycLUTtFtFrHjU/813grXcuRBXnEDL8DIcYKYd2h0nKIslt0xV0suwa0jF7td0ar58huRoXFsoAfD4JemzN2Sn2gsuDjUQO9fchcoPnxyq5VpI0OVBaM3jzeAjTuR/5P9tjhVh9kE+A7dAm5PnEADblC8Q8J4efwXbt1nEN0P+7gZT0JTx/q3seevL28KOZDHZUzJtAw/jx0WA19rr2BPlI3iu4va6BhXNeWnY13/vRvD73iA5fdgr3g9EocGDAHEtgbMKsGd8FbP3fRNWcsmfpMBBnjntprd7XQxXW8HYu0JzdxvApOoCFNyukcm4KVKwDetffRFAZ0e9iT197Mq1/Eq+CfXEfmDQorcItjMq9W3kXxGe+OGqUAutRQ2zVhwaj71bjzhHmj5EEIrmCy7UtvCDy4k2ua1EMoHoocWjKgDNMMdKhllFmnnj4fk2exQSFY3HvpHEDxINV73fzVUJ4vhlOkeaUl442y7UwDRyijHqIAdpQWS6OCGMpCokLllxlq5Uvw+ERlzEsp9Pk+aVXmmQx419HrDb4HhMKDHI+W96hZzFguveZJNaBjbfL35l3zBzcy29uxZ7IfWZB41BKym5EFlOlPdr1nGGVNvvl7GZ+4HZM7A4el+I+x8LLQh+yK/Ot8kL5xWZPvwKe4Hf1sixz8LeWZzCwWHqfshlg/5W3mIEesG7TPoX1uW0TZ5rCVR38VdEPG9vCiFles8bbsOO5TD9+huxdJ4nlxO5Q9fNV8E8e+Y/jGHlzzyGmjhvLfjoljqBMnd/Edn+al6ewZZN0JnTqNr2HbfOJC+nLvfvq3toZCqAvX28lpXz7z5/vP/OjPbrslTIv0JnGgDw4ksJfEo08OPPA/r/zB4tnjXv8Yn424sLOWvtnTTK9tqaK9G3ZS/VysXFnBredLw6FIAejOXFT4C1DA1woN5RQDbfOnUzeUGF5ysbq5A6xpAwp9eSjiVb8ZZq1Dtuj4edmK9WKd58kr8btU5TwHAZzF9A6kTEwH6jADajSDVBiKgGwOlBF75H8zXsVel9hzY4bMD2tErzTn+2Tte75E9MagWqp3ZQK9eFOiP7FnLACDmBZHx0DKCC1+nPERsmAg1oENzxbzRvVL7+OQS2lvgLKbkYUc+ZaqBiC7JrcyZga+rONPgCwYYJVuPQGya55iIa2PuRZ+6092I1mw8RVSHWD0slA0RqW8m9o29JiEL/hK+IMsuLgMe/JqcSCDsw308mGNMvb2dXM2gXL2zrWLx49DWFg/VvHp3W7WkbVzp1DXmq3UAa8ggO9G9vrh9C48hpyaqozB3ZLGRvrbuDL6LtXQiXzO7TkVnbR5a8PqK2965A3/9sObEujLjGf6EHMggb0kE7kcWP6Fi/580qIpz5V7avmE7Xt3dNIXZ8ykV3c2089282ELeCEqWWFNHk20ltMNQBHv420IXGMGBYU0Kti6mMsBykP5FG4Ho0V5qTIMK2SANFPARgp/zr0+So2ueVkyV1lB0drzhkygjM1D5QBlCApX8ffbooEbrg7Jw2eGIoMWCsbDPEtm/Kw/ASxq255e8/xl4tlQTo1M5jCDo6XogEZkHM1bEtPivSwBuEX1BmClINMSVIOWoqus1NCJ9ww8MMNvRtLxOwxvbGj1OWFtREugN5KXYJS9Jw+gjp+3O3wDvREtKGPenRAHafxzoLmIXgMh6ukLB3bcM16mhI+BySowT4bsOiAXYyQhWeUpyK6fZ3n8dmM2ENnNIB1rC3NXZSMsBiJZCLSVkF08l7mGzfPOzfHMtrvxwtrSscefjFfXzWORB9UB8cJFPO9Kn/yJdRSmquN/uPJQ5wNEA95V1oPjOONABe+A7Fg0u5BSCrsPIqMowzqUQ1umP7SeWjglSy/fx9vNKVwaRg3n7AV6ZSQOueGU+84GelV3G/18yAjeKR5BlzfspMlcx9QyBE130Qd/fdfxn7nizgeSSUscyOOASXriTuKAcOCe/37FD5fNHnfpFT3VVMkK7BgOaP8W1dJPr3uYWk/jLVrWUKNvX0ktHHfXzrElsu2witOq4O5aBCkvnEHDtu2mBXzLxQrOKN+JAORRHIyM2BV5mTI2G2gK0yt+b3TsMfe7KV8DSqXiboqMEeoq5RlzRqTI66XTZCCePjFkZuytj9rviN4RvP01qm447+a00c4e3AEM7e+nJDqaxxcvrHGZgXgdfRk0GdVhH42vFkxf5HUxWksZzGishey+6M2hpUhe8ugFwABAMJARg0rjl269BR6X8t54/voyrj8yLH3Jrhv7Q76Sz+aL0XOosqtgSaqLUaGThdDtEp4xed7Guj/eDUR2S/THPH3C7r7otd9i0Kg0BpAbt2N89XPtYPuDOpR3Qq/KXzxfEZYiB19sERfzBX3kMkj9U99EQx7dTD07GqgN3j3o2/s4NRVv51Z1dtJxNz9ADwwZQj2nLaDjRtbR8zpb6DT28A3r6qImlumXchj0uk17777qjnXvfft3b7jeS3F6nziQwF6SAeHA/V+8+G/HHzv56dBVrXzn7PA17VR58lx65eq19KPhiCdp4Iu+Z0g8XhmnF+hdzffVzuPYk3EjiZBj6sS5Bc8e55Yq4zttyzi/VC8DPpo3lXpH8yqV74yUVW1GL+MDFKFT6uFEoAEDNbCZa81MmZtR96DCe3NscLWdzBZOLPoDKeMNuHZE6PXgQmkxb6AYAoAQAyLaDi/o3/tQLb135mn09qrH6Ne0lm0Ha+7MK6fejLfEjFZAZwV++jIZUOKNnHogPJsNzOInvrj9wGA58G0eLGNX8GgNgJYikFWiXqlKgZQdSgnxgUoW7KzdJgJaHte1Zp4vMS/xm+NVGB9lgHiOvCzkyJwAAf4HQJonCxm+HIpcHoTs+uTMAUhHfbbFinlBxQsdzcc8r91ByYIX9BL0C1mQAwVS4jEEIdF4iKjqGMh4xLLblx6wsc8r4wGgLRRMgZnsOn1jKaGEXniBdTpK1WHCFHhpV/LJgkHHPHgDOeYZ94G38A4KX8NWxbdvdAMMjqijHhz8WDyTD3lwTPR9a2n8ScfQ03fsot/MmErtbdupTOYtvzq76f2X37H0c7++637P6fR+8HIggb3BO/bS81u/ePG3zpgz/i2X1Qyj0zvb6QGqYD3RQ2/mbdumCWP4WjNODwDliUMXTzu+kCwUuaRwJy1Sp0Cp4TogKC9sO8CTh+1bfN68i8q4LmxN0GQ+zFGJLTkznp7xDqwFnRh5VILiN6BnCtTqMVH2It3faj2Pln48HdKcW9EHQ1iCXvweX80lwK+cJt+/kaawZ+/u1j30sRPOp89WbKC2TuZreB0ELeEZM9yevzG9URkE2Icr48yw5/HOG8Y+2ilJixvngZQJW9xxW0yvxUwFkswIOxpDTJ7JSlwmD1j1V6YPmQrXa/Ulu+Bv3J9/luwyXwJ4sgGBLDjwV/S7l4G+5toTKLt5nr6wDa/0+oMU/Z4m1v6FOtycjuXSpz3KBuhxSfPW2ZjyXx/CYaAzUwc/FvCiW5j5MgIUuVA969wtu6hydyN187VrvUhCj/x8x88u3Me7cl2B2hHDaMwJM+mj9TtpKM+L1w8roy9UDqV3VXfT1vW71vztng3//uZvXfcHrxHS+8HHgQT2Bt+Y02uftrD6P16w9KbF8yaeCiCyubmTpneOoH+t66FvP7SDOvm0WBmDu97ZnCaAg4jlnkfkzHvaCYVkyDv3FQAfbrxAlnhWRvIdfjuWvX8ADg0M+CTuhOP14NETe2tIzhu7HBEMgI/fBJCH7RA3WHHqhoxht3LmBfAGyg+4Vei9gd4rYM8NhF7V4FY0c6em9t2q5lW38Oy+R2hm7Vj6zP7p9HDvbvrCsydTG+PnQuJnA0Yx+IhpeRz0WtXBk6e88fyXcfMA2/P2UGjpi94SsiDDwG0Fbwh/jvlbELAC3+TPQbaT63WMlUOevERlzMNk9Agtkexm+KsAIffQUp6X+vHyP6LXeyZLyq7yXx7Nk0urM6AYLXewsptXj6M36AKtHj8FT148VjYfAcRiWRgIvb5Mf2bSy52XQ9d/IcHmkedT1E6QXci4PsMHOJCHT3ZHkN5lFuvldgZ7uHcXepljp8uOm0G9G3ZxkQr6YG8zfWjsJLqivIUu6mY9DPljff3By+848zNX3pUOcsSiMkg+9yfFg4QNg6ebN3zmpX+dPn/KM+5t7aEXMg67qrOcNrZ00jvG80EKbJM8yKtFXGkGxTKTUwDcvYb/snJZzdsH4rVjxbN2K9HE0YXEoDW8vXDrw4VtXFwDhO+QRw+xfLztULB5rJFzV8gR+PNlTLHj+RBgrcovBL8DDKpx91eJBcNp3gUuF7aH+6BFyqjF629Fn1HcuhK3JXvmaigzlDrVAFgAjJFomre6Z/Pi/b0PtNN7TqmgtmnM7wpGe/CYij2IaXH9icGJ93QUmJ61iDG9lubDby9nPB3KV1QVjKXyOz5MEXjbB72et5npFvepBP9BL/osQLhEmeAJdLwLbTmQ4mWh6Hd9tl9Z6EN2AUDC9qOlqIllN1a9Xi5F6JWyw0h2s6stN6eNXi9zT6LsWvof7zWPZVe4ZzwuNaf7miMe1BpY9PIfjZEPn4g9ebJ9jyEF8FTxFfKsffvS0RvwIMZfd0SgezfvLOhgS2Z+AsdK4x5epHe5axUXLS/E8PHC/IzuDnpzVzNdQJ30dw4ROb2mjDo37tz213s2vulN37z2z5lpmD4c9RxIYO+oH2KiS86aX/PeC0+8d/HcCcfCWP739nb6j/FTGOjtped2D6NeDgLuQbJPHKTgVAGSLw+g7biZDEoYjUDxPMKHMKBYkBwZV59tZm8fPH/Im4ftBtF9LE7iyWOwKFsRfjsrbzVrClmVcbDRquC8rg7K0WygKk4bP+9RkefyPBD4LtpiKxr//so4xW/0Fl1lZYrc0Rrbnh314vEs5+3wMvaednMCVQHVAeSFjjkKS01X461vxB7zhkqNy0Dojb0h8mjcjrdcMb15tEa8K9oWi+QhXIvHz5knzxvB0GQkC5m4xTye5Mhdf7RIW3Gf7LNt50G8mMDMNWwOFJpcZoAJPxMwh58jMW/7k0ujry/5zpFdiSd0W67e0yfyDd5GtBy0LJQaA5tAXlb9hPSy66eBG4c+r2HzdZXSP4+nTJ78uz7FHnFRc9BtJkr8JpaFQE5Mr84BPI9r1jiNCw3nBTV2CCaMKmRGaOXvOb8fx4LIzUWVrJe7eVF536Ry+q/WSlo8uob+q5v1N493U0tnM8fzPf9Tv7rzBs+B9P7o5UACe0fv2ErPrv3Ui68ee/L884lXe0N4+3BdVy9dunI3bT1pIT1tfyPdsIVXhVsYuJ3L8XhQ6pzrSQ5dtLPCmMIHM5bzVT92A8aZi4kaeVuA66Kte6mOPXxdC2ZSJ+59BMDDtq0kt/UrYNVsmSStcaxLiTihotW61ptJZAxj5FbIdjJOkrQqiCzy0kHhQtGCQ+pREXuEL/rwqPgyctWSGjGLayqKzVF6ETRtcUdWB9Iy4K5MAOVhejcw0ivEHjixJ/3Qa2VKehe4gHkCQkqJeIzACm3HjHsGWPXngUPHsF3pjFQp/mdkIW+MQAv4i7F1nrwwV1VtGbCS79EfG0OzpkpLX166g6b3EGXX5oTIgpO7Iu8O+qDyIn2K5NsC+p8I2ZVxVhAZy66SKAd1Ytn1c0R47+rwc+SJkl2kKhFA6sGf8QXzHHNY+ebnovDKyaXwLK+OSHYt7k4O1ahshZPyHoTFukP5GeJfTS5VXk1f5F7DFo1zX3NE5gSXx8Ibi3OE1PAugXj8sDuwkndYzuSbi9p4Ub5uBy0dXUf3VVTzLZWj6It7ttMs7teSSu4HP7N1y77Nf7xr3Zvf8u3rrwrTK705KjmQwN5ROKwvfcrcmg+/7JQbT5g9/lQo6xczlls4aQSN4VOyPx09ljY8so0acK0Z3P9Il4LVIZIhY2sWMSE4BYZ4PFzkzaAOBy3kuxPnFa45w3OcFLScv8Phi17OCi8GyvKcZY7cmoh5UVMDb3o0412wcs5DYUrafsqAEBtAM1ymYK2Q6vdglGKRd7SULGMGXkFlfwl781b00qyjSQ526D/0P4BHL5DO4xK+9p4bM17+r+etAiDQI1tInoGOlth7E0CT561vA12BIY36k/F82W9xmZhWxxfb7gKdmeTY0Rj5+EwzzMGwxzzRMQunR/vo08GWkXHj+mRr2bcbyW64FUbHQ8oaELHn3NhI//LG+fHIrpIIdoR4x7y5ZnKi49bvtWbap4NN6ZLhV45cZsQmmtt+HsWym/H09TM/wpzy9Zs89tBLuybSJTNOo9dv+BPtrmHA2YOA2hLyFOgF/6zdUrpFC8vPumgMczOPFs8MJy8A4liU7+EFI4Ad9PQyDqe5mxfoi2YWPIDQ2xt563fJLKrirV7sIhwzqo4uaaqnb7It2NjNxoEPzu1uaF31jT/cd/HHLr99hddA6f3Rw4EE9o6esZSe/O2TL77m6SfOfCaO3m8tq6D79rXRC8qGUzdfyUPX3MMHKDh7O99+QWccy4qAlQFA3Z2rOacTf4YRWMWxediahbePFQTtrC98vv4+GsLJktsZJHZjm2ASA8MxHCciCUJN4asiClLFb0p5d2z1K7oTD+TE0kkZ71FCO1CmpnBN93pDifpMOapXSICJ1ZPTTn+eJtAXX2VlTQRvoPHAGdDYS2fep748k8FDhXrUWMTxeLGXtKgdsJPHJXjGvAfCG6LIKOV5kcL4xLSoUeqTd97Tp93JeKvQvnlmdEx9vGNfMU2x1yuMR1+GVie7sFZlLjb4sacvJOVWWuUAiMYPSlOQqzzZhcxFIMbzV+j1chmDJvNo6vyQ8o9Ddu0qPRMroV2Qnw0MvlBu5Hi7bR5nruTzc1HfC50lZFeqN6Dj5nyGFhSB7Jon7xBkN+Ax46/yNrSTJ7tRn3nsXrVrGI0fO5n+uv1hOnH4JPrZ8J2MzQzMmTygbm3Q+pc3j3z8YEZ38EM+ObN5suMyxtbAQh27dgZzOKGLg3FIy4KULACAi/gzMihABnEbxxTOhoDfbn+YyjlkZygf7Ghhnf+99nq6tIM9hFiw1FRSw47GpstufvQF7/ze328IUyO9OSo4kMDeUTGMfDbif15x+6JZ405rZWWxaF8V/XB4F71xZxft3NNMLRyL1wtwhtxN2D7EZd04WYtTobjtAkf4cax/OG8nbmNPHsAITtOyUqhiZVEOkNfSwUlZeqmHT4L1IsUKFJRcfeZshPCy6IuCkbKXYoZ+PTfi+XKgrmQ6hViR+wF1QNGUZ8aj0g+9osS5DnjdwglZ07ahQgUO/dEb0eINXyC5vzI2Xf20NYbCSIIXarzNayh1uzLyEbSaQXaGOTMXBkKLo8NikYq8urGK8fRCNNQgi9g4sOFpkbtXM19on+y7SOYsfrNkjCGXF9Hqz3vWQ29pnUzHV0+i/6haQS1dfAISxh73kwZZxxsv3zFvo98zc8TJbpFc+kVOHl/yZMG1ZaBL4jM9f0rJgudvzJeBzKOByIvNt1gZ2HBqn+RPCVko0i8xrXjWAdgiT5/V7QUqqgNzfds+Bk9racjc2XTexkr6ZOdkev6ZrbS5AqEWXvxsQZM3j+IxMp3hdIePLcbXkk8UMuTHI9apSi+AN2KqET89YiiN3F0v8dYNfOMRIeSGd1xGcQxfM8f3dS7leGuka8HiHYt8NMCptCp452YBddEdQ9vp3yqH0+squ+ip5V18vW/Typ/89cFXfvCyW9ONHBm9eOR+sBl65PZgkFN+46dfcmPvVf/Wu2TOhNMwmF/dvJ82jBlFX6sbSRsqqqh52jjqhfcO268Ac8h5h79QaJU84XGEHydvRyPujsvxVT04ZDGST4yO5JViNV/OXYMVIh/G6F48m3qH8O8AefC62ZVGwbPkV+NQSKZE1bhmgJ5otgNaM4ATKE8FTqITrYx9j9+8lw5gAf9QlVeupvBBK3SbKxNSwGg9EkPj6LWYKasvbNMZLWaU0C+jC3X4Pjl6g/HRMhmPlYEDT6/2SdqP/wEgGL0GhrmMJexFNTYMxrsQm2X0Kl8DvcpTPwbCNKNXeRvTI14v9iKIJ0bLSB3eoObxFmW5fgEiDJwQGyXPqIAEerkjwl/jq+O/0ZrxnukYZtrHB2MIeKd1oU4DvVJHjuyyZ3xoUydHNuymyr0tdF7tXJpdO0Y9eybMoN2NYYh3LCELGdl1cikeQ+NbJAs2jsb/wB+wrC/Z5Tpl5xGeSPwzeUG72ufgVffztZTsGr3aX6nOeOflRdsxOmUBIgJSLLsy10x2tH4/7+0GCnnez3ujF7Tm0Gu7DfKIztdALz5HfJN4U/4eug1XPTbyP9ZvF65oozdUHUPvHrKWNrezHsTvNidl3FW/eF2XkSffZ6WlSHatDv5dYo3BE+VLxsNpfdb5Cg8e6+hRvH07nLMlNHAWhIbnnkJDmMbjr7+Xqnh3ZhTv4lTxIbApnClh6DYGhUhwj9QtnKwZ86+bMyY82t5Nr+C8qt/vqqb31Y5kMMjlRwxZ/IGXn7ai6fK3bPnUa596IihPryObA2Ydj+xeDELq7//vV1yzeObYZ/6lt4ruK6+i15R10FvLh9KtN66iffOm81H8kTTs7/fz7RXDqBlevel8tdldHMtxKl/BA08eg7eallY64e8r6MHaGmrG9wCEWPlNHUfVK9dLCpAObA/w77KNaVs5gd8GRNSOhJNmoq31pUAkE4NVajXuBzIuk7cu8WUcLaimKKbMlL4ZHf3rPSrmyZMYQl9fTEvUlpDdH70GDqyPvj8wCLAhnm8o58rkec4s6N/ATfDWxfXE9Ma05PWnD3qVt1N7aljMxtL2jgbaCUPIvt/wCv1xtAgeAG9hWG084naifkuFA6G3P1mwepxcevdJhr8qG7iI/t5VNGzkaHrK8On0+5ZFdO5x2+j2dtwF7ccvls3+ZCGWFz+PlF9FstCPzJWQ3dHd5TRt2Bja19FMm7sZwPTnec/9vZQsqNzKUB6s7Nr4K0l+69ergX49eQczF5nGkPg60lEWBwo+8l21cnCKT7kO4duEOu9/lDrP5gMPfFWZeEk9YM/QGstunmwfzFxUem3xG9rSOuAF5Ji86nVbqZc9dL3wOCNWj7doR/BhjfrFc6hnFGKs62kI6/VO9vx1VvNiHeE5SITPOn8Ib/Eex/k+l48YIdeznTK0kq7u3kejDRmgbXYAbNxW/9CVN65+77//8Kb/K+py+uKI4EACe0fEMB0g8tbPvOTvZ5ww42zqZkXApzknP9BA2089ll75yFr6xVBelSEJ8nEM9nBCCydn+VCGeO342jKJ2Tue3fm44WIWgz947NiQljGwK+f4u27E5jEAlDx7Y0cVvHw9dkIUit28LPzeVuTBGKsoWUC3newUnW7AypRhHhgxxaiKTB5hZZZpxwy2lQFNMS2ujAAO/Sz1OIBnRsSuYQv0QplH7UgflRaJ2TH6rQH9LpcWGztXxm/deG9LAEhmCHPotTs2LWYtBNsbTQqkpJwZFs9brVs8W47+AKh0HDMeqrgeroON3g/uHUJLJs+jd0zeSHe2c1wQn/jLeI9sKx48NhJCnrw++pihxcZDAVTwLubIi7/RIAMeMPaxLNi4OB7gLbyMOCWN8IZG/nf3w/S+smPpw0ueR59a/b/0w7Mm0k62+ZLqQlieM0ZPmOzqmJaSXVvQ+HEMsosxKqeXbKigjw9bRP85chv9uYdP2iOPY2bOxrIb92cA8iJkGq14a2A+GqMQg6rFD0l2jeV9yK4Ni80RGSMHwDOHeuBJc3WiKOjiRPOiC5FmCiddkaEg40X2ptPTorIuP6uHNhPXqrQEeeyjjC1GQkwfPxRfw4bq4B1ncDf0gbWimvbzDows3CU3JX+BpMxNvHUL4HfzQ5xCi3+HbZjLuVW31xduNgL4m8/x3EPr6Ot1nXyFI27ycX3EvAVfeCv4Q7+5++RP//KOu20Gpb9HBge8xB4ZFA9SKu/58st/f8L0MS+4spavxmHF1d3eRWuqaujLvG27dgoft8chCxycwFF8JD+exmlTEHcH5QB3PyY6JjTiNqpYOWClipg9KDPcd8sAUK41w0EOPmlb2KK1bTNjujf63ij4QeHvDWcED5kHd1aHf8aMQtyOL/NEeNdQn1f6ahV8gHWuRyVYDyXoYD03pvhj4S1Rj7FLaHG8k60dtRLCxsiISfU2pf3U7o/emP959LoyulU75fa1VMHpdrp6yulX572Svtm1ni7fsZJJUEABegVMq+fLA5GStA5EFp6oMhFf7No40IabCXBzDBu5kez1mHXnerr/XDaSY0cXhiTkphsILY9Tdm0RkSu7OuSxLOhW4dBHt9JI9u418YGd9085ib42sYm2t/KCMLz685gfhOzGoC8Tv8ntBNACgBnNxccru+FQRB69bqFgIRQG+AJO4zf2ncwt1X94FB4zyRmqZTKxxDHoi8vEJlblpegQhw1I3lzEvNffvU71dciCQDsDUGb0AnA2YvuZf8N39/B95XAE4PUM3p3dzzYCCxdkYUCKLfb8zRw3jM4t66Lj2lvp6WWd9Eh9G5Vxaq0X1fTQB6tH0Jcq22jdpr37/3zHY2/+1+/+/fJYq6XPhycHEtg7PMdFqHr1OQuq3vXcJdefvHj6UzGRu3iSVjfW0csnDKE/376GGtlNT9v3FVZoWKkhJQqO2Z/FQA4ji1Na8M7h+P3iWYWy8FggaTIOZAzlyb2Tn8GqlVd0hWvNoKzQegzmckQlKEc8Y1oTj7qy8arU477Aew9a8kTSvjOjeYhlAolKb4jZckJgnklhARSo8SEWlIDIHK+sTB7v4noGwl+rWtEz/thF54GcCAxmvKie5ieQXhgLxDXd9RCd0TKcbj357fT1zdfS5xZ10+ZRLEPGN8hEiHeMaVEDVkTvwfLucciCjDGMYAygdewxpxDMjsUSTj1O5kUV4l2Dd9XmyeOUywDQ+6hHSNQxDNv3BpqUtybf+B3eSSz27llNMzuq6Zsjz6Drtq2gH14wh/ZVcztY0GW8pE+w7Pp5b/GIRVcI+j6hL4cy14xuL9+llLoyyOuBECupz3sSbPs0xAKjXl/HwdLryzt9mSF3AHrB8zboKOWlPB4BUNxwNJ1lFy8crtvE+r+DZRqhO7jNB7KChT9SaCH9Fg54wCmwp4nT9g2lpWs20S1jRtNLORTge1On8TZvPZ3fw/MC49nRTR+4/I6ln/31XfeX4nr6/vDggPn0Dw9qEhWBA7d+4aK/zJsz8Vk/beJgWzauv+sop4a2HqrlyXo5MUgDONvAW2c4To8Jz8kzJR0KtmARRIxVXCdPaBy+QHJkgAQAQeRhgvcPXosZvJWLJJy42kyUBLYgoHRtdYkvo9gyv7o1vSRbm15ZA0xpPeHmA/6M1aW/+iwz3s77I3rX02JG1ZdRGjMr+pjeaEUfgsFRn25LxEmVfQ4yr1SLPAeqUIP+dn2WfuEHr3RzvAt5ZcJhF/SPaZQTz+g31yUhccoXaULL+C20sFVm46h9zdAS885oVfBg/MejsQcC47e3vhDTNG40rWAAdNY936KbF3Lg94gphTHGthJ4jS3RXG9I7OkrwTvP24yXKKbX5NULVH+yy7/jMcmiAlp1Oy1s/TGfRzCww40w8H7jrzSDB+LXP0J24RmTASktuwKWVOYAxhHSMX86Y9ka2nD3Y/TlE6qot4Lp7+Yy8O4fcBcVj/PjlV3wM8guSAZdJrvcByE1GsfHK7thWKJ2gg6zORvJpdHh8zya7BV54Ny8Dwtjm4uuP0IL2snTqV43lNJjMsG1R1EZ01GQh7AA1DkkGNKAJOYzj8PEUQfuM8fCBQt/3MABgMcxiRKjuIC3cOEYANDDuAEMsjd4cyv/29lKZdua6Ie4xnHGLPoeLx7uYdr+s7uVvlY1nD72pqfd9+ZnLd7217vXv/1fvnX973ImSPrqMOCAt0aHATmDm4RXnzV/+Luff8J1Jx039WTop59ua6FLxk6jP5Q30itba2j/ak6dgvg7XFOGa3EQe8fbuXTS/MJVOZi8j3FcDuLzoLiwaoPCB+DD0fu12wq58fD9zIkFYCg6WKy7Uyxe0eSNCRSLVyhWJmhI/SJHvOJVacaoHEQ9cRxOUT2mlKFsoXdVGXrvgnXBjJ55+jywEKeaGichzxS155kassCqUtMqfsbzVnnnxyP25A2E3qDsPS8DasoZ577G2ujN6Q9W/gB88Ars5C2gCbz9j8WDf2VoUXCV673po52BeJj7LRMZzeDJgzGMxd7TYmMC2lV+MlvreeM8gDlw0LIL+eV/ByO7IAPxWNUM6tjAl3GC3V547qfy4i5D9hMoux78xPRm5lEJWXiyZDfMm1iXmR7ziw/ldThspvOjrzqCXsjTlfiuL/k+SL0r1Tm5xWfT36KnPODUdvEddnfWsf6HpxqH9WAXcM3aXp7DiNlDjj4k1pdQIN3mx7buDJYX7BBhmxeCwzctlbNjoZZlccXQNnoNcbqWEWV8By/bIm6nq6t7/4d+cdtZn//18vtKcSN9/8/hQPLs/XP4XtTqdZ9+yf9NO2H2c1aXV9JejpX4U2cVfX0Lr87H9NLHu2qpDdnROQ0KLeQVGJQ2tmNnMGAD4MNW2Sq+uxYrL6zosULDCx47nCjETRj4u5HraObJjntZAfZgqMULkwPcfBCzX90GvWUePzWCohN1u1ES5ZriEW0UefrUIGZOxnljqgo2/IFxiD1j+FG9MeEQh/d6oTy8C9qWnABVw2b9EaNnyhHF1Qh5b5R8p+0Im1wd8rjxri9axPIVnvXb3UVeOv4dK2uLBxNPnuNL2B5Xa23AKYwJeO49sda/HG+g0RJoivkPelVM82QBHmXIFxYOS3mxgSB2CwgP/HW8RWVSH9oxvtkYajslafHGzXiYIy8l6VW5RLsiFk4WtOkCbUpvGGelNwYq4Zk+aHmiZNcMeF+ya4sCkQObj/x38miWJ/7b1Myn8tnzivEqupIPvHXzVeroQ178lnPGC831+ENOsVdX5pHNL5tHKgs2j0KaEY+s+qMFZXNk18ZI5C6uQ8c55HjMmdM+Zs+2c/Pa6WuOmDwNpEyg14+h0y+B1yqmYZwhzya33E84niVPn+MLxhPADQc3YDeQcQG5VnG92liWC+wIwb5wrj7qYfvBqVrk+kt/ZeHCGYU8rVxVD2/3dvHp3+exx3h1XQV1V9bRa9r2E/u+kc1r2OfeeM69bzt/8b1/vXfjf7zpm9ddl+la+vBP40BsGf5phAzWhu/8wsXX9P7hXb3nnjD9OV/d3kqXVQyhX7Hb/AM8ddbWsnLm7dflnRyvh4BxA2qYhJiYuBtRYop4cnK6FFm9wQUPrwsMWjiAwRMYbvlj+ZSuGTKcxEUZUcr2UuVrRiMYNf0+rCBNe6lFE4PkFTQ+uzpNmceDLAYAijgGAKrw7I/QYwAnolfKBLSjXjitD7yAhGfSqJgCxXMejPBnwZNalz+oYX3BX7vXEuUyhzn0uUCLtuNP7obfXB8shxzqBVgCcBeyHI9DULnjg+934L/R7561NuMxsu9jemWMpKPqEXU8sXGEHOCGDtCLLSJsCUr+MX1l6HVyIG15vmk7oUhk/A/UeICWXHqVRq3ePxZi8ow+SZ3hBzQQrf227oOWHFmIacr02fXV2PZ4ZddvzRr/pW4nu5Bx2YKM5pEtpgD0YLyxkBDwE8luKVkYkOxquz4ZcEY9qF4wvWNkG71FshvLC0iO9EvJeRTxXz5q+1aHyIjSHJIXR2XAI1usiu6AvMc6SjsZZM4JX3zIK1PGzxGlJROiIIOc1WNCC/554csZR8iKxPQCkWkffZwnxgjhCMizh6svx40oLPwB/hCHC2cCvPWY0/DkcTyefI/PWMzhhZO9/FsHOxxWr+JdpI4OWt/eQ/fwYUHTGb3722nmpJHL3vjc46/df+Xbd3zwolO4sfT6Z3Mggb1/0ghc/YkX/6b3j+/uPWXBpGdu4Ji3PzZ00a/KaujXvEp665pG2rqzifY08MSCIoCiPp2vM8M2GdKjcLJXWaEB8GEbF6fFMNFPmFOYmEiizAa4nA1cGVz1mKhY2SEwd8F0qmPgN5KvzZETh4ixKrq70ymusBrn+m3FKAZMDaEoJjOgpo3MaBsAgJiZljcDb4YfP+lvUqWVtTqMFtdOSXq5TNiSNsUX02J0mHdHfxd97zyT0i+jBYBU+yyPm5dO34sMxfRqGembTrMipc/f25Yiysil88Zao8XxLXh3tIyw2/PepnNEb6aM438YR+1rZhwdX8xgoh7z3uAmFjQH+Qmntv0Yo86IL/I5loW8MrFMGVPyZCGWOVcmvIUsqAdb+gzeyoDrPydz3vNo/A3AKpJdn0g3Iy//KNnVcZauYB4r34ReJ7uWBN1AeACQJi+eXh2jIAv9yK6Uw6JRWRr4G8sCPjt6pVqlMcx7HRs/18wjHsZKB9XroDBPrd8mu04WBMC5+QJehDpUFuIYwvA4vwneM1dP0YJI2w+8i/oTFpJuzma880KQk0tXX0a/+Hkf6UsTa6HX6xedJ7Lo4UI4jDGXw32QRxWePF4IzLl5JdVs51huPDuHD/0BZHL5co4NL0OM9xheNOCAB9+xS+P5HydknnXbg1TNufq+211NOzmJfydUBD/fw0Cxk/8NrayY8Kk3nP3Iju9f2vy9t513sXYw/fkncMA05T+h6cHZ5PIvvvzKpXMnvKSiqpzGby2jb06qok+ub6SH2fHWzZ64HmzTPsRbsvCWIC6Pr7SRk3M4Nbueg2rHcszeLJ6InFKBcJsFrjcDyMOqDeXue4wPXbCXb/hQmrB8FcdQ9NDeY3hScyqWWp60FQwcW/m7MgaI3fAGYuu3KIbIKW5RXKaAAhJxSsl+s/HMK2NGUhVlyWuqTAGjLhgvLyNmPPx3CiZNiQOIhNOq9rDzOGVi8nJ+l6qdAfRbVhlxjY1kTK/V7Tvg+GLxX/jqib7KSpo0o2FER/QWlemHXsNGYlf8OMRjbRbf6islC0qXxUVl6I3GeSBlpDo/zvgIAw3A7eWzP1noT3YhkzFvjV6zshEtQts/QXYDmfE8epyyYHMNf6WqEmMd5lrOfA1fOVowNCI+fdAbnvNj0M98DUUPRo/ZQ74dP84qX0XDGn/h5M3kQMTE6cMDq7tC7wLe83OyL73raSxBrzzOtAh5XifxewsZ4RtDahjQdfBhjTHsRGjiDA0dHOdJw2tpLG/xtrLHrwU7S885tZD5QdPRVHPYUMcsDgni/I2j2GRV8E7TqexXeMHaDfSVcRPpxt4Gev2QsfSpijY6nm/b2dfUtvsrv7/3af91+R0PeslI7598DqSYvSefx9LCXz9+4W+eccqsF1NnYdvy22sbaPfkGfQNBltrqzuocwQDN3jp4GZHShRsvSAeT1bNfD9tVxefpOPbaZEmBTF7NTyzcFgDW7nw2t3HCTU5/qIXqSEmjxEX/E646nc10riH13OAduGO3Cre4m2eh7g/xGRwe+JAcl4kr2xFjwKAQVG6MkG/440igRA0rD+akbWtO1FyUDj4AUZYFVOIBwNf0I4aTfEoQvF7JRuVkdW6WQmu1jxjwSCjPqUn0Gd1OC9eES1xnxXEiI42RW1lgtUrKGopqnwJilt/wNdyQtGUrHo6PG/BcmsnbPEaX1B5Hu8iWoxEMzBmUDKB3V7p2zijbQ9auIzk6+K/AkrRjpMXibEzgs1SWRn93jcjZZ3hsrEN8mSTcSBlvCyYh0hpxePw5MW3mhTJgrVjfQafjQaVP9/neEvVtrtDTKqbI34cpc85sisxUQpWnnDZ9fNIZSrQmyO7GXpVLuU7J7siC5A/1QexXuhPdqWv8bxXWiScxLyI/N5i/IpkV8coyJ2OYYYWbcfiY2VMc+a0ePdswEv02Yukj2UM8b2o2uhF4Zxx9volM6cjebH5KmVieq3fXv8ocaZri2RXaUExScYcFNIBvYhwDMgFe/ra4eXj1Cu1D66nZt6qncA7QE18In3Potlsl9jGrFhfsE+I9d7dKF6+DuRvRYw467V6JCHnGN675k6ivWPG0ea9zfRCdmDcNruWJnAuv2dxXN+La3vHfeySp65897MXd/7i1rUXvP07119tLE5/n1wOJLD35PKX7vnSy7+/ZM74N9zOiU3fyCdqv8gJKZ9LI2jNTt5S7dxBf587jWof3kQjeDI1MhiTODvky0NcBYAbr7gqGBAu4/uo13PCy93jGAQO5e+xjQZPHrZyOeaimmO96viQRgNWY5iAfE9iBWL3OPi2gSdnGZ/I7UCd8ORhsjJ4LKz0TPlCuajSspiRYPdMSag2Dx/d9xmPh4EApymLsIV+YXY2GBZTYO53H7MStl+gV/lhyePmFHZoxxuVaJB9LJIARk+cKtM8D07c77jPeFQW81oww1td5ctK2rWXoSXmr/HCGnYGPJZbT4v1x/gW05O5hs3JQCa+B7KAhYbRG/VJuukHzxGUR0v42ctS1B/5qIYu7l/oUx69zNsgC/jdDKKTv6JxduMkxfLkRQc0I7vug18YxbJgVYY+Kd3Gs3+U7AaAa3zlv37eS9/dXDOWBVnQcZZnHqfsxjGycVtCitIS5lFMr5+vOfrHFpeZ0AWde5kx8kAvnmc6eBm9kCP/BtRBsoTDKCDLyL8DWN6D7eea0Krj4OW/T53q5kEQ81jJ+jL8m6UXAkkWzyyPgBf6HTsZtpx9vNidHt4J6uLQn5oH1lE337jU1cCx37BNuH6Nd51a2cnQO5fTLcHmcOyeHAJk7+BuDjdqe3QnHbt+K91Wx7Zq2hRqaG6mH7P9WlrVQz9oqaAXjBld9bYLl/3lxWfNW/Gjv6x82Qd/divf5ZleTyYHcqTjyWxu8NT9t49feNnTT5n9GpkAvHo65f69tHzZsfS89RvpLxW11I1V0HzejsVdhchzhBg8rJyRB++quzh9CnvfEEuBo+920IK3cSvZY9eFWAvk0EOuJLjTkVoFQBFevzs4Fm8Kb8/y5+otu6iXvXyd40cXQB5Wcebd8UHZtuI2T03w7jijKW/1czBysfg40BWMiAdPpqi9QdfvsLI3T573ntkWWKDXeUNMUWXaUs9Zpg5n0KSPfZVRxdtXmeCBVAVvnry4zxYIHrbCvfzrFuNAaYljlnxbwmLzEgTNbxbMNerBmTdCNq5qFNEfC0zP9AkfzJPqAagZKicfmT1415YHYh73+Rg6A/Re5g7sb8GCFUBhuJRegWlcJvPZ06tymqHFZN3zBU31Iy+BpV4WTL5Rp8m/GlMz7BlAauX+EbJrYgEabQtUx93PNRkDLZOZZ3j+HyW7KldB3yi9ZTgcBDIUfIJWi321bskw6pgEj6bTAxY/aCDLL3CDXCoIkjLWtv6YKaPyKN+pvGARmhE5+6Ce+RCre0BEMlkRMrKr8y7ImttpkG462Y3LWJfzdiTC1Y/gk8mCUxvoA8eND39oPXVwfHc7crsibhy2Cjc0rdzAIUSc0quT7RTe47o1pG5Bkv/NHOOHtD/wBj5tScFW8Tbwgimjqaylg8ZOH0037+fv4IHnkKXWfS301b89ePb7f3TzTU5hpbdPIAdia/0EVj04q7r3y6/45eJpoy++urqOtlVV0+KmJvr9sJF0w8Z9dPsknih8D63kMUJi4+NnFy6uRjwe0iLgNNRUnkT3rCncWwvwdw6vsgDU8AwH0Zax+7wXgbKTuByU4CZOlIyDFrgd4ww+xIHTVACYUmaMGm0oBFPoNi489BZsbyDLtjXirbwwlGrE/HZrxqirgg1bEKZ5YjHLoaWoHlcmKFFT3t5guf7IW9/WQbYjz0dKT76L6+mnTDjU4o0NDLmfE/ahL3oPhRYDGr6tuJ6ojNDrQTRoyuOd5+8htBMAq6ctrseAj8lSDMJggLElxfT9U65hO0iZKpJdBQZFcYr/YNnNLNj8XDN59ACgP1k4hDmSO9f6kKlwAreH5tIwBh7VtKl5r85XrxdUdg3ceDBXUo/lzfuB6Dq3ODKd6RfEtmDK6Evo4hiEQqZNLvL0wkBo6WseOb2cMcsApKAF011pim2AXJvJv+MULuLDYaNwCh8ePTwDRwM8fqs2Fu6ShqPh9AWF/H1yWxPHnZ/CnxF/jhO+8ALyIcGzZ46ipVt30otHVNDxHMbywbrR9K2yJtq0vXHtr65f9br/+HECfZmhegI+JLD3BDARVdzw8Qv/75xTZj/HMpBP3FPJOG4srbrhQdp2PMfObeOVDpIhI00KgBsSHJ/MkwBZ+nGoAuAMuY9O4ZxlnMNI6sEhDHj2Jowk2sLf4ag8tnaR48zAGyYjr5RoLZeFckG8Xh1PQDl4ASXijIisgJ0HyAfrhngOLiOrUhMNv+o3ZimICwrKG2Z7Hxsve1brldxXfZVR5RUS4HLZmF5Z4HI5UUgGCvyAQoFqOyG+LPpdPkaejVxDnMcH1yfzhEjKA243vtYsJJNGPwx8CQPcy4MsAz2+DPqDPvdDb1GZWMjVsEAWBOjpmEfDGwxB5mJ732elRdorRS/4YX3or0yeOjJ54Tokj5/WEV+95Q2WiGcsD9rn4D3O4YmIY39y2V8Zx8RSsqvT50Cs1z9RdoUWBybwttS1ZlL2nyi7aJ8dQTfcOZr2LZtBF1bwDkivXs8XPMNeiCGX6oXMutm0kAKd/mQ3V3eIsBR4Z1vzNq5Gi4i98tbiPQO/9fkghl6nuudiD7nQkiPbXnY9vYEmfSaEweTMxaKE9+pBNA8p9GwTgzXc3MQHDMXBMJsPZ8DDzgcC5YYmODEQK47DHLwVTLvZ1qEMQCIODuKmJ7mDfRTn7dtKs8cMoandHXTvrKl0VfteOqub04chNp1vrfnIb+8585OX335LPFPT50PjQAJ7h8a38BTH5F153KxxL/lIQw99eEwl/bG9jHZwItMvl9XRZr5Oih5YX4iPQzoUHGcHyBvJW658XF22anGUHckqAeBG8oSZxJPgZj6ohK1aJKxFfrzJ7MUbzUAPKVRCbjpVNDLJodD4M/Ls4eCFGfsMGDNviJ/kCkz8tq1tPfr4DmnDtEYsMgoYSpZxhqRkHVGZGASKosxZRdso2MnLTHA0fnQeraAfld48WqTMQOiN+myASXiAcVF6DTBLW6o4Myt4T0tEr/QtpqUPT1+ot0QZTwveA+iFeMe4HTNEMCzoUx4tJkf6uzdsebR43mbmXAmvo9Uh9UJ2Ndl0AHE5spupV/sUvsuj13us3O/W/QHJQo7sBq+SkwVhoc3ZqC2hsQ9ZeDJlF4PrA/cz15r5eW9MCZPO9ecfILvGMj7gNn1rEw0ZPoJet66XRj79VHrb3juYAEsOX4IWkSPfn8erxwbSZ21DxAzAEnoactsfLf2V6WO+BnkvpcfChM7RLzrfRRy5nJxmj3SdHPTg33GFILx2AHXw7EGX4Bq2hzYUwokmstMBMeXr2d7ZbTuwfzhUiJ0n2ESUxfVtY7n8U46jt+7bTSOqy+mzvc30/yqH0ycq22n9ln3r/3jbmle983t/vzUzvdOHg+ZAAnsHzbLCAzd+4sJfnXXSrJcBHPx5WzM9f+QU+uGQDvpEQy+t28hePHD2hNnsneOrZqBAEcOALVuAMRxdx9YrcuBB8HGiFoYFsXmLeMIA4MEFjlO22N7FM6IvTEuo8ihorwM9kBVdVMYH0XvPWEYp5IhBqAdKyjRtsDoR14L2yuGmPVOqjLZtHhUBm2okA1l5dUS0DIhePBMb+Jjk/spouz5OCFVmPHkRvdYf7zEUnWuApC/++vEsNV37KmMAA103xa2KvGi01BDIn5i/Rq89ZDT7tkvIZWinP1lQefZeUpPv2NtUmBAF+ff2K8wJD/by+DZAWgxwx3PNfzbZDZ488Fw7HQCel/883sa8i+daf3JpuqAv+Xaya/MMZBVdw+bGtE/ZHagsRHoqI3f9yK7xH7LbynpxBWcdWLWevn7um2jOjlb6Xe9a+u7JvEjm1B+F+MqsSjzQVF86yko9wWXCAstkwcbY6dTMgtwz5kmgJVd9xGPtaAhJ9HNsgC1qAfbgwIDXHam8+NSt5PCzlFIoBw/fZj6QiFO7SOCMcpA53Pa0ax/bOC6PECSO+5szpo5+191Arxgynj5b10kX8P27ApL5UOEHf3nHeZ/51V3XF6mt9MWAOFDKegzo4cFY6JbPX/SH2QumXPD72uH0lpYG+nx5Ld22cgv975zZNH9IBW16dDu1rmev3TIOXMVK5s5VfMXZjMJWLeIc4H3D8XXE2gHQAcgB9PHfqls4QSW/bz6GyyEYFvEQRZ48NbLBmDgF4j0HomhYQQaApyBK9K5pRD/8ahwzCgrK0zwOBhoMjLk6+qMlnDZT+xx76cKWsVcqsafDGlFDKjoqx0uX8UzmeQO1nmBLY89kH95A4x22huxgReBvH/TKRAHfdMYET5/7vpQ30Ojsi94MLnDjGE7zMb1Qxj6gvSiuUg1yqKsUvd4welnA97ZlZsYtpmUAsmuyYDF53pMXFI4BTciyk6lcz5nKVCkvXWYKDITeSKbEq6vjGHt1A/+1EQNWpWTX5MQOhUi1Nu/66HOY0wOQXfGSql7IlV3HEHQ1xJ3lya7pHtU1sSf7iZJdMBgLZI11ruC7XD/UNoNu69xLf13E+rOS9SR2RpChQMh3ixr5rHIZcE2JOTIQPZZBkyV2NUL8aymdqnMrDOkToVNLyG6YMzEtqr8z+r4PGyALWshXJP92Qhu2DAAPzglJ5+JkF3oHp5UBDBHO9AjnkYU3bzbbRzyDwxunckgTxwQO5TEezanHNvNd269gDL+woYneWtlJQ5nvVZxXdvPG3XuvuXv9v7/lW9f/OHQtvRkQB7yqG9ADg7XQTZ9+6e/OPH7ai6BMPretlf4wczpvI2yg947ieATekm2YwfF4SISMwxWIvUOiyZM5/m41e/YA+uDhQ8wCgJ7oI3j0OIiVT8zKaggxezxxyljQe1FG9KdOvgzTTUMEzaW/6ufgyVNQlHk2oI0+htEUUdROMFBmAMTC5NQT0xWXMQug5cwbErwLXuNan9AXj2iiOoSKmF6jQ419KONJLlVPXMYUnCnIPB44j0qRN2Qg9KILzggICRYzY/SoUQ3klZq+EW9hoOwwQ0BHThakW33x19pxZeQZP9b9yGUu/60jnl6lJQCRmHel2kFdpeR7oOMcz7cByC7GuiiRt6snloWwxdsH74q8gXlzbaB9Mtl1aqJorvUhuyryB3ibN9diWXgCZDdvRwJ5SDfzIhkxXwwYyoYPo154ieBJMs++xZ2FocuRl0OS3X70XRjOEno3zLFoDkkMaayj/Fz0cu31QI7qzewc5NER1SVyVqIe6U/0u4/rjmXI5qstqjLVqv429YX8sY9xjDm2gqdwiBJAIBwecJIgLv3+dUTPWkZlvMXb29JFZw0ro67mThoxaQT9pYPBIuvFHo5p/9Avbn/2Z399V8rTlzeEOd+VshYDfPzoL3btp1/66zMWTn5pDR942NjeTdd3V9BXGrrp/mPYW3ftfYVA0x17+WTtnAKQwwGMNQzsOM8dTWfAdz979HDZ9JbCVWWSEFmuboLMwj2tefVwoGIMC7pMCNUc3gNnhlV+AijABIqMmylIGVVfRhVeqMMZithzYKexZGgVfMi8d0DHAnYNOAQygmVQJcLtlPKo+EMG3quV8Tq6+ori15wSDwHJBpaMIIuTgzI1r0eON7BfvuiYmELL4CK05UGZTakcj6E85/uUQ4vw3OhV0BDHwGXoLeGZFE8eZERX47HHNgOM4nYOQl76rEdjlExN5MquekklRiii92BlIW8cg4pyqk7GwSyPN6DRnColu+IZM2Oo9RR54AYgu+FUpptr3htrvA3yfSiyy/TZvbVGUma+DlB2TReU9AYq7wwk5MWbml6S7jq+Bf2ic9S85gVlqGV1msF7B7CAFB+I/wLQthQ8fvxtrllaqSId5cY6lxb8XlCBB16x3nX6JQCjEnrX717EtNi8kDq00ZI61WjKsQEm4ragyLMBA5n3/XkvQZvPa+nHyMcn2xyJ55EdMsFhDqQd412uagZ+HcfNYpvJ4O9B9vwtY3uKwxzI6wfbWFNNE5bNpu/W76Bn9HTwNWxcOW8Lb9mwu/uq+za+803fuO6bfqTS+2IO5EVzJz4xB27+zEt/0fP7d/Wet2z6SxfvKqMvlQ+hN21updfv7aUHtrCA4ij6KD4ZC6OKI+d4IecdthqQv2grgzsAPyhaALpmXsXsQWwe/2arIkwYPIN0KwhqlcvlscqGsc9omYLSCUqB33vwJ1uX+E4VVLBfvg4t4+sIStYe0DI+TklWna4ewQH8P5nwTK+spEvR65/T96DVbpEI217afgbQOpq8RMZ8wW++m0W/qwKV7Wjjkz2gTA28FYYUWrO4MVNsAkRMGevvAQAL84vnTcyXaEhDO5knHb1F41iCXmsHYyH/uK/YcgkAVw1b4G8f9HpaMnyJxijurY8VDWPgZE76LsJzQJ5Aq52wzdDr5DvTTsTAUnMkj88FZhdqy+tXACf+dy8L/L3Qy7yNZcHkJZbFLFIontMGJmxO5sluXGemjJ/TsexqPwzoBSBxiLJrYMbCJESEIkYboCklC6raDrDFyaWMCfqjW8xhPkVlkKUAIS7wAGE+Bk+e0eLnKH7XeV9SR2m7NoZ+Ad3XfBV6UTf4MAC9OxCdChpAb5861QlETGuf81V5G89bL7thcvhJEukOEzOAbKG1P3odURn9zt/DQ4tbO05dSJ3z2BHCtnPqinWFGL8mtpcYa2SfAODjXbOdfCf8hVu76KLyEfTI/k46u2s4tU4dV/HGZy/5Rstv3tH7gYtOfUZe99J3BQ4ksBdJwl8/8eIre//07t6nLpr6CkQe/HTtPtrCN1ZcVj2U7q8bSmV8IrZnGqdDQYwITsfCgwfFo0qnko1BBQ5Y4PtaXpEAzPGtF7UcYDrt1pWFmAUcyjBDDIWBz2JEMBxmkLxBVqUSVlxaTpSrgRBV/H7lFsCJAZESZfx9sL0AcLat4wBOJvZPmWb0ynxGG0Z/TjtBcXI5u3orKED+MdAKkKJW0Iyg9dvKZLx0oiHVsnlAZh4LU8jGWi4TgId5EvRxn4JFvKv6e7gaybUTjKcZFxsjK6P9kL5EtIR2fBmz/I5eA0dmULwnNfBGta/IH9eHpoKzxhYOTk6CB8LTiz7k0eLH0ZUJhtgUOdo2KwD+antmPDK0Kq9t+15Anm9HZaHg+tZBc+0UyYKTbZMnA+FepmJaMl5Da0uNnNDrxtqfVn2iZVeuEDRZM77lyYvpA5M30xOlZFfrimUhAG4vE14W1CxkeIe2TS7dXA1b1LHsKq0i+24cwyEs5bPNIZuPVtzzP9Rh9KK/tijGGOXJrtIaprDpS5NRHV+TS+N/Rl+qzGVk1+RxAHo3o1NtjLT9oMdivQyR17pDLKiZ6GguBr3v+GLe16JUUjoG0pzVE+n4AdEb0YJ+mDfe5nNQPjn0muyZHoPdw4tDmHqPm8nhTpP4WlD+rqOLjr1pBVUgzAmXBSAkSoA9/zayjh7YuJeet7uXbuLLCX7BMfPf662lOp6Xn379WX/d/v1L67//9qe/sFBxenkOJLCn3Ljj8xf9uPP37+wdv2DKS57fVke72Pg9tXcEvb2pnNrZnbySFxp712yj0dii3cKADYIHL16Nbr8iQJUPXyziAxlTt3BcCQQV3jwcP+ei7SywO07iGD6sZrCNK0pEX2HVyV8GrwEPDYJazc55r4ytnL1NFT2SqVSfNUWsv+WVUWJqWYe+pGI6TRrK4FW2WdE+jDfemmLSdqweo1f0J5SsTmAz4GInnAHPxIap8pE/2k5gCp6x/uhfKWPllJ4874LfMon5YopW6kH/1NgGTx5/DmkHzLDGtKgx8zzJtBPR62fcwdJrSl2GA/QqLajHVtX4TtIkRGMk7eozxuNcWkzOlL9FZaw/JgfOSPnxkvfKs1heTD4g0ygTrmJzc8AmRcxXT0/wwJksZBBCpmRBVrxc+XFUWo3m2BslMs//A73G28ytIk+C7Hpwb7yI52tmHJ0smGxkZNfz1uh9omQX44y57eQynmsZT5/TPzLXoCsgwxAHm2c59IZxcbIdzzXTkaV0lDyqbYA/Vr6UZ8zUjm/HdIVJmNdjfejUgniZ3GnDA9Kpyhc4AYwe306ow8lhHl9Mvr3ByaXXTZ0MvRhfswF5ZZROS76M+ZIJQ9JnPE8zsq16DN9NHEubnnYCp3CZTWtPPIa6cZgRu2Nj2R4hvQs7USbzaewxfDHBmrVsY/kWqcd2N9PPemtoQ0UVvbqpinZMHjvyDc89/n/3XfHW9f/v5adxwr/0CiI72Fnxl4+96LJnnTrnNbZ9evH9u+hXC+bR6bt30YOtPdSELVhcDYNtVuQVgtAC5J22kOhv9xYCS/FCMmPE4wHkcQLJGk6M3I5UK7xKEe8eDmBg+0G2PlUBxNtaYUJAIUE56V+Zz/yQTCgoDp18YdKG5XtBAVs9otdVuWd+cADQ2mFDNrS1l960dzT9akwjba3R9sIVa1ZPQW/Kqyg2hL/TxXb2RgavkCKlHegKGq1Qt/QRfY3aKfw4wDLGQ33EYnNCHdqGAaJgdDLa3h7ugxY1fH3S68qE9s3qWJ8cvcpip+mVDjWSQiJAkx9wfAfQB5658crsWyo4CmVCQ05wzPiqYfRFYs9xAGhWKBpHfO0Teedew+af8cKrffSGtchoOFnIgMWYtzaMNn/cHBDSbd2rYN9fIej7XGoeHZRcDkS+HX0W45Tx2LhBCadrMwOlHdZ6Ss6jJ0p2Veb8giOPbzJ+KpsydH5AByq7sVw6fXkwOhVte3qNNqFLPc5Bn5n84O8AdWoRLTl6N+gyHbu8eDu51gysUp3sp1pMi5/2gdXxlzqv/MIp1KOVx/JidiIof+OH9ilMYX7jnIZF2SDidsK8wRjyPzhCkI4FOWf5nl3iu3nlijb8xp4/SeGC3YA1fMDjnCVEd/OByNoaWjS0gnaUV9HzJw2hH+1nO40ybI93bm9o/t3tay95y7ev/208Owbb50Hr2Vv+pYu/1XHl23trF898zQfLhtCDDe10aTmnPcE9sjwJbm9hoAdBQ+JHCJYpKKwysIULTx4OZCCI9MH1HGPAnwHGEL/HV5l1ohxSp6A+XCGDI+mIT/IehDBpATrUCGeUnzdQ6unDZCuqw03m2KsVJNorqEjMofA4EXTzlu30P3vuool8UfWGB6bSS4fN4IJMc9FK0Oh1BtU8Y4I7oUQVmGaAhrVrfY2BilP+BmZy+6OaZUBltK0ibxGUi2olUWSgN4+3zjg9blpMySpAyYy19cnzxo+TKkMhGYZI/+XJQgboxQY14l1mS9ZZERnamJYM0lIPiZfdmF6IDzynOJDE5YqAqeOtNyLxHDBaAj2+T64/5skr0uKet8w3GE95uf5Y/JVcaM/0micopsX0QIZex7dQrfGlH/73JVO2BRro9eOjRhXyIClHSqlynfdPuuzaHFK5yYiKym4ABOaFjuQpeKFtfErxrtQc8fqjH53qt5BFvmLZBn8hCyomRcC0lOwWCZ/qFd+nuIzS6oGeqAqlyYbWUpjI13m8K4itW41HDfXFNysa60uMnaNFivkykQ3Az8Frz+XEkxrTGvff1YFky3CQwMmCuYiQKKQvA/BDXTiYg1PZSMaMu+MlTr6RHly5mXZzu42sZ17VWEEPlVXS/ey8+PiUaUP/5QVLf7P9F/+y/ZOvPuOcnNEZNF/hTq1B9brqIy/4/rPPmPsGESZW8O/dXUbNo4fSFY+spbUL2TvXqatLXPEyilcXazkH0BzOe4ftWCRChtHC/bNIswJvHv7hGDmO/4dgYH6LE7j4xxnfC9sVqnRNaZh3rsjroitKMT46y81IIl5I5g3+mqfPJp8tp9ykLVqB2qQyxQMDzN8hWSnyWPGF14u3d9D/nvMS+sHm6+jBR5n+cdxPkRJr2ya/UyqoToy6AjyjV+IszNDy33ANW87kD548U9JxGeOL8jHjYbE+m4fTysR1mLJz44H+2+reFLs9JrF68ZeizVTZ6rO5ytd5W4W8HFqEX+Y6KFWGv88kQ44NIWi0PngQ49obUDsqU7ngT8daxmgA9NrpSCzxJTeX73tMr1P0QXRdmcB/r6b4dxmWvmQhLuP4Gwyijm0IM1AZzaO3pCxgavRH7+OUXU+vbdeKVzeWcx1/fC1s70d2c8caz+Dh/sZZZVcWSjFvVUeE9pWuILomD6pTQv/s+0hezAOXmWdu7K39jE5V+bCYWdGF9gzGTD+HKWO8ivRaxvtnZZweCfrbKgI/TC/E/Nfncul1PIm9ikYSipgNEPKhcwNTo0W5eyh4hj1/rQ/Ki0wZxyeRM8dLGwNfRIAzxtzLghbAvLGtfsRshjhcb6+UFpTFSWs4V47hdGX4+gTOV7uebTBunZIFI3+HlC24kQqfEUaFPLbI3cev3+5hALirje7pqKU2zsfYMaKO3tC+n5YNrZn4oVeefsO7nr2EvnP9w8/6jx/cdI3XKIPh/aABe3d/8eU/P/GYca98x94eWsopVG5jebmR71XsZCF9qIclaByDNlxhBuOEgxdYSWDlgOvMcPcsAFEVfw9QhAMaMKA4cYt0KaMZFGG1AfkGIMRqZCqXQbBpiLNScTLvRFBcNkmcMpI4IXgXTIm6MjK5VSGzN67gLdFJL/PLG0CnCII+1olpqzP8RWwE+jVrEq07fSx97/Zr6JOLuJ4x3McOBsWV3G9PbwBaSpfYBUtoqg257eFCP7iucArXK3NVShm+RHWYUbIyokOVX0XbC06JFSVvdlM60Isx0u9FSSs4wXeij/CdHyOlN/DP6vRlnOIPTXovoo6RtOs9MqBFn5U2+Wc7uCNbzCgflckkjNWOxPRKnb6dHN4ZLWaf8q6ny9Ab11FgVYbe3G0i9IP/mZiajPtxzHjwHH8DL/G8M5wmC/53GTovCxG9BhTF/nhPUzyOjl6j1YlRYRHk23mSZVf4ZvRGshu2BJW/Rbw9GNmN5NL4afrFy4u0E8ul0aByK3rLymBOKS32N/A0nmugw/cnbx4p/4NacbSYoPUlCwJWCuQVXjoJ/Fy0+Z7ZplaZN7DjdarXl0G2lcA+bYCSIONsekRl1+gKNoB/F1sB+nP4L3TlyEKg18lDLr34HaBey8lfY5IpCpN900++jM6FcMANNgD0GoudrrMFNO51hwNlV2MhLQscJjjsiFx8sMHYQcN7xL4D8CEufjh/j1y2+IzbqNjTtwqJmuexk4Y9FZfv76WfDhtGX+7YTx8dPZG+9LJhV198zoJtl1/78Ev+8ye33BZE7yh/Y9J01HbzLx95wfeedcbcN8II/X3Hfnp23UR6P4OYa/e00U27GOA0spcOV7U8tJGIVwG0kv8+ZXHhomesMnDsG9eWzeS4gfvXFrZwOdM34bAFZsFuFso2CBwfEQdQtCzhw/gzjpUjR1CRQovZDQXnrF+sUKS4Kkjz9hlCETDpJlVYrecNqWnDaNgxGQH4sDUtdx0yTzhYVuIMQX/eVmHwhnilZPWqEcp4Q5SeosScpiyCpnaEl6A3KOR4dej7bIqEy1hQtqxSfd9NYRkPVTl6z45t5YQVfh69GUuhmizmf39lInpBK7oXX8Nm2+UZY+t4F1JRWPul+Ov5nacGPL0lfhf2KX9N7mL+Gr0GiqQqM3pGo3koSvHWjJ0anSLRduMYZCNPFnSeeS97mGuRLASD6vgkU1Tr8AY39gQWeesfpywY6PC8DerCZBcAEO04YyzP6eegg6yfXj76mkfGc1cmeHB8HeCNkwXQF9N7YFUVARmtx/RYELdS5qk/2VUZEx5xHU61ZkfC9K4CW2tOxE3bEJEsRYeWC7wtEkzfsRJ6wZ7RceyrLXREyIKecm0NRKca4LS44kOxE2FuGeDOoSWQ5eeNerbDdLff+K/IiC6w7XfYUYA6ADb2ziEmT+7ShY3FQQ18B+/fs0/mm6nYJo/g72GHYaMR64cLDDhunsaPIlo6h2j5Gjpvxij6UP0uesv4afTjiv30lF524HA7Xfvb6TN/uHfRR35++0OlRu9o+b4PKT6yu3j3Fy++dsGcied9acQY+sj+PfS+sqG0Z/U2+sG4iTR50nBqWrWF9mOLFuh/KbuK73qEEzqysGCLFgGhqxj0QeD2NBRcydPZU4fEyHzIouLmB2goJ3lsxMXPWIlgGxcAaSQL3fxphXx6iONDPagTAmiKTCYsJqtpICgjb+xKrNAyk0jrMD0shtQbIX4vOj1qxyuz2Oti2x3IHwjPJryYSHEg9Wo9pmjwXVDkvj9GUCxW2qd4W9lODpbyAIX+oS85K/rgycDvKBx7xlTR4yfR52r8Al98A6pAfT1Fwc/gKyrK8a7Z+ARb2pcHLmi9yJPHD1uOvNh7GuImDRBZHWYA0D2xsIUf/CnGolW/AgMTwSIvqTKsL3kRHvI/xKHKVq11PEe+jc02B6RdU/iq6M0wl/QGOn2U69V1BjBvrhlggszZvZ0HXDlauXZY2GiyU0J2+6P3iZLdDNjwtLgGjBbm6bCKGmru5psHetk4BheKCERW/uVxlRcbur5ktwgxxXMNHiD+zlKH5I6jkiFta2MmBl523VD37cm2fsU6yMCDCXAJndqn3kV/bG6p/ivy8Jv3M0ykQgcPRu8Gb2BOHWGMIIvKL+ObtRPEQG0A1GQIoykxF8NYos3+5musd63PTv9kaFGZsu4UlNGBdjLzgj+E0+NRO7bzAicEDkbipinEvcv9u7yLtJFP43J+PtrAu3FwUAAMYocNsfLYDcEOWwM7a46fLWVHtLRRGV9J2sD2+B0V7VTBjo1PVnZQE5M2amg17di856E/3PLoK971/RtXZMTvKPoQW+UjumuvOmt+xSVPP/an55825xUQol9sa6ZPTp9Jb9y0iT5SN5qaIQCjWBjG8v7/vY8V0D9i7c49gVcDDNgYwMmdtcfOLNguXPCMZ+BGXshJHzfp1WZ8R594+HAAw171DPAeY/CIWD4IHe68Rd2oM7PyV+3m442KRiFowD7GQxWRxVqY8ZR5pRMnADKvlPwk9dUrEbayDGDB0SsT2DwI/tlS9Abr4bU8Kjnwucgz6bVB3H1rJ6+MAxu2AoaCDCDPKX7TP7bN4T0OGa+MtePo9WkFwio8Wplm94S0E/Eg47MpODMq/NXjutYsj16wm+sPzQt6iGQyMCSiNaOxDzwTcmqBXqvY8d/AR/x4GM4+5Ft+ssVPTn+K6iglC1qP/FFCwla4PWNGCCyJ+eLlyRqNygRxjOn1cluqnlJl1PiKzfS8df2RdgFAsuM4hoPX/7h5Id09pY7eOYxtlnTPGeeAJ7w8DEB2cz15TnZlsVlCFqSb+runV2jx8zlnrOXng5XdPHlw8h2aBP/8GGiZPNkVPiodfc2j4DUrIS95smvtxaR4m9FXmfCb16lKL+ookiHfUB7/iwgpjIG9imQh/KBvbKxzeBuGIeJ9OK3v6VW5NB3LYE1SnsEZU802FqFV89ixgqtJsQP1MDtnAAThYEF4lcTkc4O4ZQXOmIlsw5Gw+YLTCs+wYXhmRSc91FtJzx9fS99u3ye6t6e9a9tHrrjzmE/96k69KSGPH0fmd0XifmR2g+jWz1/0p2NmjH3ecN6K3cWu2W9WDaEVO1voqmNmEt3Gp3mQFgVbtjhsMWVMIUYNMQEQCHjoHmBBwNbrTv4O3jgIFFYSDP6qbn+YajkmoAmxevgOiR4l8FQnAZSWXH3GChAePgA9AYL8fdhVMU2LyYAvTeD5+4ynyYEpU3alVothDqIOm2vaTngW36POnDJ+OyDXu6MT11aCIi2eXjV+JkX90Rsmu982wpdagazyzKDmeFS8tJby7vigZe5zNZ/K6pCLuUGcN2xW2UF46QKgNn7CAJgx7YNe40tmjJQWrELt4EqeB64/j0omZs8YnCNTMm5GLwjxZSIALyLUTcOphpp6WMlKHx29IcYt9jTFHpW4nYHKi9dCeWAppjennYz8GzAq4aULVr8EvZkxKCG7IsY2Xw5Rdv1tF0Wesb5kl+lmb+WQtl46bXMHNQ6vpmn7Oun9c86np9bcxPeIstcjzJ3YGwXCPb3u99CfWHdomQy9efPIdJ6NZ4m5JnPW+ldCH4bFcQnZzZxAt3Yg76r/TP5NF2bk33SkqgkhN0+nOkCV65mM5TumVXVdeNbJduwNzFuA5nkM+7IBMrRep5awAQKoIbt59Mb2KPb0mb72Y12qjJ/XkSxgUQL9ipg+2SlwcmlDiMGE3WabXMb/5nOY1WpcSYqdOc5/O5NP7W586hLq5fvlxau3ditVcJ+68TtAH7Z3EX6F++vRV3gEGSBOnj+Ffrx7K51a2UOjuOkuDtvatnH3I3++a/2b3/qd6//uqT6S3x/xYO/aT7z46+ctm/F2KLuzNnZymNwY6mQv3Y9HcMwZTuggHx5cvZBFJEQ+ZUHh8mXEA8BFDHB3zvEM9tYXXMCr+Rnesi3jAxYjVq6lhrEjJfCzjE/s9gIMoiKsGsxj5Fd64jnQSRPuflXxkImkQpzhepitsBYlZOlxlJH5G3v6VOlkWvNtqNKT+Wug1Ar3RUueYfbKwNpVpRLad2Vs2xgGqCi+LmaP0mK89dvLTPcItnFXdJxGq4d00LvLlivwxsEXr5hiWkoYw0zTrkyIXTwI706RZ8yjZeOvAofgDXHgtIhvapz8Vmoeb32xzGnLSO7441NbhtD32k+kN41fQ7d08JwQQxPHn3reHeQ4C30DkJfAjv7kzoGwsMAy2c3hnW1DH4xXV57JA3vaj0CrLuZCH73wRLTkyW7QA66tDL1RHZARnFDELsTdq+jCxWfTqzsmUNXmnfSiZ/ZGm9VOdsM0iOj10yMeoyJ6nyDZlYUIZD6eRyYjNm0jPRUOHrl543scih+MHiul6xwtRbGxOTo1z9OX4W1eO9ZP42upOZJnKkrMEZmm+ptz9Bbbm5i3poOsrRK09CUvuXMgr99uMVMUg2jluYzZhP0tVL6jnnq276VZfEfu+oljqJwXzj2W8gx1NHF8H+flK2dQOJ3Lb6hhELhkdmFbGAtW7NydMEcOJiJm/80zR9AL9+ym/5o0hX7V3UAzEQ7R3dv+wV/cPv+zv75rYwnjfMR8fcSexr3psy/7y2nHTn5WFQ9+E68GrtrcSA+Uj6R1fK1Z75DhfGVZB3XPm1rYRkWixhEcNzcFe/sFL081VqXsheuYNKaw9YrVBKP/CvbKzeCYvPWnH0fdeAZJkxnkicqUlBL8kuPvKvgh7gzV8u+SANMmjQqwKUiZv/xbZoUWr5zUgOaVkWrd70G/oE1nsP0KDWUsu7lN+FA2psWUgtYliiEu49oSevwqTo2Gz/kVaOGiPu4sbBdbn8y7gHJolhs3IBW6lkOLlFENZnFWum05oqOM7nzsLrp+WgU9a1c3nTbnOPpE1RpMYKVFAYrUr4ot41ExI6irTiHVKbzMNrHSG3smpWrUY94QpVf6jz7b7zJQBfmSJkyOjC9Wh8mL8qKIXvyuDMv1hhSakLbCqj8aZ36+gT17791/B23taqUFQ4fR+ROX0Nca+OAan2Av9EflQGQgMvhedo23wVPiAaI+G/hv88aVMe9SJmYpoteY5uXb5pp1V5oyvpjhzwFugV6MAegw/mPclG/Bw2uy4MpIIeatgZeiWEaTKS5XSnbNC51HbwCc2g7+IOfYzvrC1tb0STRycwPrtCF04exN1NPJhgzdQBxuLLshTi6iN+NhdrIbPMMqmwcru0Ev5MmuyVHMO69fbI7geYxHNBczca02fjbWJfRuwDeRp0k8gLFOdWVCTKe2k6dTQ2iAzXOb0zl6LMhpXMbrILSlsm96OUw/p5elSGQnjF7TCbm8c3rDaLfFTUbutFzQU553Ji/K94HQW2RHdK6iTfH0oSEda7mBh1+8c9aDXTq22W2Ipefdth4cpARfzE4jVh4OGl4EtfNWbhnH4C/6+320Eg4cYAO+gcPwQBXv/F338Db6De/c7amqo2t4R6ONT/L+KzXXfOaN52x4x7MW7b7q3k1veNM3rv2DVylH0vsMzj8SCP/7p17yux6+u3birAnPOq9tKD3C3oYLu4fRxZ111LBhJ23p6KGtG3bRaKRReWRzYfBxggd/AdywAubYlln3PUYzkV4Ft2E0MQjE1msrA0Q+9r3x7BOol7dy9+O+PpwAEm+TAgRTKOLxwkoDRlMns5UJ8xHPqeA6vVF8gbjWLwOgwpyJZ7Hfg2YqgBUpY5Nay5jBEyVgZYwGVQh5V9qgGg+uStLLPxg/gldE2zIBst+FXJ2kcXxOqMNJnS8D/toqDnT5LXN5VumVtAPajGxH4B9/wWO8eed2+ugG3sbibf1LWibQeQ3VjOkdcDP6oVQC30yROV7LsKCMMSVvjJReo8vLi9CPhQD/FXodc2MDJaDTM9/zVtsN3s8SvBOREMHTAp5e973IritivOM0QivXr6a/rl5OJ3Ni7atur6aTNu8rLIj8GEUsCtRk5NLkJQySI9r6Y/yP6LWS5l2WfIEHpkigBXwVAK+89fz1c0rG0DWf8aTbPDJ6TXZRZ54sODpsrh1ggI6x9UuZLKEUsew6ecjIAsrF9ObJAn8HIAejhoSzU8bTj6c10UuabqRuBn5Uwet5HLYCCdKNWHa1HQHVRq/NI6XXTrMH8KIdLZJdnScZFhvvtN/9yW7YHVEZDgA+M/AF/vphCQDc8djqOmidqnwKesw15PWuyRn+hhP/foyM3TrGsW422cyIl+vnIduASC9kbJbOMdNHsewGD3If8h2GVHW71SHt2Hc6DrEsBN0Qja/NRV2oh8UkgF7Qn/D6qmza2HDqs+3H8zYtgF5sp/VmmR526Gw/i7d350+lVWccV9jRQ1o0eAGxlcv6bvp9j1IFH9jcw9iB+B772zhzxy/L62gt38pxfmMVNU8ZN+6Nz17y+7bf/uvO973slJm+W0fK+8iqHJ5kP++U2TVve/7Snz73xJkvM6P8gdV76bOTptHczlZq3N5AO/luPBrNCu8YRvtIwgjwtpER/xm8bXvbwwVQhkMVCObEkWzE63Gm7jqO1WvjPf1eDD6OdONwBeLtxEtnE1cNlShrzyNTlPoXP3pPn5sLB06+aR3+N5kjNlEyD+mMc+0YsgnKzeiJy+i8E73lJ6AaL+tHHr15tJSk17UjQCVSykZesOOOFvlNf7Cv5a8vo7yXicu/xfRmtuG0OuQ8hKeDXfe0aTv9smkJTZk9i55TeTs146ANABdOdGXaMVrAH1VERbT4Ml4WhDDtqfbHg1kDK5nppR46AYVxn3NoCbyK+evozdRvA6yGJnjWnLyEOpXfOFmLvFVIOfTYFpq6s52Wj38R/Xf5o/R/0xkATuOVMsBD8AqIcB3ot32fmSMlyvivpQaTfy9PJfgSZJd5bguCDE0qMxnZCBbKyZwT6lxZQFGV6ZJjpNWZp6lIdh2/7Z5dGd4MUrFK+pCFfmgx4I7T9EihJAfEMF4qy2Gs/fgbn7zsKk/CPHae5CBf/yjZ1XkQvHiO9uA99rsnkS6RxV8pWTgUnaq6MyO7sQ1QfhelybHxdvRmvIMmEwOl182TXI+tKVwTLadTjXd2DVvgk/I3Y48czwPAzrE1OlT53uMStATVgTe6kDQ1IH2KvK2g1y4TsCqDp1LrCMCeC2AXDzyWGz347469elBjbcELjth73IKF61D5elOAPDm0eRpjhr+vlHCvhe2t9NiY0fSx0WX0web6Qj18QHPTpr33XnPfxje+8evX3hOmxWH+5rDfxr31sy/74ynzJz2/kg9M8EkZdqaVUSMDs7W1PEjsaVjTwAO6lz13SJECz90MDtiE7sJBCqx4Gxm5Y9WLq8/28qmcE+cWyjXyM5PHURt78nqRMgVHvbE6kIMXtgwOElWYMRmPiynISHliJWKJLiVWzjtPHUAIut4ZuRi49CU8GVqKrKvS62nTMn41jokCb40lupQibmJ6ZFvSiIEvpQi1vsVl/ANaxvqT4Ytq1WBI+Uckm5a4nrw2VVFicgPM4eq6RXPoktYG6n3kz9R57pKClyNW1qEqo0X5UESLPRjLgqdFFeEBd6O2B5oj3hq/+2ynjzExgJQZN3yI+Cu8LTGuskDRfokXiOcTlB7PmS1L59Ki5VfT3lE8P2bP0RCFPurJbScSjlwZszKRLOQNsc0pSaECWeC/QpJtd0XzMVNH3E7M22gcw9a+l99Ssgu9YWx27fgxFnozwuca9LIXlxmAXIrOYrmHrsMd3GHHwesp1GP6yPpk/LO5ZiSpzIjMIvTlnyG7Oj4GuAurPfXkK48NlNrtPUKvlwUbi1yFoZ3Nk7tIboNY+XoiG4CfLLTDx/J6nsuiCzyHvPC/4OmK51Vf9Mb6yYlR0SLC1IHTC1ZGwKbRonIgzfq2fRmjN56YWj5Xj3l5MlpMvqP6MjbA8d/Ti9ULaBbW5+hUaw52DTt7sxgPtPLiFVgAiZqH8PyoZWywk98jbh+7d1PHFDx8qBfp1uD5A37gU72reFeIRo2ih7sraD/PqWEYPwaK0yeMWPaG5xx/98vOWbBq5Mu+yYl6D/9XfxL1T+vBnz72onc879TZ/83u1krbg+/mvfgKBlN31HfQ6et4cOClQxoUZM5GWpSJo4gW8HFs5MwDaufgTVrG4A7HtSFI6xjsAQxOGFlIyoi9eyRERjJGvODdk5cKYQAZjg2qF0IZH2wv3DQl5CZZiOMyRRRNgODVEG2hP4aGDny2rZbsXlYf9HpFp3V7AGrNyV+nPCX9h4lG5LESalRBBACYJyZxmag/oc1+RDBzybsra/ocdGdyeykt+A4TFUG6uAkF3tpRLC9Ilp3R48YEo8+PgR+nEsrf5MWUuK00M4dLHH8t1s34mGFdXtu+AOQSdcXA5hDKCL1u5ew9ZOC5JNdm3kG+kUIIHm+/DSXTBIZAFW4ue9w8CuA3LhiXyZMl5YvIgnpGirZqlcdFnhxXn8lcZh7FvFNj/Ljo1TmSiTl1sutZIN0pIXO27dsXvUEXoI4+ZDdWJ9btQIvJlMpYPAxBlVg/csaxCHAfglxan/NXcw4so30Frl7v2nPBGxjrVBHcSKd6Op0eyPA/ZpjyG2UCsPNlMDfss/I2yKzXy0aK6sswhkgdgrGInAUHawPicTcTFMQR8q5jnjlsZjYAFeTZEWV/RnYHYrNifR89Ez66ccsUiegVW4QhNXrVXiHXLXZ45rC9X/5o4cKEbfsKefdwwxUyZ6zjAxqT2as3jVO24LAGQB7u5T1lXuHAJnLrPrhBL1WYTCsr6mlRBXTmAfER21Nb1bh9W/31f7l3w4sv/erfYi9QnkL7p3znJemfQkBeoz/812eUP33JtPczyGOg1xtsTIUmbz1xRBX91+QaOo5drLSGB2kfgzkYJBh1IHPx6LGhQoBmLXsrEJeHSTluZOEaNAx0Pf+OnHrixOPf9iGuTwUYAFAkCMDAKdEwedUgyByG+9kUpAqi6ROJD+OVCMpJOzrpM145bccrN5lfXkl5WnTyZeLHrF2jFwQ4WnyckF1Kn1GQqmikH0wvPH3SjKfXT/ionWBw8sp4et3vQr8HldpmAE2qYzKXvLsy9mi4HNza0b4DGOCqO+RIRCodPq1V2Lp1dQThU1rCVmCpMpEsSHecLGAMQa+AZeOhlglXXKm8mUfC6ihSqM4g+TLy3jpvMhLRm1dG2RPolRg3pVWAnhtzfMThpElQhKzwRo844O0OtOj4ZGgxfuhf+ZPD21he4OHoTxaMXh+H5WkREA25DJNUCQwdP0CLN/aZhVMJejPtuDK+nlgW0KdSsmtiZN6dUrLQ1xw5WNn1INjLQhgqndN+Kz702/SbkzeTlwz/vFzGsmAy2o/sZmTBy6XNI89/Ix51u/mCr0UWHLCQPrvPpfR7vCUabID1J2pHvHOxHlO6g7pQWlC36B/8jfS7oxd44pzyyTwFscCCV1U6VJgjRfTFPIh1s5uLqENUuhEWhKgwVyz+Ge2IDXC6O97ZMdmVKkx3WJ+sfqNFjF+W9rCYcqAyzF0TUK0nI7uO/7I9i+e1nXa2/3bKFjdqbGNbz3H4EtIAXIAdPKRlAR7AFi9i9nAyF9k1sLBFLj/s+pmOQVgXg8EKLnt24z5ORWUyXOCbUAmA3NoxYtK0Maeef9Lsj3qOHm7vvfQfbrTRXz76wh+dPm/i60ZiGwknbMKR7ML47mzvoR9z4uQvVgwlPmxZMOYAfUi1gvQq93I+nRecUXDXVjHo28+DaUezkXgRgBADzcGbdMOKQr49bAfLfbds7OzkT9hGsAmgbMvzLgSvV8xOflYwP4wsFERfrDeD9QSU8Sf+cqtTemLvQjBeTHTw9HkFFSmKoJBKiVFffXJGpJQ3xPRdcGpFtGSO69saJqbXG6tSvB1gGbsaygxI7MkzNqDbAUQ63oimAO/xr9Saa4C09OndgayhXWacyLP2O05rk/Go5IxzQbM5L2qeLOA7W9j2xV/1KOYWAb06z7zHJDNftEz4vYRcZm5IeBJl19MrgN+E1Y+38l76HNFiUyNsS6mRy7igD1IWzFtTNB3BO9Ol2o4fh0CL0VuKtyq7mTCVvLnvZC6PlozsxgWcLAgQ0HlSRK8S3ZdXN2z3l5JL1c0D0WNhTveh6/LKGG/7mUdDO3ppz5pFdMusWnpe3T3UhrjLXH/RAO1ESXpjmYrlUuvP7J4cyjwaiOw6hVlSdl0ZO6wF+YETaPu+Aj7Avbq76wvgDrGruC4NV6rBnt++muhFjAcQn4eyOMWOA5jYIcSNHMhHiXpO511ZHHZi8Pfiqh66uLaHLqrk38LcUDoABnkut7a0bxpy8bdmlJKGw+X7viT/cKGRfv6eZ/34+afOee0IbNnyIAFNh3nNPdjKoO9Hu9voJ9tb6dEqRu7w6GGrFvfa4jq0h3nvHq5b3GmLwYe3B0geQfwYYFyHhvx6AJRA/MjMjdm1jkHjQh7DZt7zH8pCEbaR1Hiaoc8caPBsU6Mm+l2VlazsnOIXQ6FKTR5lAbJREeESK+vKRAq6VBmhjduPk5UGWsyguAnkvYFmlCQ+gv+Z90fmrdE7QFrE66LtCL2quawevyrPTVIsRGsFWpfVE9Ni9MpxfVNWEb1Wna/Dj5GxJFQRjSPaLDpZawAmVsCu4+EnZ7hsNSvFdJw9LUZDKVqKjG0sLzpekgAXsuUUb4Z3ypTQDmgxlkf0Bpr6kF3pTgnZlYq9QLh6pE3wl+mNr2GzcQleAa2mlCyY1zU3YW8ku33SG7UjZVWmDkZ2pdvWby8vbi6G7cdojvTV54zsxvKXI7vG/pK0KKNBgvf65s2RMJY5um5AstuHLAgZLLMmu/I5likYXJNV5W1IHcPfy1fxYipnjgxU72ZkN+6zjmMYQ9eOirUQZLsLNue9TmVvMFKETOK722s5L9yizhqafPyx9L3dD/CjSHsUBm9gdiLMmZgWrSfYsIi3Mb12MC6TisXRYvMnxEy6OTJQ2fWi4G1ELHdoA0BuCPPDDibBiwcnDbZvN7BzB/Z9Kjt8TuXDFqvYtiOOFbdczecDerhtA7t3AHZzGR/MRFwfewBh/5c/QtUMDI+fNpq+X9NBC2vKiP8riJhOLZwbgLfwsU17b7nyxlVvfP9Pb+VbGw7/12F/QAMsfNWXr34d/3ndVR99wR9OnTvpgjE4dYv4Op00U6rL6UNTh9Bb+dqTr+3ppO9xKpUtq3lrF/nzMPjIni3v2bULQanhbtshDGTkxkhioBHXtXI9b13xth9QO07t4vYFbPFiBWD5s2RcDUDYrFCJCF7AnDK2ujQlizZFP+RMmswktTJhBh6QPC9jIX5EJ27GoxTTa5M7U4F6VVw7oE/yCqpClcB4e8ZmQY6ghz5pO2aDjHemtM0jF+LuXN3h9DNoj0BKKJapuMBPO64vj/DvwdMSkIwCGTP4vg733oCHjbUASa1DwGT0CvQaYFcFa8AmEzytYx5svxkmyERk/MNQmTFzBj14LPyYqLyEeFKMmTd4JqtO4ZvhCX3OkTknFgHIBTbkyYJrJyhyo931QbzdmIP4q3KaR6/UYbzR5+MxKhoTlV2Tt5L0unozcufbMQbgO3jHtLGBym4QP7MakewKHyCzqBhvVD8EY9lHn+UnkyETGCffYfsQ86Ev2QX/9XnBUW685HvrhJ9LVsbLLn+XGeq+ZNfLgspd8OQZvUGACkw3/RJ0qtLsPfyB3jCBCs8Gqx21mwkB0LE1WcgcpHDPZbIi2Lj69lSeM/PK2Kg8Mh0Fm8RAr/eO1dTC25CvXfoseu6D++l7Ozkh/MlwNkRzJhYf40uRLNikUFoy5MWVOHptQQM+ik6FzXWyEculyW4QERszLysRLSgidZrsuN9NLxQG3AoWAF4D23W+AlUuRcDzyLVXyXYdTp4mtv34Ert2lnMPTp2dDYWwlMn8HHQNDqIhTg91Y9uX58XFJ0yll9b00Eur2TkkwPxAs2XABNx2Y0PLzi9efvvST1xxJ8eQHTmvnFl/eBP/kjOOKXvxGXPf/8LTj/n0UFyBxhNFkuRiaNSebWztoiu2N9M32dNX3tBKa3G8+vRFHKPHnjrs3+NAxmM8TrgzD6j+RPbsPcToH/fnwfuHRMt88XLtvib25rLA8FVpNIufwelOnNJBO3ZAzeTZUoPA/Qu6ZCXEqMjJ6IEVKIjVB23y4K8p4TCn3WSxkSrylthEgFBKJVo3FIlNblP6OrbWZimPYZj/qoykCaMF85LfG2D1/bMyfsstz7tjXkepN3SsmN54FY3ycZ8yeiD2rumq31JeSFMlPCol+as/mCcveHSUl+axPeAG0x/68HSEKVaC3uAZkA67PjvAFvhewnuc8YzllOnLuyb0DcTDbPRZedWMYYy0DvNkF3lldDylGswXldPcMXLydzDe7sBrHcfAW3w2tGZC5I2hMTiaR0F2Udaey5MpT28JL11mnEvJgp8jJdqRBaaORd6cjq9hy+gXnSPB0+rmSJj3KncDoVf4ncc7k13VHWbgM7sj1o4Hjf3No1jmVBaDK0b1YZ5OzczZHC9dWLjn9cfaUdkt6TFXOelLp1oIDbxVuAWFHRC1fJDs67un0xWjWujmKb3UWs1ABiFKkiA/r89ox2S8D1kI82GgZfK8l5g6mK82pnn6xep38yzewSrykqrsBJ2qxHpdhzb55gyJx+NsAXLoEvodIVo4lAmwB4CHa0tx+vZkPmzBqdhq2AHUjmwbk9jGIzXbOP7L3r2av9xFZSfNo0WTRtD3Kznpcl0FVWE66ZQVlkIv8TmAjdvq7/vJXx9800d+cTuj7yPvZSbuyKOcKf7Thy/40OnzJ7177Nih4+TQRVixFSZYE5+s+UJ9N/181S7aVVFNTTMY1SOXGDx2uBQZAZ3w5j2NY/Zw0IOvSBMAyHF9YMw4zjW2C3nHEAdw/JzCyZz5vMW7nQUNMYGGLnEwBNu/w1iYsPU7m8EhhBGnfCB4YaLbBDB2m3HTAsHT5xV8VCZbmavbjI8zQGFU84bZT0LfXiwKnnhXj+gbVTpx/FeRNFkdajgFk1pdTpEG9pjiChoajem/PN5ZXXEZZ2CCl4vHoyS9Vo8ZZ9CLOvAM/ua1o4Y2KPuojsALR0uoJwdsSHlu1+jFtkkAxGgLvCnBF3lGjXfG2+T5kkdvX7w1eeqnjCwAwCIPcmK5i8Y69NFkN+a/yaU9N0BaPNjIzAGtxxteT2ImVigex1KyiwZMXpS+2KPel+wGsONlAfU4eQn0Rt4Pu09U+lhKdmGUTYfYBHPyI8Nl/B2A7ObyLqo/sxXt5nlfshs8edaVUrJQpFz60AsYGpVLz6IiT6krk5EX35bTl8ZG43uQocepUzEOODjYinRhDOwAZLD9iMNlfCVY4Q5tdDem1+a00Zs3jv2UCXLQH39tcaB0CC9MxuxZX0aZlVEFebTk6RdHiwFveBgRc4e0KkvYJrNTRpw0d3I83qJZBVuNE7XIwrCRf+OTtWW3Psw7vHW0B8+dytenMi/H8/24uzgzxyvLO+jioWX0Ao7LC7w1PQB+8zmAnTsbH/3xtQ9f9L6f3nJfHneOlO9ibXyk0J2h8/L/ePYrLzz9mJ/UVHGiMBYKSZAMnaH6ch17+q5Zu4c+1lVDQ+5fR+3szdvyFPb04RQvX6tGi2Zy4mXedgfqRxzfbM48D+8c8o6tYaEawhNu7mQq5/vzKniF1YmDIIgHgDCgKb6NQz4zSBzK8QBdnLaiHUIHb6BsN5uBdt4SmyS2EjdlFHCRn0BqaPx2rwEt2WLUCfW4PHk62TIxHFa3sjvXS+c0X0kvnSkoP8mD1cBIRatS1JnjxcgYZkdvyTiWHI+KPObBG3ir/fNeOsuVaDEoeavS4FHph16pP6ZXhdP6JGVienUVX9JL56ZBiKuMZUEnghngUrRkeFuC3gytOcrZ6hbl78GKyb9WELbLSshuxqPiaAndLTGPwu8KPvLojeMUAxhQD1CRZyyWXS+3OmlDO87IlpqL8bzPhHCIxdVe5HkDISIDkF3zpB6S7ObJZQnZte1H76n3ug68zFx3lec9Q5+030V6TGXXqZjceVQku1E7Aop13ufqKDTgPGb97jagLpPpHN0sbUVzSEWloOvcvPU6FQ9ZXK3c9MS2SfLFathRLLuZcc7Th9ZOXzKl9BwSvU5cMx7zErQEHQV6+vDYhmTTXMZkA9uw2KaFzeUsGuNuvJ9mMK/uQSo1HKzEoUykh2LbXM03aXUgp+Uojt2fx44XePhGDKEyTsEydf022v2U4+jMjnb6+LhKOq22jEce5wAKvJKhV5C3bVfj/d/884qXffJXd3LuliP/dVSAPRuG//3A897xlOOmfHj8mKETM54+OVJexjGdHfQ59vRd8cgu2lxdR8NYbnYhFg8XIdutG+tZaPheXMnfB28dX6EihzgWz6ZqvjN3OK8s9mC2ns3ewC3svYNAIRYAbvYxw+i4q+6kBi6zZSYHfuJwB1YiiBcsiJL+8wYjNh5uVSSePlUqGRe9UxjBPkTgKbt/bNrHSWze0OcYsvCEabAS7cj81t+CncdnVaIZT17sxfB88Rbat3WQnrGBeHcMYIXt85jeSElmxtCUeV4Z/Gb0GgNjcNRff3wd/GzYOnfeHW8kM7FspWShFG8Phf9xfzD+Bpi4Pgtr8GJmwy4ybfMhBzRm4sKMzwfTJ+uPPusNc0aeVaZtG01ihLSAeZoyXot+5kCu5zcax4F4dYXenDlicz3Q62Qh0Guid6iye5CyEIbF8U5Y2I9nMmztPxmykOclVULDPIro9V7SoFNRj/JRHnd6V77P02NOxcrbvnSq1w32Xvlvw2AxZxl1zR/84jPXBqBpT29/tOTJto2j9eEQbID0Xz2S9riwMocvoBf2ErH12CVDCBVO1WIXDbZ23IhCVg3W12WcV7cMFy3cw44WBm+yY9fGdrquisazw2UPP9cDm4yTtfxsOdvgHgaAr+psplePqKRnIwSfY/Z6uU1J5AEbgH/8zObN9ff+5vY1r333D27kUzFHzysjQkdLt65433Pe8MKT53y/BisiyLx5+nQCrWntphs37KNP7uuinSwfzUieiDQsyzh2j927tGg2b8NywCeuVIFLeAZvx4rA8XdwF8LVvpTLXH13ITEzFAjiALEljATOHOs3lEFj87EM9moYNCJ3T/BmcNmiVb8KvlecPjYM78NxfUwUfPYTxilWP6FCmT4UeLxaDMYunthOVPoqY6BP6NWYxYzSi0WuDwUSgp9jxap1mAdIqsgrA2UHfqvOzpTROoRedeGHPFN+JkT99sBAiilvhQY1ICVp0TLhuRyj5A8HZLb/tB2hVQVFcvfFSty8GMECoFH9l0Nv6GoEpmxrL6OTc8oEubaKvGHQsYUitm3wDL1eLo02e97V47dFi+hxsmCGNXeMdKiK0rW4dmy++GvNyvnLHtYBOJUU8qmVMn4DlUubw/3Ii3gZuays/7x8O9m12DSRhRwDasA5zJE+ZKHPedSX7Dp6MtM7RxbyvLpBBrUP8lhQMjmyq3zxsntQOtXVnYkv1rkkP/P/hA6Mkb43VVXkjfWyC9pU7wTdoHzwcjkQvRtsgPGX6/EhKCav3ongp72phkxoRQ5vY1pCd5xu7q9MGGrtv+nCzNz1fNFhNV6bHMMjh9QpiE/ERQnYip3K9hfetgVTeYubY+JR514GhLCrsNfY4sbFCrgOFcmT21nfYMcOO27spKnhbfGnVPTSB8ZW0DO5SLjH2MRAvKlIv9f20Od/u/zcz/3mbs7DcvS9YvfDUdHDiz931Q9qX/aNst/c/Mi7djW0NGEFUKaTGmlb5tZW0OuPm0D3nDyB3jV/LC1o5JM9ECIO4pS0LPDkQeEjWPZR3saFwMFIYKUBQIdTPLiGDWVxfBtxfjjVYzEX40dTCw55IKffSA4alVdGC2b5LGAuNnxWRCecbRkb8MvU4BS4nBa1We4LxVogb6iVDqElfnmj+P/bew84S4sqbby6J/TkyAAzMAkk5yQGFDETBHTNut//291vw7ffmpVgQFyzguDqmlnXLIIEI6JrIKchx2Eyk3OenumZ7v/znHtOzXnrvre7BxEJ5/4YuvveulWnnjpV9bynTp1j8vqNg5M3m24aqayImVifWlnyesFEumDtlPjZ4qnvZ3l9fVpG8PD1WL9MNv6NaSCOzzWi5rHzdfhNtZUsJfbsj/WjD12o1RXKq6RUSIdOXblVXrRV2SxLXSjkbdWWDK3pZAt5pelWZfQIRo7x8NBlkGUdceOQ6/DvuT6ZLFke3yfXn4q+1OBvAXCzvrhxNBLCPllaM2ySE7YNSP972EFp1BCYArzrRCvcsmhPgO7KBot/eU47fCiLjbOR0CbibeX/0rqra4uRIhmHUiedvDbedbpQGeNWulujc5nomj7UrKlWxghGlpe6XswjqQb/kzIcB/5dM9eadLeYjlJN3frjy/l1162pFXk55akLhmOLeS/bRas9wHCrkTG/Va6X2l7lQcKXKeUlZlwWtIzsAS3aq4ip32OINFrztmGv5TykJY+kjeSOawkJIC9R0uee+zD3Y/rVP7qogQ8vatCPj+s53LXe2N2Zfr3nwPS7SSB6g92abzKCKM5duvb2L//87qPGv+3rhzxTiZ6t1L2N/DPisyvOOfVDrz5qyieG8iauOb/r+sQ9c/bm7ennG3ak86BPG0jamF+S2ReG8MIFLm7siyNZ5s+jE+huYxpm5qP2wdEvlIoRuWdB0Ri/ByZkuaBBh1Eq4mRe0EBDtGzY5iCT0RaRcnOzycgJxhmiG7pMChXYFlGGhMlxyPyi6Ddm66Q0qo3Ze65tW9BqLUCUpU7eggD4GIQVebVdCdnC/nhZSvVyi2vd8YdYver6ZPVY3WUfaz4XC487drQicnKjMleORuumQh/yNvWhWOBFXBI4biZ1uOh7ORSHbTpu7Iz824afsXdtyfi6I6mmrmi9T0QZmV/ueLHJcqJ9zhdeTC+9vH6OtMClP7rAMr11W5rUDcAcfL1aY6858rEd6esrd0+nHr42bYb+diKjQTfnnid7lTn9Z8hrlrxiajWQMV2w+Vijux5KqaMXncrm7rKMNd5PfclEqFCqLIubi2W/pIzOoX6tC74Ntw6YLlR8aLVub13La4ebP/lXh0PTyYkTvMk3zbfj6n1ca6qNGeej6aVv2w8p+++xMzmsDpXF3BCyVbHAxdb8JnlduxX9tnqrU6AxlUweHXzTDRvnLC/3Ac8ArS385I3ku+Aix0uOvKjCkzNmvSLpOxB7LNOa0ZeRpO74/Rsx9HiBhW5YjISBMoPgd//ikYPSO0d0p9MHYK4qVHnK0q0K723evHXxeT++9fALr7wTDTzzX89Iy145bK/7zC8/iQjXbZdd/8gHV9F6x8GGcsvch87tC5L27t070vzJA9K5EwanA7tQ5iEo0SZNvUYCyHh8NBXzmJb+fCQuG/EelVDy8K5p6C+OcEXx+IRCfwuJ3yPnMA0lFQJHK5IpOBdvt8BSKFlnGzLmYzqbrCzK92ktsac8W8QqE5oFfTsNEfqfhs0mdVmPyaubjmLYOLKtk5fvUV58bil4soXEZqEtUCUufgEz2bnJ2cJMGYoyApiRSv3MythxLXEyee3rVkb9OwV4wVdl9AtWfpo3ecsypoEyWPqvph5Z76w/TtYsry74Usb1uwKb9qWx2iqp87jY74aJ1zuHnbRpbbTAN9/2ralDLE34J0egeuRZOQrG+6a7/Nz60ISvG+eK9Sx/oQGu6HqhC5WHFXws88jwL3VBcZFnqlJ3VYU2daV7t61Mx499MJ2yfETaNOeQdGTHWK3YDYLJ4o8eS72sk9eXsbnZl7xsw9aGUnfz/EMZqa9Od/mlXuZIfkDx+tKX7qq+ZEikY/pypMHIrPVb5HWytpxrNs+8XtrvThcqpyN+DnDI6vpsstm6pbpi62i2yFl/WO7PWVMNx1ZrqoNNdLdYx2yO2zySgNONad+Ig0osHS6iKyxTzNdchl/0snj5bDCtzmKtM1lkTHUMK/JaX9y8p7x8TjILdEVeHQPePibBG4r9jf7zNKzQV2/F2kbGC+bs5l48HpcxKCL3ZNY3EYYVuG2dvm5N+uMe7emaUV2Z6HkjOI98Zy1cc9OXr77rmOFv+upezxaiZ7Peadiz41eEbPn8Sw7Z6/3Dab0TM7N7ysBmPxeOnlcs3pT+vWM09AoEjdfgEfdICB0DNPPmz6uPS+kemJdJ9EjoeMmD9TByN83ILMer4UzDdjTiAdH8TN9Amp158cNuXeUFjpqr3Nuvj3lIOGnKyWyTXH9KoN/aL2stbtFqOdT9LGO+T9Kcb9Mt/MJx/eLDRVcnvz1xVshQKVQ/ZWlZBxcQjq8t/IS4BT627pUWhmzpU/z7xNfVz7yWeQF0G1VtHfY9WvpUGFGJAluzYrSyBgohswW21bNcX7LYmNqxcR1mujiTQGc/OKpCC3lrx8g2CtSRw4MU+iIqwfdsjvah3wJdK3nZnuqDlWkqqzJtZswzPLjddH86cfuo9J0zPpg+9qdvpJ+dOCmtGomNKJOyOnkpc2+66/BvZRnLljxnYavtl8qbP7N2TQTThz9XF9zm37R2lLpbs45lsVS36yzZUkZ1t9d5Zvg+Xl2wDui6UIerySLq10s7lZR8rRbVfq5jLXXXy6vzzjeVsaVFvdU42xbQH3z/XN118tbN+zKsFB9eDEcGLmYMXPrezYPrHNc7cYVCvxk6TVKfojzDoL3w0Ia6wOhyyqCe9L+HIhhybVozXpDsSdu2dC0/54e3HHjRVXeuaTVSz+T3e5stz+R+S99+/P5Xv++VR0+9YCydQXGJowcbgV/DV3buSF9c3pkux7+HmYaNkbcZdoVPGFQ8BmLmkQ4vbjCUC4kczcs8+sVVb0m7xtQsZr7ejw6msBJKmhb8TsvgCNzm9cdeTVfSSwKni65ZA41s+U2ME0T2EyU7+SjD7UFmBakrk/cq11bl4oBfAG3B9AtEqVZ2VKkLvS3k4rjPichFqoW8WRbd7Et5ZTG2fmk78p5ugqbFecH2stiX9afUU/Q5L1b4jJZasfy5ulsFAG3bkfYfvFuauW211inMV4U1oWrkzcfd1nHbzFvhq/hbn+2Bwftv1clrmNX2mZWpvPlorIUuSFgYu23eC3Z5jEp90c1JjoAU/950QWBp1Q7rwJfzRu2wk6+oED4YrA2F/FS9ZdmlGDcmRsdcHzVubPrXO7ekzxyBz3lkRFWkRV8sJ33opchbV0YbzvLW6EKeLp5M2Zwu5K1Yu4t5b5hJmaIdC55tbQl2NWXyEV0vuisPcEY2yjFSeU2NTV/zOsYP3HezutfMtaxLreZIH7prc7rSp5o+myXSHjYqeqlzRCBVvXs8a6qXRUKOONzynOb72ulynLMa6OfZmt3LmtqXvFan6YLA7HXK626dvLQC7pxO+UGtsmdpHZSXJ1605NH3jmsJAyLz8gXj4jFXPYMo74G/GSKNZbgHI61ZO27Onjh9fPr8kK50GCIhI5GWtGtLn/BeuG49On/Vjb+8dfa/vue/rofl5dn7KnflZyUS13z0jC8/f/89/9+o0SB93NDlyWOnsi7s6knfW9GZLtjUllYzdRr89tp2G9m45UvFu38OgjciBx9z8tLKx6TKfEJhzl2SPT6JMITLC0EIebzL6kkMeeTLW756W3gn+H5jbzUktpGVn+N9TiBqfE7pZUSjKCsTsq+29POWljxbcB1glWZsZW7RjqwVOkOzZbKymrvatI5WVi0pqXVJUdTTFEDZb0Km/n4HsuYqq9VOGUw32E4raxTeP35ld7rsOW9IX1h6e7p4yFyIpQtgrsnhkuvsRd5ay5jHyeQtsKtY+vhZ3ZR3skg1fZTJac18WU9I7P1yHOt0oeiDHO2YpY9g1emukhZTvZbyKskyS4JwCZPT95kbmX2mukDSQv8hbkR0Dl+5Bv5A09XKT0sE9Uyrq2So6I/u9jan3bwuraQVMtGiHSPNdeOc1ZxEwHShlzGyOdRkhfPYsZAjV3X6Xae7WRb8krGvWZ/MQtqqTP6KjUcr3TXCbbpZN797qcPGOxvOWmyd2dLXSnfdetlyrvkyppt1OuN0xX+cj0ltjaqTRftf0Ze+dNemT13f3fqeSb/imZ/GTV7XDtdQ5rDnSRh98+geRevdwdMaasVLkXyf+yaDJpP4IULGa0cPTG9GIOQ3DlKfPN9/7r14dW7uWvHhH9961IVXzoBTfbxaaOyzE5gfvffV55z83OmfHk2fPGwQvLm704WmLS3Bjd2vLN2cLlu0MW3atC0tZLDlk47AzSAc5/L27W6jG+SOZI+BHg+EZe8R3OblhY1l2CxQph2RvztgMdjCxMu4Fi6BnCXdmy16UFQbFZk/+llfFrg812wS64Zox0R+HtcGIa5pp8n/o5+ymB9HX8GOKxsDBMwhOrio2MJgizIXvWJBaWqHcBFH28hKeR1xaGVR8fHxMmalNVDrqQsYq30es2pLGjegI63euDp9ZPHu6TeHDE/XDgdpyOPYSx3EJVsMy02pF8uM4Fn22S30lao8vqpzhp3A3MJKWhfgOY9LPy0q/dHvbJm0cbex1/5IHW7DKnVBYFBdyEShzsKsZermmvhnoR5a43m5S26Yi9ORWyBV1yqXaFq0I+OK8rWWySdId2Xa2xxw41gZI2tLBNo51mVIkV7nCPWH7Rj7aWEZq5DOujIqi1hJW+luUaay1jndzW1ZO7am2hgZLi3aKfWydk11a1A+7bD56tcdUxE/zwxvW1jsZ81aV3ED6Oe6W8pLPLLFXIe6rkx+aull7ZDuqK7I1LPx8n3G73IygX/0cx8IfzpEwmh0RcHNN4o5jzB29Mmbg2gWjG27eGXqQASMrTwVY4Bk+sdzzsEwMviqm1I7Qq8ctu/u6RsDtqSDhrSnDnZPOby0wLrhfz9rweqbrrx+5r+c9d0b73MT9Vn/q6n3sx4IDwAsff/x3P32eMfYMVC60tKHBXAJroV/ZfX29O0HEUtvyJC0lmnRqMx8OnlgXsMaQPLHnLuPQJGnIAbfIzjyxe3cATgWGo1j39XM3jEJ39sfx7n6JFJrdZEF1x8DtRqqYjP0G5IF5uS6nK0FxdBnIsCFh5NZJ2h+ki0me578pTxuQZCPrAK3SO5sQL/sSJht0hXLJDHwZK9ceHSxFEueyWP9c0/0sqH4hVZWLeusk8XeL+rIXXV9so3VrFH8ymY8hc7AMQP68PF9kMz87pXpW/tuT/95MBY4imM3gSt+i6Us1md7vxX+foeqk7fYgOSYR/vcShf8kzibzZY84mdyOHmz35lh68u41bhWF1i2lzLyEeT1etjk5+Y3IdXfilW31AX9u2Kx6gU7+Qj1iqO7Ho9l/Xf4m4pXLJMcR2LeSi/1Q/Zpl3XX41bMtbzB9iavCaaWPr8p9yqvt+R5/XNzPI9Rq3lf6GVevvBLpW2nU1JGC+Zp7NaOPCZuTuc+sV6r2L6s60ZuL09w/aVcU1375nvsVb20Qhupt3brjjE9gapMdS+rmyMtfTzLdcLjZnOC88jXW5TxKtoSt3K+Ur8dtrxAQX87+qiPVBelITiipWGE5bjv0dWJsWvXwY2JN2gZzeI43KydMSuNHDUkbZgFyx5z2o4dlSbc8XBagcsXb8TX34DqXk9LnnQVxhgls208XUO/NqzfsuDTV9550qcvux1HbfEqEWip5gFVSkjD9t7XPHefC4czdRqVSyxwOx9mFyJky4/mr03/ubktDbprbloDn4JVdBqdA+Vl+BYGWr7pQYn3I3599DnQWH1t981JR/3uzrTopUelZTRPM8wLJ6JYt9AIjo7BFnDEi3/0D8rzUhey0mfPj2ST34eukWYtyRPeFjvbcIoFwza5Xq2KnohqPf74qZV/ld8j6qyXrCqnhrLNspBX1h1dwKRp/zk/5FO/LtiGV29p2PJmUrYDYSvyln02Kx3ep58Xj/JB+Ifgos+Fc8ekKyd3p98djGP7gbAOWcqfJouKyisWXiUITbgoG+hLlsrn5UbBTpq8GsuqsXoWT+u6Ahi+dWWylarY8L31sk4vK4tLC0t2LqNY2DFadk2okbdiDfS6LLN2J9H3uuDbkTJOuJa663XKzcdsGUMddYGkS78nwbawjHkL3BOluxW2Wae7kJexRWlFaeXXZ+4RpSWv31ZdN855aFrIIkehDhcpz7HxuosyEsqp1F0da3mbc8nIYIs5rUPZ0pfR1KFuTbVF2ZOdVqcNIpabJ3U+cNJWSUR1TTXdLNeo3N1edNdXa7rQ0scThfOayjpr1neT0XTXrNqMS8vLi8funwYhN/xIRKVYDWNIOmRKwzJOnHjqxUuPE0alDkS86MJ62N2B/fWIaSCCcJGi/zzSku5916y06vkHpePwtS+OSukgmPG8JU/E5z6G785btPrO7yN37Ud+eAuO2OLVCoEge/3QjV995PQLjz9gz/eOGwsC18UjAl0hqHG0RCP37hfWdafvPrQsbenoSDtw23bt3rjAMWF0g+RxUjAQ5It4ewjfJYmhPxCPcnmJg7dzWeVS+Cbw4gdvI8n1cxBAmrJ59Vz2FFuZ8gql0tcNoy/Dz91TnFj6KLsSi2y10Alki44sYr3UYwttBUMvi/3uLRCFLKUVKS94Kq/121v6BAclAa3SsDVZm+pkYWPe8tibvL6Trj8iin6PP0nmeTTRiWMM+mUuwuLHp9p9Mc7SnPWff9Th4ttRIpHf8tgVBCuX6aM/Iq9tgKjDbqLLpR6ts9aS52TJfTadaiXL45G36HPFP1Pl9RCa7vJrmQi6DZO4PGHy+oaVPAg51zliemn4iv+h9icPC7+n+Fd0wePLTZaCl/j2YsnzR5hN+mJrB+ut0V0jHnmqq6VPVJT9a9xmrFp1W83pXdGFXbVMFvPV5PVrVO6Dx7DFfK3gVMrS15pqsmiD2afZrZfyUEE5vH7osFrb8pHJamPel7ym1H6tKNfdXtYOW1M9nFJlKa+2U4FC113ONWaSouy03PEBlxY97l1wTxp1+yNp+pp16R5cckyvPLpB8JjRigSdFj2kGR0Pl6b1GzanLiY04N6IOgZv3Za24dLiW3dshiWvLZ05FMLK5UkeMPTARR7f138rV26Y/c3fPvDmD37/5js8EvF7PQJ1Gh1YtUDgxx949fvPeO4+nx/C2D6cG3o8ag+hs5GG7dcL1qaL1+xIi7FhbqE/HgndkfsgUCQeOpiO7b55DfJHn4YDcYTLLBzms4dgkHLj9xe3gfTBGsigkky5RtLAepgWht+VzdgWELexNVngeiljG5GlNbN1puIr4tQj+6foJlfxx8ur7s7NrdxwrLwU7W1htTaLzUQWKBJt/NT0No0mXNvWZmkZ87KYNcr7I1XGWy0Q9rReK68r07TJQn7KJ4E/Qeh5RM/YUfyZybqusoZpf2Sp3cytHsIgjEbxcBuOjKsyjdoyusDbQwhlkX8eW20n4/142lEF80en2SfUy6tt+027ctReyGuEqSKvlqmMa83m1y9ZuLlxvukmVxcMls1lPzn5w7Vs30MdTTets8LuHLuWuqDj0l+97K0eG1rBzMtruKmsMuw2H/0YqcVcVK4Olzq95Hs15E5UqRfdlVihjnyaDpYPc2KNsv5oB63ayvFqC921W8lmmas7bagQt1brruuP6WTFBUXxZJ/l9q3qgMhqArt1N8+5XtZdP4/qfOnKMcrkXtv3D3q1lknFM8vL+QCwN5DscS/DOse0ZQw/RsMFU4kuB/kjKaMP+wm4mAhiJ4SQ+x3HcyIuJpIs0j/+unuxF05LQ1H86Lbt6aJxA9LRdJGFLDL0NqxscyDyzq/c+MA3fn3v6f9+6W1zKlM8/ugVgVKrAq5+IHD1B0/7yAsPnPTB8eOHDTFLXzcmbzsnFZ54VuNp5oK1O9KPF25Ic9vwNLMfooHjCYY+CHKzCCZuCe1wDPwU9ofFh7dyOeEWr0H+P/w9E35+cFaFiRAhXfDEw2NkJoLeB5OJpnKGf9Aj5Ya4upE1TXTrTLnRceFQ8iHkUWdUZdKbatjiZItRsTFXzr2cLBnHuo23huw1+aL4p1theG7TVRnMWiJtle2Ufa4rUydvpVLtRSsLhFcWV4a/WiBt9Sup3jBtJcsutiPNl/LW9FuSvHtZizLWrCfC4pvmv9QffPuSpU4XakhYky64MkIM0I4RJwtbJBarQmerZ7I6TwyHfsrSq36bXpKIafUiQmGZMR2gTggp5D8/HqXcj3ce9UN3xaqoUGWC4eZ0PuL2JK8vXSj6XKuX/SnTi+5mERm70oFnREcu05icXhf6uza00N2Wa6rrjy9jv5sITa4lJFgcf/dTXEuUQDfpsNOFXmWpmUe96r/prupCvrFeQypFXuo39VzbYfn5OJKlnzovMC3EfkU/PYZMYdQJ7nN0WbkbJPBF+Hsp9i3ugdzX6LbEyBU80uXlRRzfvg0WvLfihu0pHcSCMrj5QZKJMCuLF6+599IbZv3Le799/c1+9sTv/UMgyF7/cKotdenZJ7/jjGOm/0cHzdMy9/Ekog89/PNRHO9etaknfWbltrSaPnjghpJejUGamXP38H0aadngmCq3cmkJon/DQ/iMN3t5qeMwTAw+LS3E5JkGax9j/O2JCUWCZhcvOAnlqUsXuzxR7Bfb2DiJhInsXPDNh8OsTtn6YLuXr0NhyE+lVqYOHlvQfD1O3firiVKRt1BJS8Pm/aHyOqsLz5+beitbjhxOlS5pH2RztMXXy2mf6yKVLWOuEhGV48NF3W+ku6KARirKzczXUVcmC64FTV4urNpnv9fZZpR9JluMs2xYpc6VshATa7+uHt1AKjdIS0z0exLnkIKqblkxq75PefnVUi//EvKqjthFnFJ3vb6LLvSCS1/y7rLutsA2W51Vf8pi+Yi6N90t9czPFR3nrAv+M6cfQo7q6rG1J0/+mnXMhKZemo70Z43yna2RJVvGTGb+1HGjvE26q3VYVebT1jTv3RpSK2+JQ92aWshSsfw2pspOzlc314i3rRmGg64v3t9RPnK6ynSgtzzUuHzIU6dxuD3L/YtHtEwiwCxSJIE8weJFReaXp28e894yfh5CqwxZvzEd09GePj6qJ500sEHyMgxszo5r12yeecGVM0747E/vQCXxerwI1D0KPt66nnXfe9Nnf/2lIW/8StsVN81638oNnVsYT6/NFgbMof1wDfwDu+Eq+PSOdDZ+7tsFxYfzKeMEyabPm0tM34anFvHz4q0ifp8xhTgxRsJHkFH8Off4OY+APGkMYwAAop1JREFUOYHoF8Z4Q5wMXGhYvuGxqpu3Lkr5KV1JiPnf+GO6/GTMCY/vW1ozlrUNIB8T6IQvU/DkDccTQ5NFCVlZh6wnXJTd5mFl7MmOX/VpzcxJnD+puSS4/JxWTvE944JhC7ItuHW4+EXbFjEjYSZvTZm8Mesiam2xbTHmUB5Xj1Sl9fG75tBMbEVeHZemDdsWVRtH1ydpk533JMHX04ChWsbLS1n5jxsHb7GhLlkFXB0yJIqtiKkbWkVerbMiSykvyyj++chU8fBHs7zlWqaGMmxFF/BPwtzo/LC6rExFXi1vuPt2TJa8+3lZKHsLeSs6pfi2klfGlmUUP7/Be3ll+HT+mqxlO3Xy5jIqr/SlF921LnpSae/ZfDLdzb5jqgu5LY6PzlOv0+Vcq9VLI1usU2WtWOHtfalMbzwX+u3llUIy+W3AVHcdcZJ+cP0xvddfKtj5daGU0cr3saaavE26oPVlkXQtzK4ztu5pn7O8rg8igsml8zGv714+nXP5+LnYAxpKbZO1oZu9pjVjeQ2XInqpD4QVlwvINRjnrIwmwYsXjJNH33QhfCB7JH/wvZO0ZjRwcO7Cl13EYG55ZMR406a16Rf4+vVjt4Po0WfdET3qGlyW5i5Zd8OXrpzxgglv//oBQfRsvj/+n36GPP5a4puCwBXnnHrWyUdN/ewQkDxRbJ+GDUjPxPHu1cs2p38fPDpt5ERgLCHe2uMTETcy+ugxE8ctD2MijWs8AfEGL5+G+CT1MIgij333wiy5Gbd8GbR5AS4AMDMHiR8tHySMdmxEIezGH+WpjLZb4DLpssVn59ogccXk1lsvqiJ1NzVQaIUu6rXVOFlaWUNsvcvWQL8QEmuTHT/Fz8hvBiaKa6dlf4qFv6W8dtygG2EreASWQlaKIxssrWL2uVqsaudSb9iWfSrbonwcTyN4Kmh5RFuxqJR16NjlkCIitFMSj690rBd94feUlFdkcHWYLKYLTdWxT6rbdZaxrAq0BJqsdfKyYrfh1mKvZSrj6Ae7n7qbfaGsMzVzLR+d1+H7ROoux9PpVKm7mRSpblfO1AwyJRCZOLj+VHDcFd0tB0DH2Qh/5URCy2ZZbR7VjLOVkX62mqiPRxdaKQzxNXlcmayXXKP6kOUJTcOG9vylrCxSH2uH4WYnOUag+TX6IPPSIfccGi0YR5YhyPg7/cwfhSsSL2zc+ghi6B0o5HDQ+k3phME96b3DutNpQvAK/Fgn5vPGjZ1zz7/09kMQDBkbX7yeKAR60/wnqo1nXT0/+cDJ//LSIyZ/dvyYYaPy7d2857SllTjevWBZZ7piZWd6dCCeeKbjeJZPRLx9O34UJgpCt3TBWse0ai85okEWeHz7wFzE6oPZnE9UM3HEy82Q3+H3SR7XY24glp9MuEoaNl2YzYpW6wjsypSf5zASXDS42OsinI8y3J7Z0n+Q38X3ZIIrIciOwm7hywRAnygroR1KVfJlDGCUsbRmctxXI29lv9U65L1iY/LyeuzsCV0spDaFSnntffupG0ClHeKhG6/Uxd+1vIWZsPU472GlvEYSXTuZWOhqyjr7TGumoOR2SnmVPOfjPsUr6wK/z8Xab26tdMrrQlFGRG4hb2WTMpxM91roQiY1SqzrdNfqzRY202/tkx0b1+qu63BfusDvlymxyrmWs3G4sSvLtNQFnV8yFdxca9LdBsR96m4mLC10N1t6nS5k/VZdyBt63RpEK5TqgoxBqS98S/W7yUJq81U/N72RsbJ5pPPfSGJLXSh0V+pqobtGfEwX6tbU3Keyz27hEQubttNqTc3Hw8V66dddE7XPdbcOf7dmlvibqPZQx6/z0hlzxHMv4vtMa0ZLHfPB0xePFzB44YK+6LTu8XTq7jmyl52574T0/uHd6Xgc1w7Uix6Vk2tY8mbNXfmba2bMe/87vvmn+90qEr8+QQj4pfkJqjKqMQR+/uHXnP3SQ/f6zLBhIGR8mY+dri2PYj5csXRT+mTngLSBx7LTJ6YBmCz57gV99niTSfIFgsyR4DFYM+P3zcKTE4+DV2PCHY8nJ4ZooUmdMfloQidp9O3lyZxncUMm2RRs89UFofFBdSDz0SrK9DcNm2ysrKfO2qMbiLVda+XRRVtEyTucymV/y45UvMcmdaEXH0Rb1Mo6rIt+Yy3117UjGGhdZjjMWPnNoVU7LOzldW2ZZTJfkjGZ827pMLD36qavk9f7EmV87Tt+THrD1rAvyoi8irE/3q7A58Y+E4c6fFUWKr7d/qyT13yLetWFGnkNrl1Kw8a+lUTa6YvNBznu9uPg9YX6X6e7pgYtdNeTjYxtL7prRKdJHQrdrbOMZSxtrtW0Y/LkvhRjWCtvOc4eO8Vkl+X1c75mjbLpZSajvuTN7dfNo1yZs9LVzV1bOzg9e6kn667HxdZAvJfXk9LCb/XrGi1fb3UK4NYKP3/8A1ppyc7H836d8euuyksdZ0Bk7mXca7j38KLh3tiLaMFjoGTczm1bvi718AIG3JPa4ZP3qnFD0v8Z1pNeNxDlfROsVk6hetK2LV1Lz/nhLXtfdNWdGjG5le7E+38OAq2088+pM75bIHDp+09+18uPmnLxOE4KiRlUTcO2HHGGLli6JV2LAM2Lt3WnFQzI/NIjcVT7QErPwREtJ9h9cxuWPhK7vXFtfZ7ehOIERBy+AcjcMRi+e1uQxo1BLSU2Xw7RosTBRpuTjseztCKgPUlCjawgEtjZZqQt4JlX2EKHSuwGZMXiwcVOy9iaLESyGwmqB6RhPQPS2h4sCOYPYhjlI05bYFw7Xt7SGuj9fvL+1MIa2GsatizIzuNOWee5UasswmtIRNzRcAUXt/i2sqiY1auy4NXJiwJ2EUH6X4NLaWkqLTf8WiXMx+OwBnornd/MS8tYxdJXI2/dw4S3BprsFXzrrHS62fUmS0VfZNDcTNQ5IA8X+lG2GrNYsRSa3CX+lSrtO33J28IyUzeOpSW71zRs2r3Khl3qi+pCn7qrxFT6V6cvOgd6s7wTDnvIEtF2UXfF/UQHp2xHhojC9WIxrGDHtnVcLcRRrkN1wa91rayB+eFM9amiuyaL6mYr61qpl3VrajmPWpXRNbV6YsGOOQsorWqsj1Y4riXcP6iC2ZrI3/EHCRxdh/hdSRFKjFUP+FOMBdQF/OBpEQIdJyYNANkbsHBF2sEbtZIlg65I+Bx53gdeeUNqQ3iVlxy+d/rEgM50JCIhD1ZsrYvyDAVL3qPzVl77q1vnnPXu/7r+HjdR49e/EAJB9v5CwNZVe81HT/+PFxww8R0jSfpq0rDN2daTvr+qM33tvmWpc9SItGYicunCKtUDJ9eeuQhWyaPaAyY3jm3pEzEZAZgfxs3d50yEP8TmNASTcANCurQj3lEP4vT1cLLLyw8zZu4KmNsR1FImvAa4FFM8Jqtc/qh8xyxAbkGRdVwXU25GOZJ9sYhKPT3pw2v3SC8ftn9687A709LtIKdNoUBkl2g0W2u9sd05r4Dap3Iz9xt8uaDrxtNjPoglLjZiThaxXukiWgne7NrJ1stdkCXj24u8TT6IdfIWuNT6uD0B2GX98fIWPpEyfNz0bOPrh7zyMIJ6GNzbk/c+dYF1l3qp7dfqrumClTFd0A3P3m6yzHi91O+IJc/ripMl60IpSy/jbPIKCW2h30ZaqLv5GLg4Ms/9tnlEebVMnv5PgC6YLOZvWsFbcakcl5LAcXjLraZGd0lITRXKOVJbh+lYia+ND1XSKiS+9n5hGZM+KW72sCaMxLOjQt78gOXakgbKdcx/br/7taKURdfVio5Z26pXXk1K3aV+SLBjFCKeDHfCXLN8jzdn7bSH5I2RIVbjAiDl5iVB7gc8EWL0B5I3xn3dF2HDuO/wgZl7DWPFIt/7IBgIupAKtA1WvB5Ekhh/9+y0ChcvTh/fkd6OMCpvoCXPi0sDBy4w8rV1S9eSD/7wlmlfuOpOsNJ4PVkIBNl7spB27Vz6gZPf96pjpl0wejifpprTsC1HbtWvLNyYvr96Wxpw34K0+Jj90sYXIFE0n6xI8Hix41Zc4thnEkzp8JU4bHpjQo4fmQZdf1868rZH0szTnpfW7YOJ2oE2uIBJAne8aH7H5+no/dIAXPrY7f45aTWsel2cxLxNxYXEiAbXrXxkawunbnrevy0fQepToX+ih1VvIi6lDETmkcVIYP3N3V6Vvj5scbq1cwHkakx+WWz8ZmCL7+PxBzON9gt4xRqF5mBdzQTVr6NmOaD8cvvTSEILC1y24rEPNdadXmXR1bw/ZXg5x25ql1aXbBkjcdFMBxWLCuHlJqokTHDRzTHjUhCHWuxcGV9H1mvd1Fm3WA2Y4q9sRzvbdDmg0KmKNdDJm9sq5fWbveqT6JR+obbPOrbcxMSaUaO7VkEmIG4z9/32ZKpJd7Udv9I2+QaannmmU2cNpO7yYUXnTZ2Pp4w9x9rYwl9Qdyv+dqW8qnNyaYyyKHalFdqPdZNequ4a1ruiu6Xvq6iRJ3yFzlnd3iLeBqwFb4bW6o2IUnds7LzeOf03tfR6aTLZfPVlshnMdb60qucTFoWXRfmwfsfMRvgTnPR0IKhxF7I5dVNnGN6L9fJC4D1zcHHioDQQfnfjH5yfVsD3u/vgqY1QYHzxhi1j5U3fMw3FqdJ29L+LcjKHLcOpMMA/woFN+uPdaT3aOmbC8HTxkO3pQFjyhuiautMnD2/g0uKseSuvufKmWe846zs3YiOL15ONQJC9Jxtx195vPnrGF567/x7vGTMaT14VSx+GBWvLAmTk+Mba7ekbDyzDCetgxOrbnjYdMKVhlmdwZfrm8aYuiSCdZ2lhow8fHWP3AincDkdaTv4Vaxs3pWbi4gdv8nISI/jlIBwB74ccvXNRZgsIpTzVMcQLCSV8L9I4BMxkYEy/OVcCxtqmq4sa13uWlQvB6AMJExNdX3dXetVuB6U3739CetvvHkknnDIg3TYMT5oMT9HSkuctNwTNW5JMbX0Zv2HaqulIgCzmlFd+2XnCJxcXtD6zjMmRorVRbvDWjlXiGYXWLQ3kR3M34p6U9NEf2TtcGbOcyRN7Ia91N7ObVlbSCuNwINTJwkpbWc9q+pzl1U2v4tOnEMh7Dd2ut+Sp3lR88urGuk5ej23NGEnT1h/b6J1VRb5ObFXWWv9M3cUqx391RM3L0kp3+5KXxMR0lT892bbd1Fkms+6a6vU2R3rT3br+WHnTb4e/4Mp5DFm8WmR/R6dm+baxqpY94Mn3Smzxd+X93uaZydWPMtlYR3xtrG2u+bWhO71r095p+nP2T19ddmd6ZAfWzDasq3kKtZLXTfeyT3ld8OtEq/VFy+QHaYUoD12hu2JVx4ckp9fd1whcDOvdiNXrUiesd9uZrpGpOHmDljFc6fN94N6pA/Fe90XYlJkbt6btJx7WeJDgvsJgydwD4A40Zh5OmnDy07kKVkAe4yL/95BVqBeXM17fszW9kZa8IQBW13+mNRtAq6Ldrt3QOf8Tl99xMMKnwCIRr78WAn71/2vJ8Kxv98fvP/ndpx037aLhfFrCROnRGxq21y/YvD39CNk4LlmzLS3Y0Za2koRxoWRYFsbeY3BmhmJhXD5cbxdT+xiU4eSj5YIOtfz7l7c2fABpBeSTH5/QWA+e7NKR+A4XjJsRKJPhXbgoHIMycrRSLqa2OPNnQbhkD8N7JK88GqZP4ZxF6bSu3dK5Qw9Jbx83My0+YPe0lcRUkmOXG4nVrYtp9kcqyZ/blLyfXEWb3IJYEiEpp+pvRxskUdmH0CpyU0REKhdnltONuGI9qyGr8n0lPFJ9X2VqSAO/bz6IYo3yG4fJQjLTaowKeWs3JEKjG7o/Bqvzgau8Z2Np2Knu0KpswaQFTi9zSdzKPhdjYj5dpSx+3FqVsaa9P1jWFxIWJS3mP9gkq+mcV7IW8vaqC9qnvvQlW+ms3fLhhiqk7Qu+pS543WXf+tC53uZRn7pr9e+cVs3jDAGyTx/lrdP/x6m7tacArMuRstIPVwiSZ3yqmnwLR45f+80WWLumpbPaZ6aD9tk/3bN5GVybqctOv+VXm9M6FqUFLpP2FrKItc+IZk2Zil+rk7c8sWBfmMUC/ttC6HhsSoJHVx0cscpD4nX3NE6EeIxLKx33Ar7PfYQhUnh0+xAuAsKiJ2X2x36xBO/tDkPAb+/E5cA90pCRQ9NRSGv29TFt6YDB8MlD/8W1lEMvqor/4Vh47uK1v//RHx/6pw99/+bZfsbE738dBNxq8NcRIFrdicCvzzvj4uP32+NdY8eBtOHYk8TAp2Fb3Lk9/cfa7vSdx9anpcNQhqlmGKKFPhmMyUeLHZ640ovwhMa0NbT6ccLyH276plmw7D2Gix0M1wITvsTuo08HfTH2xwIwFCZ81rESk3sCvs8Jz2wfnuxVbpUVG52U0+MOWsvwBChycTHBEfOEm2amFceCmLJu25eM7DU5y5eq2YrseQ1yZWT9LY9eis3O+7iJc3gD88rRdcWXzNpqZXVsIYu8XWfpq5G3MiEKfKU45CSeEsORGDnrTmO51X+7IktJWOrkLcda26kMkyuTdQHvSZowWn5U3kzgucmZzCZvDXnaubtqoV0sk0mVkXKDqMDfiIFPa+YJQcUn7y8tL3VX8TEu4R8ORHQjeNRdxaSiZo9HF4o5siu6W+GaztJnVTaYAP5ZQdMJfSt//kTrbi9rR17bVBYTj5fhZs5LRx54THrOjMfSP530yvTGkY+mtdv0Abk3XcjjVYe/EULTwT7GyI7kjdiZdZxEuXJbXAXn2sDUZczfzugNdOXhQz11mic39P2GpU6Oa0+AlY7GAYrEECl8uMdlwbQOFjymQtsXRI9+3LiMIT7em7amtw7ekV4/ckB67RASaSPmOl6a8WL1qo0P/ec1973kvB/egkri9VRBIMjeU2UknByXfuDV/3b6cdO/xKNbmYn06+NDqC7kc3C8+92NPemb8IVbzNy7zJ3Lo13ejmLg5eNB5PgEev+8hp8GLYVMtXb//Eaw5oexCNCSR8sbj21JCmklJEF7DAsFTfgPoCwXChJJ/i1kqG6TVcEleLNXJwrM7+Afs34wJtNwLDAklLw4khdZT1CMwCgpyBuDEQtdYIzElVZFK5/BskXeVnCz9BFTdombaV6ZGx2RLqocOSCvyWWblKunIou1U25q9r61xZ+2K5NgUg7fZxNE26ncyPQKq/XlNGLap+xYrvJa3tBMBG0cHS7yq8e/pow0bWVMRr95q7yW4q5iOXFy59vGdbgoFjkNmy9jdRhpoywOx9yEwz87DpXyqq7ahlXRXa3IrJr5WN/GyI8B5SV2OkEFcq8vhe5W5K3Rlyyv6VqhuyZvk+5ybmhZUS/DoBfdrZXF4+n1uNRdIy296K6Ze0zvKg90Wl9eN3Q86CcnlmusaTIZKY8SsVqLrdMFW2/qLObmD1g5uy3GkdjSCs0wWLzQsG59esOyYemdXXulv91/ZZo3fQzWMKyHOci8jZEfK9WRLItfM00XdK6ZL20ri3mpu578W3/y+oM66bbDucXUZMyxzgftZfjHUx2+aOXj+s4TlblLU3ouLHkMn8K+8sH/UOwBPIlhzNchWKd54nPQ1DS0a1s6FJeovj62LR02AHHy1JKXjaIkkh0D0sIl6/70zWvu/5t/v/RWVBqvpxoCdeaGp5qMzzp53vT5a7489I1fbfvl7XPOW70BaTcGIw2bBqLkHN9n6IB0/oRB6e7nDE8fGDcg7b0NFjSSM5I6ri201uEpTGLuceHKx0H4lROdT3Usw7L056MFjoSMf/N74/AESHLI21qDuOiqhcxGwh8r2FqW89OykMph5JBEdE88IdJKKCneyidtq1itGbmdYjGVdk0W/cxkyQsmNx9Ta9vQtUKx3FE8vJ/TmvFvyqsd4QomKdpQR07D1uhS9VUnS6vFXzGRH66M/MrNSuW0YlaEG6E8yas8dlSZ5eUX8F362dg4GLZeXmun3Og84W6SxfCtkddb2vLHiqFs3pTXSIDDVgA0ebVvJkOv8pbY23joRt/qKFk2R+pCga+NA9WQ7TbprtMF0QfqbNkPJ5PppRfTj7O0p/oiohi29gWbL17eUne1jBz5OZ0RUbU+wZyfUV73fivdleZrxlne97gVZUT/rD8eW5PR67nVo5h6ecVKrXNe3t+RTt88Pr1ld1icEkhXxtCvCyZLOabF2uG61liPVN6KLrg6TDd5sYgP2Tz9OHjfdO0Ld0+vHnZvmjcRD9KjsS7aXDPsMhZ+8E0W67O14zBotabm8ajRXWsz33hXbEnUeFObx7X02eZRLk9zuNZyD+DRLNdehtdiQGSuxyTUDNFCn+7NeNjn/sLTJPp7D8Q/ugTh5OhtXZvSZWN60m27daej2nekgfqwIUsRvwNDw5JVG+688Me37T357y55SRC9mrXqKfJWkL2nyEDUiXHaJ37+8fFv/8bAy/70yLu3clJy8lp4Aky6CUMGpM9N7Ej/M31Y+vj+49L4TXhCo/WMfhhMXyOWQby2YjHg7KTJfg1M9PybJn1OXPikyMSXSU7SgAlPIkhrH4+CSQzlCd1ZybjYyJ9ckPC7rGW2c/IP/WcbrSdS9rTOJ1B53zYD/b7d4PQO6XZcmdsoZJGFnP9MBtZbWKmyvFrUy5uPj2w6sE/4R0snP5NqlShaKJad7EqHjmUVE7NGVMpoP3MZyuHl5Xe5Uavs9hQvfoTWH36nbEc3EFYvTeBz8fk0jPimlpGfDpcsi73PTdHGWrFgGcliwLqtHq3D/MSaQtS4drK8xaaf+6Xy+tuMNjYiC+vym7L1Rz/L1iOVUbiACO3ktToUS6+7YgGzdnQo5fs18vJjYpHD4hS6m+U1EmqyGKaGr80PI0eUC2Uyvk5euX1tKsbfmQfb4ZJxK3TX/dm7LqiMXi+b5pEjfvZwVZHVdFexzGVsffAEzfrm9FLWEPQJPmYvnrUxff6hcelV60fi5JAWqXJO2zyyepzuSln+c7qbf/e66+Za1hdVG3ZhwmhYwfCwi/m/bu36tIm+bQiFledRXu9ULyunAyavm3/ZommD0qJMre6W9aBNuXXPbuJ3uvHMwdGs5bnm+7TkdYDkcb/gyQwv75H8cS7SakcfbpK/kVjbqcv04+ZR7fI1Qop5m/b5G9alP00bkr6/+4B06iCng6yf9cInb/n6zhkfvOT63Sf9/X8d8/7/vgE+QvF6KiPgV9GnspwhGxC48tzTPvbiQyadN26M+vTZLiB7R1tiyJbPIzjzZbjIMZ9p2GjK5ySmbx8vf8yHrwYnPcngSUc0FgGGIZnxKEz40zD5sQDMg3mfN7poAWS2Dj4FiqXGbUyyP5jqcCEwQmKbW6lWvoxudCzaVxq2yqjrolc6P9tRoSyU2o6IsSvyqtzCEXRTqGuHBFUsPVpe9hv+zz0zKc9okiWTRI9NIS9vJ7M/PgCuWSWaNgwDxxZiGwMjPey/brKyr/qNSclI3i/LPrNulLHjS6mmKGMbk5TxzILtmixurEtcyv6YT5/Up/LWhbMQUR6HvEbwKvjaRmpYKkYypPi9pS5Q1VQXRP9q5LWpIPI+Ht1l+9Qvk6U7PSeNSiftNi19e80DmKJ4YDO9k3IGcAv8Td66cC321d50t3KkWDfXVN58HN7LGGW4VVb+zQcrBoRHKKmBo0H0EDT+m0edkV478F6sEy10V+p5HLpgD6NNx6RanzSnOm2hWIzIV9Y6nUc+JmadLtiUr5tHXpZS50SHVAns4Y+WOZ7C8JIe1yNa7VgHw3Hdh3sQTFnG8FqvPLphxaO1j+UZYYGREeizzdh67DsvZGzHT1r/ZsJvG0e4b9l3XPq7ke3pFYN4EUXH1MaL8sEXcP5jq2762W1z/v6d37oOMbzi9XRBoNyVny5yP6vlvOKcUz58ytHTPt5BXz2uBf5YFCP6UFdb+uni9emzWweljUvW4Ir9ZLkFv52kjQSLDrovxlEJLXxcCOjLd/CUxrEtL2hggWgn4UMQzm465tLqZ6m3RGNKtVFS0fS+ldUFWUat+K4dg9BXJsfjyruOG2fdfG2zqtVcvknyo0QvHyXm3aXYHCqrcKFTttMWbzcFOzZCYxi06nPZJ7c5m2UsH8/UyFvxtys/t02vRhYxIuB/YhAR5qAY+H55omDlyinmykhaM2898eOq5LPJGb8/uOjm78PhtJTXCBTJlvXJK4XqAps1eSsWmJIceflc30UNvH67cbS3s7x1umD1mry94Wu6i3qyRVRlgQzvXrFbunD8CWnsgF+DTyNhfDcin2XLppbL8tZMEJtrYkU0WXvRSyP0mUjWzSNhxo0PauekzaOinTxdlCzT2kQfs9sfSv/f5KPS/xl3XLpj9u3pPaeMamT2aam7JpOb86XqZqu/qn7uT806lolzUYmtKfm5rpxH2r49fMrX6+aazVXKYrrbJLCboyqjTF3UJ6nKQNron83wKTzJ4dHtEfs0/O94NLsAhO6FhzTK0QfPLHkT4UpDH23I2AafvZ79YAwANoPh1/cCxMk7a0RPOnkAj4QLeTSEyvr1nQ99/LLbX3DBlTPW1kkc7z21EQiy99Qen16lu/zsU85CWppPjB89bJDd3s2cAL8sw82qz8DSd/3slelR+H2t5/V73tRF4GW5kMHXvXMaDrokfyR2y7AY4KlxNIJlpjEj07qDQAJH4f3eLAcVzlQ+aeumUilTZ1FBAbP0yf6hG7/foG2/kCfOmnYqnLPczG1T0kL5qbWwrvVmpSutUb2mYdP2e2uHffBBXEvLZH+sgXbc7TfPWmugtpVJRIlvH/JmP00SRwuGXVp1dXPLfe6PLrQo48dXNtiSTHt5a+owFRP1M+WrszBzw6U+WRstcMlMpoXuCumy/tfors3kx6O7rBbVD4Jz/Y4h7ekg3Mi/ZvhJ6dhps9OyLstIo/otHFf7JG3WjRHhpLxKKitlHGHtdY6U5KpmHpU8sk6/WYaWKBKSpSArWINesH5wmrZmR7p0/3b0F0SP/2h94svX6a3qFSu0yaJEssmSV+oLK1YrXVa13vRSB7NJFsVOSBwxtp/E2S9OeSFTvW6lLygnY4l/JG58OOcpzHNxrEyLHEJaSUgVEjiSPVruELw4XXd/I/YqXX9maxn6IPJ0hz7aiLnX/rMbUzfcfP7m2GnpHYO70gtxVNtAGBmbTH04/jAoIBjyH6+ZMe//vuObf0Ik/3g9XREIsvd0HTkn988//JqPveywvc8byvyGXFMsbpxudA9iDbgMN3e/ee/StBF+eOsQ+mQQjk224/JFD8Os8CjguAMax7h07kUsJabEkdtZOQyFLoh0BM6vQn1sUy1v4Ul5v9jpQppXb7dqykdc/LBgV9KwtRoolrXFkouqLyc7X+ONfOO1JA29yJLl66WMhUKgQ7lZu+zIpSKykyWHfPGbsiOndbj0R5b+lMlBWsu2S9xcn32ImoxvSab5/crupxV6cuSPe608f7YqAxnkaFstUU1t2xt+nLW6fNxe004en97kNdJT6K7shHUbt1NxsaRq3bUhOry8rEvbqvRPceF7CHgr4TP+eGf62yHT05uf84I08fq70yvevltaBY+O/P0K/vq+D3ZcWoetGyav/F3OIavfWVKbLHmPV3dVRQwO+hPTbWQR1iAG9p3KDEAgL4wXl9VRf8kEvrSeFbIIvJ5ceT21daMyUQuh7LOiHcFKAczjVpYpPu9rXZAqC/yZtowEjkey6+FH/eC8hnsOj22Z9pK+2TyFYT50lqF+Mnj+IZMbllLuBXDfGYAsGjtwUjMSx7XbMZ9fNHVs+pchO9JrnSVPllH8r52p09A3pDVbfO4Pbzn8oqvujNu1dSryNHsvyN7TbMB6ExeWvnNeesSUT44dicd/TPIeTOp8WoMvrtjUlS5YujldhTh9afbSNPf5h6QuLhI34kmQEdd5QeMe+H2Q7DFdzmF4zyqgIzAXFSa+zr5gWNy68bQpt0a5PugTtSwW+mrl92SalxdNW3ht88P3JUSH3whrykhqLjmjxL/SiuEXTvwubdVYd/qUxdVjdeSFmW/oIs8naWIo+4nK6n9nO7uUhk03ttIHriKvJ7MqS+Vz7bOX16x0tEzydqGUr8FW3ia5sU2sKGNWqpaWMWctsbqazohaWFSyAmln7Das3KKukdeIi+Bdkg95s7rp+3FsDJiWyQ3XW4+lHm3MhwIpx0jSbPWhu5VjvBa6a35Z9LOCbk3Z0JP+Zdmo9ImDtqQtiIHGzVlujnp86/xNS8tkLqNja2nYBLs6azfH3rBVnarzX9OhafalM/wV37p5xJuwnM+8XWoZGOyCirUll4Xw5ZyGrdQFfqxtZRPVnzmPSmxNXYxcuyWraR5RFnv49f6MlTEynUNZebjmAzUq5e3ZmxAs//gD4VazPe05Y2ZaCQvntiMQNot18bh28u6N41wEwh+CbEi0knZyLOmPJ2svyuFBYfdf35Y24yLGS/bdLZ03aFs6HHYBZDZriMsprqpK69+jc1b86Vd3zP1/777k+gfcbIhfn+YIBNl7mg9gnfjXfPSMT7zwwIkfGsGbtFg8e7Awtpn/Gkb8kc7u9IO129J/3rcsjUdYl3lYeLYfOr3h/0OfD5r6SVpIBHk0YHsnjxIYEJkkgZYGXvSYMKrxJLkfbnTxyZKL1TDe4NXNQQT0lhtbVWx3NhU00qIrqW3YVg9/WlqzvLGXZI6rltuIK9rtGZBv2zqXV/DGClixRJZl3Oeynronejkdw5vix6WWrJaWsf7g0pcstrtan+qsZ15eYkRCyra5sZgMeL9Mw1a5Ieva6ZdljOV31ZJX6II16X3yLGMEj/lNMcX6x43SbZpe75rkfQJ1wVv6bHP18pqV2rCVv5WwZv1UgmXie+sO5wHnIIOlyxzG9xlm6Xgc09Ffq8lq1Ye+VNKasaxevjKsiSOt914XeGxvR5IiYyFvkz+pzZc8UfU7frWqK2PH4DYFtR0/XH2lYZMmi3XBz+eW1jWPm+luKX8vZWzs7cJOk+7aksL553Awefgej2kZKmsqLlPwhi0v1uGiSpq6exqANXna7IVpEay8ncx+RNca+l7zYh2DKOPEZrj4W29Mm/iTMfSwFnfAqrcVF+1OG7Aj/S9Y8t4wmESe/BMB+/FLI60Z1wOx5C2CJe8wWPLW+JGK358ZCATZe2aMY20vfvKBk//tlUdPvWj0CNzDlxh8unjpBrFo0/b0taWb0o9XbEnzkfexi8fAPMplto1HQeB4XPDHexoEj8Tu2P0an3O1IrHjbV5uCr+7C34gTMGDY4bn4jiYPjh88ub1fpY1vxPZSCiqJzm2edjiX7Ph2+oo4Vq4PznrTu65bvjmIN2qnSZrVCmL1m2Pu7X19FVG+ym3dyGgWKP8xqGbiXSZi38rXFw7pWUm97ssU25IRVuZuXt5UIf4D/IGHutTUlHRKkfcvN9ZZf/WMn7jK33tMjlh31uRAnvfdKNmkyVpMauur1PkMV1Q4QznxyNLn/qi+loxjxQkXsTX+ZddE0oioX1ucoVgdzgewJYWG84xBiaXMEys0w9AX3qputBkzvHyKuGy4347ji514UnRXR1HC7DtdddbJg1bM09ZGjZ7HvAPHa10N/vsEVOnl01WdQW8tzLZH5KgaX0Vf1/VhXwUrWU4/5jznEe0v5khJE/WXa6pXEv4j4GO6Z/HB++bcRl2P6zV9Lk+fDpIH0gegua3Yc3ugS/fQISQoS/exbh4cTDMeE1pzSgefPIenb/qTz+7ZfY/IHwKjnXi9UxFIMjeM3VkXb+uPf/MzyIN21mjRoN86fEuNx+x9sFaNgsZOb69Znv6yoL1aS2JHY+F+LRHh17689GPj7H3XnpkI3YfFxr6ijD45kQ+feIJcx6eRBmX72gQQjpbc7Og5YGkEAmzsx+hyLWLZK9iGSN5JOlD/RKrzTqazTo7e17xr6ohAVKy7mm9VApXRjaQmrZ8n8S658indJnHeva9gozk5mqIZ5N+Pg55vfN5XZ+lWcgkadiMPeDNbI0iznVLRX9kqbNelp1yZWQT9eNajJFZxvh2lpeEWUmzFG8l7y7KUpLGEjshtRxTJ2+pc1kXVCeMmFRUqA99knYNF0cum+ZRXT1ed4mRXa7RMWglrz+azz6I/cVWBscaUNllEhQDX5Yp9Kmca6Ka+gBlNcl7Ot72MGs+ni11odRbL6+vuI6Q+y74/hipU7Kcv0rMHRTZDQBv+jJ8IGTYK7rAMNUZ3WgYKouvF+HyHIk+H66Z2nIfuNnw0grDrCxAmUm7IQg+UlvyRIYXWVD29Qid8kYY/97AtGYl7BL1AIczm7YtPP8ntx33+StmoOF4PdMRCLL3TB9h17+fnHXye04+auoXRjDESs4IwLWSC093egzHu19CGrZLl2xKC0jkGJiTxI+3dHlh44VYdJj2jD4iTMPG7Bpj+RmsgEzD9ijKHAmyxxAAzMoxEYsQ6xgFkkl/Nnk5EiSETa1KrW5b5pAvtjLqU7f4wuh7dkQtq5o9lfsFXI8spX1fxhMy27h7KSObD79jdbCsfY+LPf9pmUqYD3mzQXj5/T5TWRlOtilZn8xyY+3b+1p/lsX3oQ95bUyyvA7nnIaNm2kpi22w2qeKla4/8ppi2ubITVF/b4Co2LoxklhxjSOnnWnubJPlsFCfyjG1dry81DnDyH1uG3Emcb3pQl/y0vLodcGNqflMmhU6H4sWuit+n9pOlrcGl9pxNwxtvvRXd6ljhX7nsTVS63XK9IVjV6eXbpxFFF+mjvx5PWuhu/4CmOxgmTVpY3izVhdK7Op0t0be2rRmLNdKdwudEos5sfP67cvo2MAnT9ZbOblAeT543z+3cSRL693S1Y1psR9OUTg34XctmY5uwyVZ+ON1DEQ6sx1d6ZLx7enAAT3Zkpef0zTjxZzHVt946R8fftsHv3/zfJMifj7zEah7FHzm9/pZ2sM3fu7XF418y9farr1j3mfX8pYXb7nlY422NJkZOSYMTNfthzRsYwak3bagDJ8kjagwAwd99fjkyfyLcBqWdZb1cJEaDlLHW3R8j4nEH5jX8O/jsa69sg+eLn6yTsuKvfOnEVHhUbrw+e9bUX4mDs1Y+HJGjnLh10XZ9hD+mS0Ajtj4YxxfJlenZGFnR1hR4y+2L3uYypOP4nSztj7KZQjdiIiLvGo2qqopoCqvFFdrkv++9amS6cNwtaZUHi+vHNu6z3P/8GZOw4bved++PBalLGU9bE/L+CMrP5aiD7YROh0oywhf18skpbzyN/4nuqDjm8c4d2inLO6tnb+qggixcZjIEOkY5Z918jpdsD6VumtjbQ8nltZM9N21k/uusngV2SXdtXprdNfqESu56q5lRMl91u8bUTFrX50uiMx1eumUpUl3TV/cT8O/6ti280EqH5FqW+U421xzmYaaxi8POkl/VqbGuxWstU91S4pZaCu6a/3QOrMVlw8o/j3+7tc8HZ9pOJLlOkp9YO5aWvl4wkKLHtc3rq28vELfPs6F0biKTUsebtq+pW1r+uGIHel2nPwe3t6dBus6K9NOM16s3dD58PnfvmHKvv/n2ycE0atdBJ7Rbxa7wTO6r9G5AgH69J123PQvDZXgzNzQdVXTB+A5W3ekb67Ymr47cGhavAbEbzx8SWjZQ/w9RrpP+2Bxon/J7fAdwbX+tHwdjnFxU2wRLHv0/7sRN8l4c2wsYkHJE6tuXvlcwZ603RNvxRpSPInLxQfuKXhfRHUWiEwi8Z5ssvYcU2fJK60LfsEvrUv8zFs62K6+l4kKF2vZMXW3cNNK3rb33QDwffE50/q843nedfqShZU460I+TvSyKHGxo29rp0K+WEaJYNOlCt0Buam1DB5su6FZd3STzEfkxThWxt92WKcLMs4qT87O4Xdildcf7zeRZpS329z5SL2VLuyKvNZXv1krUWypuw7DjLvXXdMLfa9Wd3dBF7Jl1GNrbSjOYjHUeVSru0bCS93V9+34N+ceLqyBOUak6WI5j3ZBd/0DTMX3VesQwqf9qVj23ViJpc/mbqlzNv5maezNwmxl/JpRp994zx5EDfpMGm2d1blJ+UnI6B4zHxcyGPz4JlyEpc80w6ccD5+9+Xi4ZnozxtnjKcqkCWkoUp8d0rY9fWs0ginAkjdQ/APdGsP2cQFvwZJ1N3/7t/efdv6PbkVl8Xq2IhBk79k68q7fv/zw6R95/oF7fmjsGAS5Yvq0fDzaICqLNm9LFy3Zkn6wfkdaOgDE8OCpOLJ9rHGRgy8eL9B/hAsqgzMzHhQfZh9EGcbJYigXOpXzd6lbF8zMSexJlwuyI3L2uTRSZ4Q2q4Qt4qrOtJqJNaLcAKwdP+haR94HXJm8z7GM1i1ERD8QS15jn5E+9SpvTTti5eP3FQ+py7XjAyZXZFH5pSitExwnv6PYRq2bIG9/2pG5FLP37Xut+ly0I/u17iZSn8rrnc/9imKbcImL2+d3yqLjJQRaQeUGKM2pvHnYepFX2tfP+cOOzoUMeHldmQxdiYvTO4+/iGTyZsXpW3dzv3vBP6c1U/JUXhCw5rzOVfzAKJdCkMs4ncr6peV60908bqXu2gOEjQ3bU50T+bSjGbNC53IfdE631F3irLpQucygOmj678RokD7KUqe7NtDoj8mW29Y+5bqKNUq+qvqY53wv40gfu9VYE+nTTEscdZlzx1JR8nda6TpxUsLYgmvgE92JB2rm4KVPHcOqMFjyRqylDJAssQYhLB+kcdv2bVPHpDePG5ROG8hTEz/pKCa+j/LLlq27+7t/fPitZ33nxofy1IlfnrUIFFryrMUhOg4ELjvrlHedftz0iwdbnDw7rlEteWR7W/oeYvR9feuAtBJWvPYDJqd2lNnO8jxqoFPxCUjTw3yMPLrlpY7dRzcSc/NvWYBN5WwD4aajG6eQn8LqVnG299YDW609k3C/m9XNwkg0WSCsrDAYvzM4XfCshBuIbmgir1cZJ0uW13abUrUqzGfnh7Jv6OaRfRBNNqvfWxx0I6yzdMgTvv5rsuQ5/G0Dk58mb9Hn2jJa3CyTshHXWUOcpU+67UGz9lyZnIaNGzbHxB1/5fiIVkdv8hrbMcKkMDelYbNxN710upDltfEq9KVXXWB9+oBQd3xtcFf8Oh0zEj3A3/liRNGfzO5NXqeXWaPKcWSZklxZv4206FwsraSi036OeL001XG6W7ohVNinjlsmcSZwOdcIgT6kZLWp091inuU/qUOsOzOz5rmWnx99mRq97I/uSu0qH4kdb9FuBlHbY0xKd+GS6z7ws+MFjIOmNcgci9KVhi+un8xhTv0nGaRPHi16IIFtDy2QvLY9CLUyZP7SdOTuI9OFo1N6fvsOcNqi72wXdS9duXHGl35+90s+ddntOIaJVyDQQCDIXmhCEwJXf/C0959w8KTzxo0ZNlLSsGVfI2pMW1qyuSt9bsnmdOOsleleXOTYSkfiIxH76WYe2+7TWMToWEwC+NIjGmSP1jCGiuCCJoGFsTDRisfcvHwSZdBmurGNkZQAzXcgsjVQiWHeQFSLZd2zDdYv2HhbLH1cYHUDqFhLrPvFRuLLyD6mMvt2ZAb5TaqBz05Ln9ZZa+loJS/qK8NeNFl32I8W7Zjlpl/yeuJkhMFhV+FVnhDI7tf45/3ZKlkjijIVi62NIXHVRrLlsScNRXDdY9L4dEM3Lv4I4XPWHRGvhSx18mbtVnnNcpbHrhx//bvE14iZ319r+2TErBgjEbvU3ZpxLK2k2TKpulanu95HVIi+b0d113zYbI406S6/o9DWlRF+p/LaBM3t8A2z9tlRdE8ahlOAzeIeog96Rt5K3LIsTqdaltF28jiUuqCyCN5aKPfL5quSWnP9sPbtIkomiVpHdsMo54gbazYlfrjo/wasafdi/Tt4ciP8FOMijqPrC6xyx+ACG8vyctuf7mm4uYyAZY9lGDtvCNxfuAYuXSuXLtL/3CV+0m89blr6W3z8ama8EHldWjNiBcK4YOGaP1x1y6z/+65LrodfTbwCgSoCQfZCI1oicMW5p77z1KOnfnEwj1/58mnY8Of93e3pRws3pB/fuyQtQST3LUjOPQTHB51c1LdiwWPIlhcfBiKHBZBHGgzNsgC+fgdiEbMNilZA+veR9DFiPC8F0HdlL/it2A1ev/E2Vjr953deW9HtvaKMLezeupN9uTwErh47UvRhPfKOqO3UWvL6kCX3QTequj6JIYWbC/6R6/Qlq4wPy5bWmydAloqlxpFBkVv/lmcCtu/wz5ukLTOFLCavD96M37++bHo6acJh6c3df0gPpHVpq2WiyDjxF2+VMxONH3tfxnTB5NVN2+K35VWwXA6F4TSUo9aSZ+WtjJIIj0upL61013zg6nTBupUDImt/mlbvQt4nLK2ZZ0s6/5rSsLk5pP6A56zeK43dZ3I6e+0NaVj78LR5B+Z4BWun//KdbGrTyvxctDng56qtBeU8KmSxMchtF9Y+wbcghiILj3vxJX4kRNU9+Mp7WiF/bIAVj5clqKtMYzYXoah2G9UgbAxNxfWNAZN58sFjXa5DvISxD0KmMHIBb9sixt5ABKTvhnvMIIS8GrliTTrwwEnpw0O3p1e128kIVREBkdH+APjjUYY167fcP+6tX8dCG69AoDUC5ewKrAKBjMDrPv3L/+h4w1farrpp1lmrN3b25Nu7uuYdiltfn5wyPN3y0qnpX0e1pYNmLkiTGGCZC9tBUxuXMrgokrzxdi4vcDAMgdyeZYBYLHK3wp2EKxeI3nik+xlIUsgLIySILMNNgLfQeLwh1jn+KzYBfxHAytglCnNa5lf4BMzbbSQZkj1CF+u8VxiBw8d8SGeYD4vllzcEd8ycZdHNIpeRnWCnvKUs3qGolbwkTXa7l0nPJdSMa0d+16NwuTyBPy31WfZZYiHdsEweTz4qm1xv8iqZkeNlLhm2WdrmR5Kn+PJ4TvB1TMTjYlhT5pyqTfdayo0Hil/Pujkd9th/pTkPPJj+Z/6+aZ+hY3RAtD/setaFGlnyxq36kvuseMn3KS/GN6f6M7Un1oqzWcnsxmUOjm2d0P7bsXtOh2XtWDmHbSvdzWRemH1j3LLuUhcob4mtyurHUaytbE9vfpe6YGQzuzU8Tt0VS6EIpfJqX/mDhAfz+Ytr70rnzr027T93a7p8xwkoavqquiuQaz05/p/HX3XX5oF/6OhVdx0uwosVf5G3Zu0QVdV+yLzHG1x7qBvMKESStwFkLI8RdddSQqIM/ZzvQNgUnmhAf/d6aF4aQX+88SB7XOvozmL5bPF5B4jgyDtx0YKxStmPcSjHh9tjDkjDZy9JQ++alc4Y35GuOHy39IdhnZnoZUMlsB8Aa+DshWt++x9X3nlgED2bu/GzNwTcihxABQK9I/CzD7/mY684fPJ5Q0jGuBWBgDXSsDW+d39Xm2Tj+MZ9S9JueMp9GCFa2pCGrZtPrjy2xUKWENVdcu7ySZYbBjN1MPo7HJLHrd+Y1iEK/A5GjGcYl0WwAsIvUJyUSRxpYZRbpaa23rrjF3JK4xZ1EZarqh4Byd/aV8mxqfXl253cXK0Nbp78275Q145VuCtl6qxRRg5UXospx+rN0sdfzHrXVxo22fQ9FvZ7KW9fljGW70teLWNWOu/vaUMhPBl/5FRnxTjSkstUTzPnp5d3TE3vO+jU1PPwvelfXwDjyAD6N1l5L4u957Brsp5xDPl10xeSASUA2TLJ9/Cvr5R83qKY8bV5009ZGrPHKWGNTnmda6W7lWDHbmzzqs5f+pojdbJYf2rmkXxUWMaEYJKYqd6ya7Dqt93yUPrW896eDpy7Jm3evjm94hUkUKqTMh5124/OoZa663FrIUvThR4nr01Ru7Bj3c++rXiDN2J3H9vwIODJA04sJAoB0o4JsaNu01pH3zpb0/gwy7UNJHcSXFjW4YF3Ey+w0XJ3z5zG8S0zXMCHuQMPtIPxbwPCV3F97IGbyyD45rVPHJdOGNqe/q2jK52JzBcNw37DitcOrNoGNQjpls1bFwx701en+lGK3wOBvhAIstcXQvF5EwKXn3XyWS89cupnx47CDdvy9i4Wo0WbtqX/QMiWKxZvSPMG4oIvFyksZBIkdMnqRs7H6+9tLKgkci/E0QatfySEzPPIQKFrsLjegmChDOnCp+xjQQAZ549HJby9xpcRoKZjXhPZkYLaMqr+djwtVjxjJlaH24h7a8c2uv6UyaTFb8S2ceXdhzun/nP94cZvPohCTPLupYWU0OpRWvYfrIyiK9N0zKgF7UjNLgs0yVJHemvkFcsZCaqSq0y4XJ9YFcdgOTbMVRv4FJGOWdiV3r9tWvqHA1enzXtDTwbjqJ9jL90t2ynGzXyvKtiovIbLTgWyDstGmrOzCCltpQvuK4KPHwOVpVf8+1NGdVfqYXt1xI246efy0FXqwpOtu2ifF7GY8osuHNu2pX9fPzFtRo7uL+67JW3h/LdcrHZM2kov+z2PFP86/0GzzJq+lD6TOWafw5Zr0O2w0h00BRcrYH2jBY/rDgMbM4SUXSDjQwnJ3zW3N0gerXfMJc61ibFI5+CIdjrWMeYJ//3d+B2kj2saSZ98d2xqQzDkHo4fLmCcPrQtfRC5a48e0pYGUV41gmY+jGxGj85dcc01M+a9753fug7O0fEKBHYNgSB7u4ZXlHYI/PqjZ3z6RQdOPGc4FnMugt1KmvgUyo3yAWTkuHT1tvSFxzakTXspaWNuXS6oq7Gp83Yav/PyoxtHulxISfa4KbAcF8U5yNbBVEBM+M2/6fBMssPjWIYpyLd3KZgjT2I5qLMAuTKVOG2QWfKt4otm6ZO+Wh2u4xVLX4sytdaFUn0cUajIa+UKImGWMeIrsrPL9HM0YmCkp2ynFan05eosJr3Jq0SisoL4/tAsoWUkrZmzqtWlYWNRHpnRiZ0XeybhWOtebLb7w4DBuGM5jZvJVGexatFv4UAFNoJ3oS9GwgVffuaIFKvgEXXTillD9poK7WKZ7L/mGit1TvqkFishRsRbSXWGoUZ3K5Y+r7sCiH6zr4eQXuYaZSHxYcpEZM4ZuXpzGrlsfVp8FCz0HerWYeeRT0gathLbuj4VZfJcI2ZOn7im0KpHssb4oYhjJ+vRKpBXHLFK1iD6yTGgPL/HtYgEjjdp2ecTEHZqKE4fVoMc8j2uYXshixAfcBejnpGwEO4PHz6udZ18uB2QXjOgK/2vYT3p9bVpzWha7EnbtnQt7XjjVzTOVanj8Xcg0D8Eguz1D6co1QsCl5118rtfcfiUi0Zr7t3KLTgsVo8h9+6FWBOvXgRL32AQQ1r6+MQ7CrfO+AR8AuJIMdQAyRytflwkuREwphSfmunIfDhurTH/Lp+ErQyfokkWJSMEN2bbjPX3nFbJhMfnsrjrY7MchwqD0gJKUIxImS9W7QZpG4htqHXkw3YSsxDlnUU3apPFiIjJYuUor9+ozHrj5KXscmNT+y6mgGKzzs7lztJTsUaZpc/a70XeMsl7RS9UXrNqiZi+zyq3pWHz/mssamYMBPOWPJ8M4k2LnkBcYCciUt4+8M/EqQXhsoC9ogslKVB5LfVWHbZCIvlV0zkd04yLkWLqFuvvr7w1hMt0Qappobvm51dJyecHyfezThb/nulLHflTrPIFJae7Qu7xjxl2SII4z3HDuhq+RcllvuGrONbONeo3CW0LvTRdyGVakNVedVfbp0WS5IwuI/Qp5frD/LO08E3FAyvdSbge0SePD7n0T+ZxLfzsJCIBH0TnYJ1ilqGpExrr3B24HMv1i9EK8OAyACTxBFxWuWhcezpkYE1aM/YDl9Zgybv257fMfu/7vn0DIizHKxD48xAIsvfn4Rffdgj89vwzv3j8/nu8cyRz4QoJcxs09pBHNu9I3127PX1p2ea0YSOsN0diAWQ+XYZrYVBm+vbxJtuhUxpWAN5eY7R4WgBp3eNPEkBW+4KDGk/HfE0Y1bgNx+TgORCvEQQng+xHzhoiXy6sO/5GoGWdMEtUni2FxaTJ0sd6S6tKK+uCVyHbQCmniYZfsuN/Ka+RRCUb8nX8jwaByquUpdwMbVPvRRaz0lVWDNcnsSxpO1neGvJkhK7O0ifNs09OXiE4/ZG3zkpnBML65cpU/B2tU60sWhRB+1K5ma3yVix5Jams04U+9E7ELeoR3VU57YizYpl0uiBfV3Jp3K6V7vYL2xYkuOJzV2BnvnkWQqXJMlnorvn9NRkjH4/uqizKJxv+mV5xa+aijSFJ9FqcOlA/75uLVGQgeHSXoLWZsfLoSkDL3DIQQloC752T0klHpsR4ePuC9LFqkj32l8HkGXLFHlrxQPs3g7anN40ekN5QWvL4PfPJ27T1sY9cets+F145Q+OslPM5/g4Edh2BIHu7jll8ow8EYOl716uPnnrxiOGN492dadj4dN6TFiD0wEWrutIVPR1pAWNSMQ4fb+HujeM6OjwfjSdkhmFh7CkGJeXiykscdHDuwGLLRfgVR8MSuAbHKPgeHai3gvgxzAH9a/hUzojz3HBaWsZsJ1BrgfzpN31739Uhx9NmbTMQSmuNEafCYujTmuV2TAYjDUoChJ/axp53LLxHC5xu4rUhX/gZvrfLadiMXGifpWtmOSJx0HYbtxyUiPA9/Opzq8qxZwt5my54SCedvJ7keQunx9Pkss3aZOmrjMPTLHlNPm429ophK10QmdXSVJvWrE4WI0IOuwqZK610Jq+OtQBFfDy2/ZVXcZYqjPmVuuvxlEHdqX8ip34v+zuWZVSWPNcUAyGjboxEFO1Dk2+s4m4+ivJ5L7JkXexDd73V3stidVOcgVhTLFc1ySnXnX1B7Hgpg0e6fPAchX882jUfZV4so0WPsfJ4CYOE7ng8gNIizTViFj5nvc+ZlDrwEHt4d1f61ri2dCDSmg0266sNA/uK49/ZC1b/+rLrHvmnc793ExOSxysQeEIRsBXjCa00KgsEiMBvzj/z48fvt8eH5Xi3Jg3b7E1d6WtLNqVLwM/WDMRx3YHIBfnAvEbYFsbpW4MPHoMPzUgcAz0fC6mlZLsVFzdOPHynbxBvzPFm3GiQRvp87Ta6cczSgTq54Nq+IZsNNiDPsVpZ4OrK5Isc5YalxMf2Vfm4tC5oGd23d16cMDKp+yCFLS1jlT2+xtJh7cpsNhLGzVM3wvyVgqjlem2zV72V72k9mf8VBEvKUHb8oOUjb+TarnyPBEWXGPlb6yix5Wd20aCSNcIKsh43p8pLEb3Kq98zS15F3l4sef2RVyBAhSKbIzWZqBuepS7U4VuUMUuekFLTDae7Ak0f1mNpXstky6TKlMmW013fZ1WdRtvlOPJDJ69UqTrgw89U6nDymq5YWJg8tk6W3iyTGQ8nh6lHf3TBytiwLcLR7RQcuTL+HTNf8NIYXUWYBvIRpHzkpSHGC90b/nckdXzA4QMlffZ47Ev/Y5I8rlH0y2M967B2zV+W3jxpeHr9bkNg0cPDq81Tk1UCy7endes2P/L5q+88/JM/uR3mw3gFAn8ZBILs/WVwjVodAj/5wMnvPu250y8aSj8WuWmmq55uOHOxNn55wfr04y1tMNZtTkP2mYh4yjvSDi6mfLrmce1xuOlGskcnfgYsZb5Ihjvg6xE8CPMGLy92iN8ffvJW4DiQPx61sL3sL+VJi6m/X4VtJ3FEw6/SsgnpLiYkkpspf/FWDLOgWNmCrJTWEtssxTLmp6SXxTb2UlbbUevkZfuoQ7JGUEzK4WTzVjqzNOXN0rfD3511RyK6eqJhn/vN1z4vcemjjMjrLp1U0rCZUnmro/arYnWUzuqY2NCw74ZRKa+VN9ncGMhXDAvfJ6qylsu3jX27JfnzxNJ/5seNZE7HLFseTZY+9NLrUMbCsyLVRZ8losl65sbZdL4pvExRRqAp9Vvfy9bLUhd0HP08qrPASWxJzgmbF31ZJlmvL+PHzZNpxYX956kBTwN465+3//cG6aNP3lFwMaFbCU8YZoLwHTKtcRuXR7TMfsGLGYdNbVzWwMlE24PzU9uY4al70oQ0BJfKDt1teLoEPnkHIa3ZILuQktUX7eKEYv6itb//zu8eeOtHf3QrnALjFQj8ZREIsveXxTdqdwj86iOnf+H5B+75njGjYamrsfTNh6XvIqRhu372ynQXLH09XIAZsuDu2TjG3acRhPThBQ2y9apjGke3/J1P0SSGCN8yEr4znbDqdZFYvvTIRhgIWvjk0gc3aBUo74NqLbH9yVv6yjJ5tqgFQgI9c6MriQTb0Q0u7+tmlbE38LlZL7KfYSFL7e3PGnn9nu5JW+YJaIt7nV1YySFF9Is52KzfK60d7VuOj6ZET3BsYSXN8ijgxmfMxGpy2WZvx1ryvuKZ4ymiUBkCpaW8bhz8SWQl9VajiQYfKsaoCTsjb46cNIWOUXktrZlwIU8q2ZaWMR3L2Dl5s14qcSqtoC3rcPqcrbq+jzVjxG6ZvIK5l5cESwXNvKnQBSNm0lcr35deOn0p51rue6kvOkbythYSa6BiJHrjxqhJXsPBlcn9xXdp9b/+vsYt24mw2jHFIy9kMIbeMXAl4XEsffRuwwUL3g5HiBRZi+iTTMLHDBkMFE9/4ttRBuvT23Dr+PXjBqUz23Vtyv6VkEVy1w5Iy1esv/vbv3vwFed87yZc0Y1XIPDkIJC3ryenuWglEEgJPn1nn37cPp8ZTP87ruE+AC/+fBBp2C6Zvy797p5F6f79p6QeELehKLtZ/OVAsJhyjTl3V2ChpQWP39+CxXXa7qkDOSW7sCF3M/zBsYhpResfE43zc+ad3A1+NzzizWe7ugnJwNimYLuP3509S3TsyiwxlMvSejVZQ6we3RD9DdFsybM6rZ1WljzPWHeljLIfti2/4qcnV1kxZQdXAqCy9JWGreIAbyzLlhZv0SosY7U3U32fdDOvpOYyguYJSmHpk4+MEGRmQUVr9LJJXr5ZyuaJnmGuBL1VgG0butq0Ziav0wWRxYB3BKaUpdY/s9SXFrpbuRhhZZwuyPGrWn79nKjsDPzDH9vqh7nM49Vdw9UIowKS6y2s0IKV139i5gWt0V3TBb/wch2xtImw5g0BAeuB1W4rY30i17dY8k7E+kJceCTLsCmMqTcQ33sMv+OW8UD48rXDZ68LgcAnP7og7X74tPTl4dvTcW07wPUbg5rTmtF/GN9dvGLD9V/55T2n47h2rRcnfg8EngwEguw9GShHG7UI/BwZOV544KSPjB2DiKI1lr6lCM782WWd6doHl6YN6zrTYy+C3wzDGSAyf3oejnGRO1Ju7NJXj0SLuXflVi4W7NsRo+3lR4lPzcRHH0tLp+6ZenbD53wqz2E2dJO3WeA3kzIAa6WMJw22waAuhppgCi7ZU83q4H6XPRPvWwYMsYxxw/IEjyTMNj/d2HZVloqsbJ/+QmyLFknWrZsoLZMMLyE8R+U1WYywZnlrZPFWi1rs1AJqo1/62sn7rkzTZs7PlaDQEsXvE7smeT1Z8KQpM6lGPZ4YyEccRyOnRiLdkpitazZGKosdB/emL8TWgnRLlR5fN77+kkdFF6wfKo/J26sutNCXyuyr0118j9lL6PIgkBe6a2Mg+JvsNbrb1zyqxZ/QGL46Bp4XVwg65VLdpT7QUtaErdVHOVGeZXj5gl9jSB8SNmbv4VrAhz6EVZl818zUjYw9i0bB347rCm/YwpVk0NJVaSwiAKwA4ethwGRa/egfjOPaodfcIVWeedy09LdYUl7VDvcSnQ/5HguxwoPmgoWrb7z8xllveN9/34Az4HgFAn8dBILs/XVwj1YdAlecc+r7Tj1m2gVm6evBQp7diVDuvh3t6buLN6afPLA0DcXi/cjIEakdEe67GbiVx7NcnPfD5Q7GwaK1jxY8BkNlWjb40ozFYr8WjtU9R8HSxxvCiEYvYQ7yEZCRApsOpTWqIA3eupCtdMpL5LiUHEL9zozg+SPDxo7qLCr6905PfK1MSULl7JkVlvLqJi/1FtY+/Pn3K0elN0w4Kn1/9PL0g+V3oQjIMEkp2zPHffMXZPVmycs+btamtpstl3Wy1Fny+sLX+lmDv3zViA9xZffxPx8OR/iaK5NxbCVvg1zstqU9/WfHUeljo+enB9fj6K5HibqNj/z02CrZ8/h7y1kpq4wx3jQrUq5OZc2rryjJzhnRZBn2LMpj2R9dKHS3Ii8/awTuraiex5diGcFrJW+TRbzQlwqe1s26MmbJcypusmUV6aOMXRSxoZqJW7EH4Jb+XIwvL3oxNh4vYPDB0HyHmZaRFy7unYuYn8jmw9MAhEwZgBh6I3BMux6ft+FyRvfk3VPbffPSmN1HpYNwO/f8IV3pFfDJE9GAq3HUNpJmPHyuWrv5zs/9dMaJn7tiBhaqeAUCf10Eguz9dfGP1h0CV55z6jknHrb3x8eOHjqQlr4eLMY7SV9bWghLH0O2/GzB2jR7yJCGGw8j0pPc0aLH41r6zjC2Ff35eIOODti09N2DhRxP5LIiM2QCffqkAk9OKIwjS9nCtqtl9LhP/KJYJS0QftPVdmS/40ZLUsD3atqxMtmBva8ynpygSpDal9+zLo1d35kum7AtveaA49L6MR3pTyvge5StSjzWUlLSm7yNbU3lLdrhR/1JseZDbthlgZ1nmaykmBMtSIGF9CEReTxpzTAm7du60/B5K9KGUQPT3w/fL82YNCjdsxaXfSQnsXRoZ19bjpHKKzt9DVk18mEXe8TapzjmnjoSaXU0BQTvjyxq6epLp8TKyHH0x6QmjL5nRKgJ2ydRd0UknUsyHI4Ue2ug+b7y4024xT8SYVJMF/nQR2L3p3sbhG4T1gkGOJ40FmsGL2DAusfv0//ut3c2/Pd4I/doPBjyli3XjJkL06DlcA/BuvHm0QPTP+Nm7YmIk2fDmC15lA9rzZx5K/74qzvm/f07vnUdFp14BQJPDQSC7D01xiGkcAhc/aHXvO+VR0y+YAhJGlbSHSB+XEclDRv+u3drT/rJys70hcWb05YpuIXLJ3Uu1nxxoWagZgZYPumIxqUORr1nSJbd4XfDG3U8urQjKVrgKi9vGavZvGVTtzL+i46E5WweJHFK8iTFm33PNvei6Yp/lW50TZpRkqw6WVwZxglDjs/deoam7x705nQ44hKeNX1l+uEA3CI0YksI7MgxW/poPbNjNSUaFVlqyF5TGBt+wZPTFv2uC+PR5IulOMrGrqSGZeqCHVd88kzoQl5u8rDSDF29MX38eW9Nb/n9w+nfXj0qXdmxeicuFb/OmnpkbImRETj8Wo6hWR35dSGMSqrFv9NefeiTFKvDrj+64C2GJHhFxO1W8pqsMoQ8MjV5+xjDpj6ZvavUBa/fVqbojwy5HdUSW63DQ2c3onlcy6NzpjYjYeMNfcbfXIDQTYdNb9zkZwozZsfgTX4+ENIFZBXep6/eoVPh8oE1ghe62N8DpzTGC98bNHxwenHb9vSuod3pNR3Uv2JS6vH3pk2dM8/78W2Hf+GqOzHp4hUIPLUQgCkkXoHAUwuBMz758wsh0YU/PfuUd5x06N6fHTt22NDs04eF9vCOtnT4XkPTv+DW2+fxdH7l+o3pMTpSU5uH46jmSF7MwKJOskhfpFVY5Hnbjkc4Q3hZA/41JHw8BoavjvxOwsGjzbz5kUjgPTnS5PGeLvLeulDZ2LgDsQxZKTdvPc81SxY3BH8DUjaswkpXIZFaX2Vn4cZHwmANtyhjFilufAwRgWa6N21I62++IZ0+YXW6cxwwYjwwIZ+oQ/Z/3Wgz71B5c9qtkliQ4KjVRcRoJa+zzJTHq9INlUH6xDqtHeukkgt5W8mK3Hb18moZScNmxLSFvPweLTZr8VCAWI7T4b71sntXpCP2mpvWboUbQBe+z+O+jHELsmqyNM7sd04gGW/2Ge+JLvBv+5zy40+5fW361Oh2vVXX6ZwRndxSSbpa6YLqovAp1XM/3WUMdfz5s053xe+N84DY2pfrrN1+zDz+UvFOfZEqPC6mf6oLMo9YpCgjbSu2VgcfokhEQdqFqI3HQx+/y7nNm7RMV0aLHsOrHDxNHw5Qnv57XCN4U58BlHmbf/ka3MKFb94dMxvxPeHXd8bg7nT2iK503KAeLC8ql0FAefAg+ejc5X+45vZ5//jOS66DuTxegcBTE4E8dZ+a4oVUgUBKvzrv9I+dePBe5w0bDgsdCJgc77oF916kYbt0LcK2LNyYtpCYcfFmoNSDpzZu0nFjxS3ddNQ+jcV/PSyBjMHHp3zG5iMR4ybHGH3jQIJoBSAJZDDojXhIB9ds+C3ZZlYQCbPeVKxR3qJiBARC2yZPIjmwnH6lhafcUMsN3jbJzEyUdJFk4DPGJ2RYGm5ijEN4H/J34vg70Xcxb6Yt6rC+WJ7ginXHNutS/l2UV8TWOlpip2WEL1h7Bf7ekic3u5UkVNLMOex4xE9M6KCPCz9H37k83T26J3XT59PCbeSJ58fR9bvSdW/VZfMEn8SOOsO/64iRNlDcRN+p2KUuFH2Wr/dRRtruQ6dK3S0tfaWV1I5Mm4yRT6DutsI2q7nOJ1rytuHBjTmyeVwr/rogdsykQ1+9Lowz9ZcZeWjV4wMOUy4iP62sAwyYzM93H90Iq7IEOoE8vu14GDi5bVv6+5Ht6XVD8bkfPsrA27UY2K1buhaf84ObD7r46ruwYMQrEHhqIxBk76k9PiGdQ+Dys07+wMuPnPq50bxRy4XeyJdazxZs2Z4+v6Y7/QzWmQWbsZEznh+j3yMkS5q1OKUX4viGRzUkgrTe0GJxCI5rumjBwz8e6TCeFp25t2Oj4EZJf0BG16c1iJc7JEizEiRp34ickQx9z6xYmVQ5QmYWQAl2rNaSnIbNl1PSsNP1G+21sKhkWYy8UTQlONzQuMnResHUdLpZ7YSWVhfuX6zbNm0jF2ox4qZvwYNzn0sC4uX1v1tLRrho9XKkqLReShe0TL45XeKiZTKZ8bKQZFFevYWZjy4VOyNhPMol4R8GgjAWGz4tRBVS7+TN5KrO0oe2s0+eWtKyFdP32ZH+8nhbxDd9sDHwbT0eXVDS2ySLyttSd02/Td5Cd6kjFqi7Uodfrvorr45zPgr3bWn7OY8y9VQ/59xlth3mrmU3mcGCZI8PNZzD98wBcQO5o/vG85DZgrHyuB7w8gX98baCJOIChsx5zm08GLaD/L+kqzN9ftyAdCgseYNtTLP6oiGkNZs5d+UffnnrnH9+77evx5X/eAUCTw8Eguw9PcYppHQI/Pb8My943v57vm/EKJAvkrSckaNBcB7Y2JW+jzRsX0JGjk3Ma0mLDX14Dpna8O/jpQ3mtuTT/BHTYf3C35vxPi93vPKYRkok+vGQKPI7tAKQFOyFTWQDLGUjsIkISVOiJvtPC0telrvG6mWkQzYzVNJ067EXy41xyib/tIKAmV+ZyWtEyhOsWh+3GlIp3IFEgeLif4X7V78sTbap5+PoQl4ZS+13mYYtY9kfq5eOj12SMR/NXIeNny6BZrGq4GKE1E+/Ahfh+EpsfFgS+0omtb6OkiTzM+2TjJf+q1g7jTTWyFKnC8rRmx4OLDh2rrvUF09IdZwfb1qzyqrl2qnIy0K2DZWyuM/85Rf+zgtZFjCdY0wrHS5RiBWPc5Y3a0eA3BFLXtAg6aPFnjdvGUaF6dC4DjBDBjLyvHZcR3rLHkPTG8q0ZhSJN/fx6tzctfhDP77l8C9ceScqi1cg8PRCIMje02u8QlqHAIIzv+tVR0+7eOQwWN9ySjTuHdywetKCbT3pgsc2pCs3dKeFCKo8Ck/8G1Gum5c0+ITPI59DpzWOeDfBh2slyN3zDmxYAXk0dP+8xue05tECwNhctCgi9IJs8JLlwciJbVS2KZuFwnY2233Lv9khEihao2yT596P+sUPrpV1h19TsimYWHtKPIy8UM6WacL4vdKS59+ztn0ZJ69Y+vg3LWKUtyQxZuVS8ZouXVhbat0RQmYWIcPLW7k84SlxYV2lvPa3LnPZp8+1m/XJ6tbvyDCZDP4nx8mVqWQjUSykTmGB+s//rQ1K/V4XnPwylNpOJumlLpTymi5kJqVjq2NideYVvze9NNlNVtbtsTRZFBfTXetPhUgawIWltzfdzT579p1Cv9kO/G7bQNw6cAGpk/OZbhe8mMUHOP6bhZB2DNrOSxiHT28c444ZmdpmLZKgxzv22SsNmr80HT26I31j94HpwPbuhiXPv9gPWPJmzVt1/U9vnPl353z3pvDJqyIUfz2NEAiy9zQarBC1HoFrzz/j88fvv+f7R8nxrhEw3WdBHh7auC1dsmhj+s38ten+dmwAB0xu3NRDDktx2ma+y7n4fTBI46uP3enczY2Ct3xhDRyNjWQDCGP3WNziO/FQJDnH5sEbfSSMsjGrbMYLLJVY3v9JuvBHZT9RkpNnoRIbI5E5ZIvbVKU+3dilrqIOu53Ij7KliWRSG5aq+H0/9R0h8LLY7/LVsozWYZY+ueBSEDVPMLIlr84ypg3VpTUTWBWXjF+NLJUbsU7erDJOXvmYxLLQJ+E/ZVteXtQhPoxaVznWJbb+cxNJumpETX+tG0e7hcqLEfkrnsi5cczjVGBr+JvOSEWqL1kvtS+Vqssype620BfDVfz+tG/ZnUHfsDy3LXVXQes1DRvKIG9tOwjcKFjs1rIuXqwg2eODGckdL1nQssd6JmAO0093wljMdVj05yxNbzpkYvobWvLgm1cNdI66eHEJY7xhfef8T11xx4mfufyO+YWmxJ+BwNMOAb/iP+2ED4EDAY/AZWed8r5Tjpt2wTDevpMQF7ZRNjaQWUjDdtGcNenOexelWw57ThoMK90g3NDdxA2C1hpa+15yeEqz4d/GOhgtn7H79hidBuNItws3/3o64ed16BRYCnCSQx84+vvsNho+gDjqJdG0jbVi5bKdzzM9YwL2XmE90/1U0sPlG7FmpSvHnWSOXSSBKRlMH+1kJmEN8o1W1rRWZZR4ZD8ubvRKELK/Ib9bWHdqgzcrIcly2RLVympXylv3tyNXmbSSuDnLZMVnzNrkT7PiEWMjeeWYlVYvYT36z2MpyqH/Cl2oXIwoylhRsyiaL2bT6u3l1bYqZR6PLpgs3trn2pFx8uOKz0wX7cnGbihXYk0WsgjZLDtUJ6/iyhv0D4CD0YqHY9g0DnOQLhZ8iBvL2HggdfC3HQCXjMHIsb0FxO85Mx5Jo47eJ3175I50ELLKDNIHoJzWjH6AsAbOXbzmxh/87qG3feRHtwTJiy3mGYNAkL1nzFBGRwyBaz56xidg6XvfmNFDh9SlYVuwqSt9Yenm9Jv7l6RVXT1pxQmw1I2BFQBx18SZey7IHsOz8BhoFI6I+JNHgEyH9jCsBy87MrU9sCDtvWh5emwKkqBPh1M4k6rzc5JE+S6shHYb1KwYxgFk1jkLkr3v481ZGX53B9OwNawN+ejPrCdmyRMfOneUapYa2fP5vucYpWXGiJki2EpeWy3kc0/8lFjYW0KiUKd00TdMgqVyyFfcZt4febOKl/LWyNLK0pfbURLD0B2GUZ28RrS8z1g51fyNV8PO98fHihNISvwdGTUeZcemZXo03iAm0ZGv+Ebc7xXiVGM9rowjBfJlVJZKGX+0rbprQ5eHuKaM6a7ImzvW6L98X8lzFr3ERXU3412QWbpZwDWjvXNr2uue2WkZLtxsO3QfpDrDfKS/LR7C2m+4P43CA9mpz52e3oCAyGd0w09PrYYiBqvkvMG/FSs33PW1a+5/7XlB8koNj7+fAQgE2XsGDGJ0oR6By8865T2nPXf6FzponcPK3qNHYm3qZ/UQLH3/OW9d+sPDy9LmAQPTfGwObftNSt0r1jeOb+/AhQ0eD/EGKzcP1sP4XUi/1IbbfiNhEVz/CCwIrzlecvCKJZG3Aek8ziNe7iQkibUO6HXWM0daZANUi4ntceKHRyKlm6QFb1aupaylsXnLJqzl5A+Tw+/ixmRaWfL8sWA/5BWCpTs3f0he3cyWGiKI7La7F5abShq2Ut5WsnhLZmlhMzwL65qJVBJCub1rVirDifIao+FnTo4mv7NdkcUTVCN7Tt6KpU/H0o+zFFVdMPFknE1eK1yHWwtc7PsN5VG9LcZIPissedm/sKF21ZvW/Nt0ArJ4F4SKb1/JQPE3x8Ksr8R6PmLf7Yv5dSdCCCF3rQRQh3W9DQ2ORNikTbiA0QZr3na6Ztw3N02CW8ek3Yal/xy2PR2TduA+EdYAiwdJokeLPm6mL1q+7pav/eq+l37iJ7dh4sYrEHhmIhBk75k5rtErh8DPP/Sa8084eNJHYemTo9ZKGjZsOHPh03cx0rD9YsG6NAfGQCFoR8JCwIDM3KhI3h5e0MjEQSvAiYc1jm9pxVu0upGyjbf/eNOP4VkmjgMhRK5efkZ/IQl1olY32/hEPr8R2yaqm6O3euW+aB0WiuIJS8PmiYc21p/UZxUta0EK8iUWfC5ZGDLT0m8bQVIzS9MlDyOs+r1WKdYyXmyiThZd6kqrV+6DEi4SPivzeNKwWSgSMxuJ2OU4m0XL+tybLpAgFWRVZFZ5aZn0PoQVfB2JNJ9JsRjin3Bp1qtW0l7ldWXyg0uN7soxs7aZg2MbwEoQxbVCCWlfKQQZSmU3PDRtAKnj15gNZi/Mrd/f07ghzxh7tOQxhA4DKCN2YgcuYGydPim9fXxHeuvg7elkZrzgi89JCrc8cODBbeHCNTf+5KZH/+59374hQqjEjvGMRyDI3jN+iKODhsAV55z6odOOmfaJQbTQYfXfgfh6jdPRxuZzH6KtfGfxpnTJqq1p7d6wIDAg8R5jGuSPMbkenN8I48B0S/NgZRiKo9qHQAIZpJg5N0kA5yFG35H7Iv4XwrTQ90+CtYIEMvbXAPUlzEPiSJbsgaVVqSBhZRo2+VitUcJvjBiVY96CVFaKlYTPW6msYCGvEZva/nB3VXmEaOhuy103W3fq5C3lMMLSW5/6KCP7vS51Zk0SvL2VrigjPp+0nOn7gq+Snl5xq5OlP32qwbZieSRujijLr5RHyZPJ21saNpTdB7zpK2OPS/dOHJrOmvM/jTFq5yUjrUf61kpfWpTJuuuAkZiB/m/9g9Y03miXZlDG5PW6yzEigVuL+bQn5ttjmD8kesvw8HQ8bssvgBWdD2D8nJetGCwZQZWHwJJ3dNqePglL3ksQJ6/pQpQd167edNeFV8049rM/nVF2tFSy+DsQeMYgEGTvGTOU0ZH+InDVuad+6MUH7/WJscyWYda7vNe3pfmb4dO3vjv9ZOmmtHQwLAjcUEfjKJcWAW46tPrBZ09i8TFeFzcksfyhrFkCp4IsMvYXrYHM0kHSmK1culGLwCRB+JF9mnwv1JJnG3GljFmv8DOnYTPyVOxhltZMqrbvOeLgrTu9lTFZM1a+Du1TDvmi1psmSxPK8TidYtQGTNY+VHwSa4iodKMVQTHC5bFtQbhM3layyNdUXiHjdVa6xjDuTGXny6gsFquu1jLm5WVbde04XaikYbPx1EHxl5JKncIt0/0Wdab/e/vG9IljBsK1bXLad6/J6dIl96leFHqZdaGO/CmemaS1svSpXCR4jFfHWHh8YLLbtqYLOfWePhQwKDJvvk9GjMx78TtJIh+YJtGydzdu3GIOzoT/LOudNCG9Gb54/ziqLb0U0y3PKZtKGkJl7rxV//OL2+b8G9KaPexnWfweCDwbEAiy92wY5ehjLQJXf/C0s1991JTPDNbbuz0gTWbl4xfu2dKdfrhmW/oy/Po2j8Fx7HhsPsyxSX+h+bDgDcYGNA9WhpNwrLuKOWixUTGlG/PRclO6G5sUA79OGI3NCdY+HufajJOjRiNEJp5toLr3yqlYaf3y1ij3mZAWchG1llRmdmmlqyEsFTOMbpiZiHL/LCxjIrK3RvF3bafii+WtUU5eISW6sVdyrpaWsZKkKXlokrcXK10efVfGsJXq8qBon+wLDjeL40gfxMqt0f7IW2clLeRtSmtW9LuUt86nz8tlt9F94GtWSRK1aVM6cfzUdN7Waalj+vh0wmiEKckhe8r+FOMsf9bpQqFTMuw63iRktIyPxSUoWrun4VLTQljGafVmkGe+OE/G4OGLUDFFIYMgM3QKfWeZKYM6wktP+8OSx+DIHQPSYIRfOWHHtvReTMtTh6BBLwLb1/m2YUPn7FFv+RqiLccrEHj2IhBk79k79tFzRQDHu+896fDJF4wZNbStVRq2C9b3pCs2dqdFW7GjjACh4zES06jxSIlp2O6f38jOQVLADYpkjwSQ1j9uVsccIAFa00j4DebwGzUWoBwHrYVlLB+JGkkxUmAWHuyW9OOy48de07B5Faix0vUqix5pNqWy8vXU+XrVWOlEXvbXSFEdLlav9dO3YwQF3xMLmx2H++/oUldJw9YKf0+wizIivr7HPMzy6kveXbWMFZa+fFwujI8CVHUoW9dKbFFWiuJ/G3ELlQ8hy9ZAL9elz7YdkO6HsfrHe21KXUw5Zre9pf4W8mant7KMyquhTMSvVcLwkBjjd1rgeNSKvLPi10orHX+nRVzC7+D7zGhDa/iDsJBz+BjgnJkwGMeSvnu0XjOO3iHTRdbXbd+c3ju6PT2PbrT2oGPqrJa8R+esvPaaGXPPeue3rrsnFrtA4NmOQJC9Z7sGRP8zAr/6yBmfeMmhkz401DJyVNKwwVC3YXu6FEe7F29pT520OowHqaOFgn5DvBlI6wV9+A7FhvQcWP8YlJn+gQzwymMnOpXzWDe/dFM1TmK3O/tjGSvryH8rUTHZvV9ULtOHpS9bZZRQyt8FkbAy/Kjf8raw0smtZQWBRKrid9YfS5MIoVZTW9J6sYzVpTUTbEpc6uRVAmu3rIXQ+ElUJ2+N1avJkleU2RWrrgwP5SgJu5NlKx46VsMithaWaV4aWgnSR/08EgYvhhfK41jT58otaetr2ScFgUSOQcon76a+rrhEQV9W3mgfiDIzcJMWN2dlfrzwkBwPL23E/CHpZI5bZquh7+sh02BFhxVwXeOS7IAFS9Mrh7Wnf0Qw5Nd2oP06Sx70tHNL18Jzf3DLlIuvvrMEJFa7QOBZi0CQvWft0EfHWyHw07NPec/Ljpjy6dEjh3SIRcEsFhrnbn5nd/rcok3pZzjiXYhjqHHjRqTV3PHpi8RNiv55jL23BBtXFzZZbuzTsZGRgFl95qNkdVeOdL3lxlunStLlLEC5M2b50bLcqMXKQjJEKwoDSJcburOESXMkNJmBas1apuLjZo0WlqZsueTnfkc2MlX2yVmsTF6LUdhkpVP5KuFlDJcaq5eJUElrxva9FdETNJNF2I8jUR5rJ79ASXxZnHW2sHr1R16zGGZdMFxVlky4TRYva29lKBb7gp+d0Melq6GXuACxJ3zgeHtcwuMYdt4qqt+p7BKGHX6aL6NgSZIO3WK+WVriSNIYIoUWPIYt4pEtM9xsBvlbBGv4MPx+IG6xM60ZH4j4HT404UatHN/iGLcdR7vdB00VeV8Jo/un9uxIhw/oSYOyNVn1Ty15M+eu/MWvbpvz/vf81/WImRSvQCAQ8AgE2Qt9CARaIHDt+Wd+4gUH7Pmh4cyOQZLmLX0gD/ds6Eo/XLQhXYFUbLNIUngLl/5JtGQ8BxsZj28Zf49WjVce1djoxLKHjXIrysmtYPIO3VSbrGckaOUUrTti8x2oscxYUGW7IMJjNh/sOPtgOVJQJ4t9p5W8rdKaZfHqrF6lvNrnbJnE52XIFuFaLOflLY5S+acRPCM6lRh5Rs56w66QVzhRib/KK5dkFFY7QrfLKZWbplqmPPptSsNW4FJ707oPXaiTt039Rs1nMl+gcQ8J8j3tl2DdworHj6hLJHh0baBLA4+LcUwsN9bvmtMIVo70ZOmgKQ1LOHFaAGs458gJsOzNQNQT5qGmZXf30Y35wzmCz9sffiydvu9u6a17jUBaM1jNJW5j+84QKrQiQv6tW7oeG/LGr6CBeAUCgUArBILshW4EAn0gcPlZJ7/nlUdP+8JIXr6osfTNhpHkgtlr04P3L07XHbF/GoEwLD1Izr4JAV4Tj88YluV5CBlx77xGajUep9GHiTHDpD7dNOVo0KxG3grmLWeeBOxKGbUC8etCLLQeSWvmlwFnubELGrTg9JqGrbSMEdA6/7vyvX6UyZZJHaR8YaWFNcrgqcTIK9vpj7y+TK60OC711j6nREL8iLFa+gRek5e/K7FqadVtJa/phqujQtbMqmjjyYcIG2v7jivjgx1bSBwfzqXJN9D6qJZfVkXfVVoJSdQYV5Ix8Wg5PPFwXMaA1Y5zgITuOPis8oLGGhwj8yiZvnh746iXvq6wjLcjM81wWMY37D0hPeeWB9KEI6alr44BR0Ras8Eav68RXaYHxuk2BE8elB5dsOpXV9006/+d9Z0bMbHiFQgEAr0hEGQv9CMQ6CcCv/3Ya798/H57/L+RozQPbt6syW3a0qNIw/b1JTjefWBpWgYLynr6JLEMndKPQSw+Op/zqEvCReAIjbd7GfqFFzeYHYBhJqrByRz585u1F7gvSx/L1pShXBaypZLVQgmFWc+EtylpqIRRcYQuG4UKa1TlONdk7kvemjpI+OxiBdtqOpI1UlNH0DxWSqKyvDVWut5u+Zb9Ef7WQl4GZ7Yj6OwKaA1TXpUrE+1eZGkpbx++l7XytrDSURfkkob1yVv6zMpH0o/fWZbWVv5kyCFa855/cBpx/X1pT1j1Zk2DPx7DE9E/b38EF+fN2uE4tsXt2Om3P5RWbOlKG/edBN/WaY2btbytjqDk45Gm8GXHTkuvn9CR3rCDfnpueyJeDN0CTDdu2DL73y+9bb/PXxU+ef1cuqJYINC0sgUkgUAg0AcCsPSdc/Kx0z89jNYJHI2Ky5Zsko0NchbSsH1+zpp090NL04JBg9PKiePTjqm7px4JwzImpdtxYeNFyMfLIzBaOWjto/WPMfvkqNWIHX/WWfLUSrczjosjhXW+aK4OISjqYyU+afwq/leXhi377Xk/Li+byedJlicTvfnFGci+TI2lT+QtSJIEO9b32Jd8BGoMqpW8ZtXyJMnktT44K17Wg1ZlFD/DycYtcxQloXZJJscF1AKVtHElbtYX/qyzgJq89r1WVlLrj5b3lj6RRwmeDaX8pEIr7hl//E19RYYK0VMe3TIHNI9g+cAyaWwaeNfsNASx/DYuWZPSycemdBv0fC9Y71jXIMwVPNQMR4Dkrbi13oYLTV1MRYibutNxcWPsnqPTf43YkQ5CWrPBmtqvRx+m2tguwiPNWbj6tz/6w8N//+Ef3IxrufEKBAKBXUHAPTrtyteibCAQCFzz0TM++/wD9jxrFB3PYeVg7t0G6Wv8e3jj9vTV1dvS1Y+tS/M7YA3kpv9cHOfSX2kE/mYWjsXwc+KGSr8++jl1YgPl0ZdwKlRSGnzqrHTOCNN0a1Y2b0fGZMaX1iglTDmkiB7TVYbYESRpr6yD8moZ4ylN7aiFyOqVcnWWMcWwlszyO5SXpFgbyj6IVrEnn1pXk7z9lMX8/TLGNRa43A8jTr6MytLfNGy2IufLGjV9ailLOUb8bmnJc0S45eUXdMguQcgxv8N5CY5oR8EFgRa9pSB1xIeWueNA3BjWhdY+6vdh01K66aGGGwPL7AcLHzPOMDNG59bUgXh/WxFq5a27D0tv6NiRzhyscmbdgegcV5DHlSs3PvCVX993wkd/dMvaWHUCgUDg8SEQZO/x4RbfCgQyApeffcoHTj122ueGDG6kndoOS8cAWJ7a9CLDIzB8fPmxjeknCN2yfHcc3zJMC60j3NCZkeP+eY0LHUdMb/g50RoyD07t03GjcaCmshIjDEkOU035HdFbqTyL0jL+8kUr8iSGQiU/dtvWW/oqcdwyGynkUBLWpBc1xKdVGW/5avIjtP7wy2YZM8sZPrMbpUKECiIn7dWQytqDjV2Rt5DFfByb0ppRHi3bcDxrHIOK1cxwy38oOq2OdX25VkfiLcqIVc/0RTFsCs6supD9CvE35eUR6mrE6WMwcViqxRXBUgjyxixzQfMzvmYvxoMLXBjovkBytxAPNPvg2JY5o/GAMxQ5qg9p256+OaQrHQb1bkqZTD1HwPLHlqy78Vu/ue+NOLJFhfEKBAKBPweBIHt/Dnrx3UDAIfCLD7/m0y88YNI5Y8bB0oe8uztDtjQ20Nnw6fviuh3ph8u3pFWDefyFjZCkj5smj4Sfgw3xnrlyZCXHvUznRtLDcBm8ubt8fcMR3jv2twyAq4IJaaohBdnSZcSoKENSwA3dSGC2CFqHHTk0glVnMcxWQCNGdQQFJMSIXl07FgJF6jdLkye8uoz1Ow2bEq26YMi7nNasBYnUSwX1afCUpFoX5PJLC1wEtl7kNeIvZVqRP+pii3Emnn3pgh0jM1c080MzYDhy0ab98YDCmHgcH1ruHpzXOFLfC2SQoVauQxq2aQhBtAaWPurulD3S29u3pjcPb2tkvODL4OMQ8uEIer5k8dq7fnDdI2d84Ds3It9gvAKBQOCJQCDI3hOBYtQRCDgEfnrOqWe95thpnx00iKFVenC8W03D9gDi9H1r1bb0nTmr05opsN5ZDLw9afmAdYTHuLSKnHR44/YiN0QeFdOyQuuIBSCu7JYqgAXAFSugsQndUfOfamnqTzBkI5a0KMpFDlsy+LNcPlpZo7x6OHIk8mgdFUueq6e2TEGwvCXPLJNyu1SJkjRfyluStLoyfK8vWerK6FFpPgJuRcKoHmrpE4ObjovIW1ps6+Sts+p6eYmBz5dWyMo/DV/Dv8nS58cc5RdDLxmb73aEstsXFzHEiIgy8EnlJQtJUcaHkyP2adzUhe4PRYaUo7o602dHdKcTOoT97zRO8/s4quWR7bJVG2+/+Gd3veozl9+xJhaUQCAQeGIRCLL3xOIZtQUCGQHk3v3wiw/Z6+NjcGyVtheWPmzs87ZsTxdu6E4/WduVlsPIl32aaL3jxY1jcYP3Zvg9cTNlKjbeYNxzrF5IIB/A+5JblHVz01WSQQsPSSJv+xqZkuNfJTxN1jP1NcvHqMYCpFJlBCiT07CxvL3vLWzmD6bEpdXRqVmgWvqMsUklLb2WIZmxJaw4fpWQLbRMEqf+pjVrYaUTfOssY0YgjUz7MoaPlfF1lKQLZaQKxbIvS59omI57Zb71ZaVz8mZLallPIa+k24Ps1DX60DFG3njoJ2PnITet+JjSiseHEH7GCxt3INTKQQiQjIwxb+3alP5u1ID0cvDDxkUgJ7Dkux2U5i1YdePPbp3zt++65DqwxXgFAoHAXwKBIHt/CVSjzkDAIXDVB0/7yMlHTf33wR2w9IklR3c8JUz34Hj3e8u2pK9takubxo1qbKqMVzYdlpP1IG3066ND/PEHNY59eZzGCx2zcCmRvlC2+fNYmPXTUsJUWPT9Y0y/sajTsmhI2Vakxg9bjZVOLoyo7BZ+o7KC9GGN6peVzogM+YxVXsrbH6uXEi8LHiwBefme9dFIje9z0Y4n0PmLBS7i72hHyzXEM9fRSzvykfbJ9IM+iBVrZylviYmSqaRBk3NzpbyKQX+sukbuGA+Sl4l4wYLpzPjQQR1k2JQ5cKejb+nBU/EedBbksAMZMp4/qCedvceQ9OoOErzCkichVNrSuvWdD378stuPv/DKGTBZxysQCAT+kggE2ftLoht1BwIOgSvOOfVjJx2+93ljeCRbsfQ1NuC5tPQt2ZKugE/fElzM2G04wrbQKsXgs8gmkPaDj9RuIG68BckNlL599J9iXSQyTDhPgse4ZrRE8XMeue0GC5+laqtLEyZHdyQtShjYpvi/mRnGky4ji3hPYrMpORMLUB0pM6sX6/Mkzerme2qZ5Fu7lNbMwLV6S3mN6Kj8Jq8QHW03j4+zaEkXjRgKW+Mb+lMtYUZcpWr7TLGQOoUFuu962fB+Pi4t2jV5LLYg/xZLn7ZbJ2+TLB4XJVvZqqukNx/xF1NUusIyaJPWZD5o0L2AmWAoMy8XUR9p1aOebEYZ6t1B06T8a9u60nsnDU0vGNiNOzOO5Ak06APCr8yas+IPv54x75/e+a3rEIgvXoFAIPBkIBBk78lAOdoIBBwCv/zI6Re99NC93z1kGCwltLgVadjuQhq2Hy1cny5d3pkW8KLHcQjXQid3kjommJ+B+GXL1zYCM7/kiMYGvA1+UnwPlr825CIdPX9pWsv36TtFR3pkKBCLDI82B9DCqAKx/a1M4wZZ1qI8shmkubgRPJ1lrZARGv+3t5yR26CepjRsSn5klWlhpeMxtH2U07D14uMmYpeksh9WOvGHwz/2Sb6O/5XubEJ0SqthcTxsFsKKv6L3k9N2KhpfYyXt099Rj9Yt7mLuohFPhSHH+mGDRTtGAvmRT2s2l/mbxzauwTLlmKXTIyYIiyJHsfS7W7y6YT1+5THwx5vTeHjg+7w8hNuy7cCiG5d4Bj20IL1s0qj0z5NHpjNB9hpWZJWfbWtas84tXXPP/cEtB1989Z1givEKBAKBJxOBIHtPJtrRViDgEPjp2ad86GVHTvnEaOberbH0zd7WnS5CGrb7H16a/nT4fmkcbj5umTg2bWGqNR7P8obuoVMbCecnggQy9AWP1Lq6sI8PSDtWr2sc95o/1bjRjST0JH9iWcL0v3N248YkrY3LUJ4O9iRdjJ2WU8OVljMjb450+bRmZqHLfn1GUMynT0kjuUn2T7OlyMp6S5onMt5aZmD67/C93uQ1sojvWD5badrISWGBk8sE7r1syTMMvMXNy2bfaSWLtzriexVLn/XLMKAMWkb859TSl1dvj1WNvCy3CSSOgZChP2IZ5uUhPgzsCeIm+ZrxooWODwJT4YO3HLpAyx7lor7cN69xExyXMMaCDK45YEra+/aH02EHTUwfGz8wHdnenQapX2jjJJr6herwEDFz3srf/fqOuf/87kuuB2OMVyAQCPw1EAiy99dAPdoMBBwCvzn/zItPOHDiu4YxmTwJiLeogXg9uHl7+u+FG9Llj6xIi3C8uw0xzNqQlqqH1ryDpzWsLiNA1miVYfiWFSCCPO59FNkOuOkiL+nw392ZJmzfnuZNhR/gfihjRIcWP+QkbcOGf+jvZ6QlSGW1kknrj4JFcDutXyB/3MT5O+MIZuJTWtfQoUxItHM5gJqRMRIWJUk5e0RNPX3eRM1mLodiYYFrspyV7ags5svIj5sCvinBMqugtFZa6ZQoVjT6z7RMCnersQbyfUtrRiCbXBeL5dysl7QO83b3HFhsX3pUGnDHI+kAWH4f3As3aI95Dhtr9J3hVNjGmOFp+i0PpQ0ge8z+kp6LvLYMlMwHB2TA2P3+uen5R+yd3rYnAiJ3g0AaryUG/J0PDPhl65bti8/53o0HX/zzu8Ec4xUIBAJ/TQSC7P010Y+2AwGHwOVnnfLBVx495ZMjYTnpoaUPs1MmqIZmmb29LX129pr0CNKw3TtkSNqKpPFb4U/VvRYbMS92kPSddERjYyZh5D9abcYOTwNw1DYQxGYrw7e88uiUbnxQ4p4JeaNfHxoaDqvfNoTL6Nod1p/p+IwJ7A+dDqvO3JQOxO1K5jf1t3r9capYcsxyZ0SK7ykTkJvD6Ev2yVPSJ/03tuBJnPd5K0mWsRyp0CFYWMuyfN66RsuXtp2tdI745TRs+l62Thqp86SxtOT1JovJ5mU30a0eV8YsfUL8CJEpg+uu95mUt7UMiRtzLXPseXzLSxXUD6Y3m7Jbart/Pj4akDbPxOWKk49rWPRI5EcNa1iYx45IQx5bnnbg93YE+d72vINSD8jiPlu3pr32HpsuHtWTDu3ZmdZMRhDyttHiCJ+8mXNX/v5nN8/+pw985waYjeMVCAQCTwUEguw9FUYhZAgEHALXnn/mF5GG7Z0jeLxbpmFDubtxkeN78Oe7dPGmtIg5R7nJPg9+ffSxGofLGCRnvMUrmzusN0fvi5uTIIAkEExaPx3v/QkBb5nTlGTiGIR4YVBnBshdASMMQ7YwnAbTXQ0HYdiGcofA2kdSmF8lGTLrnTfzQK6c65eWKB6V+s+VoJgVTqqoscD1x0rXnzJGnGpv1irhYngZI3hyXOrlLSyK/Zb3cfjs9bi2a8Pc6NJtMRf5p/FIys08zDyqZZ7ambjcw98Zv/FE5GReAV0gCQShS0dBNxjIm6nO+DkvX/BChtysxdfumJm6YN17w6QR6Q0IhPyGQaozfsby9jcw3bSpc975l95+zAVXzoAixisQCASeSgiUBwFPJdlClkDgWYnAK8+/6l0j3/K1tp9eN/OcTVu3pzbdTLfD0kKXrSOHDkwXThmR/nTcHumfxw1Kk4ZgI5+NuGeMzbcB/+iDty/8rEgAxsOiw82foVzoXL8U+zAJGMO67DGmceRGx3sJAM09G2XXwvrHMC4kjvQL5O8kicyoYdzHbvA2vlQlgfyL5eyIlMTCbuuSnFiMN5+z177TrxHvzzOqKyP8kVZGJ2vJOeXmKP6ZrMRBfBaNgJLcGqFVIcs6+iW7L9SiAnlb5TWiVysvilFe/mM5IX74SV+7udAH+ugxB/NRPKrFZ9SFgRxP6AFdANZgnDfjgQCWPEnXxzy20DexvsKfbwjG+4ijp6d79x2avj9iezPRYzn45M1euv66T/341r1HvPlr04Po7bISxBcCgScFgf6smk+KINFIIBAI1CPwm4+e8R/H77/nO0YzOLP36RNDE3z6Nm1PX1+zLX0bWTk2DIJ1Zgs2+Wm4qMFYewzNwpuXd+FEjRs/j2j3HJ3SH+5FsvrpsAYiTAstOjzGW4ALHiQKh8Nfj8GbYdURn8C78V0SgpE45mNdclyoFrhsLaPsIB2WJqy2jLOe8fNs6SPRalTbqJvEimSHP40FFhZDs6pZGVwQkMJiRGtYmnamqzOSVrTjyZu3nnlZMtnDLxKcucZK1ySvksRMLtknimd9KrEriKTIou1kWYitw8CXyWqjZUja7sGYcayWwA9vEqy0HK9VIHdHwpL3yILGhQ1ab3m7+04c1w+FbpDYz8LR7rSJ6a2DutLrh7Wl1w5VzJQLS7NKMNes3jTzol/c/aqPX3rbvJi7gUAg8NRGICx7T+3xCekCgfSqj139zjFv+3rbT6+feV4nE8uL9akNvIrkpicdPGxA+uKkIenOA0amfx3ZlvagFY9O+Qx5waNcWur4HVpz7oNfH/2ydgPho6WOTve83MHjWrHggeyRILBuHvNyheDNXN7o5U/xH1RSlp0KGzwrE71cRgfPHimNq/DSh4RZgRw8Nq1YCbUuIWt1RE/fk8+0DIjMcUt70qw1L0wnjIZvYQ+IjGTPUOIoYqgQWRYlXEqYK2pmZYx4CilVS5+FbvFf8LKY6TPXoZ2ulHkcsoiVlN3wfXJCCHHVz+mrxzA7JOgsT2svMWeoHwZr5jiPxJjzAg+Pa6fiwWDEMPjxDUrH7b9HumcMUvmNA9Fj/lrl0FI1fQFx3D9/5cZbz//eTRPH/e03DgiiFwtUIPD0QCAvSU8PcUPKQCAQ+NVHTv/CCw6c9J7Ro8uQLQ1S8yh8+r6ENGzfX70trWEaNvrjkewxfho3/wORtP76BxoO/PTtkmM+kixs7rjIIXlO6evHXKejEOaF2RKYB5VkQS+O5FAl+cIGCZESqDxEfE/rzUe9NWXETw7leg0ebEyxxkqHvv3dvZ3poK1D03dglDxtxOR01fjN6ZE1OMrMdepSZ5bHVpYxCbWipEn64S15Si7Nr7DfadhKayAZtFn3jLwV7TSAUyKsOFIWHtVyLLeA0A4meWMxlLW0ZmJ5A7FbhuP6DsZOxNgx3A5fJPYcex7548Ztwo1buaSDyxtv37E5vWlEWzoNHDDfazGrItvDcf/Spevu/u7vH3r72d+7CcoTr0AgEHg6IRBk7+k0WiFrIOAQ+Ok5p5512jHTPjuYx29yhKmESI4VuxGyZUf62orO9IP13Wn1ngihsRFWHPrm8XiPSepp8cFty3Ti4Y0jWpI6BlSmxY+XNnBrUwggCR4d9xGipUFATAhnTZK3CiKX88la+ZLo8X0lOznYMa2JpUWuXKasHpbD7xtwbP0IZIU18+37HJe+N2NIOv3F29LPR64FJjzadPJKVVZfHQnzKlYnrxKvbOFDGbG4eUy8vDV1sFkhof5VyGLBpmmdZdgU3HIVCyx9J3lxhxY5Wu7I9kjGSOh4sYZH9/TLYxYVhuFh7lrezuY4T4DP3hEg9vTlw9gMXbUmHZG2py/sPjg9D7wQtuKdWFFs+ori35IVG+7+0i/uecmnL7sdTwbxCgQCgacjAkH2no6jFjIHAg6Bn3/otE+ccPDeHxozBiSAx7fm20XSh/169saudPGyzvSjpRvTqo4haQKyH6zgLV5a8O6fBwIAPy5e0qCVjz58vOCxN8ghyR4sewNh7RuCY9+NzNghBIPkxixs/id/J2nx7MeTGCM43krHyuw7akryIUVyOJfM2BoEx6xeJDIkrktXpiOXbEs/2/9N6e+WXJv+53noG30RSZaypc5MVfxpsrSSV/tQCXbsrIoNEBpWNTliRZ2PO62ZtWVYkcTqACM2YloAIkfL6kL8ZOBrC8J92NTGePNonxlWmCpvxRoQQlj9iCEDZYsVEMf3vIhz+yOIsQjLHuR807aN6R8mDU+vGKzWwwq8aBx5nBcsWH3rFbfM/rf3/Nf1d8SECwQCgac3AkH2nt7jF9IHAhmBqz542sdOPmrKeYNJ3BppDHYSM8z0uzd1pe8/tiH9ZPXW9NhWkIHnMlwLrEZDcJzLIMy3PQyLEfz7aBFCIOY0D6E5cJw7AH5+gxFjbwste8MYk8+WjdJqVVqrys8rJjCVu6aMpTUTHgg5xdLnBlq6pW/wogECBEu6t/Ej0kH/MzM99PxpjZReLCOpuuzliKfwvl2UV75TWgMpGwmfkSaSPn3PTIpNlrw6a5/2MWOr/aYF72bERHwhQqbQwsdxZZiU8aMb40RSR7KLbBaNmHnww7PUaLx9i9h5A5HfdjvIXgfi6T1v3JD0oSkj0yvaQRAZ1kctq9IsLXn4ZfW6LTM/ffkdDKECZYhXIBAIPBMQCLL3TBjF6EMg4BC48pxTP3TS4ZM/MZqWHQmX4klfW5qF8BpffXR1unXuqnTjgfuk3ZevTuum7pG2kiAxZAdTaE2GZYxp2Jh+jZkXGFSZPn880uWLViwSsXyN1pYSZ50yv7MsW41lrD9lSGgkFAkJkfmvqZXOjIm0bG1meBiQGPqhVWLkGck0klX+LR1SKb2Jq5SXR8KUw1smvTVTqzB5JUCzl5efa9usQ8i44pZXYnsPb1Akxj6Eb90kWFSXIs5hN29T80iWQZLpf0fix0s49OE7bBp8LhFTj8e5D8xP44DF6kP2SeMfmp9etvfo9P/2HJJeNLA7tYl8jRfTmknEGRwTz16w6g+/un3e+955yXV3xYQKBAKBZxYCQfaeWeMZvQkEMgK/+PDp573ssL0+NoQXKyyjhn0KIvIALnL89/z16Sdz16QFSMPWhkwJbSB73SSJJEzMyDEOx7aMxbYf4rDR4Z/kcTBYyGM8WsR7Rv6k3sdjyeuHdS1Xq9YzWq6agh2TV2ldQqJqrGdN+cVqrHQVE6LQIadRhWUy3yIuCCL/lKNdlbPsolRbY8mr6K5aBxlGBeFQ9tywMS1H9pNuhsWhdZUkEMGPp86ARQ/p8eYzJuILDxGfvnZYdrvxc4+7ZqUjD9g9/QuOa8/swRj60C2UUdKagSdu3vrYB79/81SkNfMdiZkUCAQCzyAEguw9gwYzuhII1CHw07NPOeulR0z57Bj4enUj7Ea7HRVqiJTZXT3pC7NWp4ceXppuGTYiDZwyIW0ZOzJtp7WIWTNmI/baiUfgWBcWJR75kvTBp0v893yg5Wylc9YpCkSy461YmUD1w+ImR6BmYdPlSnz68DvJiwVrzj6E1mDJW7yVzpO2OktkIX/2KTR0nWVPipKE2mdK4vIb+Nvk9VZQ6Ypvx5FT3vLNN34BHi9d0E9vDqyuxJs3pXlRY+KYNJD+fLh0M2jGzLQNx/I71m9Jk1evS0dOHZfOHdOejsMFjIE59qG2SRLKtGZzVlzz6zvmvf/dl1wXt2tj6QgEnuEIBNl7hg9wdC8QMAQQnPmLJxw06Z3DGFMPlqcehPHILmJYCW7f0p0uXbY5/XDp5rSE4TtI9I7CrVxeDOARL0N1kGwwbh+PDA/A0S5vdtISSEIzAATQ/ATZKOP6kdDwqBHHxA3/Ox7/+jGpsQYKB/JLU1nGjpBZPc8gW1j6mhz9CgtckxWvP5bJGmtghejV1WFkV/kd49U1WSa1v/Sb4yUZxsFjSBoSP96s5aWZ+Qh6TZ88jgvjJDKWHrNg4MJN+4xZacCYYenVe41J/wsc/PUDcZxd8l3xX+xJnVu6Fg5941cwePEKBAKBZwsCQfaeLSMd/QwEFIHLzz7lg684csonR5H0gaTtwL92kDC7rzAbHO1zuMjxyx0D06J2EDgStNEIzcJjv/Ugdw/PT+kFBzfSqdHHb29kYpiHnwdN3RmHj8eMJCX8Dskib4ouWd3wAeQN0mzVUmtTU3YIvm/LkxKoCldT65xdRDFLGYkfq69Y+kpy6Ze9cgn0lr5SZZx1sMmSp5+Zsa4MUeO7Ymnk+J5YKPW7lJlkeiWwYyo73o7m3yR4zGHL4/TtGByJkwdGtz/IHnLfDkDonRNwJ+eiYV3pEGQSGdzEJfEGLXlzV17z81tmv/v9/30DruXGKxAIBJ5NCATZezaNdvQ1EHAIXPvRMy943gF7vG+kWeby7d0GWbobadi+h8DMX1vblTbTaod8qULoeGHj4CmN0Cb34BIHY/jxwsBYEEJm3WDgX9z8FEJCwnf/3MbRLy8USFvK40h6JEiw3pitpCzjcag75qwEQzby58ugTvPVk2DHrax0mY3xCw4Nf7RrZWraaZlizcnSREq1ndy0XvSQv/VN/qT/42oQvVnA9/kg0yTQy9c2QuQwziGP09diDPg7A17jOPf1gPNNyHTxektrxp6jqny7FoSZPnnD3vRVDFi8AoFA4NmKQJ3r8LMVi+h3IPCsQuCVH7vq/aPeijRs1818/8ZOWIxoFQNLsDRsRyIN24V7D0337Tci/ROyK0zaBssSj2sZxJlp1Xicy5yqjHXHfKskW7wpilAfQvRI7Jiai++TuDCrA8kgL36Q4/AI0vzZ5CiWL33+ND84Mhex+pFMFc+mdivWyJWlNSOJ5D9vLcwj601s5XBbRVamRha5Fdwgw5WXl9e6IcGtCz5pf/Iz9pnWOoa7YTkGtabMDJ5Mvkqfyd1HN7KaECvexEXaug6Uee6BE9O9u3WnH4ypEj1pmtZChKKZtWTdtZ++9NbpQfSeVdM6OhsI1CIQlr1QjEAgEBAErj3/zC8cv/8e7xlVa+mDgQ4ZOb6FbByXrOpMG3dg6WA8P/qW0TePPmTMn3vdfY0Ua8vXNI56GQeP5HDGoykdiZukY3D0ex+sUrzcQVK4P2708sYpff8YMoS3VPmiJU8IlTs6rRA2d/QphIrkzriifs8In1gOzUpXlMmE0bXj6xFLnpwLqzwtrHSexFVk4QfqUyj12pJLH0fIxZAxPKpl/QiRkgYDU5LW5+7fIM7E6VacupI84yj8LT2d6fUwoL4O/3KXzJJH8oh/69ZufvRzV9152Kcuux0AxysQCAQCgZ1OMYFFIBAIBAKCwOVnn/yuU46ZftHQIYNg5gOTsONdWoxAoGZv2ZEuWLktXbmmKy2bAh88pupiwGUSP8Z8o6/ZUpC9lxzRCGrM+HfMxsF8rMwE8QiOeJnGi8fAjN+3Fb/Tj28sPic5HAEmU94grZjI7EJGaaUrzWi0rKllUG7uerLlj22FXeo/VYJcFUke33MkzeuJ3Bb2ByRFPVJWP+fFDN6sZa5iYsI+09rJyxesfw2OcOfiEgZJ9DEgeySB23akoZs2pUO7tqVLJgxIB8Ihb1DFH5DVk3gPTPMWrf2f7/72gbd+9Me3Ihp2vAKBQCAQ2IlAWPZCGwKBQKAWgV+fd/pHnn/gpHNHjxoyVPzJcnDmBsF4GGnYvoI0bN9dtCFt6OhIo2HBW0MCx5u3tN4xQwePcUliFiL7A2PEjQYhpMWK6dp4bHkIXMnugNWPlkEe8U6GPxr9/Hi8K85n9G+jeMKqnJzekqfvV6x09h0jaiSqtKApOWrEg3H1KfkTiyLeFiuclclvNEiZEFGtV8LXUBZbSj0BVbLJdkgImdaM5I4xDNn3FSDFxIv9ZDzD5cCJ+Wt/f08jqDXI8Zs2rUtvmzQivQZ+eQ3i6kSmJQ9kevny9Xdd8rsHXoZYeWDY8QoEAoFAoBmBIHuhFYFAINArAri9+2+nHTv9Sx2MrVda+vD3g7D0feOx9ekKWPoe24abvcfun7oXwLhESx4DAN8xEym9cLHgCJA9WvIYWoQWPNaFkC6DcOQ7CDd1N2+CH+BJag1ciDIMLUKrHwmR52WPK3izHsNKWjOSORIwcje3BPbHSsdjZkuHRuvkIFTCSxUkezyGJkmVdHXkkvgffRt565mklzlq/wQi94qjG5ZPWu74+WgQPZC8wUh7tg3HuEOR/u3wEYPSf8Bf8ugBPWkgQrCQpjI+Ypv5+iFEy6LlG677yi/vPfVTl90Wac1iDgcCgUCvCATZCwUJBAKBfiHw8w+fdv6LDt7ro6ORb7UpDRsI0Exk5PjuwyvTtcs2ptv3npj2XLE6rdx/ctq+F27r8vYoiR79Ae/B79OYhg2XNg6FZW8+iCHDt8xCiBHme6XfGi2BCOwsPm3PwdEvrWJ2UaPPFGtmASyPde1vMds1bgKTReY0bFwOndVO/PTUoiYWvcbX5B/JG8KepMmQm76HvKxCayXdA5HZohFCBZY75hc+FrEKeXzLo1uWO/o5eB/HtUw/h9A143HjdtWh+6RhS1en140amN42eWR6NePkiXyNl/BT9J/ZMRYsXnP9T2+a9eb3fvsGXM+NVyAQCAQCfSMQZK9vjKJEIBAIOASuPPe0D51y9NRPDBZLH/mRXVrAH7A4PYSLHN+YtzZdtXBDmoc0bIOO2S/twI3SboYQIYHjRQRas0iYmIaNfmr7gNDdeD/+BtlDGJfJv7k9bcVx7nKGemGoEV7i4EUPHl3S142hXCq3bWuOZCujVvrSKXmzOvh1ErVyRRRLHvuI71PeDhQi8UR+YbmMQkskiNzBtz+YZo8dnbbCqpmGg9ASE15cYWzBvXdPExF+ZvSCpenh4SDKLzpUPh+EY+suXNLY/e5H08EIhvyhKSPSy9tI8iw2jbI8Wg9Bplet3XzHZy674wUXXHUno1XHKxAIBAKBfiMQZK/fUEXBQCAQ8Ahcde6p55546N4fHzN66IBuEKBqGra29OjWHekrj65Odz26LN0wclQaAwvfelziEKbCo1D6rD0Pfn33zsMx5ujG5Q5aAUGS2hnOBUfB3XuMhWUPhPCPOP4EaZTjX/q10fonJNNMbay0sMrl414tI1Y686+j1Y7fMesZ/qBfIi9R5DRsWj3J3jocQ/OGseUMngrLJI9gp8P/btFKXKxtS913z0k9jI+3HYSNgZAPntawTCIeXjtyCbfhOHfw9fen7YdNT12IBbjbkpXp5VPHpH8cNyid1NYF1z8zHSrKcvFiUJozb+Vvf3H73P/vXZdcB2fHeAUCgUAgsOsIBNnbdcziG4FAIOAQ+NmHXvPRVx4x+fwOZGmgBayShg1k6vZtPenHizelH6/YmhaTz/AyxkE4vgVJSuNB3G7S1KwkeMeD/E0C4SORWoW/eezLUC5/ghUNacHkdi+tZ0NxBJpXL0/4TLAaSx7JlOTZ5b+az+VoV491aV0j8ZPyeJFk8giWJPNWpI1DPlo5ln05/O+MdPLiyUE4qma5u2c3ZBwN2Q8F6VuLCxnMJvLA/DQcP5+/95j0jtHt6fR2UF9/2sy2JK0ZINi09cHzfnTLYRddfVfFYzGULxAIBAKBXUUgyN6uIhblA4FAoBaBn55zyjkvPXzyp8eMHJJ6cNTaDQvZALnMQH7VlmZ37kgXLtqUrkodaQl+l1AtJIi8xMFLDiRLJx4uR8FpMaxovABBq9o+sKI9BmLISx64xJCm48iX8ecYwqTp3JWiGZkjpzPrX0OGnS9H+DLZMmsffjLQ8TAcFW8EoWNuWh7N0vJ4AKyMq/DZPMjKo9wTDmkEleZni1COlj4eNzPbCP8mYWQOYZI9EMkzRwxIZw3Znp7bvkM+qgZdxhtMazZnxa9+fce8f3r3JdfBPBivQCAQCAT+fASC7P35GEYNgUAg4BD49UdO/8yLD9nr7GEjGrl3s6VPjWp3bu5OP1rZmb68fkfqZHgTEiGGZXn4sZSOw1EtQ7DcNw8XHXCEy8sOyAaRboA/H4M082Yv/fX4PoM48yV+d+VlDB7HKtFrlYat8hVH/ugTeCfCwRx3QIPI8eIIyeVI+NvxJ+UkAaWMPLalX95jvGQCn8TJsEoyL/Aj+M5h01K6a1Zqh6yngOT93ZDu9DpwRhN1Z1qzRtDnzi1dM4e+8StoNF6BQCAQCDyxCPjAVU9szVFbIBAIPCsROPnjPztn+Ju/2vbT62e+b93mbVvbeHyZ07DhMurQ9vT5ycPSg/sOS//MNGyMU8cjVpI65n5lajCGMcHt1PQYLm+QHfEol6FLeATMHLsTQQQbV1TxP3eMa+9ZBo7SmtcqrVkeKbXu8TYt/e3W4t8kkEqGkuEFDR7fMoA0L2nwmJZWyE4c8ZJ4PgoSuBJEj0Gh4WfYDsvkyw/YM92+Z3u6YkyV6LE5CeMH6+HMhWt++YUrZuweRO9ZOV2i04HAk4JAWPaeFJijkUDg2YvAb88/88LnHzDxvcNHNix9ElLE+BnI2F2bt6cfrMNlDvj0bSFZo1/cHiBUDF3CjBs85r0FfnK0rNHS9kJY01hOcvk2uKBemW0Qv1aWvBwQ2cqUrnAqFInnbQ/vjOGMNGUiN8kocwHfjs/oi8fctSfgZi3DxFBG3LgVWRBW5nXwxXsLLHmvH04LY2Psq5Y8cMTNW+d+8Ee37nfRVXdqtOdnr45EzwOBQOAvi0CQvb8svlF7IBAIKAKXn3XKua88euqnRtIXToIbKwvSNGxzEafvM2t2pF+s2JIWT4P1bhYsZQzNwuwTtLTxQgcvSrwYfnI8apXbs6ict3rpx2c5ZhvUyligO+F1PnktAzPjeySREhcP/oGIgZfGw5pIH7zd8ZPWPF7AoKWP7R2LU1eWh2/f4K6udFTX1vTNcW3pAKQ1G1ymNSMJhU/eo/NWXXvFDTP/9znfuylu18bsCAQCgScFgSB7TwrM0UggEAgYAr/92JlfPX7/Pf9lJC5yiMXMcu9yNQLxu2/DtvTtFZ3p6ws3pu72gWkQCNKGEXB2YwDjBxc0buySfNHKx1h2DMTMLBW0GNqxbj7i1VbF2qdhVyStmlrx5FiX1jf+xOcklrxdy0DPvGzBo1sGRmYgaV4Y4ZEuL4swJdxv7mjEDURA6L/ZuDa9dfeh6XUw+DXxSB71ot4N6zY/+omfzjjmcz+9A4LHKxAIBAKBJw+BIHtPHtbRUiAQCDgELjvrlLNOOXbaZ4fxVm1p6QP5mg1L3xdxe/fq1VvTAnCugbjEsX0xiNY4WNjIqHhsyssdL4Klj/H4GKqFvnN8j8eq9K/Lr/ISBz9Ql2USPZJHEkYSPV4Q+cPduHxxUINM0o+QRI8ZL9DOYGTM2AZr3hBY/Q6DkfK/JnWk/dBUB3Lv0tjIeIPttFZKnLyBac7CNX/4we8feuN5P7wFwscrEAgEAoEnH4Ege08+5tFiIBAIOASu+egZF8Gn792jGFOPR6N2vCuWvvb0wOaudPWDy9IPN3anB0aMTHvg0sTyQ6ennr1x8/V23M5lsGW+HlnYyLZBy9vh0xq+e5XAy+orSEsePxNLHn4yJ+/dII4kd7QWkvjRX/A4xPNbsbZhfcSlkbG4hbsB6d8GoM43tW1Nr5s6Np0xqJrWLHcL1rwVqzbd8/Vr7z/9Iz+8BebIeAUCgUAg8NdDIMjeXw/7aDkQCAQcApeffcq5px07/VMdkoYNxIwBmvFTTl1xo3cWLH1fmrcu/XzJpjQXx7tDEdqk88F5qecQEDukHUtz4QInN2TxO492SdL4O2PgdSJe3kQcA5P8ydEx/jEN2Qa8z4B381Yg08WENB65aqc8MCfdPQSxAl+IyxewOg5Ztyl14jbwuPvnpIPGDU0XI63ZUe3daYCkNXMv+u6hvYXL1t3ytV/dc/InL7sDTDFegUAgEAj89REIsvfXH4OQIBAIBBwCv/jwaz7zgoMmnj0WfnLd8JnbmYYNhWDpm9m5PV3yyMp0w9zV6aaRI9MExONbg5uy2+nXtwnkjcewzFpxxyON9Gb8m6FakHpMSN5GXPLYCOvdaPjh3YRbvrQMMmQKY+ctXp3aRgxJHcjY0Q7ytxlHxqMXLEtn7D0yvWnC0HRKO4hjeYmXJA9kdNGiNTf/+IaZ//D+/74RlcYrEAgEAoGnDgJB9p46YxGSBAKBgEPginNOhaVv2qcGMSOFWvo8QHfjBPW7CzekK1ZtS/O3w/zHCxw8xiVx44WKmx5s3MPg7dmXIDMHY/WR+PGIlqFTeIR7KwghLXzMkHHkvo2YebQkzlmaxiDH7aF7jU7/Pm4gcteiMTteNiFI8vBv+epNd1x45Z3P+9wVd0QIldDgQCAQeEoiEGTvKTksIVQgEAgYAlede+rHTjxs7/MkDRtuQPBoV6x9cqG2Lc1B6rWLcZHjJwOGImIK/O8m4AIHb8AyLt/yNQjOjH8vPqwRF4+pzpgKjUGbJXDzWvjo4UIH06Lth3Av9BmEdfAtowel/zNkR3rpAJA8vjRKjPwuFy8GpbnzV/7+Z7fOefe7/+t6JO6NVyAQCAQCT10Eguw9dccmJAsEAgGHwNUfPO3TrzpyyjkdzKdLfz6EWpGIKrqK3dPZnX6AkC1f2dCTNtEvbx2Oao+GtY7hWp6HQMy08M3EJY5JuNhBCyB97his+TiEcrl3Tho8ekR60ZiO9L6h29PJuMxbGwwZba3f0DnrY5fedsgXrroTZ7rxCgQCgUDgqY9AkL2n/hiFhIFAIOAQ+OnZp3zspUdMOW/MKDAyxjph2JZM+trSfPj0XbCsM10J4rdo/ynIcIHLFzzixUULubAxHynY9sEFDvrxkRCC9P1NW1d6Lyx5xw/ExQvW5S15Fgx5zorf/fqOeee+65LrEGAvXoFAIBAIPH0QCLL39BmrkDQQCAQcAr/6yOmfP+nQvd8/RDJy4HiXlj5+zvB5+OXuzd3p0nWI1Qfit4WEjWnYcOlCsl8gJt+AfSamVw3pSf/YsT2dOQxfqk1r1pM6t3QtPucHN0/+4tV3lVczYjwCgUAgEHhaIBBk72kxTCFkIBAItEIAlr7zX3bElI+OtowcRRq2Bci9+7n1Penq5ZvTwimTwAN70it3bEmfhMve4R1taVBTWjO0hNu9j8xZ8dtf3Tb3g+/99vVhyQv1CwQCgac1AkH2ntbDF8IHAoGAIXDt+Wd+6wUHTvyH4SOYOk0tfXa8i5/3bOhKN67dliaMGZbeMJJBlfFNZlCjIZDleMSLV+fmbcvP/eHNUy+++i7c2ohXIBAIBAJPfwSC7D39xzB6EAgEAg6By846+dOvOnrqOSMr+XJRgLdoSe1w3NsFMtiOsCkD5FYv/iF48sx5K6+76qZZ/3b2d2+M27WhUYFAIPCMQiDI3jNqOKMzgUAgYAj87mNnfvv4/ff83yNGIoYeb95K/l2a8bSEhm5Zv7Fz8ccvnzH1gitnaJyVwDAQCAQCgWcWAkH2nlnjGb0JBAKBAoHLzjrlvJOPnfax4bDe5Zu7iJM3Z+HqW3/wPw+95bwf3YLEuPEKBAKBQCAQCAQCgUAgEHhaI/Cbj57xqY0//r+L13z/n2ae/+bjEXclXoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAoFAIBAIBAKBQCAQCAQCgUAgEAgEAk9PBP5/E5Y8EhJZfuQAAAAASUVORK5CYII=", val defaultLatestCode: Code = Code(code = defaultCode, language = defaultLanguage, lastSavedAt = Instant.MIN), + val defaultPvPLatestCode: Code = + Code(code = defaultPvPCode, language = defaultLanguage, lastSavedAt = Instant.MIN), val defaultLockedCode: Code = Code(code = defaultCode, language = defaultLanguage), + val defaultPvPLockedCode: Code = Code(code = defaultPvPCode, language = defaultLanguage), val defaultLatestGameMap: GameMap = GameMap(mapImage = defaultMapImage, map = defaultMap, lastSavedAt = Instant.MIN), val defaultLockedGameMap: GameMap = GameMap(mapImage = defaultMapImage, map = defaultMap) diff --git a/server/src/main/kotlin/delta/codecharacter/server/config/GameConfiguration.kt b/server/src/main/kotlin/delta/codecharacter/server/config/GameConfiguration.kt index 7b2a00e..5523073 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/config/GameConfiguration.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/config/GameConfiguration.kt @@ -22,6 +22,9 @@ class GameConfiguration { speed = 4, price = 2, aerial = 0, + weight = 1, + numAbilityTurns = 5, + abilityActivationCost = 1, ), Attacker( id = 2, @@ -31,6 +34,9 @@ class GameConfiguration { speed = 2, price = 2, aerial = 0, + weight = 2, + numAbilityTurns = 5, + abilityActivationCost = 1, ), Attacker( id = 3, @@ -40,6 +46,9 @@ class GameConfiguration { speed = 4, price = 4, aerial = 1, + weight = 3, + numAbilityTurns = 5, + abilityActivationCost = 2, ), ), defenders = diff --git a/server/src/main/kotlin/delta/codecharacter/server/config/QueueConfiguration.kt b/server/src/main/kotlin/delta/codecharacter/server/config/QueueConfiguration.kt index 8801534..1d55752 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/config/QueueConfiguration.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/config/QueueConfiguration.kt @@ -10,4 +10,5 @@ import org.springframework.context.annotation.Configuration class QueueConfiguration { @Bean fun gameRequestQueue() = Queue("gameRequestQueue") @Bean fun gameStatusUpdateQueue() = Queue("gameStatusUpdateQueue") + @Bean fun pvPGameRequestQueue() = Queue("gamePvpRequestQueue") } diff --git a/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/DailyChallengeController.kt b/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/DailyChallengeController.kt index 1dc1b95..2195278 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/DailyChallengeController.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/DailyChallengeController.kt @@ -4,6 +4,7 @@ import delta.codecharacter.core.DailyChallengesApi import delta.codecharacter.dtos.DailyChallengeGetRequestDto import delta.codecharacter.dtos.DailyChallengeLeaderBoardResponseDto import delta.codecharacter.dtos.DailyChallengeMatchRequestDto +import delta.codecharacter.dtos.MatchDto import delta.codecharacter.server.match.MatchService import delta.codecharacter.server.user.UserEntity import delta.codecharacter.server.user.public_user.PublicUserService @@ -38,4 +39,10 @@ class DailyChallengeController( val user = SecurityContextHolder.getContext().authentication.principal as UserEntity return ResponseEntity.ok(matchService.createDCMatch(user.id, dailyChallengeMatchRequestDto)) } + + @Secured(value = ["ROLE_USER"]) + override fun getUserDCMatches(page: Int?, size: Int?): ResponseEntity> { + val user = SecurityContextHolder.getContext().authentication.principal as UserEntity + return ResponseEntity.ok(matchService.getUserDCMatches(user.id, page, size)) + } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/match/DailyChallengeMatchRepository.kt b/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/match/DailyChallengeMatchRepository.kt index 78077a8..cbf162c 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/match/DailyChallengeMatchRepository.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/match/DailyChallengeMatchRepository.kt @@ -2,10 +2,11 @@ package delta.codecharacter.server.daily_challenge.match import delta.codecharacter.server.user.public_user.PublicUserEntity import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.data.domain.PageRequest import org.springframework.stereotype.Repository import java.util.UUID @Repository interface DailyChallengeMatchRepository : MongoRepository { - fun findByUserOrderByCreatedAtDesc(user: PublicUserEntity): List + fun findByUserOrderByCreatedAtDesc(user: PublicUserEntity, pageRequest: PageRequest): List } diff --git a/server/src/main/kotlin/delta/codecharacter/server/game/GameService.kt b/server/src/main/kotlin/delta/codecharacter/server/game/GameService.kt index d091ae9..a009c0b 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/game/GameService.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/game/GameService.kt @@ -7,6 +7,7 @@ import delta.codecharacter.server.exception.CustomException import delta.codecharacter.server.game.game_log.GameLogService import delta.codecharacter.server.game.queue.entities.GameRequestEntity import delta.codecharacter.server.game.queue.entities.GameStatusUpdateEntity +import delta.codecharacter.server.params.GameCode import delta.codecharacter.server.params.GameParameters import org.springframework.amqp.rabbit.core.RabbitTemplate import org.springframework.beans.factory.annotation.Autowired @@ -45,8 +46,7 @@ class GameService( val gameRequest = GameRequestEntity( gameId = game.id, - sourceCode = sourceCode, - language = language, + playerCode = GameCode(sourceCode, language), parameters = parameters, map = map ) diff --git a/server/src/main/kotlin/delta/codecharacter/server/game/queue/entities/GameRequestEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/game/queue/entities/GameRequestEntity.kt index 72db6a8..743324b 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/game/queue/entities/GameRequestEntity.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/game/queue/entities/GameRequestEntity.kt @@ -1,14 +1,13 @@ package delta.codecharacter.server.game.queue.entities import com.fasterxml.jackson.annotation.JsonProperty -import delta.codecharacter.server.code.LanguageEnum +import delta.codecharacter.server.params.GameCode import delta.codecharacter.server.params.GameParameters import java.util.UUID data class GameRequestEntity( @field:JsonProperty("game_id", required = true) val gameId: UUID, @field:JsonProperty("parameters", required = true) val parameters: GameParameters, - @field:JsonProperty("source_code", required = true) val sourceCode: String, - @field:JsonProperty("language", required = true) val language: LanguageEnum, + @field:JsonProperty("player_code", required = true) val playerCode: GameCode, @field:JsonProperty("map", required = true) val map: String, ) diff --git a/server/src/main/kotlin/delta/codecharacter/server/game/queue/entities/GameStatusUpdateEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/game/queue/entities/GameStatusUpdateEntity.kt index f3f5bb9..d9a833f 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/game/queue/entities/GameStatusUpdateEntity.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/game/queue/entities/GameStatusUpdateEntity.kt @@ -1,9 +1,11 @@ package delta.codecharacter.server.game.queue.entities +import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonProperty import delta.codecharacter.server.game.GameStatusEnum import java.util.UUID +@JsonIgnoreProperties(ignoreUnknown = true) data class GameStatusUpdateEntity( @field:JsonProperty("game_id", required = true) val gameId: UUID, @field:JsonProperty("game_status", required = true) val gameStatus: GameStatusEnum, diff --git a/server/src/main/kotlin/delta/codecharacter/server/leaderboard/LeaderboardController.kt b/server/src/main/kotlin/delta/codecharacter/server/leaderboard/LeaderboardController.kt index 1c64cc1..cf4fa3c 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/leaderboard/LeaderboardController.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/leaderboard/LeaderboardController.kt @@ -2,6 +2,7 @@ package delta.codecharacter.server.leaderboard import delta.codecharacter.core.LeaderboardApi import delta.codecharacter.dtos.LeaderboardEntryDto +import delta.codecharacter.dtos.PvPLeaderBoardResponseDto import delta.codecharacter.dtos.TierTypeDto import delta.codecharacter.server.user.public_user.PublicUserService import org.springframework.beans.factory.annotation.Autowired @@ -18,4 +19,11 @@ class LeaderboardController(@Autowired private val publicUserService: PublicUser ): ResponseEntity> { return ResponseEntity.ok(publicUserService.getLeaderboard(page, size, tier)) } + + override fun getPvPLeaderboard( + page: Int?, + size: Int?, + ): ResponseEntity> { + return ResponseEntity.ok(publicUserService.getPvPLeaderboard(page, size)) + } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/logic/verdict/VerdictAlgorithm.kt b/server/src/main/kotlin/delta/codecharacter/server/logic/verdict/VerdictAlgorithm.kt index 80c3afe..0eace17 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/logic/verdict/VerdictAlgorithm.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/logic/verdict/VerdictAlgorithm.kt @@ -11,4 +11,11 @@ interface VerdictAlgorithm { player2CoinsUsed: Int, player2Destruction: Double ): MatchVerdictEnum + + fun getPvPVerdict( + player1HasErrors: Boolean, + player1Score: Int, + player2HasErrors: Boolean, + player2Score: Int, + ) : MatchVerdictEnum } diff --git a/server/src/main/kotlin/delta/codecharacter/server/logic/verdict/WinnerAlgorithm.kt b/server/src/main/kotlin/delta/codecharacter/server/logic/verdict/WinnerAlgorithm.kt index 542c364..e3dc4d1 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/logic/verdict/WinnerAlgorithm.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/logic/verdict/WinnerAlgorithm.kt @@ -36,4 +36,19 @@ class WinnerAlgorithm : VerdictAlgorithm { if (player1Score > player2Score) return MatchVerdictEnum.PLAYER1 return MatchVerdictEnum.PLAYER2 } + + override fun getPvPVerdict( + player1HasErrors: Boolean, + player1Score: Int, + player2HasErrors: Boolean, + player2Score: Int + ): MatchVerdictEnum { + if (player1HasErrors && player2HasErrors) return MatchVerdictEnum.TIE + if (player1HasErrors) return MatchVerdictEnum.PLAYER2 + if (player2HasErrors) return MatchVerdictEnum.PLAYER1 + + if (player1Score == player2Score) return MatchVerdictEnum.TIE + if (player1Score > player2Score) return MatchVerdictEnum.PLAYER1 + return MatchVerdictEnum.PLAYER2 + } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/MatchController.kt b/server/src/main/kotlin/delta/codecharacter/server/match/MatchController.kt index c7c5c51..cc5e9e7 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/match/MatchController.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/match/MatchController.kt @@ -4,6 +4,7 @@ import delta.codecharacter.core.MatchApi import delta.codecharacter.dtos.CreateMatchRequestDto import delta.codecharacter.dtos.MatchDto import delta.codecharacter.dtos.MatchModeDto +import delta.codecharacter.dtos.PvPMatchDto import delta.codecharacter.server.exception.CustomException import delta.codecharacter.server.user.UserEntity import org.springframework.beans.factory.annotation.Autowired @@ -27,13 +28,25 @@ class MatchController(@Autowired private val matchService: MatchService) : Match } @Secured("ROLE_USER") - override fun getTopMatches(): ResponseEntity> { + override fun getTopMatches(): ResponseEntity> { return ResponseEntity.ok(matchService.getTopMatches()) } @Secured("ROLE_USER") - override fun getUserMatches(): ResponseEntity> { + override fun getUserNormalMatches( + page: Int?, + size: Int?, + ): ResponseEntity> { val user = SecurityContextHolder.getContext().authentication.principal as UserEntity - return ResponseEntity.ok(matchService.getUserMatches(user.id)) + return ResponseEntity.ok(matchService.getUserNormalMatches(user.id, page, size)) + } + + @Secured("ROLE_USER") + override fun getUserPvPMatches( + page: Int?, + size: Int?, + ): ResponseEntity> { + val user = SecurityContextHolder.getContext().authentication.principal as UserEntity + return ResponseEntity.ok(matchService.getUserPvPMatches(user.id, page, size)) } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/MatchModeEnum.kt b/server/src/main/kotlin/delta/codecharacter/server/match/MatchModeEnum.kt index da1cdc5..b5b27be 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/match/MatchModeEnum.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/match/MatchModeEnum.kt @@ -2,6 +2,8 @@ package delta.codecharacter.server.match enum class MatchModeEnum { SELF, + SELFPVP, MANUAL, - AUTO + AUTO, + PVP } diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/MatchRepository.kt b/server/src/main/kotlin/delta/codecharacter/server/match/MatchRepository.kt index a5d94a5..6a98462 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/match/MatchRepository.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/match/MatchRepository.kt @@ -1,6 +1,7 @@ package delta.codecharacter.server.match import delta.codecharacter.server.user.public_user.PublicUserEntity +import org.springframework.data.domain.PageRequest import org.springframework.data.mongodb.repository.MongoRepository import org.springframework.stereotype.Repository import java.util.UUID @@ -8,6 +9,7 @@ import java.util.UUID @Repository interface MatchRepository : MongoRepository { fun findTop10ByOrderByTotalPointsDesc(): List - fun findByPlayer1OrderByCreatedAtDesc(player1: PublicUserEntity): List + fun findByPlayer1OrderByCreatedAtDesc(player1: PublicUserEntity, pageRequest: PageRequest): List fun findByIdIn(matchIds: List): List + fun countByPlayer1(player1: PublicUserEntity): Long } diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/MatchService.kt b/server/src/main/kotlin/delta/codecharacter/server/match/MatchService.kt index 74beeb7..5043460 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/match/MatchService.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/match/MatchService.kt @@ -1,16 +1,8 @@ package delta.codecharacter.server.match import com.fasterxml.jackson.databind.ObjectMapper -import delta.codecharacter.dtos.ChallengeTypeDto -import delta.codecharacter.dtos.CreateMatchRequestDto -import delta.codecharacter.dtos.DailyChallengeMatchRequestDto -import delta.codecharacter.dtos.GameDto -import delta.codecharacter.dtos.GameStatusDto -import delta.codecharacter.dtos.MatchDto -import delta.codecharacter.dtos.MatchModeDto -import delta.codecharacter.dtos.PublicUserDto -import delta.codecharacter.dtos.TierTypeDto -import delta.codecharacter.dtos.VerdictDto +import delta.codecharacter.dtos.* +import delta.codecharacter.server.code.Code import delta.codecharacter.server.code.LanguageEnum import delta.codecharacter.server.code.code_revision.CodeRevisionService import delta.codecharacter.server.code.latest_code.LatestCodeService @@ -20,20 +12,30 @@ import delta.codecharacter.server.daily_challenge.match.DailyChallengeMatchEntit import delta.codecharacter.server.daily_challenge.match.DailyChallengeMatchRepository import delta.codecharacter.server.daily_challenge.match.DailyChallengeMatchVerdictEnum import delta.codecharacter.server.exception.CustomException +import delta.codecharacter.server.game.GameRepository import delta.codecharacter.server.game.GameService import delta.codecharacter.server.game.GameStatusEnum +import delta.codecharacter.server.game.queue.entities.GameStatusUpdateEntity import delta.codecharacter.server.game_map.latest_map.LatestMapService import delta.codecharacter.server.game_map.locked_map.LockedMapService import delta.codecharacter.server.game_map.map_revision.MapRevisionService import delta.codecharacter.server.logic.validation.MapValidator import delta.codecharacter.server.logic.verdict.VerdictAlgorithm import delta.codecharacter.server.notifications.NotificationService +import delta.codecharacter.server.params.GameCode +import delta.codecharacter.server.pvp_game.PvPGameRepository +import delta.codecharacter.server.pvp_game.PvPGameService +import delta.codecharacter.server.pvp_game.PvPGameStatusEnum +import delta.codecharacter.server.user.public_user.PublicUserEntity import delta.codecharacter.server.user.public_user.PublicUserService import delta.codecharacter.server.user.rating_history.RatingHistoryService +import delta.codecharacter.server.user.rating_history.RatingType import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.amqp.rabbit.annotation.RabbitListener import org.springframework.beans.factory.annotation.Autowired +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort import org.springframework.http.HttpStatus import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder import org.springframework.messaging.simp.SimpMessagingTemplate @@ -47,6 +49,7 @@ import java.util.UUID class MatchService( @Autowired private val matchRepository: MatchRepository, @Autowired private val gameService: GameService, + @Autowired private val pvPGameService: PvPGameService, @Autowired private val latestCodeService: LatestCodeService, @Autowired private val codeRevisionService: CodeRevisionService, @Autowired private val lockedCodeService: LockedCodeService, @@ -62,26 +65,31 @@ class MatchService( @Autowired private val jackson2ObjectMapperBuilder: Jackson2ObjectMapperBuilder, @Autowired private val simpMessagingTemplate: SimpMessagingTemplate, @Autowired private val mapValidator: MapValidator, - @Autowired private val autoMatchRepository: AutoMatchRepository + @Autowired private val autoMatchRepository: AutoMatchRepository, + @Autowired private val pvPMatchRepository: PvPMatchRepository, + @Autowired private val gameRepository: GameRepository, + @Autowired private val pvPGameRepository: PvPGameRepository, ) { private var mapper: ObjectMapper = jackson2ObjectMapperBuilder.build() private val logger: Logger = LoggerFactory.getLogger(MatchService::class.java) - private fun createSelfMatch(userId: UUID, codeRevisionId: UUID?, mapRevisionId: UUID?) { - val code: String - val language: LanguageEnum - if (codeRevisionId == null) { - val latestCode = latestCodeService.getLatestCode(userId) - code = latestCode.code - language = LanguageEnum.valueOf(latestCode.language.name) - } else { - val codeRevision = - codeRevisionService.getCodeRevisions(userId).find { it.id == codeRevisionId } - ?: throw CustomException(HttpStatus.BAD_REQUEST, "Invalid revision ID") - code = codeRevision.code - language = LanguageEnum.valueOf(codeRevision.language.name) + private fun getCodeFromRevision(userId: UUID, codeRevisionId: UUID?, codeType: CodeTypeDto): Pair { + when (codeRevisionId) { + null -> { + val latestCode = latestCodeService.getLatestCode(userId, codeType) + return Pair(latestCode.code, LanguageEnum.valueOf(latestCode.language.name)) + } + else -> { + val codeRevision = + codeRevisionService.getCodeRevisions(userId, codeType).find { it.id == codeRevisionId} + ?: throw CustomException(HttpStatus.BAD_REQUEST, "Invalid revision ID") + return Pair(codeRevision.code, LanguageEnum.valueOf(codeRevision.language.name)) + } } + } + private fun createNormalSelfMatch(userId: UUID, codeRevisionId: UUID?, mapRevisionId: UUID?) { + val (code, language) = getCodeFromRevision(userId, codeRevisionId, CodeTypeDto.NORMAL) val map: String = if (mapRevisionId == null) { val latestMap = latestMapService.getLatestMap(userId) @@ -111,17 +119,43 @@ class MatchService( gameService.sendGameRequest(game, code, LanguageEnum.valueOf(language.name), map) } - fun createDualMatch(userId: UUID, opponentUsername: String, mode: MatchModeEnum): UUID { + private fun createPvPSelfMatch(userId: UUID, codeRevisionId1: UUID?, codeRevisionId2: UUID?) { + if (codeRevisionId1==codeRevisionId2) { + throw CustomException(HttpStatus.BAD_REQUEST, "Codes must be different") + } + println(codeRevisionId1) + println(codeRevisionId2) + val (code1, language1) = getCodeFromRevision(userId, codeRevisionId1, CodeTypeDto.PVP) + val (code2, language2) = getCodeFromRevision(userId, codeRevisionId2, CodeTypeDto.PVP) + val matchId = UUID.randomUUID() + val game = pvPGameService.createPvPGame(matchId) val publicUser = publicUserService.getPublicUser(userId) - val publicOpponent = publicUserService.getPublicUserByUsername(opponentUsername) + val match = + PvPMatchEntity( + id = matchId, + game = game, + mode = MatchModeEnum.PVP, + verdict = MatchVerdictEnum.TIE, + createdAt = Instant.now(), + totalPoints = 0, + player1 = publicUser, + player2 = publicUser, + ) + pvPMatchRepository.save(match) + pvPGameService.sendPvPGameRequest(game, GameCode(code1, language1), GameCode(code2, language2)) + } + + private fun createNormalMatch( + publicUser: PublicUserEntity, + publicOpponent: PublicUserEntity, + mode: MatchModeEnum + ) : UUID { + val userId = publicUser.userId val opponentId = publicOpponent.userId - if (userId == opponentId) { - throw CustomException(HttpStatus.BAD_REQUEST, "You cannot play against yourself") - } - val (userLanguage, userCode) = lockedCodeService.getLockedCode(userId) + val (userLanguage, userCode) = lockedCodeService.getLockedCode(userId, CodeTypeDto.NORMAL) val userMap = lockedMapService.getLockedMap(userId) - val (opponentLanguage, opponentCode) = lockedCodeService.getLockedCode(opponentId) + val (opponentLanguage, opponentCode) = lockedCodeService.getLockedCode(opponentId, CodeTypeDto.NORMAL) val opponentMap = lockedMapService.getLockedMap(opponentId) val matchId = UUID.randomUUID() @@ -152,6 +186,55 @@ class MatchService( return matchId } + private fun createPvPMatch(publicUser: PublicUserEntity, publicOpponent: PublicUserEntity) : UUID { + val userId = publicUser.userId + val opponentId = publicOpponent.userId + + val (userLanguage, userCode) = lockedCodeService.getLockedCode(userId, CodeTypeDto.PVP) + val (opponentLanguage, opponentCode) = lockedCodeService.getLockedCode(opponentId, CodeTypeDto.PVP) + + val matchId = UUID.randomUUID() + + val game = pvPGameService.createPvPGame(matchId) + + val match = + PvPMatchEntity( + id = matchId, + game = game, + mode = MatchModeEnum.PVP, + verdict = MatchVerdictEnum.TIE, + createdAt = Instant.now(), + totalPoints = 0, + player1 = publicUser, + player2 = publicOpponent, + ) + pvPMatchRepository.save(match) + + pvPGameService.sendPvPGameRequest(game, GameCode(userCode, userLanguage), GameCode(opponentCode, opponentLanguage)) + + return matchId + } + + private fun createDualMatch(userId: UUID, opponentUsername: String, mode: MatchModeEnum): UUID { + val publicUser = publicUserService.getPublicUser(userId) + val publicOpponent = publicUserService.getPublicUserByUsername(opponentUsername) + val opponentId = publicOpponent.userId + if (userId == opponentId) { + throw CustomException(HttpStatus.BAD_REQUEST, "You cannot play against yourself") + } + return when(mode) { + MatchModeEnum.MANUAL, MatchModeEnum.AUTO -> { + createNormalMatch(publicUser, publicOpponent, mode) + } + MatchModeEnum.PVP -> { + createPvPMatch(publicUser, publicOpponent) + } + else -> { + throw CustomException(HttpStatus.BAD_REQUEST, "MatchMode does not exist") + } + } + } + fun createDCMatch(userId: UUID, dailyChallengeMatchRequestDto: DailyChallengeMatchRequestDto) { val (_, chall, challType, _, completionStatus) = dailyChallengeService.getDailyChallengeByDateForUser(userId) @@ -193,16 +276,21 @@ class MatchService( gameService.sendGameRequest(game, code, language, map) } fun createMatch(userId: UUID, createMatchRequestDto: CreateMatchRequestDto) { + println(createMatchRequestDto) when (createMatchRequestDto.mode) { MatchModeDto.SELF -> { - val (_, _, mapRevisionId, codeRevisionId) = createMatchRequestDto - createSelfMatch(userId, codeRevisionId, mapRevisionId) + val (_, _, mapRevisionId, codeRevisionId, _) = createMatchRequestDto + createNormalSelfMatch(userId, codeRevisionId, mapRevisionId) + } + MatchModeDto.SELFPVP -> { + val (_, _, _, codeRevisionId1, codeRevisionId2) = createMatchRequestDto + createPvPSelfMatch(userId, codeRevisionId1, codeRevisionId2) } - MatchModeDto.MANUAL, MatchModeDto.AUTO -> { + MatchModeDto.MANUAL, MatchModeDto.AUTO , MatchModeDto.PVP -> { if (createMatchRequestDto.opponentUsername == null) { throw CustomException(HttpStatus.BAD_REQUEST, "Opponent ID is required") } - createDualMatch(userId, createMatchRequestDto.opponentUsername!!, MatchModeEnum.MANUAL) + createDualMatch(userId, createMatchRequestDto.opponentUsername!!, MatchModeEnum.valueOf(createMatchRequestDto.mode.name)) } else -> { throw CustomException(HttpStatus.BAD_REQUEST, "MatchMode Is Not Correct") @@ -268,6 +356,42 @@ class MatchService( } } + private fun mapPvPMatchEntitiesToDtos(pvPMatchEntities: List): List { + return pvPMatchEntities.map { pvPMatchEntity -> + PvPMatchDto( + id = pvPMatchEntity.id, + matchMode = MatchModeDto.valueOf(pvPMatchEntity.mode.name), + matchVerdict = VerdictDto.valueOf(pvPMatchEntity.verdict.name), + createdAt = pvPMatchEntity.createdAt, + game = + PvPGameDto ( + id = pvPMatchEntity.game.matchId, + scorePlayer1 = pvPMatchEntity.game.scorePlayer1, + scorePlayer2 = pvPMatchEntity.game.scorePlayer2, + status = PvPGameStatusDto.valueOf(pvPMatchEntity.game.status.name), + ), + user1 = + PublicUserDto( + username = pvPMatchEntity.player1.username, + name = pvPMatchEntity.player1.name, + tier = TierTypeDto.valueOf(pvPMatchEntity.player1.tier.name), + country = pvPMatchEntity.player1.country, + college = pvPMatchEntity.player1.college, + avatarId = pvPMatchEntity.player1.avatarId, + ), + user2 = + PublicUserDto( + username = pvPMatchEntity.player2.username, + name = pvPMatchEntity.player2.name, + tier = TierTypeDto.valueOf(pvPMatchEntity.player2.tier.name), + country = pvPMatchEntity.player2.country, + college = pvPMatchEntity.player2.college, + avatarId = pvPMatchEntity.player2.avatarId, + ), + ) + } + } + private fun mapDailyChallengeMatchEntitiesToDtos( dailyChallengeMatchEntities: List ): List { @@ -299,27 +423,75 @@ class MatchService( } } - fun getTopMatches(): List { + fun getTopMatches(): List { val matches = matchRepository.findTop10ByOrderByTotalPointsDesc() + val pvPMatches = pvPMatchRepository.findTop10ByOrderByTotalPointsDesc() + return listOf(mapMatchEntitiesToDtos(matches) + mapPvPMatchEntitiesToDtos(pvPMatches)) + } + + fun getUserNormalMatches(userId: UUID, page: Int?, size: Int?): List { + val publicUser = publicUserService.getPublicUser(userId) + val pageRequest = + PageRequest.of( + page ?: 0, + size ?: 10, + Sort.by(Sort.Order.desc("createdAt")), + ) + + val matches = matchRepository.findByPlayer1OrderByCreatedAtDesc(publicUser, pageRequest) + return mapMatchEntitiesToDtos(matches) } - fun getUserMatches(userId: UUID): List { + fun getUserDCMatches(userId: UUID, page: Int?, size: Int?): List { val publicUser = publicUserService.getPublicUser(userId) - val matches = matchRepository.findByPlayer1OrderByCreatedAtDesc(publicUser) + val pageRequest = + PageRequest.of( + page ?: 0, + size ?: 10, + Sort.by(Sort.Order.desc("createdAt")), + ) + val dcMatches = - dailyChallengeMatchRepository.findByUserOrderByCreatedAtDesc(publicUser).takeWhile { + dailyChallengeMatchRepository.findByUserOrderByCreatedAtDesc(publicUser,pageRequest).takeWhile { Duration.between(it.createdAt, Instant.now()).toHours() < 24 && - it.verdict != DailyChallengeMatchVerdictEnum.STARTED + it.verdict != DailyChallengeMatchVerdictEnum.STARTED } - return mapDailyChallengeMatchEntitiesToDtos(dcMatches) + mapMatchEntitiesToDtos(matches) + return mapDailyChallengeMatchEntitiesToDtos(dcMatches) + } + + fun getUserPvPMatches(userId: UUID, page: Int?, size: Int?): List { + val publicUser = publicUserService.getPublicUser(userId) + val pageRequest = + PageRequest.of( + page ?: 0, + size ?: 10, + Sort.by(Sort.Order.desc("createdAt")), + ) + val pvPMatches = pvPMatchRepository.findByPlayer1OrderByCreatedAtDesc(publicUser, pageRequest) + return mapPvPMatchEntitiesToDtos(pvPMatches) } @RabbitListener(queues = ["gameStatusUpdateQueue"], ackMode = "AUTO") fun receiveGameResult(gameStatusUpdateJson: String) { - val updatedGame = gameService.updateGameStatus(gameStatusUpdateJson) - val matchId = updatedGame.matchId + val gameStatusUpdateEntity = + mapper.readValue(gameStatusUpdateJson, GameStatusUpdateEntity::class.java) + val gameId = gameStatusUpdateEntity.gameId + + val matchId: UUID + if(gameRepository.findById(gameId).isPresent) { + val game = gameRepository.findById(gameId).get() + matchId = game.matchId // for normal matches, each match has 2 games + } + else if(pvPGameRepository.findById(gameId).isPresent) { + matchId = gameId // for pvp matches, matchId is same as gameId + } + else { + throw CustomException(HttpStatus.NOT_FOUND, "Game not found") + } + if (matchRepository.findById(matchId).isPresent) { + val updatedGame = gameService.updateGameStatus(gameStatusUpdateJson) val match = matchRepository.findById(updatedGame.matchId).get() if (match.mode != MatchModeEnum.AUTO && match.games.first().id == updatedGame.id) { simpMessagingTemplate.convertAndSend( @@ -366,7 +538,7 @@ class MatchService( ) val finishedMatch = match.copy(verdict = verdict) val (newUserRating, newOpponentRating) = - ratingHistoryService.updateRating(match.player1.userId, match.player2.userId, verdict) + ratingHistoryService.updateRating(match.player1.userId, match.player2.userId, verdict, ratingType = RatingType.NORMAL) if (match.mode == MatchModeEnum.MANUAL) { if (( match.player1.tier == TierTypeDto.TIER2 && @@ -448,6 +620,7 @@ class MatchService( } } } else if (dailyChallengeMatchRepository.findById(matchId).isPresent) { + val updatedGame = gameService.updateGameStatus(gameStatusUpdateJson) val match = dailyChallengeMatchRepository.findById(matchId).get() simpMessagingTemplate.convertAndSend( "/updates/${match.user.userId}", @@ -480,6 +653,61 @@ class MatchService( ) dailyChallengeMatchRepository.save(updatedMatch) } + } else if(pvPMatchRepository.findById(matchId).isPresent) { + val updatedGame = pvPGameService.updateGameStatus(gameStatusUpdateJson) + val match = pvPMatchRepository.findById(updatedGame.matchId).get() + if (match.game.matchId == updatedGame.matchId) { + simpMessagingTemplate.convertAndSend( + "/updates/${match.player1.userId}", + mapper.writeValueAsString( + PvPGameDto( + id = updatedGame.matchId, + scorePlayer1 = updatedGame.scorePlayer1, + scorePlayer2 = updatedGame.scorePlayer2, + status = PvPGameStatusDto.valueOf(updatedGame.status.name), + ) + ) + ) + } + if (match.game.status == PvPGameStatusEnum.EXECUTED) { + val verdict = + verdictAlgorithm.getPvPVerdict( + match.game.status == PvPGameStatusEnum.EXECUTE_ERROR, + match.game.scorePlayer1, + match.game.status == PvPGameStatusEnum.EXECUTE_ERROR, + match.game.scorePlayer2, + ) + val finishedMatch = match.copy(verdict = verdict) + val (newUserRating, newOpponentRating) = + ratingHistoryService.updateRating(match.player1.userId, match.player2.userId, verdict, ratingType = RatingType.PVP) + + publicUserService.updatePublicPvPRating( + userId = match.player1.userId, + isInitiator = true, + verdict = verdict, + newRating = newUserRating + ) + + publicUserService.updatePublicPvPRating( + userId = match.player2.userId, + isInitiator = false, + verdict = verdict, + newRating = newOpponentRating + ) + + pvPMatchRepository.save(finishedMatch) + notificationService.sendNotification( + match.player1.userId, + "Match Result", + "${ + when (verdict) { + MatchVerdictEnum.PLAYER1 -> "Won" + MatchVerdictEnum.PLAYER2 -> "Lost" + MatchVerdictEnum.TIE -> "Tied" + } + } against ${match.player2.username}", + ) + } } } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchEntity.kt new file mode 100644 index 0000000..d523847 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchEntity.kt @@ -0,0 +1,21 @@ +package delta.codecharacter.server.match + +import delta.codecharacter.server.pvp_game.PvPGameEntity +import delta.codecharacter.server.user.public_user.PublicUserEntity +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document +import org.springframework.data.mongodb.core.mapping.DocumentReference +import java.time.Instant +import java.util.UUID + +@Document(collection = "pvp_match") +data class PvPMatchEntity ( + @Id val id: UUID, + @DocumentReference(lazy = true) val game: PvPGameEntity, + val mode: MatchModeEnum, + val verdict: MatchVerdictEnum, + val createdAt: Instant, + val totalPoints: Int, + @DocumentReference(lazy = true) val player1: PublicUserEntity, + @DocumentReference(lazy = true) val player2: PublicUserEntity, +) diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchRepository.kt b/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchRepository.kt new file mode 100644 index 0000000..cc0aad8 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchRepository.kt @@ -0,0 +1,13 @@ +package delta.codecharacter.server.match + +import delta.codecharacter.server.user.public_user.PublicUserEntity +import org.springframework.data.domain.PageRequest +import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.stereotype.Repository +import java.util.UUID + +@Repository +interface PvPMatchRepository : MongoRepository { + fun findTop10ByOrderByTotalPointsDesc(): List + fun findByPlayer1OrderByCreatedAtDesc(player1: PublicUserEntity, pageRequest: PageRequest): List +} diff --git a/server/src/main/kotlin/delta/codecharacter/server/params/GameCode.kt b/server/src/main/kotlin/delta/codecharacter/server/params/GameCode.kt new file mode 100644 index 0000000..c6860fe --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/params/GameCode.kt @@ -0,0 +1,9 @@ +package delta.codecharacter.server.params + +import com.fasterxml.jackson.annotation.JsonProperty +import delta.codecharacter.server.code.LanguageEnum + +data class GameCode ( + @field:JsonProperty("source_code", required = true) val code: String, + @field:JsonProperty("language", required = true) val language: LanguageEnum +) diff --git a/server/src/main/kotlin/delta/codecharacter/server/params/game_entities/Attacker.kt b/server/src/main/kotlin/delta/codecharacter/server/params/game_entities/Attacker.kt index c3708a5..2e6be36 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/params/game_entities/Attacker.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/params/game_entities/Attacker.kt @@ -10,4 +10,7 @@ data class Attacker( @field:JsonProperty("speed", required = true) val speed: Int, @field:JsonProperty("price", required = true) val price: Int, @field:JsonProperty("is_aerial", required = true) val aerial: Int, + @field:JsonProperty("weight", required = true) val weight: Int, + @field:JsonProperty("num_ability_turns", required = true) val numAbilityTurns : Int, + @field:JsonProperty("ability_activation_cost", required = true) val abilityActivationCost : Int, ) diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameController.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameController.kt new file mode 100644 index 0000000..8896390 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameController.kt @@ -0,0 +1,21 @@ +package delta.codecharacter.server.pvp_game + +import delta.codecharacter.core.PvpGameApi +import delta.codecharacter.server.pvp_game.pvp_game_log.PvPGameLogService +import delta.codecharacter.server.user.UserEntity +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.ResponseEntity +import org.springframework.security.access.annotation.Secured +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.web.bind.annotation.RestController +import java.util.UUID + +@RestController +class PvPGameController(@Autowired private val pvPGameLogService: PvPGameLogService): PvpGameApi { + @Secured(value = ["ROLE_USER"]) + override fun getPvpGameLogsByGameId(gameId: UUID): ResponseEntity { + val user = SecurityContextHolder.getContext().authentication.principal as UserEntity + val userId = user.id + return ResponseEntity.ok(pvPGameLogService.getPlayerLog(gameId, userId)) + } +} diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameEntity.kt new file mode 100644 index 0000000..cb04596 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameEntity.kt @@ -0,0 +1,13 @@ +package delta.codecharacter.server.pvp_game + +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document +import java.util.UUID + +@Document(collection = "pvp_game") +data class PvPGameEntity ( + @Id val matchId: UUID, + var scorePlayer1: Int, + var scorePlayer2: Int, + var status: PvPGameStatusEnum +) diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameRepository.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameRepository.kt new file mode 100644 index 0000000..4e085ce --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameRepository.kt @@ -0,0 +1,7 @@ +package delta.codecharacter.server.pvp_game + +import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.stereotype.Repository +import java.util.UUID + +@Repository interface PvPGameRepository : MongoRepository diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameService.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameService.kt new file mode 100644 index 0000000..a1e1261 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameService.kt @@ -0,0 +1,87 @@ +package delta.codecharacter.server.pvp_game + +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import delta.codecharacter.server.exception.CustomException +import delta.codecharacter.server.params.GameCode +import delta.codecharacter.server.params.GameParameters +import delta.codecharacter.server.pvp_game.pvp_game_log.PvPGameLogService +import delta.codecharacter.server.pvp_game.queue.entities.PvPGameRequestEntity +import delta.codecharacter.server.pvp_game.queue.entities.PvPGameStatusUpdateEntity +import org.springframework.amqp.rabbit.core.RabbitTemplate +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Service +import java.util.UUID + +@Service +class PvPGameService( + @Autowired private val pvPGameRepository: PvPGameRepository, + @Autowired private val pvPGameLogService: PvPGameLogService, + @Autowired private val rabbitTemplate: RabbitTemplate, + @Autowired private val parameters: GameParameters +) { + private var mapper = ObjectMapper().registerKotlinModule() + + fun getPvPGame(matchId: UUID): PvPGameEntity { + return pvPGameRepository.findById(matchId).orElseThrow { + throw CustomException(HttpStatus.NOT_FOUND, "PvPGame not found") + } + } + + fun createPvPGame(matchId: UUID): PvPGameEntity { + val pvPGame = + PvPGameEntity( + matchId = matchId, + scorePlayer1 = 0, + scorePlayer2 = 0, + status = PvPGameStatusEnum.IDLE, + ) + return pvPGameRepository.save(pvPGame) + } + + fun sendPvPGameRequest(pvPGame: PvPGameEntity, player1Code: GameCode, player2Code: GameCode) { + val pvPGameRequest = + PvPGameRequestEntity( + gameId = pvPGame.matchId, + parameters = parameters, + player1 = player1Code, + player2 = player2Code + ) + rabbitTemplate.convertAndSend("gamePvpRequestQueue", mapper.writeValueAsString(pvPGameRequest)) + } + + fun updateGameStatus(gameStatusUpdateJson: String): PvPGameEntity { + val gameStatusUpdateEntity = + mapper.readValue(gameStatusUpdateJson, PvPGameStatusUpdateEntity::class.java) + val oldPvPGameEntity = + pvPGameRepository.findById(gameStatusUpdateEntity.gameId).orElseThrow { + throw CustomException(HttpStatus.NOT_FOUND, "PvPGame not found") + } + if(gameStatusUpdateEntity.gameResultPlayer1 == null || gameStatusUpdateEntity.gameResultPlayer2 == null) { + val newPvPGameEntity = oldPvPGameEntity.copy(status = gameStatusUpdateEntity.gameStatus) + return pvPGameRepository.save(newPvPGameEntity) + } + + val gameResultPlayer1 = gameStatusUpdateEntity.gameResultPlayer1 + val gameResultPlayer2 = gameStatusUpdateEntity.gameResultPlayer2 + + val gameStatus = + if (gameResultPlayer1.hasErrors || gameResultPlayer2.hasErrors) { + PvPGameStatusEnum.EXECUTE_ERROR + } else { + PvPGameStatusEnum.EXECUTED + } + + val newPvPGameEntity = + oldPvPGameEntity.copy( + scorePlayer1 = gameResultPlayer1.score, + scorePlayer2 = gameResultPlayer2.score, + status = gameStatus + ) + + val pvPGame = pvPGameRepository.save(newPvPGameEntity) + pvPGameLogService.savePvPGameLog(pvPGame.matchId, gameResultPlayer1.log, gameResultPlayer2.log) + return pvPGame + } +} diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameStatusEnum.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameStatusEnum.kt new file mode 100644 index 0000000..4482aee --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameStatusEnum.kt @@ -0,0 +1,8 @@ +package delta.codecharacter.server.pvp_game + +enum class PvPGameStatusEnum { + IDLE, + EXECUTING, + EXECUTED, + EXECUTE_ERROR +} diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameVerdictEnum.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameVerdictEnum.kt new file mode 100644 index 0000000..d88f4af --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/PvPGameVerdictEnum.kt @@ -0,0 +1,7 @@ +package delta.codecharacter.server.pvp_game + +enum class PvPGameVerdictEnum { + PLAYER1, + PLAYER2, + TIE +} diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogEntity.kt new file mode 100644 index 0000000..04baaaa --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogEntity.kt @@ -0,0 +1,13 @@ +package delta.codecharacter.server.pvp_game.pvp_game_log + +import org.springframework.data.annotation.Id +import org.springframework.data.mongodb.core.mapping.Document +import org.springframework.data.mongodb.core.mapping.DocumentReference +import java.util.UUID + +@Document(collection = "pvp_game_log") +class PvPGameLogEntity ( + @Id val gameId: UUID, + val player1Log: String, + val player2Log: String, +) diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogRepository.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogRepository.kt new file mode 100644 index 0000000..9795d11 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogRepository.kt @@ -0,0 +1,7 @@ +package delta.codecharacter.server.pvp_game.pvp_game_log + +import org.springframework.data.mongodb.repository.MongoRepository +import org.springframework.stereotype.Repository +import java.util.UUID + +@Repository interface PvPGameLogRepository : MongoRepository diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogService.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogService.kt new file mode 100644 index 0000000..4601a0a --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogService.kt @@ -0,0 +1,45 @@ +package delta.codecharacter.server.pvp_game.pvp_game_log + +import delta.codecharacter.server.match.MatchRepository +import delta.codecharacter.server.match.PvPMatchRepository +import delta.codecharacter.server.user.public_user.PublicUserEntity +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.data.annotation.Id +import org.springframework.stereotype.Service +import java.util.UUID + +@Service +class PvPGameLogService( + @Autowired private val pvPGameLogRepository: PvPGameLogRepository, + @Autowired private val pvPMatchRepository: PvPMatchRepository, +) { + + fun getPlayerLog(gameId: UUID, userId: UUID): String { + val match = pvPMatchRepository.findById(gameId) + + if (!match.isPresent) { + return "" + } + + val pvPGameLog = pvPGameLogRepository.findById(gameId) + val player1 : PublicUserEntity = match.get().player1 + val player2 : PublicUserEntity = match.get().player2 + + return if (pvPGameLog.isPresent) { + if (player1.userId == userId) { + pvPGameLog.get().player1Log + } else if (player2.userId == userId) { + pvPGameLog.get().player2Log + } else { + "" + } + } else { + "" + } + } + + fun savePvPGameLog(gameId: UUID, player1Log: String, player2Log: String) { + val pvPGameLog = PvPGameLogEntity(gameId, player1Log, player2Log) + pvPGameLogRepository.save(pvPGameLog) + } +} diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/queue/entities/PvPGameRequestEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/queue/entities/PvPGameRequestEntity.kt new file mode 100644 index 0000000..c647575 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/queue/entities/PvPGameRequestEntity.kt @@ -0,0 +1,13 @@ +package delta.codecharacter.server.pvp_game.queue.entities + +import com.fasterxml.jackson.annotation.JsonProperty +import delta.codecharacter.server.params.GameCode +import delta.codecharacter.server.params.GameParameters +import java.util.UUID + +data class PvPGameRequestEntity ( + @field:JsonProperty("game_id", required = true) val gameId: UUID, + @field:JsonProperty("parameters", required = true) val parameters: GameParameters, + @field:JsonProperty("player1", required = true) val player1: GameCode, + @field:JsonProperty("player2", required = true) val player2: GameCode +) diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/queue/entities/PvPGameResultEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/queue/entities/PvPGameResultEntity.kt new file mode 100644 index 0000000..935c3c0 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/queue/entities/PvPGameResultEntity.kt @@ -0,0 +1,9 @@ +package delta.codecharacter.server.pvp_game.queue.entities + +import com.fasterxml.jackson.annotation.JsonProperty + +data class PvPGameResultEntity ( + @field:JsonProperty("score", required = true) val score: Int, + @field:JsonProperty("has_errors", required = true) val hasErrors: Boolean, + @field:JsonProperty("log", required = true) val log: String, +) diff --git a/server/src/main/kotlin/delta/codecharacter/server/pvp_game/queue/entities/PvPGameStatusUpdateEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/queue/entities/PvPGameStatusUpdateEntity.kt new file mode 100644 index 0000000..c0c3103 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/pvp_game/queue/entities/PvPGameStatusUpdateEntity.kt @@ -0,0 +1,14 @@ +package delta.codecharacter.server.pvp_game.queue.entities + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties +import com.fasterxml.jackson.annotation.JsonProperty +import delta.codecharacter.server.pvp_game.PvPGameStatusEnum +import java.util.UUID + +@JsonIgnoreProperties(ignoreUnknown = true) +data class PvPGameStatusUpdateEntity( + @field:JsonProperty("game_id", required = true) val gameId: UUID, + @field:JsonProperty("game_status", required = true) val gameStatus: PvPGameStatusEnum, + @field:JsonProperty("game_result_player1", required = false) val gameResultPlayer1: PvPGameResultEntity?, + @field:JsonProperty("game_result_player2", required = false) val gameResultPlayer2: PvPGameResultEntity? +) diff --git a/server/src/main/kotlin/delta/codecharacter/server/schedulers/SchedulingService.kt b/server/src/main/kotlin/delta/codecharacter/server/schedulers/SchedulingService.kt index 64eff9f..4dd4c29 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/schedulers/SchedulingService.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/schedulers/SchedulingService.kt @@ -40,6 +40,7 @@ class SchedulingService( publicUserService.updateLeaderboardAfterPracticePhase() } + @Scheduled(cron = "\${environment.promote-demote-time}", zone = "GMT+5:30") fun createAutoMatch() { matchService.createAutoMatch() diff --git a/server/src/main/kotlin/delta/codecharacter/server/user/UserController.kt b/server/src/main/kotlin/delta/codecharacter/server/user/UserController.kt index 5ac26af..2a3a2a5 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/user/UserController.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/user/UserController.kt @@ -5,6 +5,7 @@ import delta.codecharacter.dtos.ActivateUserRequestDto import delta.codecharacter.dtos.RatingHistoryDto import delta.codecharacter.dtos.RegisterUserRequestDto import delta.codecharacter.server.user.rating_history.RatingHistoryService +import delta.codecharacter.server.user.rating_history.RatingType import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.HttpStatus import org.springframework.http.ResponseEntity @@ -34,7 +35,8 @@ class UserController( } override fun getRatingHistory(userId: UUID): ResponseEntity> { - val ratingHistory = ratingHistoryService.getRatingHistory(userId) + //EDIT AND TAKE AS PARAMETER + val ratingHistory = ratingHistoryService.getRatingHistory(userId,RatingType.NORMAL) return ResponseEntity.ok(ratingHistory) } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/user/public_user/PublicUserEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/user/public_user/PublicUserEntity.kt index 3241383..65bef52 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/user/public_user/PublicUserEntity.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/user/public_user/PublicUserEntity.kt @@ -17,6 +17,7 @@ data class PublicUserEntity( val tier: TierTypeDto, val tutorialLevel: Int, val rating: Double, + val pvpRating: Double, val wins: Int, val losses: Int, val ties: Int, diff --git a/server/src/main/kotlin/delta/codecharacter/server/user/public_user/PublicUserService.kt b/server/src/main/kotlin/delta/codecharacter/server/user/public_user/PublicUserService.kt index 636bbbf..7a56f13 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/user/public_user/PublicUserService.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/user/public_user/PublicUserService.kt @@ -2,12 +2,14 @@ package delta.codecharacter.server.user.public_user import delta.codecharacter.dtos.CurrentUserProfileDto import delta.codecharacter.dtos.DailyChallengeLeaderBoardResponseDto +import delta.codecharacter.dtos.PvPLeaderBoardResponseDto import delta.codecharacter.dtos.LeaderboardEntryDto import delta.codecharacter.dtos.PublicUserDto import delta.codecharacter.dtos.TierTypeDto import delta.codecharacter.dtos.TutorialUpdateTypeDto import delta.codecharacter.dtos.UpdateCurrentUserProfileDto import delta.codecharacter.dtos.UserStatsDto +import delta.codecharacter.dtos.PvPUserStatsDto import delta.codecharacter.server.daily_challenge.DailyChallengeEntity import delta.codecharacter.server.exception.CustomException import delta.codecharacter.server.match.MatchVerdictEnum @@ -55,7 +57,8 @@ class PublicUserService(@Autowired private val publicUserRepository: PublicUserR // registering after practice phase tier = TierTypeDto.TIER2, tutorialLevel = 1, - dailyChallengeHistory = HashMap() + dailyChallengeHistory = HashMap(), + pvpRating = 1500.0 ) publicUserRepository.save(publicUser) } @@ -160,6 +163,38 @@ class PublicUserService(@Autowired private val publicUserRepository: PublicUserR } } + fun getPvPLeaderboard( + page: Int?, + size: Int? + ): List { + val pageRequest = + PageRequest.of( + page ?: 0, + size ?: 10, + Sort.by(Sort.Order.desc("pvpRating"), Sort.Order.desc("wins"), Sort.Order.asc("username")) + ) + return publicUserRepository.findAll(pageRequest).content.map { + PvPLeaderBoardResponseDto( + user = + PublicUserDto( + username = it.username, + name = it.name, + tier = TierTypeDto.valueOf(it.tier.name), + country = it.country, + college = it.college, + avatarId = it.avatarId, + ), + stats = + PvPUserStatsDto( + rating = BigDecimal(it.pvpRating), + wins = it.wins, + losses = it.losses, + ties = it.ties + ), + ) + } + } + fun getUserProfile(userId: UUID, email: String): CurrentUserProfileDto { val user = publicUserRepository.findById(userId).get() return CurrentUserProfileDto( @@ -270,6 +305,33 @@ class PublicUserService(@Autowired private val publicUserRepository: PublicUserR publicUserRepository.save(updatedUser) } + fun updatePublicPvPRating( + userId: UUID, + isInitiator: Boolean, + verdict: MatchVerdictEnum, + newRating: Double + ) { + val publicUser = publicUserRepository.findById(userId).get() + val updatedUser = + publicUser.copy( + pvpRating = newRating, + wins = + if ((isInitiator && verdict == MatchVerdictEnum.PLAYER1) || + (!isInitiator && verdict == MatchVerdictEnum.PLAYER2) + ) + publicUser.wins + 1 + else publicUser.wins, + losses = + if ((isInitiator && verdict == MatchVerdictEnum.PLAYER2) || + (!isInitiator && verdict == MatchVerdictEnum.PLAYER1) + ) + publicUser.losses + 1 + else publicUser.losses, + ties = if (verdict == MatchVerdictEnum.TIE) publicUser.ties + 1 else publicUser.ties + ) + publicUserRepository.save(updatedUser) + } + fun updateAutoMatchRating(userId: UUID, newRating: Double) { val user = publicUserRepository.findById(userId).get() val updatedUser = user.copy(rating = newRating) diff --git a/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryEntity.kt b/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryEntity.kt index c198e2d..25f154b 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryEntity.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryEntity.kt @@ -9,5 +9,6 @@ data class RatingHistoryEntity( val userId: UUID, val rating: Double, val ratingDeviation: Double, - val validFrom: Instant + val validFrom: Instant, + val ratingType: RatingType ) diff --git a/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryRepository.kt b/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryRepository.kt index d7cbbcb..e55bf4d 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryRepository.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryRepository.kt @@ -4,6 +4,6 @@ import org.springframework.data.mongodb.repository.MongoRepository import java.util.UUID interface RatingHistoryRepository : MongoRepository { - fun findAllByUserId(userId: UUID): List - fun findFirstByUserIdOrderByValidFromDesc(userId: UUID): RatingHistoryEntity + fun findFirstByUserIdAndRatingTypeOrderByValidFromDesc(userId: UUID,ratingType: RatingType): RatingHistoryEntity + fun findAllByUserIdAndRatingType(userId: UUID,ratingType: RatingType): List } diff --git a/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryService.kt b/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryService.kt index 873c41d..30bc745 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryService.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingHistoryService.kt @@ -17,15 +17,20 @@ class RatingHistoryService( @Autowired private val ratingAlgorithm: RatingAlgorithm ) { fun create(userId: UUID) { - val ratingHistory = + val ratingHistoryNormal = RatingHistoryEntity( - userId = userId, rating = 1500.0, ratingDeviation = 350.0, validFrom = Instant.now() + userId = userId, rating = 1500.0, ratingDeviation = 350.0, validFrom = Instant.now(), ratingType = RatingType.NORMAL ) - ratingHistoryRepository.save(ratingHistory) + val ratingHistoryPvP = + RatingHistoryEntity( + userId = userId, rating = 1500.0, ratingDeviation = 350.0, validFrom = Instant.now(), ratingType = RatingType.PVP + ) + ratingHistoryRepository.save(ratingHistoryNormal) + ratingHistoryRepository.save(ratingHistoryPvP) } - fun getRatingHistory(userId: UUID): List { - return ratingHistoryRepository.findAllByUserId(userId).map { + fun getRatingHistory(userId: UUID, ratingType:RatingType): List { + return ratingHistoryRepository.findAllByUserIdAndRatingType(userId,ratingType).map { RatingHistoryDto( rating = BigDecimal(it.rating), ratingDeviation = BigDecimal(it.ratingDeviation), @@ -45,12 +50,13 @@ class RatingHistoryService( fun updateRating( userId: UUID, opponentId: UUID, - verdict: MatchVerdictEnum + verdict: MatchVerdictEnum, + ratingType: RatingType ): Pair { val (_, userRating, userRatingDeviation, userRatingValidFrom) = - ratingHistoryRepository.findFirstByUserIdOrderByValidFromDesc(userId) + ratingHistoryRepository.findFirstByUserIdAndRatingTypeOrderByValidFromDesc(userId,ratingType) val (_, opponentRating, opponentRatingDeviation, opponentRatingValidFrom) = - ratingHistoryRepository.findFirstByUserIdOrderByValidFromDesc(opponentId) + ratingHistoryRepository.findFirstByUserIdAndRatingTypeOrderByValidFromDesc(opponentId,ratingType) val userWeightedRatingDeviation = ratingAlgorithm.getWeightedRatingDeviationSinceLastCompetition( @@ -80,7 +86,8 @@ class RatingHistoryService( userId = userId, rating = newUserRating.rating, ratingDeviation = newUserRating.ratingDeviation, - validFrom = currentInstant + validFrom = currentInstant, + ratingType = ratingType ) ) ratingHistoryRepository.save( @@ -88,7 +95,8 @@ class RatingHistoryService( userId = opponentId, rating = newOpponentRating.rating, ratingDeviation = newOpponentRating.ratingDeviation, - validFrom = currentInstant + validFrom = currentInstant, + ratingType = ratingType ) ) @@ -167,7 +175,7 @@ class RatingHistoryService( ): Map { val userRatings = userIds.associateWith { userId -> - ratingHistoryRepository.findFirstByUserIdOrderByValidFromDesc(userId) + ratingHistoryRepository.findFirstByUserIdAndRatingTypeOrderByValidFromDesc(userId,RatingType.NORMAL) } val newRatings = userIds.associateWith { userId -> @@ -180,7 +188,8 @@ class RatingHistoryService( userId = userId, rating = rating.rating, ratingDeviation = rating.ratingDeviation, - validFrom = currentInstant + validFrom = currentInstant, + ratingType = RatingType.NORMAL ) ) } diff --git a/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingType.kt b/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingType.kt new file mode 100644 index 0000000..a85f464 --- /dev/null +++ b/server/src/main/kotlin/delta/codecharacter/server/user/rating_history/RatingType.kt @@ -0,0 +1,6 @@ +package delta.codecharacter.server.user.rating_history + +enum class RatingType { + NORMAL, + PVP +} diff --git a/server/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/server/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 47917fa..83b1f12 100644 --- a/server/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/server/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -18,17 +18,17 @@ { "name": "cors.allowed-origin", "type": "java.lang.String", - "description": "Allowed origin (Frontend URL)" + "description": "Allowed origin (Frontend URL)." }, { "name": "sendgrid.api-key", "type": "java.lang.String", - "description": "Sendgrid API Key" + "description": "Sendgrid API Key." }, { "name": "spring.sendgrid.template-id", "type": "java.lang.String", - "description": "Sendgrid Template ID" + "description": "Sendgrid Template ID." }, { "name": "environment.reCaptcha-key", diff --git a/server/src/main/resources/player_code b/server/src/main/resources/player_code index 16fbce4..ce8ccff 160000 --- a/server/src/main/resources/player_code +++ b/server/src/main/resources/player_code @@ -1 +1 @@ -Subproject commit 16fbce4e8d3df4ba3dedb1c4622651184ac6adb7 +Subproject commit ce8ccff27639c3276f3ede05246d47a42971c6bf diff --git a/server/src/test/kotlin/delta/codecharacter/server/SecurityTestConfiguration.kt b/server/src/test/kotlin/delta/codecharacter/server/SecurityTestConfiguration.kt index cb1a426..e63823d 100644 --- a/server/src/test/kotlin/delta/codecharacter/server/SecurityTestConfiguration.kt +++ b/server/src/test/kotlin/delta/codecharacter/server/SecurityTestConfiguration.kt @@ -33,6 +33,19 @@ class TestAttributes { loginType = LoginType.PASSWORD, isProfileComplete = true, ) + + val opponent = + UserEntity( + id = UUID.randomUUID(), + password = "password", + email = "opponent@test.com", + isEnabled = true, + isAccountNonExpired = true, + isAccountNonLocked = true, + loginType = LoginType.PASSWORD, + isProfileComplete = true, + ) + val dailyChallengeCode = DailyChallengeEntity( id = UUID.randomUUID(), @@ -61,7 +74,27 @@ class TestAttributes { tier = TierTypeDto.TIER_PRACTICE, score = 0.0, dailyChallengeHistory = hashMapOf(0 to DailyChallengeHistory(0.0, dailyChallengeCode)), - tutorialLevel = 1 + tutorialLevel = 1, + pvpRating = 1000.0 + ) + + val publicOpponent = + PublicUserEntity( + userId = opponent.id, + username = "TestOpponentUser", + name = "Test Opponent User", + country = "Test Country", + college = "Test College", + avatarId = 1, + rating = 1000.0, + wins = 4, + losses = 2, + ties = 1, + tier = TierTypeDto.TIER_PRACTICE, + score = 0.0, + dailyChallengeHistory = hashMapOf(0 to DailyChallengeHistory(0.0, dailyChallengeCode)), + tutorialLevel = 1, + pvpRating = 1000.0 ) } } diff --git a/server/src/test/kotlin/delta/codecharacter/server/code/code_revision/CodeRevisionServiceTest.kt b/server/src/test/kotlin/delta/codecharacter/server/code/code_revision/CodeRevisionServiceTest.kt index 711fffc..d09072f 100644 --- a/server/src/test/kotlin/delta/codecharacter/server/code/code_revision/CodeRevisionServiceTest.kt +++ b/server/src/test/kotlin/delta/codecharacter/server/code/code_revision/CodeRevisionServiceTest.kt @@ -77,7 +77,7 @@ internal class CodeRevisionServiceTest { ) } returns listOf(codeRevisionEntity) - val codeRevisionDtos = codeRevisionService.getCodeRevisions(userId) + val codeRevisionDtos = codeRevisionService.getCodeRevisions(userId, CodeTypeDto.NORMAL) val codeRevisionDto = codeRevisionDtos.first() verify { diff --git a/server/src/test/kotlin/delta/codecharacter/server/code/locked_code/LockedCodeServiceTest.kt b/server/src/test/kotlin/delta/codecharacter/server/code/locked_code/LockedCodeServiceTest.kt index 183151c..dab8e23 100644 --- a/server/src/test/kotlin/delta/codecharacter/server/code/locked_code/LockedCodeServiceTest.kt +++ b/server/src/test/kotlin/delta/codecharacter/server/code/locked_code/LockedCodeServiceTest.kt @@ -49,7 +49,7 @@ internal class LockedCodeServiceTest { every { lockedCodeRepository.findById(userId) } returns Optional.of(lockedCodeEntity) - val latestCode = lockedCodeService.getLockedCode(userId) + val latestCode = lockedCodeService.getLockedCode(userId, CodeTypeDto.NORMAL) verify { lockedCodeRepository.findById(userId) } confirmVerified(lockedCodeRepository) diff --git a/server/src/test/kotlin/delta/codecharacter/server/game/GameServiceTest.kt b/server/src/test/kotlin/delta/codecharacter/server/game/GameServiceTest.kt index d9ba6e1..ad43d9a 100644 --- a/server/src/test/kotlin/delta/codecharacter/server/game/GameServiceTest.kt +++ b/server/src/test/kotlin/delta/codecharacter/server/game/GameServiceTest.kt @@ -8,6 +8,7 @@ import delta.codecharacter.server.game.game_log.GameLogService import delta.codecharacter.server.game.queue.entities.GameRequestEntity import delta.codecharacter.server.game.queue.entities.GameResultEntity import delta.codecharacter.server.game.queue.entities.GameStatusUpdateEntity +import delta.codecharacter.server.params.GameCode import delta.codecharacter.server.params.GameParameters import io.mockk.confirmVerified import io.mockk.every @@ -93,8 +94,7 @@ internal class GameServiceTest { val expectedGameRequest = GameRequestEntity( gameId = gameId, - sourceCode = "code", - language = LanguageEnum.CPP, + playerCode = GameCode("code", LanguageEnum.CPP), parameters = gameParameters, map = "[[0]]" ) diff --git a/server/src/test/kotlin/delta/codecharacter/server/leaderboard/LeaderboardTest.kt b/server/src/test/kotlin/delta/codecharacter/server/leaderboard/LeaderboardTest.kt index a81daaa..a782727 100644 --- a/server/src/test/kotlin/delta/codecharacter/server/leaderboard/LeaderboardTest.kt +++ b/server/src/test/kotlin/delta/codecharacter/server/leaderboard/LeaderboardTest.kt @@ -39,6 +39,7 @@ internal class LeaderboardTest { score = 0.0, dailyChallengeHistory = hashMapOf(0 to DailyChallengeHistory(0.0, TestAttributes.dailyChallengeCode)), + pvpRating = 2000.0, ) private var user2 = PublicUserEntity( @@ -58,6 +59,7 @@ internal class LeaderboardTest { score = 0.0, dailyChallengeHistory = hashMapOf(0 to DailyChallengeHistory(0.0, TestAttributes.dailyChallengeCode)), + pvpRating = 1800.0, ) private var user3 = PublicUserEntity( @@ -77,6 +79,7 @@ internal class LeaderboardTest { score = 0.0, dailyChallengeHistory = hashMapOf(0 to DailyChallengeHistory(0.0, TestAttributes.dailyChallengeCode)), + pvpRating = 1600.0, ) private var user4 = PublicUserEntity( @@ -96,6 +99,7 @@ internal class LeaderboardTest { score = 0.0, dailyChallengeHistory = hashMapOf(0 to DailyChallengeHistory(0.0, TestAttributes.dailyChallengeCode)), + pvpRating = 1500.0 ) @BeforeEach @@ -136,4 +140,6 @@ internal class LeaderboardTest { verify { publicUserRepository.findAllByTier(any(), any()) } confirmVerified(publicUserRepository) } + + // todo write pvp leaderboard tests } diff --git a/server/src/test/kotlin/delta/codecharacter/server/match/MatchServiceTest.kt b/server/src/test/kotlin/delta/codecharacter/server/match/MatchServiceTest.kt index b57d784..9621377 100644 --- a/server/src/test/kotlin/delta/codecharacter/server/match/MatchServiceTest.kt +++ b/server/src/test/kotlin/delta/codecharacter/server/match/MatchServiceTest.kt @@ -1,13 +1,6 @@ package delta.codecharacter.server.match -import delta.codecharacter.dtos.ChallengeTypeDto -import delta.codecharacter.dtos.CodeRevisionDto -import delta.codecharacter.dtos.CreateMatchRequestDto -import delta.codecharacter.dtos.DailyChallengeGetRequestDto -import delta.codecharacter.dtos.DailyChallengeMatchRequestDto -import delta.codecharacter.dtos.GameMapRevisionDto -import delta.codecharacter.dtos.LanguageDto -import delta.codecharacter.dtos.MatchModeDto +import delta.codecharacter.dtos.* import delta.codecharacter.server.TestAttributes import delta.codecharacter.server.code.LanguageEnum import delta.codecharacter.server.code.code_revision.CodeRevisionService @@ -17,6 +10,7 @@ import delta.codecharacter.server.daily_challenge.DailyChallengeService import delta.codecharacter.server.daily_challenge.match.DailyChallengeMatchRepository import delta.codecharacter.server.exception.CustomException import delta.codecharacter.server.game.GameEntity +import delta.codecharacter.server.game.GameRepository import delta.codecharacter.server.game.GameService import delta.codecharacter.server.game_map.latest_map.LatestMapService import delta.codecharacter.server.game_map.locked_map.LockedMapService @@ -24,6 +18,9 @@ import delta.codecharacter.server.game_map.map_revision.MapRevisionService import delta.codecharacter.server.logic.validation.MapValidator import delta.codecharacter.server.logic.verdict.VerdictAlgorithm import delta.codecharacter.server.notifications.NotificationService +import delta.codecharacter.server.params.GameCode +import delta.codecharacter.server.pvp_game.PvPGameRepository +import delta.codecharacter.server.pvp_game.PvPGameService import delta.codecharacter.server.user.public_user.PublicUserService import delta.codecharacter.server.user.rating_history.RatingHistoryService import io.mockk.confirmVerified @@ -44,6 +41,7 @@ internal class MatchServiceTest { private lateinit var matchRepository: MatchRepository private lateinit var gameService: GameService + private lateinit var pvPGameService: PvPGameService private lateinit var latestCodeService: LatestCodeService private lateinit var codeRevisionService: CodeRevisionService private lateinit var lockedCodeService: LockedCodeService @@ -61,11 +59,15 @@ internal class MatchServiceTest { private lateinit var mapValidator: MapValidator private lateinit var matchService: MatchService private lateinit var autoMatchRepository: AutoMatchRepository + private lateinit var pvPMatchRepository: PvPMatchRepository + private lateinit var gameRepository: GameRepository + private lateinit var pvPGameRepository: PvPGameRepository @BeforeEach fun setUp() { matchRepository = mockk(relaxed = true) gameService = mockk(relaxed = true) + pvPGameService = mockk(relaxed = true) latestCodeService = mockk(relaxed = true) codeRevisionService = mockk(relaxed = true) lockedCodeService = mockk(relaxed = true) @@ -82,11 +84,15 @@ internal class MatchServiceTest { simpMessagingTemplate = mockk(relaxed = true) mapValidator = mockk(relaxed = true) autoMatchRepository = mockk(relaxed = true) + pvPMatchRepository = mockk(relaxed = true) + gameRepository = mockk(relaxed = true) + pvPGameRepository = mockk(relaxed = true) matchService = MatchService( matchRepository, gameService, + pvPGameService, latestCodeService, codeRevisionService, lockedCodeService, @@ -102,7 +108,10 @@ internal class MatchServiceTest { jackson2ObjectMapperBuilder, simpMessagingTemplate, mapValidator, - autoMatchRepository + autoMatchRepository, + pvPMatchRepository, + gameRepository, + pvPGameRepository, ) } @@ -123,7 +132,7 @@ internal class MatchServiceTest { mapRevisionId = mapRevisionId ) - every { codeRevisionService.getCodeRevisions(userId) } returns listOf(codeRevision) + every { codeRevisionService.getCodeRevisions(userId, CodeTypeDto.NORMAL) } returns listOf(codeRevision) every { mapRevisionService.getMapRevisions(userId) } returns listOf(mapRevision) val exception = @@ -150,7 +159,7 @@ internal class MatchServiceTest { mapRevisionId = UUID.randomUUID() ) - every { codeRevisionService.getCodeRevisions(userId) } returns listOf(codeRevision) + every { codeRevisionService.getCodeRevisions(userId, CodeTypeDto.NORMAL) } returns listOf(codeRevision) every { mapRevisionService.getMapRevisions(userId) } returns listOf(mapRevision) val exception = @@ -177,7 +186,7 @@ internal class MatchServiceTest { mapRevisionId = mapRevisionId ) - every { codeRevisionService.getCodeRevisions(userId) } returns listOf(codeRevision) + every { codeRevisionService.getCodeRevisions(userId, CodeTypeDto.NORMAL) } returns listOf(codeRevision) every { mapRevisionService.getMapRevisions(userId) } returns listOf(mapRevision) every { gameService.createGame(any()) } returns game every { matchRepository.save(any()) } returns mockk() @@ -186,7 +195,7 @@ internal class MatchServiceTest { matchService.createMatch(userId, createMatchRequestDto) verify { - codeRevisionService.getCodeRevisions(userId) + codeRevisionService.getCodeRevisions(userId, CodeTypeDto.NORMAL) mapRevisionService.getMapRevisions(userId) gameService.createGame(any()) matchRepository.save(any()) @@ -233,12 +242,31 @@ internal class MatchServiceTest { assertThat(exception.message).isEqualTo("Opponent ID is required") } + @Test + @Throws(CustomException::class) + fun `should throw bad request if opponent id is empty in pvp match`() { + val createMatchRequestDto = + CreateMatchRequestDto( + mode = MatchModeDto.PVP, + codeRevisionId = UUID.randomUUID(), + mapRevisionId = UUID.randomUUID(), + opponentUsername = null + ) + + val exception = + assertThrows { matchService.createMatch(mockk(), createMatchRequestDto) } + + assertThat(exception.status).isEqualTo(HttpStatus.BAD_REQUEST) + assertThat(exception.message).isEqualTo("Opponent ID is required") + } + @Test fun `should create manual match`() { val userId = UUID.randomUUID() val opponentId = UUID.randomUUID() val opponentPublicUser = TestAttributes.publicUser.copy(userId = opponentId, username = "opponent") + val publicUser = TestAttributes.publicUser.copy(userId = userId, username = "public-user") val userCode = Pair(LanguageEnum.CPP, "user-code") val opponentCode = Pair(LanguageEnum.PYTHON, "opponent-code") @@ -251,10 +279,10 @@ internal class MatchServiceTest { opponentUsername = opponentPublicUser.username, ) - every { publicUserService.getPublicUserByUsername(opponentPublicUser.username) } returns - opponentPublicUser - every { lockedCodeService.getLockedCode(userId) } returns userCode - every { lockedCodeService.getLockedCode(opponentId) } returns opponentCode + every { publicUserService.getPublicUser(userId) } returns publicUser + every { publicUserService.getPublicUserByUsername(opponentPublicUser.username) } returns opponentPublicUser + every { lockedCodeService.getLockedCode(userId, CodeTypeDto.NORMAL) } returns userCode + every { lockedCodeService.getLockedCode(opponentId, CodeTypeDto.NORMAL) } returns opponentCode every { lockedMapService.getLockedMap(userId) } returns userMap every { lockedMapService.getLockedMap(opponentId) } returns opponentMap every { gameService.createGame(any()) } returns mockk() @@ -269,8 +297,8 @@ internal class MatchServiceTest { verify { publicUserService.getPublicUserByUsername(opponentPublicUser.username) - lockedCodeService.getLockedCode(userId) - lockedCodeService.getLockedCode(opponentId) + lockedCodeService.getLockedCode(userId, CodeTypeDto.NORMAL) + lockedCodeService.getLockedCode(opponentId, CodeTypeDto.NORMAL) lockedMapService.getLockedMap(userId) lockedMapService.getLockedMap(opponentId) gameService.createGame(any()) @@ -283,10 +311,58 @@ internal class MatchServiceTest { ) } + @Test + fun `should create a pvp match`() { + val userId = UUID.randomUUID() + val opponentId = UUID.randomUUID() + val opponentPublicUser = + TestAttributes.publicUser.copy(userId = opponentId, username = "opponent") + val publicUser = TestAttributes.publicUser.copy(userId = userId, username = "public-user") + + val userCode = Pair(LanguageEnum.CPP, "user-code") + val opponentCode = Pair(LanguageEnum.PYTHON, "opponent-code") + + val createMatchRequestDto = + CreateMatchRequestDto( + mode = MatchModeDto.PVP, + opponentUsername = opponentPublicUser.username, + ) + + every { publicUserService.getPublicUser(userId) } returns publicUser + every { publicUserService.getPublicUserByUsername(opponentPublicUser.username) } returns opponentPublicUser + every { lockedCodeService.getLockedCode(userId, CodeTypeDto.PVP) } returns userCode + every { lockedCodeService.getLockedCode(opponentId, CodeTypeDto.PVP) } returns opponentCode + every { pvPGameService.createPvPGame(any()) } returns mockk() + every { pvPMatchRepository.save(any()) } returns mockk() + every { + pvPGameService.sendPvPGameRequest(any(), + GameCode(userCode.second, userCode.first), + GameCode(opponentCode.second, opponentCode.first)) + } returns Unit + + matchService.createMatch(userId, createMatchRequestDto) + + verify { + publicUserService.getPublicUserByUsername(opponentPublicUser.username) + lockedCodeService.getLockedCode(userId, CodeTypeDto.PVP) + lockedCodeService.getLockedCode(opponentId, CodeTypeDto.PVP) + pvPGameService.createPvPGame(any()) + pvPMatchRepository.save(any()) + pvPGameService.sendPvPGameRequest(any(), + GameCode(userCode.second, userCode.first), + GameCode(opponentCode.second, opponentCode.first)) + } + confirmVerified( + codeRevisionService, mapRevisionService, pvPGameService, pvPMatchRepository, pvPGameService + ) + } + @Test fun `should create auto match`() { val userId = UUID.randomUUID() val opponentId = UUID.randomUUID() + val publicUser = + TestAttributes.publicUser.copy(userId = userId, username = "public user") val opponentPublicUser = TestAttributes.publicUser.copy(userId = opponentId, username = "opponent") @@ -301,10 +377,11 @@ internal class MatchServiceTest { opponentUsername = opponentPublicUser.username, ) + every { publicUserService.getPublicUser(userId) } returns publicUser every { publicUserService.getPublicUserByUsername(opponentPublicUser.username) } returns opponentPublicUser - every { lockedCodeService.getLockedCode(userId) } returns userCode - every { lockedCodeService.getLockedCode(opponentId) } returns opponentCode + every { lockedCodeService.getLockedCode(userId, CodeTypeDto.NORMAL) } returns userCode + every { lockedCodeService.getLockedCode(opponentId, CodeTypeDto.NORMAL) } returns opponentCode every { lockedMapService.getLockedMap(userId) } returns userMap every { lockedMapService.getLockedMap(opponentId) } returns opponentMap every { gameService.createGame(any()) } returns mockk() @@ -319,8 +396,8 @@ internal class MatchServiceTest { verify { publicUserService.getPublicUserByUsername(opponentPublicUser.username) - lockedCodeService.getLockedCode(userId) - lockedCodeService.getLockedCode(opponentId) + lockedCodeService.getLockedCode(userId, CodeTypeDto.NORMAL) + lockedCodeService.getLockedCode(opponentId, CodeTypeDto.NORMAL) lockedMapService.getLockedMap(userId) lockedMapService.getLockedMap(opponentId) gameService.createGame(any()) diff --git a/server/src/test/kotlin/delta/codecharacter/server/match/RabbitIntegrationTest.kt b/server/src/test/kotlin/delta/codecharacter/server/match/RabbitIntegrationTest.kt index 96c58a4..e99992f 100644 --- a/server/src/test/kotlin/delta/codecharacter/server/match/RabbitIntegrationTest.kt +++ b/server/src/test/kotlin/delta/codecharacter/server/match/RabbitIntegrationTest.kt @@ -20,6 +20,9 @@ import delta.codecharacter.server.game.queue.entities.GameStatusUpdateEntity import delta.codecharacter.server.game_map.GameMap import delta.codecharacter.server.game_map.locked_map.LockedMapEntity import delta.codecharacter.server.game_map.map_revision.MapRevisionEntity +import delta.codecharacter.server.params.GameCode +import delta.codecharacter.server.pvp_game.PvPGameEntity +import delta.codecharacter.server.pvp_game.pvp_game_log.PvPGameLogEntity import delta.codecharacter.server.user.UserEntity import delta.codecharacter.server.user.public_user.PublicUserEntity import org.assertj.core.api.Assertions.assertThat @@ -65,6 +68,7 @@ internal class RabbitIntegrationTest(@Autowired val mockMvc: MockMvc) { mongoTemplate.save(TestAttributes.publicUser) try { rabbitAdmin.purgeQueue("gameRequestQueue", true) + rabbitAdmin.purgeQueue("gamePvpRequestQueue", true) rabbitAdmin.purgeQueue("gameStatusUpdateQueue", true) } catch (e: Exception) { println("RabbitMQ queues are not available") @@ -135,8 +139,12 @@ internal class RabbitIntegrationTest(@Autowired val mockMvc: MockMvc) { val gameRequestEntity = mapper.readValue(gameRequestEntityString, GameRequestEntity::class.java) assertThat(gameRequestEntity.gameId).isEqualTo(game.id) assertThat(gameRequestEntity.map).isEqualTo(mapRevision.map) - assertThat(gameRequestEntity.sourceCode).isEqualTo(codeRevision.code) - assertThat(gameRequestEntity.language).isEqualTo(codeRevision.language) + assertThat(gameRequestEntity.playerCode).isEqualTo( + GameCode( + codeRevision.code, + codeRevision.language + ) + ) } @Test @@ -220,10 +228,13 @@ internal class RabbitIntegrationTest(@Autowired val mockMvc: MockMvc) { assertThat(gameRequestEntity1.gameId).isEqualTo(game1.id) assertThat(gameRequestEntity1.map) .isEqualTo(opponentLockedMap.lockedMap[GameMapTypeDto.NORMAL]?.map.toString()) - assertThat(gameRequestEntity1.sourceCode) - .isEqualTo(userLockedCode.lockedCode[CodeTypeDto.NORMAL]?.code) - assertThat(gameRequestEntity1.language) - .isEqualTo(userLockedCode.lockedCode[CodeTypeDto.NORMAL]?.language) + assertThat(gameRequestEntity1.playerCode) + .isEqualTo( + GameCode( + userLockedCode.lockedCode[CodeTypeDto.NORMAL]!!.code, + userLockedCode.lockedCode[CodeTypeDto.NORMAL]!!.language + ) + ) } val gameRequestEntityString2 = this.rabbitTemplate.receiveAndConvert("gameRequestQueue") as String? @@ -235,10 +246,15 @@ internal class RabbitIntegrationTest(@Autowired val mockMvc: MockMvc) { assertThat(gameRequestEntity2.gameId).isEqualTo(game2.id) assertThat(gameRequestEntity2.map) .isEqualTo(userLockedMap.lockedMap[GameMapTypeDto.NORMAL]?.map) - assertThat(gameRequestEntity2.sourceCode) - .isEqualTo(opponentLockedCode.lockedCode[CodeTypeDto.NORMAL]?.code) - assertThat(gameRequestEntity2.language) .isEqualTo(opponentLockedCode.lockedCode[CodeTypeDto.NORMAL]?.language) + assertThat(gameRequestEntity2.playerCode) + .isEqualTo( + GameCode( + opponentLockedCode.lockedCode[CodeTypeDto.NORMAL]!!.code, + opponentLockedCode.lockedCode[CodeTypeDto.NORMAL]!!.language + ) + ) + } } @@ -345,6 +361,9 @@ internal class RabbitIntegrationTest(@Autowired val mockMvc: MockMvc) { mongoTemplate.dropCollection() mongoTemplate.dropCollection() mongoTemplate.dropCollection() + mongoTemplate.dropCollection() + mongoTemplate.dropCollection() + mongoTemplate.dropCollection() mongoTemplate.dropCollection() mongoTemplate.dropCollection() mongoTemplate.dropCollection() @@ -352,6 +371,7 @@ internal class RabbitIntegrationTest(@Autowired val mockMvc: MockMvc) { try { rabbitAdmin.purgeQueue("gameRequestQueue", true) + rabbitAdmin.purgeQueue("gamePvpRequestQueue", true) rabbitAdmin.purgeQueue("gameStatusUpdateQueue", true) } catch (e: Exception) { println("RabbitMQ queues are not available") diff --git a/server/src/test/kotlin/delta/codecharacter/server/pvp_game/PvPGameControllerIntegrationTest.kt b/server/src/test/kotlin/delta/codecharacter/server/pvp_game/PvPGameControllerIntegrationTest.kt new file mode 100644 index 0000000..f2b4a28 --- /dev/null +++ b/server/src/test/kotlin/delta/codecharacter/server/pvp_game/PvPGameControllerIntegrationTest.kt @@ -0,0 +1,119 @@ +package delta.codecharacter.server.pvp_game + +import com.fasterxml.jackson.databind.ObjectMapper +import delta.codecharacter.server.TestAttributes +import delta.codecharacter.server.WithMockCustomUser +import delta.codecharacter.server.match.MatchModeEnum +import delta.codecharacter.server.match.MatchVerdictEnum +import delta.codecharacter.server.match.PvPMatchEntity +import delta.codecharacter.server.pvp_game.pvp_game_log.PvPGameLogEntity +import delta.codecharacter.server.user.UserEntity +import delta.codecharacter.server.user.public_user.PublicUserEntity +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.dropCollection +import org.springframework.http.MediaType +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.security.core.Authentication +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.get +import java.time.Instant +import java.util.UUID + +@AutoConfigureMockMvc +@SpringBootTest +internal class PvPGameControllerIntegrationTest(@Autowired val mockMvc: MockMvc) { + @Autowired private lateinit var mongoTemplate: MongoTemplate + + @Autowired private lateinit var jackson2ObjectMapperBuilder: Jackson2ObjectMapperBuilder + private lateinit var mapper: ObjectMapper + + @BeforeEach + fun setUp() { + mapper = jackson2ObjectMapperBuilder.build() + mongoTemplate.save(TestAttributes.user) + mongoTemplate.save(TestAttributes.opponent) + mongoTemplate.save(TestAttributes.publicUser) + mongoTemplate.save(TestAttributes.publicOpponent) + + val context = SecurityContextHolder.createEmptyContext() + val auth: Authentication = + UsernamePasswordAuthenticationToken( + TestAttributes.user, TestAttributes.user.password, TestAttributes.user.authorities + ) + context.authentication = auth + SecurityContextHolder.setContext(context) + } + + @Test + @WithMockCustomUser + fun `should get pvp game log`() { + val pvPGameEntity = + PvPGameEntity( + matchId = UUID.randomUUID(), + scorePlayer1 = 0, + scorePlayer2 = 0, + status = PvPGameStatusEnum.EXECUTED, + ) + mongoTemplate.save(pvPGameEntity) + + val pvPMatchEntity = + PvPMatchEntity( + id = pvPGameEntity.matchId, + player1 = TestAttributes.publicUser, + player2 = TestAttributes.publicOpponent, + game = pvPGameEntity, + mode = MatchModeEnum.PVP, + verdict = MatchVerdictEnum.PLAYER1, + createdAt = Instant.now(), + totalPoints = 100, + ) + mongoTemplate.save(pvPMatchEntity) + + val pvPGameLogEntity = PvPGameLogEntity(gameId = pvPGameEntity.matchId, player1Log = "game log player 1", player2Log = "game log player 2") + mongoTemplate.save(pvPGameLogEntity) + + mockMvc + .get("/pvpgames/${pvPGameEntity.matchId}/logs") { + contentType = MediaType.APPLICATION_JSON + } + .andExpect { + status { isOk() } + content { contentType(MediaType.APPLICATION_JSON) } + content { string(pvPGameLogEntity.player1Log) } + } + } + + @Test + @WithMockCustomUser + fun `should return empty string when pvp game or match not found`() { + val randomUUID = UUID.randomUUID() + + mockMvc.get("/pvpgames/${randomUUID}/logs") { contentType = MediaType.APPLICATION_JSON }.andExpect { + status { isOk() } + content { contentType(MediaType.APPLICATION_JSON) } + content { string("") } + } + } + + @AfterEach + fun tearDown() { + mongoTemplate.dropCollection() + mongoTemplate.dropCollection() + mongoTemplate.dropCollection() + mongoTemplate.dropCollection() + mongoTemplate.dropCollection() + + SecurityContextHolder.clearContext() + } +} diff --git a/server/src/test/kotlin/delta/codecharacter/server/pvp_game/PvPGameServiceTest.kt b/server/src/test/kotlin/delta/codecharacter/server/pvp_game/PvPGameServiceTest.kt new file mode 100644 index 0000000..f9a7049 --- /dev/null +++ b/server/src/test/kotlin/delta/codecharacter/server/pvp_game/PvPGameServiceTest.kt @@ -0,0 +1,210 @@ +package delta.codecharacter.server.pvp_game + +import com.fasterxml.jackson.databind.ObjectMapper +import delta.codecharacter.server.code.LanguageEnum +import delta.codecharacter.server.config.GameConfiguration +import delta.codecharacter.server.exception.CustomException +import delta.codecharacter.server.params.GameCode +import delta.codecharacter.server.params.GameParameters +import delta.codecharacter.server.pvp_game.pvp_game_log.PvPGameLogService +import delta.codecharacter.server.pvp_game.queue.entities.PvPGameRequestEntity +import delta.codecharacter.server.pvp_game.queue.entities.PvPGameResultEntity +import delta.codecharacter.server.pvp_game.queue.entities.PvPGameStatusUpdateEntity +import io.mockk.mockk +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.springframework.amqp.rabbit.core.RabbitTemplate +import java.util.UUID +import java.util.Optional +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertThrows +import org.springframework.http.HttpStatus + +internal class PvPGameServiceTest { + private lateinit var pvPGameRepository: PvPGameRepository + private lateinit var pvPGameService: PvPGameService + private lateinit var pvPGameLogService: PvPGameLogService + private lateinit var rabbitTemplate: RabbitTemplate + private lateinit var mapper: ObjectMapper + private lateinit var gameParameters: GameParameters + + + @BeforeEach + fun setUp() { + pvPGameRepository = mockk(relaxed = true) + pvPGameLogService = mockk(relaxed = true) + rabbitTemplate = mockk(relaxed = true) + mapper = ObjectMapper() + val gameConfiguration = GameConfiguration() + gameParameters = gameConfiguration.gameParameters() + + pvPGameService = PvPGameService(pvPGameRepository, pvPGameLogService, rabbitTemplate, gameParameters) + } + + @Test + fun `should return pvp game by id`() { + val pvPGameEntity = mockk() + val gameId = UUID.randomUUID() // gameId and matchId are the same for PvP Game + + every { pvPGameRepository.findById(any()) } returns Optional.of(pvPGameEntity) + + val result = pvPGameService.getPvPGame(gameId) + assertEquals(pvPGameEntity, result) + + verify { pvPGameRepository.findById(gameId) } + confirmVerified(pvPGameRepository) + } + + @Test + @Throws(CustomException::class) + fun `should throw exception if pvp game not found`() { + val gameId = UUID.randomUUID() + + every { pvPGameRepository.findById(any()) } returns Optional.empty() + + val exception = assertThrows(CustomException::class.java) { + pvPGameService.getPvPGame(gameId) + } + + assertEquals(exception.status, HttpStatus.NOT_FOUND) + + verify { pvPGameRepository.findById(gameId) } + confirmVerified(pvPGameRepository) + } + + @Test + fun `should create pvp game request`() { + val pvPGame = mockk() + val matchId = UUID.randomUUID() + + val expectedPvPGameRequest = + PvPGameRequestEntity( + gameId = matchId, + player1 = GameCode("player1 code", LanguageEnum.CPP), + player2 = GameCode("player2 code", LanguageEnum.JAVA), + parameters = gameParameters, + ) + + every {pvPGame.matchId} returns matchId + every { + rabbitTemplate.convertAndSend( + "gamePvpRequestQueue", + mapper.writeValueAsString(expectedPvPGameRequest) + ) + } returns Unit + + pvPGameService.sendPvPGameRequest(pvPGame, expectedPvPGameRequest.player1, expectedPvPGameRequest.player2) + + verify { + rabbitTemplate.convertAndSend( + "gamePvpRequestQueue", + mapper.writeValueAsString(expectedPvPGameRequest) + ) + } + confirmVerified(rabbitTemplate) + } + + @Test + fun `should receive pvp game status update`() { + val pvPGame = + PvPGameEntity( + matchId = UUID.randomUUID(), + scorePlayer1 = 0, + scorePlayer2 = 0, + status = PvPGameStatusEnum.IDLE, + ) + + val pvPGameStatusUpdate = + PvPGameStatusUpdateEntity( + gameId = pvPGame.matchId, + gameStatus = PvPGameStatusEnum.EXECUTING, + gameResultPlayer1 = null, + gameResultPlayer2 = null + ) + + + every { pvPGameRepository.findById(pvPGame.matchId) } returns Optional.of(pvPGame) + every { + pvPGameRepository.save( + PvPGameEntity( + pvPGame.matchId, + pvPGame.scorePlayer1, + pvPGame.scorePlayer2, + PvPGameStatusEnum.EXECUTING, + ) + ) + } returns mockk() + + pvPGameService.updateGameStatus(mapper.writeValueAsString(pvPGameStatusUpdate)) + + verify { pvPGameRepository.findById(pvPGame.matchId) } + verify { + pvPGameRepository.save( + PvPGameEntity( + pvPGame.matchId, + pvPGame.scorePlayer1, + pvPGame.scorePlayer2, + PvPGameStatusEnum.EXECUTING, + ) + ) + } + confirmVerified(pvPGameRepository) + } + + @Test + fun `should receive pvp game status update with result`() { + val pvPGame = + PvPGameEntity( + matchId = UUID.randomUUID(), + scorePlayer1 = 0, + scorePlayer2 = 0, + status = PvPGameStatusEnum.IDLE, + ) + val pvPGameStatusUpdate = + PvPGameStatusUpdateEntity( + gameId = pvPGame.matchId, + gameStatus = PvPGameStatusEnum.EXECUTED, + gameResultPlayer1 = + PvPGameResultEntity( + score = 100, + hasErrors = false, + log = "player1 log" + ), + gameResultPlayer2 = + PvPGameResultEntity( + score = 100, + hasErrors = false, + log = "player2 log" + ), + ) + + val updatedPvPGameEntity = + PvPGameEntity( + matchId = pvPGame.matchId, + scorePlayer1 = pvPGameStatusUpdate.gameResultPlayer1!!.score, + scorePlayer2 = pvPGameStatusUpdate.gameResultPlayer2!!.score, + status = pvPGameStatusUpdate.gameStatus, + ) + + every { pvPGameRepository.findById(pvPGame.matchId) } returns Optional.of(pvPGame) + every { pvPGameRepository.save(updatedPvPGameEntity) } returns updatedPvPGameEntity + + pvPGameService.updateGameStatus(mapper.writeValueAsString(pvPGameStatusUpdate)) + + verify { pvPGameRepository.findById(pvPGame.matchId) } + verify { + pvPGameRepository.save( + PvPGameEntity ( + matchId = pvPGame.matchId, + scorePlayer1 = pvPGameStatusUpdate.gameResultPlayer1!!.score, + scorePlayer2 = pvPGameStatusUpdate.gameResultPlayer2!!.score, + status = pvPGameStatusUpdate.gameStatus, + ) + ) + } + confirmVerified(pvPGameRepository) + } +} diff --git a/server/src/test/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogServiceTest.kt b/server/src/test/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogServiceTest.kt new file mode 100644 index 0000000..e4849f3 --- /dev/null +++ b/server/src/test/kotlin/delta/codecharacter/server/pvp_game/pvp_game_log/PvPGameLogServiceTest.kt @@ -0,0 +1,71 @@ +package delta.codecharacter.server.pvp_game.pvp_game_log + +import delta.codecharacter.server.TestAttributes +import delta.codecharacter.server.match.PvPMatchEntity +import delta.codecharacter.server.match.PvPMatchRepository +import io.mockk.confirmVerified +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.util.Optional +import java.util.UUID + +internal class PvPGameLogServiceTest { + private lateinit var pvPGameLogRepository: PvPGameLogRepository + private lateinit var pvPMatchRepository: PvPMatchRepository + private lateinit var pvPGameLogService: PvPGameLogService + private lateinit var pvPMatchEntity: PvPMatchEntity + + @BeforeEach + fun setUp() { + pvPGameLogRepository = mockk() + pvPMatchRepository = mockk() + pvPMatchEntity = mockk() + + pvPGameLogService = PvPGameLogService(pvPGameLogRepository, pvPMatchRepository) + } + + @Test + fun `should return pvp game log`() { + val gameId = UUID.randomUUID() + val player1Id = UUID.randomUUID() + val player2Id = UUID.randomUUID() + + val player1 = TestAttributes.publicUser.copy(userId = player1Id, username = "player1") + val player2 = TestAttributes.publicUser.copy(userId = player2Id, username = "player2") + + val pvpMatchEntity = mockk() + every { pvpMatchEntity.player1 } returns player1 + every { pvpMatchEntity.player2 } returns player2 + + val pvPGameLogEntity = mockk() + val expectedPvPGameLogPlayer1 = "pvp game log player 1" + val expectedPvPGameLogPlayer2 = "pvp game log player 2" + + every { pvPGameLogRepository.findById(gameId) } returns Optional.of(pvPGameLogEntity) + every { pvPMatchRepository.findById(gameId) } returns Optional.of(pvpMatchEntity) + every { pvPGameLogEntity.player1Log } returns expectedPvPGameLogPlayer1 + every { pvPGameLogEntity.player2Log } returns expectedPvPGameLogPlayer2 + + + val pvPGameLogPlayer1 = pvPGameLogService.getPlayerLog(gameId, player1Id) + val pvPGameLogPlayer2 = pvPGameLogService.getPlayerLog(gameId, player2Id) + + assertEquals(expectedPvPGameLogPlayer1, pvPGameLogPlayer1) + assertEquals(expectedPvPGameLogPlayer2, pvPGameLogPlayer2) + + verify { pvpMatchEntity.player1 } + verify { pvpMatchEntity.player2 } + + verify { pvPGameLogRepository.findById(gameId) } + verify { pvPMatchRepository.findById(gameId) } + verify { pvPGameLogEntity.player1Log } + verify { pvPGameLogEntity.player2Log } + + confirmVerified(pvPGameLogRepository) + } + +}