From 14be55df672d858216d9379eefa26690dc3b2c84 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 31 Aug 2020 15:51:18 +0200 Subject: [PATCH] Client & network general improvement --- client/src/components/Game.vue | 133 ++++++------------ client/src/store/gameModule.js | 30 +++- client/src/store/socketPlugin.js | 4 +- .../gltronic/tronio/business/GameManager.java | 37 +++-- .../main/java/gltronic/tronio/model/Game.java | 10 +- .../gltronic/tronio/model/GameSettings.java | 11 +- .../gltronic/tronio/model/GameUpdate.java | 17 +++ .../tronio/business/GameManager.class | Bin 8164 -> 9096 bytes .../classes/gltronic/tronio/model/Game.class | Bin 2417 -> 2450 bytes .../gltronic/tronio/model/GameSettings.class | Bin 1592 -> 1707 bytes .../gltronic/tronio/model/GameUpdate.class | Bin 0 -> 1196 bytes 11 files changed, 125 insertions(+), 117 deletions(-) create mode 100644 server/src/main/java/gltronic/tronio/model/GameUpdate.java create mode 100644 server/target/classes/gltronic/tronio/model/GameUpdate.class diff --git a/client/src/components/Game.vue b/client/src/components/Game.vue index 2e392bd..e279e3a 100644 --- a/client/src/components/Game.vue +++ b/client/src/components/Game.vue @@ -17,6 +17,10 @@ export default { x: 0, y: 0 }, + camera: { + x: 0, + y: 0 + }, renderTimer: null } }, @@ -34,7 +38,20 @@ export default { return this.$store.state.game.player }, players () { - return this.$store.state.game.players + const pastUpdate = this.$store.state.game.updates[0] + const nextUpdate = this.$store.state.game.updates[1] + + if (pastUpdate === undefined) return [] + if (pastUpdate.players === undefined) return [] + if (nextUpdate === undefined) return pastUpdate.players + + return pastUpdate.players + + /* + const currentTime = Date.now() / 1000 + const dt = (currentTime - pastUpdate.time) / (nextUpdate.time - pastUpdate.time) + return pastUpdate.players.map(player => this.interpolatePlayer(player, nextUpdate.players.find(nextPlayer => player.color === nextPlayer.color), dt)) + */ }, isPaused () { return this.$store.state.game.paused @@ -52,26 +69,28 @@ export default { this.context.clearRect(0, 0, this.canvas.width, this.canvas.height) this.renderBorders() + if (!this.players) return + this.players.forEach(player => { + if (player.color === this.player.color) { + this.camera.x = player.x + this.camera.y = player.y + this.renderDebug(player) + } this.renderPlayer(player) this.renderWalls(player) }) - - this.renderDebug(this.player) - - // this.checkPlayerColision() - this.updatePlayer(this.player) }, renderBorders () { this.context.strokeStyle = 'black' this.context.lineWidth = 1 - this.context.strokeRect(this.canvas.width / 2 - this.player.x, this.canvas.height / 2 - this.player.y, this.settings.arenaSize, this.settings.arenaSize) + this.context.strokeRect(this.canvas.width / 2 - this.camera.x, this.canvas.height / 2 - this.camera.y, this.settings.arenaSize, this.settings.arenaSize) }, renderPlayer (player) { this.context.save() - const canvasX = this.canvas.width / 2 + player.x - this.player.x - const canvasY = this.canvas.height / 2 + player.y - this.player.y + const canvasX = this.canvas.width / 2 + player.x - this.camera.x + const canvasY = this.canvas.height / 2 + player.y - this.camera.y this.context.translate(canvasX, canvasY) this.context.rotate(player.angle) @@ -86,41 +105,16 @@ export default { this.context.lineWidth = this.settings.wallSize this.context.strokeStyle = player.color player.walls.forEach(wall => { - const canvasX = this.canvas.width / 2 + wall.x - this.player.x - const canvasY = this.canvas.height / 2 + wall.y - this.player.y + const canvasX = this.canvas.width / 2 + wall.x - this.camera.x + const canvasY = this.canvas.height / 2 + wall.y - this.camera.y this.context.lineTo(canvasX, canvasY) }) - this.context.lineTo(this.canvas.width / 2 + player.x - this.player.x, this.canvas.height / 2 + player.y - this.player.y) + this.context.lineTo(this.canvas.width / 2 + player.x - this.camera.x, this.canvas.height / 2 + player.y - this.camera.y) this.context.stroke() }, renderDebug (player) { const canvasX = this.canvas.width / 2 const canvasY = this.canvas.height / 2 - /* - this.context.beginPath() - this.context.lineTo(canvasX, canvasY) - this.context.lineTo(this.mouse.x, this.mouse.y) - this.context.lineWidth = 1 - this.context.strokeStyle = 'blue' - this.context.stroke() - - const x = canvasX + 30 * Math.cos(player.angle) * 2 - const y = canvasY + 30 * Math.sin(player.angle) * 2 - this.context.beginPath() - this.context.lineTo(canvasX, canvasY) - this.context.lineTo(x, y) - this.context.lineWidth = 1 - this.context.strokeStyle = 'yellow' - this.context.stroke() - - const canvasX2 = this.canvas.width / 2 - const canvasY2 = this.canvas.height / 2 - this.context.beginPath() - this.context.arc(canvasX2, canvasY2, this.settings.playerSize, 0, 2 * Math.PI, false) - this.context.lineWidth = 1 - this.context.strokeStyle = 'purple' - this.context.stroke() - */ this.context.fillText('player x: ' + player.x + ' y:' + player.y, 10, 10) this.context.fillText('a:' + player.angle + ' a_t' + player.targetAngle, 10, 20) @@ -152,17 +146,10 @@ export default { var dy = this.mouse.y - this.canvas.height / 2 this.player.targetAngle = Math.atan2(dy, dx) + this.player.angle = this.player.targetAngle send({ message: this.player, type: 'update' }) }, updatePlayer (player) { - /* - this.player.lastWall++ - if (this.player.lastWall > this.settings.wallUpdate) { - this.player.walls.push({ x: this.player.x, y: this.player.y }) - this.player.lastWall = 0 - } - */ - player.x += this.settings.playerSpeed * Math.cos(player.angle) player.y += this.settings.playerSpeed * Math.sin(player.angle) @@ -177,55 +164,19 @@ export default { } */ }, - checkPlayerColision () { - const playerX = this.player.x - const playerY = this.player.y - // BORDERS - if (playerX - this.settings.playerSize < 0 || - playerX + this.settings.playerSize > this.settings.arenaSize || - playerY - this.settings.playerSize < 0 || - playerY + this.settings.playerSize > this.settings.arenaSize - ) { - this.context.fillText('DED BORDER', this.canvas.width / 2, this.canvas.height / 2) - } + interpolatePlayer (pastPlayer, nextPlayer, dt) { + var player = {} + player.color = pastPlayer.color + player.walls = pastPlayer.walls + player.lastWall = pastPlayer.lastWall - // WALLS - for (var i = 0; i < this.player.walls.length - 2; i++) { - const wallA = this.player.walls[i] - const wallB = this.player.walls[i + 1] - if (this.isCloseToWall(wallA.x, wallA.y, wallB.x, wallB.y, playerX, playerY, this.settings.playerSize)) { - if (this.isCrossingLine(wallA.x, wallA.y, wallB.x, wallB.y, playerX, playerY, this.settings.playerSize)) { - this.context.fillText('DED WALL OWN', this.canvas.width / 2, this.canvas.height / 2) - this.context.beginPath() - const canvasX = this.canvas.width / 2 + wallA.x - this.player.x - const canvasY = this.canvas.height / 2 + wallA.y - this.player.y - this.context.lineTo(canvasX, canvasY) - const canvasX2 = this.canvas.width / 2 + wallB.x - this.player.x - const canvasY2 = this.canvas.height / 2 + wallB.y - this.player.y - this.context.lineTo(canvasX2, canvasY2) - this.context.lineWidth = this.settings.wallSize - this.context.strokeStyle = 'purple' - this.context.stroke() - } - } - } - }, - isCloseToWall (xa, ya, xb, yb, xc, yc, radius) { - var xar = Math.min(xa, xb) - radius - var yar = Math.min(ya, yb) - radius - var xbr = Math.max(xa, xb) + radius - var ybr = Math.max(ya, yb) + radius + player.x = pastPlayer.x + (nextPlayer.x - pastPlayer.x) * dt + player.y = pastPlayer.y + (nextPlayer.y - pastPlayer.y) * dt - return ((xc >= xar && xc <= xbr) && (yc >= yar && yc <= ybr)) - }, - isCrossingLine (xa, ya, xb, yb, xc, yc, radius) { - var a = xc - xa - var b = xb - xa - var c = yc - ya - var d = yb - ya - var result = (a * d - b * c) / Math.sqrt(Math.pow(xa - xb, 2) + Math.pow(ya - yb, 2)) + player.angle = pastPlayer.angle + (nextPlayer.angle - pastPlayer.angle) * dt + player.targetAngle = pastPlayer.targetAngle + (nextPlayer.targetAngle - pastPlayer.targetAngle) * dt - return result < radius + return player } } } diff --git a/client/src/store/gameModule.js b/client/src/store/gameModule.js index 937f3bf..bc5bbca 100644 --- a/client/src/store/gameModule.js +++ b/client/src/store/gameModule.js @@ -6,9 +6,7 @@ const state = { targetAngle: 0, color: 'red' }, - players: { - - }, + updates: [], settings: { playerSize: 10, playerSpeed: 2, @@ -22,11 +20,20 @@ const state = { } const getters = { + // Interpolation position des joueurs + currentPlayers: state => { + if (state.updates.length < 2) return state.updates[0].players + else { + const dt = state.updates[0].time - state.updates[1].time + return state.updates[1].players.map(player => interpolatePlayer(player, dt)) + } + } } const actions = { socketConnected ({ commit }) { commit('SET_CONNECTED') + commit('CLEAR_UPDATE') }, login ({ commit }, player) { commit('SET_PLAYER', player) @@ -35,10 +42,11 @@ const actions = { commit('SET_SETTINGS', settings) }, update ({ commit }, update) { - commit('SET_PLAYERS', update) + commit('ADD_UPDATE', update) }, dead ({ commit }, player) { commit('SET_PLAYER', player) + commit('CLEAR_UPDATE') }, error ({ commit }, error) { alert('Error: ' + error) @@ -60,9 +68,23 @@ const mutations = { }, SET_CONNECTED (state) { state.socketConnected = !state.socketConnected + }, + ADD_UPDATE (state, update) { + if (state.updates.length > 2) { + state.updates.shift() + } + update.time = update.time.epochSecond + state.updates.push(update) + }, + CLEAR_UPDATE (state) { + state.updates = [] } } +function interpolatePlayer (player, dt) { + return player +} + export default { namespaced: true, state, diff --git a/client/src/store/socketPlugin.js b/client/src/store/socketPlugin.js index 73a0f45..a3ede38 100644 --- a/client/src/store/socketPlugin.js +++ b/client/src/store/socketPlugin.js @@ -18,7 +18,7 @@ export default function createSocketPlugin () { } connection.onmessage = function (message) { - console.log('[WS] message', message.data) + // console.log('[WS] message', message.data) var data = JSON.parse(message.data) @@ -43,6 +43,6 @@ export default function createSocketPlugin () { } export function send (message) { - console.log('[WS] send', message) + // console.log('[WS] send', message) connection.send(JSON.stringify(message)) } diff --git a/server/src/main/java/gltronic/tronio/business/GameManager.java b/server/src/main/java/gltronic/tronio/business/GameManager.java index b70ee04..dc22812 100644 --- a/server/src/main/java/gltronic/tronio/business/GameManager.java +++ b/server/src/main/java/gltronic/tronio/business/GameManager.java @@ -1,6 +1,8 @@ package gltronic.tronio.business; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Random; @@ -13,6 +15,7 @@ import org.springframework.stereotype.Service; import org.springframework.web.socket.WebSocketSession; import gltronic.tronio.model.Game; +import gltronic.tronio.model.GameUpdate; import gltronic.tronio.model.Player; import gltronic.tronio.model.Wall; @@ -54,8 +57,12 @@ public class GameManager implements IGameManager { } @Override - @Scheduled(fixedDelay = 1000 / 60) + @Scheduled(fixedDelay = 1000 / 120) public void step() throws InterruptedException, IOException { + Instant currentTime = Instant.now(); + Duration durationSinceLastUpdate = Duration.between(game.getLastUpdate(), currentTime); + game.setLastUpdate(currentTime); + // CHECK OUT OF BORDERS & COLISIONS game.getPlayers().forEach((id, player) -> { // OUT OF BORDERS @@ -92,21 +99,29 @@ public class GameManager implements IGameManager { // ADD WALLS & MOVE PLAYER game.getPlayers().forEach((id, player) -> { - // WALL - player.setLastWall(player.getLastWall() + 1); - if (player.getLastWall() > game.getSettings().getWallUpdate()) { - player.getWalls().add(new Wall(player.getX(), player.getY())); - player.setLastWall(0); - } + // On cherche le nombre de pas pour atteindre les 60 up/s + long tickToSimulate = ( durationSinceLastUpdate.toMillis() * 120 ) / 1000; - // MOVE - player.setX(player.getX() + game.getSettings().getPlayerSpeed() * Math.cos(player.getAngle())); - player.setY(player.getY() + game.getSettings().getPlayerSpeed() * Math.sin(player.getAngle())); + for (long i = 0; i <= tickToSimulate; i++) { + // WALL + player.setLastWall(player.getLastWall() + 1); + if (player.getLastWall() > game.getSettings().getWallUpdate()) { + player.getWalls().add(new Wall(player.getX(), player.getY())); + player.setLastWall(0); + } + + // MOVE + player.setX(player.getX() + game.getSettings().getPlayerSpeed() * Math.cos(player.getAngle())); + player.setY(player.getY() + game.getSettings().getPlayerSpeed() * Math.sin(player.getAngle())); + } }); // Broadcast une fois sur deux if (game.isUpdateNeeded()) { - SocketUtils.broadcast(game, "gameUpdate", new ObjectMapper().writeValueAsString(game.getPlayers().values())); + GameUpdate update = new GameUpdate(); + update.setTime(game.getLastUpdate()); + update.setPlayers(game.getPlayers().values()); + SocketUtils.broadcast(game, "gameUpdate", new ObjectMapper().writeValueAsString(update)); game.setUpdateNeeded(false); } else game.setUpdateNeeded(true); } diff --git a/server/src/main/java/gltronic/tronio/model/Game.java b/server/src/main/java/gltronic/tronio/model/Game.java index 9b6e17c..01ca4a0 100644 --- a/server/src/main/java/gltronic/tronio/model/Game.java +++ b/server/src/main/java/gltronic/tronio/model/Game.java @@ -1,5 +1,6 @@ package gltronic.tronio.model; +import java.time.Instant; import java.util.HashMap; import java.util.Map; @@ -17,18 +18,13 @@ public class Game { private Map sessions; private GameSettings settings; private boolean updateNeeded; + private Instant lastUpdate; public Game() { this.updateNeeded = false; + this.lastUpdate = Instant.now(); this.players = new HashMap<>(); this.sessions = new HashMap<>(); this.settings = new GameSettings(); - - this.settings.setArenaSize(1000); - this.settings.setPlayerSize(10); - this.settings.setPlayerSpeed(2); - this.settings.setPlayerTurnSpeed(10); - this.settings.setWallSize(8); - this.settings.setWallUpdate(5); } } \ No newline at end of file diff --git a/server/src/main/java/gltronic/tronio/model/GameSettings.java b/server/src/main/java/gltronic/tronio/model/GameSettings.java index 1509914..6f3728e 100644 --- a/server/src/main/java/gltronic/tronio/model/GameSettings.java +++ b/server/src/main/java/gltronic/tronio/model/GameSettings.java @@ -1,12 +1,10 @@ package gltronic.tronio.model; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; @Getter @Setter -@NoArgsConstructor public class GameSettings { private double playerSize; private double playerSpeed; @@ -14,4 +12,13 @@ public class GameSettings { private double wallSize; private double wallUpdate; private double arenaSize; + + public GameSettings () { + this.arenaSize = 1000; + this.playerSize = 10; + this.playerSpeed = 2; + this.playerTurnSpeed = 10; + this.wallSize = 8; + this.wallUpdate = 5; + } } \ No newline at end of file diff --git a/server/src/main/java/gltronic/tronio/model/GameUpdate.java b/server/src/main/java/gltronic/tronio/model/GameUpdate.java new file mode 100644 index 0000000..4e4cc8d --- /dev/null +++ b/server/src/main/java/gltronic/tronio/model/GameUpdate.java @@ -0,0 +1,17 @@ +package gltronic.tronio.model; + +import java.time.Instant; +import java.util.Collection; + +import org.springframework.stereotype.Component; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Component +public class GameUpdate { + private Collection players; + private Instant time; +} \ No newline at end of file diff --git a/server/target/classes/gltronic/tronio/business/GameManager.class b/server/target/classes/gltronic/tronio/business/GameManager.class index 6644b40cc18db293245cda291c69372dfeb87c0b..838bc431662b08e2acf38b6685186ebb55ebc893 100644 GIT binary patch delta 3821 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 Rl3xe;FX7Ad1U-rD{{TUtmJ9#@ diff --git a/server/target/classes/gltronic/tronio/model/Game.class b/server/target/classes/gltronic/tronio/model/Game.class index f4e57ec0d6ce8368633633c6b7737141b6f6880d..2dfd38dd9ee24403d0925e259744ed6fcc0e8480 100644 GIT binary patch literal 2450 zcmbVMX>%Jz6ztJqS*!IKti*O=a@m#@$U-;*k#R5%If!x;3Qm!4D`}87k#<$u5w83v zK0p<&P*BAW;73vP%<5Q2N+Mh?Yxd32ucu$X`RCt1{|0a!_cKTctTdXc6Zl@uF+~f=~6d6D)~^#x~vO`X98B!4b=~3 zTHsVfRCz7w-19@_`iiHfwmsidI|9kV*1kY$k2+gOV#&q=7PAmIZDR(r7M8J^!HU3w z5jPHF<1Ee*!4D1vmI_;A5^13KZOmalgA08OQ=q$Uc)%iAIFBX0>#~hBru5f_jVz`z zCiblwF^97KJ2!tvh&2PHd*<_a!_-d`)Hn!dj_nC9L3QiNGAjLg*c7(GLm z&5(3CW;82|X|-55-XA6HvRkiBoGyuRI3tnJ2iRR(^k*w*0`!W zRbcu1uCI6Rd!a|)@A`hATot`u#qzjohSW?3ih??OL8~43l5YhruqTszqti&}7G;!S zl)yCq*{FlqV$2RSYkWD($j0ZG;~u^${5{1;5lwKsR(yr`ipeA7i+|wE5!Q--;rx%U zaPbJ2hM(*F+~n-m5sJSV%n$gxW;mQi3TMb+nORn_%D)XhNgv}JKI9%d%7UwK7zupD zl|+yGHN(W=tY(rf{*Ft(@*@EauCD|>enXJ-0OXSx$fqM9>yv?ehR>NoYY}Hqob2#* zd@<&5{+Eg9e@SgdGp|jS`36cXT~z#SL%&SL5?Zn16&6jGCmB<4)+m^dcJvg~>P9B| zio2r{>6vNL+Tl!EFqmg>GnVY@(PZnBCW{;zPnLx>OqMmtcJU2a>Vn*e-za9d$P;h6 zk+%JSf6hSe;noP`D!o4R@Y^AE2Mr)+4CJ@CJpxJH*CvJ3%{YM6K>~MpMJU5rhUNOX{>O-G;U4w3ZAc zZMW_@vK2D_eth8Kk5e#IW#&cvuWuXfKM%Y`1ET6k9^nM9qM)Zy>(x zdahhC5X~=^4aC++#1x{qWMUFiNf@|nVjL3*Trn`+7j44{4@kHaGPuNE*G!Bc&Z{{S zNyHPlZs6A0Dh)=J!n_8!X<`g!y!ca*Y7DUbEfcqKhr$kpTn&Wh*zS*1omhT-u^cZh zC_m+R*TmcGI8Khb1ne0vY7gb|o{9I_WuouWA{m(G0}~(e(M<1Xw;gykAIFQiKFh}@ zK4HHx^4oJ74b746JA7(lr5gr2JW*wwYBwt)*mWumN@1qtSDi-L30&SE8DsLm zrCQGYM=hy_sd_g@@}15v9`7x3SUYLEQ2UM%%~7)vPk9*3i}bLj>9+B0qB*~1D8~sm z)Z%tqnYx#nIn}Em#XOTgAr1HU`U&F&v|*Uc@%p`Lb!#}aDdd4)+jjUBOEqe^UHVGy z4H3Zq7m{PrJY6a82J_g!b?lJ!d*twpQZCXxQ-_OHd`>%hvRJ1Sb@E@Rlq|AxN+U!&`wJ~1U_#b} zuNT#?a_E6P8~}NA3M4xm$OgV73Kn%Rg4}SIzrxpNTu%Q?Xy=Dt!X}kbYp|svDABD5 z-HjLSQXN_K=zs|0N`#E|I1+&c(v9?(ytT?~8Ktnc$ueTlhP&pQ0kWObWZ7ZKbe9g2 zCF#FSk1VOkZ0u5$tm%WnjB-pesLPu&;@0m$jw#3|D4&ANQr`~?$*-sfIZluf?5SwJ M#Z&rno9zSt0*5IwiFTWGh?FF=AQMX;r6*KZVv!ASHEDoGK4U)r0pWZ9DKR--TBqZkt# z6My&sK9q6hZr!eN6E{tEc24g(Gka$4&tKnv0NBEA7Ab+Xp5uFc*X~$4^sV!LS2@-T zd9K>Z_ieW~$RaIJJd^LF~yEJ0m^i%HR)U*LH3HiNI82^H?DLjM~Xz3Plqp3dY{2J!&49 zfk4$n9)_{Et`QAgGLc~@Ks0pOL>300zhYvV|6HA%Uo+_pLDdO#-9!!swSQo{>ecZ4 zM0rQ@#Gy9J2mOw8j-_XFJ_ghNsXY)_Kls0oI|7BC@(&}f(MOVRvlBEhsvH6l_woe3 z4Gm-^&i;64QvP&eV`1=jp>$)P?JT}j{%OB^C_S35uRQ8}c5-rDJOrY}35*mUXJK*5 zBgJ#={?O~F=Qg`mimpLm}^#18lggAeuPB|OCu}?*$RcLBh)Bd8)4&%7Oc~AjTQnZB8?KQQki}g zETM{3%%hG4w6KTC1vX)em?@%Z?qa`23 pOGfSlQ$W{|_x@im`8tM@IW74(Ub2$3OTO8mWL`^dQ|%OXegjB&;m800 literal 1592 zcma)6U2hUW6urau$F#JlXti34)dFf)eKApEVr=vUwFwoU2bj=pmR+(e8vRqoL}TKE zKfoVlJa-nBY9?`CxO2~)bM}4>zkdJtNkk9nL6I_onqw~t1K%Aw#stnJ7-`RWsV2Io zqsaBgvm)gLZJet2%JGyxcHRt5^)M1tnR@D@4twq=El3;+sxE-3)+0f+*gKwwelJE! zAC%|C*DHK{I~}PA+_DO_uj0VkSVu=^bhE8ClylFKRhf{6q&yWM-gSNbdOjKG@K_Bz zh}65mP#J{b!u-99l8XGjdJ;yA#P!|isUWnyU!g2HlJ3x5%(@rM!=ZlRa;rDb zZpQ&H2JNDPEY_+(86FT$8E%}h=#a7Kh_R^OSd>+l@#1Z=+Zm1>{OwN3mM z3|OQ(&N9A0=jc2BxI@6J6$RKf=t+ z)H?g$sP+X&hF0w^nRYjT@c`m>^NHsx=oZktfU^N--!9omE6J;pNEVG`%PzTwq0^Ntt2mSB3U+)9lPXCT1kEjiDbn{-nL6N(@OHQN+hdB5>FUV{J!v4&6DeV Qr-OeL%hvaG&#cA$KRxr%CIA2c diff --git a/server/target/classes/gltronic/tronio/model/GameUpdate.class b/server/target/classes/gltronic/tronio/model/GameUpdate.class new file mode 100644 index 0000000000000000000000000000000000000000..bb16940a9e862f211cc708bc1815db37a3ca62ad GIT binary patch literal 1196 zcma)4YflqF6uq;3(NbvvDOP*{qL!Mt-+Vzx42i~MRRY-W({@}2w=3Wm@r6m{q2oqtJ9947J)7}# z<;Z0MZj}mDgvlT~PUc^^fSYo_*50a8nJN$#PY7$avUORPo@c*ZDa`gZrtLcmFD$IT z73M_u4|s&lG09$(ASe~z7%@(1w{VpsJ&bzdm9(FxHum|k-6%%Q!=bW09T`h}t(Qt^ z!|igx`nIPd-;KjaD*vM;{i35!ZVYOn4Rm1@2ccF1dqNwqDTk$Z@Pn_2N=Y<_7~bK3 z0Xb|8&TEKV+{|X%KWO1wQd-Ap8;KTZ5vMJ+ZA-L_o6&Vz0ayy#xCC-et6x#%&=_Ps z6QoHu0X41QoYwxY`8M6bkg0;Z35W+u2gJqpqwln8=g3V+s3sB`X~k#`J7a3lI;ztd xZDtO#HjpVW1a$5EB{waaGBBC)RjgDeBmp)G(pbe|sJwoX4J`U`V_0S5p8 literal 0 HcmV?d00001