0aC(™X*ÿ·-D²Ó¨%qye3M“Fƒ÷±«w¶Þâæ!ž¼�™® 0ÿÿÿÿÿÿÿÿ2Tv˜ºÜþïÍ«‰gE#2Tv˜ºÜþ2Tv˜ºÜþ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÀ ÀÀ À ÀÀ À À À ÀÀÀ À ÀÀ ÀÀÀ ÀÀÀ ÀÀÀîÝÝîÎÌÌìÝÝÝÝÍÌÌÜÝÝÝÝÝÝÝÝÞÝÝíîÝÝîwffwWUevfffffVUUffffVeffgfUvwffwÂÌÌ,,ÌÌÂÌÂ,ÌÌÌÂÌÌ,ÌÌÌÂ,Ì,ÌÌÂÂÌÌ,Ý ÐÝ Ð Ðàà Ð ÐÝ Ðݰ »»°»» °° ° ° ° ÿÿð`ff`ff`UU`ffðÿÿ""""""""""""""""""""""""""""""""ÿÿ@ðDD@ÄD@DDDD@ðÿÿ»» °° ° ° À °»»ÿÿðÿÿÿÿÿÿÌüÿÏÿÿÏÿÿÿÏÿÿÿÿÿÿ33333333333333333333333333333333wpwppppwpwÿÿ@ðDD@ÄL@DDDD@ðÿÿÀ DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD™� ™ � � � �™� ™ÿÿ@ðDD@ÄL@DLDD@ðÿÿUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUª  ª        ª  ªÿÿ@ðDD@ÄL@ÄLDD@ðÿÿffffffffffffffffffffffffffffffffÿÿ@ðÄL@ÌÌ@ÌÌÄL@ðÿÿ ÀÀ À ÀUUUUåîî^UUUUÕÝÝÝÕÝÝÝÕÿÿßõýÿÿõýýßUUUUîîî^UUUUÝÝÝÝßÝÝýÿÝÝÿÿÿÿÿÿÝÿßUUUUîîî^UUUUÝÝÝ]ÝÝÝ]ýÿß_ÿýß_ÿßÿ_wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww    ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆDDDDTTUEUETUUUUTUUEUUUDUUDTTUTUTDUUUTEUDTUDUTTETTTUTDUUDTUUUDUUUUUUUTUEDETUEUTEDUTEUDUDUTUTUUUUUUUUUDTUETUETDTEUUTEUUDUDUEUEUUUUUUUDDUTEUDTEETUEEUUDDUDEUUTEUUUD™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™EDUUEEUUEEDUUUETUUUUUDTUUTEUEUETªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªªEUETUTEUUDTUUUUUUUETEEDUEEUUEDUUîÿÿÿÿÿîþÿÿîþÿÿÿÿîþÿÿîþÿÿÿÿïîÿÿïîîþÿÿîþÿÿÿÿïîÿÿïîÿÿÿÿïîÿÿïîÿÿÿÿÿîÿÿÿÿÿÿïþÿïîþÿÿîÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»UTUTUDTTUUDUUUEUETETUTUUTEUEDDDDîÿÿÿÿÿîþÿÿîþÿÿÿÿîþÿÿîþÿÿÿÿïîÿÿïîîþÿÿîþÿÿÿÿïîÿÿïîÿÿÿÿïîÿÿïîÿÿÿÿÿîÿÿÿÿïÿÿÿÿþÿÿÿîÿÿÿîÿÿÿÿïþÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîÿÿÿÿÿîþÿÿîþÿÿÿÿîþÿÿîþÿÿÿÿïîÿÿïîîþÿÿîþÿÿÿÿïîÿÿïîÿÿÿÿïîÿÿïîÿÿÿÿÿîÿÿÿÿÿÿïþÿÿÿÿïîÿÿïþÿÿÿÿÿþÿÿïþÿÿÿÿîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝUUUUåîî^åîî^UUUUåîåîåîåîåîåîUUUUUUUUîîî^îîî^UUUUîîåîîîåîîîåîUUUUUUUUîîî^îîî^UUUUîîå^îîå^îîå^UUUUUUUUåîî^åîî^UUUUåîåîåîåîåîåîUUUUUUUUîîî^îîî^UUUUîîåîîîåîîîåîUUUUUUUUîîî^îîî^UUUUîîå^îîå^îîå^UUUUUUUUåîî^åîî^UUUUåîå^åîå^åîå^UUUUÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîîåîî^åîî^åîî^UUUUåîåîåîåîåîåîUUUUîîî^îîî^îîî^UUUUîîåîîîåîîîåîUUUUîîî^îîî^îîî^UUUUîîå^îîå^îîå^UUUUåîî^îîî^îîî^UUUUîîåîîîåîîîåîUUUUUUUUîîî^îîî^UUUUîîå^îîå^îîå^UUUUUUUUåîå^åîå^åîå^UUUUåîî^åîî^åîî^ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝåîî^åîî^åîî^UUUUåîåîåîåîåîåîUUUUîîî^îîî^îîî^UUUUîîåîîîåîîîåîUUUUîîî^îîî^îîî^UUUUîîå^îîå^îîå^UUUUåîî^åîî^åîî^UUUUåîå^åîå^åîå^UUUUåîî^åîî^åîî^UUUUåîå^åîå^åîå^UUUUåîî^åîî^åîî^UUUUåîå^åîå^åîå^UUUUþÿÿïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿïïîîþîîîîîîîîîîîîîîîîîîîîîîîîïîîþÞÝÝíÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÞÝÝíÍÌÌÜÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÍÌÌÜ  303ÃÃ0üó<3ÌÃ<Ã3"Ã33"3033wpwvwpfgvwfgvwwwwwwfwpwfÝ@D @@DÝÐDD ÐDDDDÌḬ̀¼» ¼»Ë̼»ËË»»»ËÀ¼» Ḭ̀ ÛÝ Ð½ÛÐÝ» ÐÛ½ »ÝÀÀ À ÀÀ ÀÀÀÀ gfvwwwwwwwwwwgffwwwwgvwwwwfwwwwwÿÿðÿÿÿÿÿÿÌüÿÏÿÿÏÿÿÿÏÿÿÿÿÿÿ 2 3" #2# #2# # ppp`ffÆlÆlffff````DM@DÔÐDDDDÐDÝ@™™˜�ˆ‰ ™ˆ‰˜‰ˆˆ˜‰ˆˆˆ�ˆ˜ ™˜€ ˜€ˆ˜‰€™ˆ€‰ˆˆˆÀÀÀ ÀÀÀÀ À À wwwwwfwwwwgvwwwwffvwwwwwwwwwwgfvîîàfîîîînãîD>ãNDîãîîîîn>ãîàî˜˜ �™ˆ™€™€™ˆ‰�™™ �™™ ™™ ˜‰�™™™™™™™™™™™‰˜™™ �™™ ˆ‰ €˜™™™™©šª™©Ÿú™©Ÿú�©šª�™™‰€ˆ@Ý @DMDÐDD @DDDM À ÀÀÀ ÀÀ ÀÀÀwwwwwwfwgvwwwwwwwgffwwwwwwwwgfvw™€™˜ �™ˆ™�™€™™™�™™ �™™ ™™ ™™�™™ ™™™™™™™™™™™™™ �™™ ™™ �™™™™ ™©šª™©Ÿú™©Ÿú�©šª�™™™�™ ÝD@DD@DD@DM@D @Ý™™ �™™™™™™™™™™™�™™™™™™�™ ™�™ ™™™™™™™™™™™ ™™™�™™ � ™��™ �™Ÿš™™šŸ™™™™�™©š��©š �™™@@ÔM@@ÝÝDÝÝDDÝÝD@ÝÝÔM@ÁÁÁÁÁÁ"" """"""€ˆ¨¨ˆˆˆˆ ˆˆ""""€ˆˆˆŠˆŠˆª¨Šˆˆˆˆ€ˆˆˆˆˆˆˆˆ""""€ˆ¨¨ ˆˆ""""ÝÝÝÝÍÝÝÜÍÜÍÜÝÜÍÝÍÍÜÜÍÝÝÜÝÜÍÝÝÍÜÝwwwww|||ÇÇÇww|||ÇÇÇww|||ÇÇÇwwwwwUUUUåîî^UUUUÕÝÝÝÕÝÝÝÕÿÿßõýÿÿõýýßUUUUîîî^UUUUÝÝÝÝßÝÝýÿÝÝÿÿÿÿÿÿÝÿßUUUUîîî^UUUUÝÝÝ]ÝÝÝ]ýÿß_ÿýß_ÿßÿ_€ÐÔÔÔÔÔÔÔÔÔÔÔÔefgÔÔÔÔÔÔÔÔÔÔÔÔÔÒÐÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔeffgÔÔÒÐÔÔÔÔÔÔÔÔÔÔÔefffgÔÔÔÔÔÔÔÔÔÔÔÔÒÐÔÔÔÔegÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔegÔÔÔÔÒÐÔÔÔÔÔÔÔÔÔÔÔÔÔefgÔÔÔÔÔÔÔÔÔÔÔÔÒÐÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÒŸŸŸŸŸ¯¯¯¯¯¯¯OOO¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ñò·àòñò3˧§àòñòA«QQàòñòAàòñò1àòñòàòŸŸŸŸŸŸ¯O¯¯¯¯¯¯¯¯¯¯¯¯¯¯OOOOO¯¯¯¯¯¯¯¯¯ñò«�·�sàòñòés«!éàòñòÓÔÕ»ÓÔÕ«!àòñò1«!sàòñò«!«ƒ„†ÐÑÑÑÒSàòñòàò¯OOOáâ1s‘#‘»«àâáâ÷ÑÒƒ„…„†és!§àâáâA1s1àâáâÓÕA1àâáâAðññáâàâáâà⟯¯¯OñòÐÒ¡·¡1àòñòàáâ»ùAàòñòÓÔÕÚÚÓÔÕàòñòÓÕCÓÔÔÔÕàòñòÐÒ«sàâ�àòñòàòŸŸ¯¯¯¯áâðò±·±ÐÑÒÔÕàâáâðñÕàÔÔÔÕàâáâéÓÔÔÔÕéàâáâÚQ««Qsé«àâáâàâs1ÐÒA!àò±àâáâà⟯¯¯¯ñòA1·àáâàòñòA!àáâ1àòñò!»ôA!ôCàòñòùQé�ÚéàòñòàáÑÑÑÑÑâÓò«àòñòàò¯¯OOáâAðñòàâáâéðñòàâáâôsssôàâáâ«ésÚ‘!Úàâáâðññññññò1àâáâàâO¯O¯ñòË«àòñò!s÷s1sàòñòsô1é��àòñòÓÕù±1àòñòAÓÔÕ1àòñòàòŸ¯OO¯áâÐÒ1Ëàâáâ«ðÔÔÕÓÔÒ!àâáâQôÐÑÑÑÑÑÒùQàâáâÓÔÕAÚQ1àâáâAs1sàâéàâáâcà⟟¯OO¯ñòàâéàòñò1Q÷»àòñòÓÔÕQÐáòðáÒQÓÔÕàòñòAsÓÔÔÔÕàòñòÐÒ!«Ðáò!ÚàòñòàòŸ¯OO¯áâàáÔçôs1ËàâáâÐÔÔÔÕés1àâáâ1ÓñÕ!ÓÔÕ1àâáâA«QÓÔÕùsQàâáâàâàòùàâáâà⟯¯¯¯ñòsàâôÔçé»àòñòéAéQàòñòA»àòñò1ésùÓÔÕàòñòðáÒ1Qàòñòdàò¯¯¯¯áâàòôôàâáâùsQ«ðÔÔÕAËàâáâ!«!àâáâsùÕ1àâáâ!ðñ«QÐÑÒàâáâà⯯¯¯ñò«ôËùàòñòðÔÔÔÕsàòñòàòñò!Ú!àòñòÓðñòsàòñòàòŸ¯¯¯¯áâdàâáâdàâáâdàâáâdàâáâdÚàâáâà⟟ŸŸ¯¯¯OOOOO¯¯¯¯¯OOOOOO¯¯¯¯¯¯¯¯¯¯¯ðñÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔññðñÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔññðñÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔññðñÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔññðñÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔññðñÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔññŸŸŸŸŸ¯¯¯O¯¯¯¯¯¯¯¯¯¯¯¯¯¯OOO¯¯¯¯¯¯¯¯¯ÐÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÒñòàòñòàòáâàâñòàòáâàâñò#####àòáâàâñòàòáâàâñòàòáâàâñòàòáâàâñòdàòáâàâðñÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔññ >m##!    À?UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUåîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^åîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^åîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^UUUUUUUUUUUUîîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUÕÝÝÝÝÝÝÝÝÝÝ]UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUåîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîÕÝÝÝßÝÝýÝÝÝ]îîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîå^åîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîÕÿÿßÿÝÝÿýÿß_îîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîå^åîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîõýÿÿÿÿÿÿÿýß_îîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîå^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUõýýßÿÝÿßÿßÿ_UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ñÿÿÿÿÿÿÿñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿODÿÿÿÿÿÿÿÿÿÿODODÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôÿDODOôÿDOÿOÿOOÿDÿÿUUUUUUUUîîåîîîå^ñññÿñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôOOODOOOôÿÿOôODOôÿÿåîåîîîå^îîåîîîå^ÿÿñÿÿññÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôOôOOOOÿOOÿÿOÿOÿOÿÿåîåîîîå^îîåîîîå^ÿÿÿñÿÿÿÿÿñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿôÿDOOOôOôÿÿOôÿOOôÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿñÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿOÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿDDDDîÿÿÿÿÿÿÿÿÿÿÿDDDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿTTUEÿÿîþÿÿÿÿÿÿÿÿTTUEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2ÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿïîþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUETUÿÿîþÿÿÿÿÿÿÿÿUETUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ 3ÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUTÿÿÿÿÿÿÿÿÿÿÿÿUUUTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ"ÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUEUîþÿÿÿÿÿÿÿÿÿÿUUEUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ #ÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUDUîþÿÿÿÿÿÿÿÿÿÿUUDUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2# #ÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUDTTÿÿïîÿÿÿÿÿÿÿÿUDTTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ2# #ÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUTUTÿÿïîÿÿÿÿÿÿÿÿUTUTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿEDUUÿÿÿÿÿÿÿÿEDUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿ÷p2ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿEEUUÿÿÿÿÝÿÿÿÿEEUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿ 3ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿEEDUÿÿÿÿ@D @ÿÿÿÿEEDUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïîþÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿfff"ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUETÿÿÿÿ@DÝÿÿÿÿUUETÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿoÌfÌ #ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÐDD ÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿofff2# #ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUDTUÿÿÿÿÐDDÿÿÿÿUDTUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿff2# #ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUTEUÿÿÿÿDDÿÿÿÿUTEUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿoo ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿEUETÿÿÿÿÿÿÿÿEUETÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿEUETÿÿÿÿÿÿÿÿîÿÿÿEUETÿÿÿÿÿÿÿÿÿÿÿÿ÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUTEUÿÿÿÿÿÿÿÿÿÿîþUTEUÿÿÿÿÿÿÿÿÿÿÿ÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUDTUÿÿÿÿÿÿÿÿÿÿîþUDTUÿÿÿÿÿÿÿÿÿÿÿÿ÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿfffÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUETÿÿÿÿÿÿÿÿîþÿÿUUETÿÿÿÿÿÿÿÿÿÿoÌfÌöÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿEEDUÿÿÿÿÿÿÿÿîþÿÿEEDUÿÿÿÿÿÿÿÿÿÿoffföÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿEEUUÿÿÿÿÿÿÿÿÿÿïîEEUUÿÿÿÿÿÿÿÿÿÿÿfÿfÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿEDUUÿÿÿÿÿÿÿÿÿÿïîEDUUÿÿÿÿÿÿÿÿÿÿooÿööÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUTUTîÿÿÿÿÿÿÿÿÿÿÿUTUTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUUUUUUUUUÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUDTTÿÿîþÿÿÿÿÿÿÿÿUDTTÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUDUÿÿîþÿÿÿÿÿÿÿÿUUDUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUEUÿÿÿÿÿÿÿÿÿÿÿÿUUEUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUUUUUUUUUÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿETETîþÿÿÿÿÿÿÿÿÿÿETETÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUTUUîþÿÿÿÿÿÿÿÿÿÿUTUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿTEUEÿÿïîÿÿÿÿÿÿÿÿTEUEÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿDDDDÿÿïîÿÿÿÿÿÿÿÿDDDDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUUUUUUUUUÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷ÿîÿÿÿÿÿÿÿÿÿÿÿÿÿ�™ÿÿ�™ÿˆùÿåîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷ÿÿîþÿÿÿÿÿÿÿÿˆÿ�™øÿ™™™™‰ÿåîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ÷ÿÿÿîþÿÿÿÿÿÿÿ�™ÿ™™‰Ÿ™™™ª©úåîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿofföÿÿÿÿÿÿÿÿÿÿÿ�˜ù˜™™™™™™ú©ÿUUUUUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÆlÆlîþÿÿÿÿÿÿÿÿÿÿ˜‰˜˜™™™˜™ú©ÿåîåîîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿffffîþÿÿÿÿÿÿÿÿÿÿ™™™�™™™ÿ™ª©úåîåîîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿoöoöÿÿïîÿÿÿÿÿÿÿÿ™™™ÿ™™™ÿ™™™øåîåîîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿööooÿÿïîÿÿÿÿÿÿÿÿŸ™ùÿ�˜øÿÿˆˆÿUUUUUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïîþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿïîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿ÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿ÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿ÷ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿofföÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïîÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÆlÆlÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿffffÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿþÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿoöoöÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿïþÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿööooÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿ ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿ2ÿÿÿÿÿÿ÷ÿpÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿ 3ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿ"ÿÿÿÿÿÿfffïîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîåîîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿ #ÿÿÿÿÿoÌfÌïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîåîîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿ2# #ÿÿÿÿÿofffÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîåîîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿ2# #ÿÿÿÿÿÿfÿfÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿ ÿÿÿÿÿooÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿ2ÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿïÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿ 3ÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿþÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿ"ÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿîÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿ #ÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿîÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿ2# #ÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿïþåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿ2# #ÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿïþåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿ ÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿÿÿÿÿïîþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîî^ÿÿÿÿÿÿÿÿÿÿÿÿåîî^îîî^UUUUUUUUÿÿÿÿÿÿîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿïîÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^îîåîîîå^ÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿïþÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿåîå^ÿÿÿÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?3ÿÿÿÿÿÿUUUUôDÿÿÿÿÿÿÿÿÿÿUUUUUUUUîîî^îîî^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3óÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3óÿÿÿÿÿÿÿÿÿÿOÝôôÿÿÿÿÿÿÿÿåîî^îîî^îîî^îîî^ÿDÿÿÿÿÿÿÿÿÿÿÿDÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?3ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?<<ÿÿÿÿÿñÿÿÿÔÝMÿÿÿOôÿÿDôåîî^îîî^îîî^îîî^OÿÿDÿôOOÿDOÿOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÃÃóÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÃ?ÏóÿÿññÿñÿÿOÔÝMôÿÿôôÿôôÿåîî^îîî^UUUUUUUUÿôOÿOOOôOOÿÿOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?üó<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?Ã<ÌóÿÿÿÿÿÿOÔÝMôÿÿôôOÿDÿUUUUUUUUîîåîîîå^ÿOOÿOOOÿOôOÿOOÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ3ÌÃ<ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?<#2üÿÿñññÿÿÿÿÔÝMÿÿÿôôôÿÿôåîåîîîå^îîåîîîå^OôÿDÿôOÿÿDÿÿOôÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÃ3"Ãÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?3#2óÿÿñññÿÿÿOOÝôÿÿÿDÿÿÿDÿåîåîîîå^îîåîîîå^ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ33"3ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ333ÿÿÿÿÿÿÿÿÿÿÿDOÿÿÿÿÿÿÿÿÿåîåîîîå^UUUUUUUUÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ?33óÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿUUUUUUUUåîî^îîî^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUîîî^îîî^åîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^åîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^îîî^UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUåîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîåîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîåîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîîîåîUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUûÿ-- === Flush to Freedom === -- title: Flush To Freedom -- author: MattC-Bit -- desc: A game about a sentient piece of poop trying to escape the sewers -- script: lua -- -- // --------------------------- -- -- // SECTION 1 : Constantes et Configurations -- -- // --------------------------- local SCREEN_WIDTH = 240 local SCREEN_HEIGHT = 136 local TILE_SIZE = 8 local PLAYER_SPEED = 32 local FRAME_RATE = 60 local DEFAULT_LIVES = 7 local STARTING_SCORE = 0 local colorPalette = { transparent = 0, red = 1, brown = 2, lightBrown = 3, yellow = 4, darkBlue = 5, lightGreen = 6, green = 7, purple = 8, pink = 9, beige = 10, royalBlue = 11, white = 12, electricBlue = 13, grey = 14, black = 15, } local flagsConfig = { BLOCKING_FLAG = 0, -- Flag 0 : Obstacles bloquants (murs, bordures, débris) SPAWNER_FLAG = 1, -- Flag 1 : Spawners (points où les ennemis/objets apparaissent) COLLECTIBLE_FLAG = 2, -- Flag 2 : Collectibles (fragments, bonus) SPECIAL_EFFECT_FLAG = 3, -- Flag 3 : Tiles avec effets spéciaux (glissant, ralentissement) DANGER_FLAG = 4, -- Flag 4 : Obstacles dangereux (ex: jets de vapeur, rivières) INVISIBLE_FLAG = 5 -- Flag 5 : Tiles invisibles (ex: spawnpoints, triggers) } local effectTypes = { speedUp = {percentage = 50, duration = 300}, -- +20% de vitesse pendant 5 secondes (300 frames) speedDown = {percentage = 50, duration = 300} -- -20% de vitesse pendant 5 secondes (300 frames) } -- ============================= -- // --------------------------- -- // SECTION 2 : Variables Globales -- // --------------------------- gameState = { currentLevel = 1, score = STARTING_SCORE, lives = DEFAULT_LIVES, requiredFragments = 5, gameOver = false, isWinScreen = false, isGameOver = false, timeLeft = 0, spawnX = nil, spawnY = nil } player = { x = 10, y = 120, width = TILE_SIZE*1, height = TILE_SIZE*1, targetX = 10, targetY = 120, speed = PLAYER_SPEED, speedMultiplier = 3.0, sprite = 257, fragmentsCollected = 0, prestigeLevel = 0, isMoving = false, isColliding = false, visitedTiles = {}, isInvincible = false } -- Entités entities = {} entityConfigs = { -- Enemies mutant = { type = "enemy", behavior = "randomMovement", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, speed = 20.0, -- Vitesse en pixel par secondes sprites = {258}, timer = 0, patrolRange = 3 * TILE_SIZE -- Zone de patrouille de 3 tiles }, parasite = { type = "enemy", behavior = "patrol", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, speed = 20.0, -- Vitesse de déplacement en pixels par seconde sprites = {274}, patrolRange = 3, initialDelay = 0, direction = 1, timer = 0 }, worm1 = { type = "enemy", behavior = "linearMovement", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, speed = 1.5, sprites = {290, 289, 288} -- [1] = tête, [2] = corps, [3] = queue }, worm2 = { type = "enemy", behavior = "cyclicEmergence", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, speed = 1.0, sprites = {336, 337, 338, 339}, -- [1] = enfoncé, [2] = émerger, [3] = émergé, [4] = disparaître state = "enfoncé", initialDelay = 0, timer = 0, emergenceHeight = TILE_SIZE }, levelSwitcherTileLeft = { type = "interactable", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "switchLevel", sprites = {362}, -- Remplacez par votre sprite isActive = true }, levelSwitcherTileCenter = { type = "interactable", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "switchLevel", sprites = {363}, -- Remplacez par votre sprite isActive = true }, levelSwitcherTileRight = { type = "interactable", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "switchLevel", sprites = {364}, -- Remplacez par votre sprite isActive = true }, spawnPointTile = { type = "interactable", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "setSpawnPoint", sprites = {0}, -- Remplacez par votre sprite isActive = true }, -- Collectibles fragment1 = {type = "collectible", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {259}, scoreValue = 100}, fragment2 = {type = "collectible", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {275}, scoreValue = 150}, fragment3 = {type = "collectible", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {291}, scoreValue = 200}, fragment4 = {type = "collectible", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {307}, scoreValue = 250}, fragment5 = {type = "collectible", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {323}, scoreValue = 300}, easterEgg = {type = "collectible", subType = "bonus", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {269}, scoreValue = 500}, -- Interactables portalTrans = {type = "interactable", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "nextLevel", sprites = {260}, isActive = false}, portalPrestige = {type = "interactable", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "prestige", sprites = {276}, isActive = true}, fanaticFec = {type = "interactable", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "speedUp", sprites = {277}}, fanaticTrans = {type = "interactable", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "speedDown", sprites = {261}}, -- Special Tiles slipperyTile = {type = "specialTile", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "slippery", sprites = {329}}, dirtTile = {type = "specialTile", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "slowPlayer", sprites = {345}}, brittleTile = {type = "specialTile", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, effect = "collapse", sprites = {361}}, -- Obstacles générés par les spawners vapor1 = {type = "obstacle", obstacleType = "vapor", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {263}}, river1 = {type = "obstacle", obstacleType = "riverTile", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {265}}, debris2 = {type = "obstacle", obstacleType = "debris_floating", x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {283}}, -- Obstacles infranchissables debris1 = {type = "obstacle", obstacleType = "debris_static", isSolid = true, x = 0, y = 0, width = TILE_SIZE, height = TILE_SIZE, sprites = {267}}, } entitySpawnPoint = { [100] = "spawnPointTile", [101] = "levelSwitcherTileLeft", [102] = "levelSwitcherTileCenter", [103] = "levelSwitcherTileRight", [33] = "mutant", [49] = "parasite", [81] = "worm2", [35] = "fragment1", [51] = "fragment2", [67] = "fragment3", [83] = "fragment4", [99] = "fragment5", [7] = "portalTrans", [8] = "portalPrestige", [37] = "fanaticTrans", [53] = "fanaticFec", [5] = "riverSpawner1", [21] = "floatingDebrisSpawner1", [39] = "debris1", [115] = "easterEgg", [1] = "slipperyTile", [2] = "dirtTile", [3] = "brittleTile" } spawners = {} spawnerConfigs = { wormSpawner1 = { type = "spawner", spawnType = "worm1", spawnRate = 300, -- Nombre de frames entre chaque spawn maxSize = 4, -- Nombre maximum de segments par worm speed = 1.0, -- Vitesse de déplacement des worms générés active = true, }, floatingDebrisSpawner = { type = "spawner", spawnType = "debris2", spawnRate = 300, -- Nombre de frames entre chaque spawn maxSize = 1, -- Nombre maximum de débris flottants speed = 1.0, -- Vitesse de déplacement des débris flottants générés active = true, }, vaporSpawner = { type = "spawner", spawnType = "vapor1", spawnRate = 300, -- Nombre de frames entre chaque spawn maxSize = 4, -- Nombre maximum de jets de vapeur speed = 5.0, -- Vitesse de déplacement des jets de vapeur générés active = true, } } spawnerLocation = { [65] = "wormSpawner1", [21] = "floatingDebrisSpawner", [4] = "vaporSpawner", } levels = { { title = "Level 1", levelTag = "Dev", mapX = 0, mapY = 0, mapWidth = 30, mapHeight = 17, playerStartX = TILE_SIZE*2, playerStartY = TILE_SIZE*10, timeLeft = 60 -- temps en secondes }, { title = "Level 2", levelTag = "Start", mapX = 30, mapY = 0, mapWidth = 30, mapHeight = 17, playerStartX = TILE_SIZE*2, playerStartY = TILE_SIZE*10, timeLeft = 60 -- temps en secondes }, { title = "Level 3", mapX = 60, mapY = 0, mapWidth = 30, mapHeight = 17, playerStartX = TILE_SIZE*2, playerStartY = TILE_SIZE*10, timeLeft = 60 -- temps en secondes }, { title = "Level 4", mapX = 90, mapY = 0, mapWidth = 30, mapHeight = 17, playerStartX = TILE_SIZE*2, playerStartY = TILE_SIZE*10, timeLeft = 60 -- temps en secondes }, { title = "Level 5", mapX = 120, mapY = 0, mapWidth = 30, mapHeight = 17, playerStartX = TILE_SIZE*2, playerStartY = TILE_SIZE*10, timeLeft = 60 -- temps en secondes }, { title = "Level 6", levelTag = "Final", mapX = 150, mapY = 0, mapWidth = 30, mapHeight = 17, playerStartX = TILE_SIZE*2, playerStartY = TILE_SIZE*10, timeLeft = 60 -- temps en secondes }, { title = "Dev Ground", mapX = 210, mapY = 119, mapWidth = 30, mapHeight = 17, playerStartX = TILE_SIZE*2, playerStartY = TILE_SIZE*10, timeLeft = 0 -- Pas de limite de temps pour l'écran de Game Over }, { title = "Game Over Screen", mapX = 180, mapY = 0, mapWidth = 30, mapHeight = 17, playerStartX = TILE_SIZE*2, playerStartY = TILE_SIZE*10, timeLeft = 0 -- Pas de limite de temps pour l'écran de Game Over }, { title = "Win Screen", mapX = 210, mapY = 0, mapWidth = 30, mapHeight = 17, playerStartX = TILE_SIZE * 2, playerStartY = TILE_SIZE * 10, timeLeft = 0 -- No time limit for the win screen } } local currentMapX = 0 local currentMapY = 0 -- ============================= -- // --------------------------- -- // SECTION 3 : Gestion des Niveaux -- // --------------------------- function init() loadLevel() -- Initialisation des variables par defaut du joueur player.width = TILE_SIZE*1 player.height = TILE_SIZE*1 player.speed = PLAYER_SPEED player.speedMultiplier = 3.5 player.sprite = 257 player.fragmentsCollected = 0 player.isColliding = false player.visitedTiles = {} -- TODO: Autres initialisations... -- Message de test addDebugMessage("Initialisation du jeu terminée.", colorPalette.lightGreen, "GameState") end function loadLevel() local level = getCurrentLevel() currentMapX = level.mapX currentMapY = level.mapY local prestigeFactor = 1 - (player.prestigeLevel * 0.1) -- Réduction du temps en fonction du niveau de prestige (10% en moins par niveau) gameState.timeLeft = math.floor(level.timeLeft * prestigeFactor) -- Initialiser le joueur player.isMoving = false player.visitedTiles = {} -- Charger la map du niveau drawMap() -- Initialiser la variable de spawn local spawnFound = false -- Chercher et traiter les tiles de spawn du joueur for y = level.mapY, level.mapY + level.mapHeight - 1 do for x = level.mapX, level.mapX + level.mapWidth - 1 do local tile = mget(x, y) if tile == 100 then -- Tile numéro 100 représente le spawn du joueur gameState.spawnX = (x - currentMapX) * TILE_SIZE gameState.spawnY = (y - currentMapY) * TILE_SIZE -- Remplacer le tile de spawn par un tile vide -- mset(x, y, 0) spawnFound = true break end end if spawnFound then break end end -- Si aucun point de spawn trouvé, utiliser la position par défaut if not gameState.spawnX or not gameState.spawnY then gameState.spawnX = level.playerStartX gameState.spawnY = level.playerStartY end -- Positionner le joueur au point de spawn player.x = gameState.spawnX player.y = gameState.spawnY player.targetX = player.x player.targetY = player.y -- Réinitialiser les entités et spawners entities = {} spawners = {} -- Générer les entités en fonction des tiles de la map for y = level.mapY, level.mapY + level.mapHeight - 1 do for x = level.mapX, level.mapX + level.mapWidth - 1 do local tile = mget(x, y) -- Gérer les spawnPoints pour les entités statiques local entityName = entitySpawnPoint[tile] if entityName then local entityX = (x - currentMapX) * TILE_SIZE local entityY = (y - currentMapY) * TILE_SIZE createEntity(entityName, entityX, entityY) -- Remplacer le tile après création de l'entité -- mset(x, y, 0) end -- Gérer les spawnPoints pour les spawners local spawnerName = spawnerLocation[tile] if spawnerName then local spawnerX = (x - currentMapX) * TILE_SIZE local spawnerY = (y - currentMapY) * TILE_SIZE createSpawner(spawnerName, spawnerX, spawnerY) -- Remplacer le tile après création du spawner -- mset(x, y, 0) end end end -- Après avoir créé toutes les entités, mettre à jour leurs vitesses updateEntitiesSpeed() end function resetGameState() gameState.currentLevel = 1 gameState.score = 0 gameState.lives = DEFAULT_LIVES gameState.requiredFragments = 5 gameState.gameOver = false gameState.isWinScreen = false player.fragmentsCollected = 0 player.prestigeLevel = 0 loadLevel() respawnPlayer() end function gameOver() gameState.isGameOver = true gameState.currentLevel = 8 loadLevel() addDebugMessage("Game Over! Réinitialisation du jeu.", colorPalette.red, "GameState") end function handleTimeOut() addDebugMessage("Temps écoulé ! Perte d'une vie", colorPalette.red, "GameState") handlePlayerDeath() end function displayWinScreen() gameState.currentLevel = #levels -- Set to the last level (win screen) loadLevel() gameState.isWinScreen = true end function drawGameOverScreen() -- Dessiner la map de l'écran de Game Over drawMap() -- Afficher un message de Game Over print("Game Over!", SCREEN_WIDTH / 2 - 40, SCREEN_HEIGHT / 2 - 10, colorPalette.red, false, 2, true) print("Appuyez sur R pour recommencer", SCREEN_WIDTH / 2 - 60, SCREEN_HEIGHT / 2 + 10, colorPalette.red, false, 1, true) end function drawWinScreen() -- Draw the win screen map drawMap() -- Display a win message print("Felicitations !", SCREEN_WIDTH / 2 - 43, SCREEN_HEIGHT / 2 - 10, colorPalette.electricBlue, false, 2, true) print ("Vous avez atteind la transcendance !", SCREEN_WIDTH / 2 - 60, SCREEN_HEIGHT / 2 + 5, colorPalette.electricBlue, false, 1, true) print("Score final: " .. gameState.score, SCREEN_WIDTH / 2 - 30, SCREEN_HEIGHT / 2 + 15, colorPalette.yellow, false, 1, true) print("Appuyez sur R pour recommencer", SCREEN_WIDTH / 2 - 55, SCREEN_HEIGHT / 2 + 30, colorPalette.lightGreen, false, 1, true) end function getCurrentLevel() return levels[gameState.currentLevel] end function drawMap() local level = getCurrentLevel() for y = 0, level.mapHeight - 1 do for x = 0, level.mapWidth - 1 do local tileId = mget(level.mapX + x, level.mapY + y) if not fget(tileId, flagsConfig.INVISIBLE_FLAG) then spr(tileId, x * TILE_SIZE, y * TILE_SIZE, 0) end end end end -- // --------------------------- -- // SECTION 4 : Gestion des Entités et Spawners -- // --------------------------- function createEntity(name, x, y) local config = entityConfigs[name] if config then local entity = { name = name, x = x, y = y, type = config.type, width = config.width or TILE_SIZE, height = config.height or TILE_SIZE, } -- Propriétés supplémentaires facultatives if config.sprites then entity.sprites = config.sprites entity.spriteIndex = 1 -- Par défaut, utiliser le premier sprite end if config.speed then entity.baseSpeed = config.speed -- Stocker la vitesse de base entity.currentSpeed = entity.baseSpeed * obstacleSpeedModifier -- Vitesse actuelle local speedMultiplier = math.random(80, 120) / 100 entity.delta = (entity.currentSpeed * speedMultiplier) / FRAME_RATE end if config.patrolRange then if config.behavior == "patrol" then entity.patrolStart = x - (config.patrolRange * TILE_SIZE) entity.patrolEnd = x + (config.patrolRange * TILE_SIZE) else entity.patrolRange = config.patrolRange entity.patrolCenterX = x entity.patrolCenterY = y end end if config.behavior == "cyclicEmergence" then entity.baseY = y end if config.initialDelay then entity.initialDelay = math.random(0, FRAME_RATE) end if config.subType then entity.subType = config.subType end if config.direction then entity.direction = config.direction end if config.behavior then entity.behavior = config.behavior end if config.scoreValue then entity.scoreValue = config.scoreValue end if config.obstacleType then entity.obstacleType = config.obstacleType end if config.isSolid then entity.isSolid = config.isSolid end if config.isActive then entity.isActive = config.isActive end if config.state then entity.state = config.state end if config.timer then entity.timer = config.timer end if config.emergenceHeight then entity.emergenceHeight = config.emergenceHeight end if config.effect then entity.effect = config.effect end -- Ajouter une propriété pour les buffs/débuffs if config.type == "enemy" or config.type == "obstacle" then entity.currentSpeed = entity.baseSpeed * obstacleSpeedModifier end table.insert(entities, entity) addDebugMessage("Entité créée : " .. name, colorPalette.electricBlue, "Entity") return entity else addDebugMessage("Type d'entité inconnu : " .. name, colorPalette.red, "Entity") end end function createSpawner(name, x, y) local config = spawnerConfigs[name] if config then local spawner = { name = name, x = x, y = y, type = config.type, spawnType = config.spawnType, spawnRate = math.max(50, math.floor(config.spawnRate * (1 - player.prestigeLevel * 0.1))), -- Réduire le délai de spawn en fonction du niveau de prestige timer = math.random(1, config.spawnRate), maxSize = config.maxSize, speed = config.speed, active = config.active, currentEntity = nil } table.insert(spawners, spawner) spawnEntityFromSpawner(spawner) addDebugMessage("Spawner créé : " .. name, colorPalette.electricBlue, "Spawner") return spawner else addDebugMessage("Type de spawner inconnu : " .. name, colorPalette.red, "Spawner") end end function updateSpawners() for _, spawner in ipairs(spawners) do if spawner.active then spawner.timer = spawner.timer - 1 if spawner.timer <= 0 then spawnEntityFromSpawner(spawner) spawner.timer = spawner.spawnRate -- Reset du timer end end end end function spawnEntityFromSpawner(spawner) if updateSpawnerFunctions[spawner.name] then updateSpawnerFunctions[spawner.name](spawner) else addDebugMessage("Type de spawner inconnu : " .. spawner.name, colorPalette.red, "Spawner") end end function updateWormSpawner1(spawner) -- Déterminer la taille du ver local size = math.random(3, spawner.maxSize) -- Définir la vitesse (pixels par seconde) local speed = math.random(20, 50) local direction = 1 -- 1 pour la droite, -1 pour la gauche (vous pouvez le randomiser si nécessaire) -- Créer la tête du ver local wormHead = createEntity(spawner.spawnType, spawner.x, spawner.y) if not wormHead then addDebugMessage("Erreur lors de la création du Worm Leader", colorPalette.red, "Spawner") return end -- Initialiser les propriétés de la tête local prestigeMultiplier = 1 + (player.prestigeLevel * 0.2) wormHead.baseSpeed = speed * prestigeMultiplier wormHead.currentSpeed = wormHead.baseSpeed * obstacleSpeedModifier wormHead.size = size wormHead.direction = direction wormHead.segments = {} wormHead.positionHistory = {} wormHead.moveTimer = 0 wormHead.spriteIndex = 1 -- Assigner le sprite de la tête -- Calculer le déplacement par frame local delta = wormHead.currentSpeed / FRAME_RATE -- Calculer le nombre de positions par segment (pour l'espacement) local positionsPerSegment = math.ceil(TILE_SIZE / delta) -- Générer les positions initiales pour `positionHistory` for i = (size * positionsPerSegment), 0, -1 do local posX = wormHead.x - (wormHead.direction * delta * i) table.insert(wormHead.positionHistory, {x = posX, y = wormHead.y}) end -- Générer les segments (du corps et de la queue) for i = 1, size - 1 do local segment = createEntity(spawner.spawnType, wormHead.x, wormHead.y) if not segment then addDebugMessage("Erreur lors de la création du segment " .. i, colorPalette.red, "Spawner") return end -- Attribuer les propriétés nécessaires au segment segment.isFollower = true segment.direction = direction -- Différencier le corps et la queue par un sprite if i == size - 1 then segment.spriteIndex = 3 -- Sprite spécifique pour la queue else segment.spriteIndex = 2 -- Sprite pour le corps end -- Ajouter le segment à la liste des segments du ver table.insert(wormHead.segments, segment) end -- Ajouter la tête du ver aux entités pour qu'elle soit mise à jour table.insert(entities, wormHead) addDebugMessage("Ver créé avec " .. size .. " segments!", colorPalette.lightGreen, "Spawner") end function updateFloatingDebrisSpawner(spawner) -- TODO: Implémenter la logique de spawn des débris flottants end function updateVaporSpawner(spawner) -- TODO: Implémenter la logique de spawn des jets de vapeur end updateSpawnerFunctions = { wormSpawner1 = updateWormSpawner1, floatingDebrisSpawner = updateFloatingDebrisSpawner, vaporSpawner = updateVaporSpawner } -- ============================= -- // --------------------------- -- // SECTION 5 : Gestion des Comportements des Entités -- // --------------------------- function updateEntities() for _, entity in ipairs(entities) do if updateFunctions[entity.type] then updateFunctions[entity.type](entity) else -- addDebugMessage("Aucun gestionnaire de mise à jour pour le type : " .. entity.type, colorPalette.red, "Entity") end end end function updateEnemy(enemy) if enemy.isFollower then return -- Permet de garder les segments du ver synchronisés avec la tête (leader) end if updateBehaviorFunctions[enemy.behavior] then updateBehaviorFunctions[enemy.behavior](enemy) else addDebugMessage("Comportement inconnu : " .. enemy.behavior, colorPalette.red, "Entity") end end function updateRandomMovement(enemy) -- Si l'ennemi n'a pas de case cible définie, ou s'il a atteint sa case cible, choisir une nouvelle direction if enemy.targetTileX == nil or (enemy.x == enemy.targetTileX and enemy.y == enemy.targetTileY) then local directions = {"left", "right", "up", "down"} local validDirections = {} -- Déterminer les directions valides en vérifiant la zone de patrouille et les obstacles for _, dir in ipairs(directions) do local targetX, targetY = enemy.x, enemy.y -- Définir la tuile cible en fonction de la direction actuelle if dir == "left" then targetX = enemy.x - TILE_SIZE elseif dir == "right" then targetX = enemy.x + TILE_SIZE elseif dir == "up" then targetY = enemy.y - TILE_SIZE elseif dir == "down" then targetY = enemy.y + TILE_SIZE end -- Vérifier si la tuile cible est dans la zone de patrouille local dxFromCenter = targetX - enemy.patrolCenterX local dyFromCenter = targetY - enemy.patrolCenterY -- Ajouter la direction à la liste des directions valides si elle est dans la zone de patrouille et n'est pas bloquée if math.abs(dxFromCenter) <= enemy.patrolRange and math.abs(dyFromCenter) <= enemy.patrolRange then if not isPathBlocked(enemy, targetX - enemy.x, targetY - enemy.y) then table.insert(validDirections, dir) end end end -- Si aucune direction n'est valide, rester sur place if #validDirections == 0 then return end -- Choisir une direction aléatoire parmi les directions valides enemy.direction = validDirections[math.random(1, #validDirections)] -- Déclarer la tuile cible en fonction de la direction choisie if enemy.direction == "left" then enemy.targetTileX = enemy.x - TILE_SIZE enemy.targetTileY = enemy.y elseif enemy.direction == "right" then enemy.targetTileX = enemy.x + TILE_SIZE enemy.targetTileY = enemy.y elseif enemy.direction == "up" then enemy.targetTileX = enemy.x enemy.targetTileY = enemy.y - TILE_SIZE elseif enemy.direction == "down" then enemy.targetTileX = enemy.x enemy.targetTileY = enemy.y + TILE_SIZE end end -- Calculer le déplacement progressif vers la tuile cible local delta = enemy.currentSpeed / FRAME_RATE local dx, dy = 0, 0 -- Calculer la distance à parcourir pour atteindre la tuile cible if enemy.x < enemy.targetTileX then dx = math.min(delta, enemy.targetTileX - enemy.x) elseif enemy.x > enemy.targetTileX then dx = -math.min(delta, enemy.x - enemy.targetTileX) end if enemy.y < enemy.targetTileY then dy = math.min(delta, enemy.targetTileY - enemy.y) elseif enemy.y > enemy.targetTileY then dy = -math.min(delta, enemy.y - enemy.targetTileY) end -- Tenter de déplacer l'ennemi vers la tuile cible if not attemptMove(enemy, dx, dy) then -- Si le déplacement est bloqué, annuler la tuile cible pour choisir une nouvelle direction enemy.targetTileX = nil enemy.targetTileY = nil addDebugMessage("Mutant bloqué, changement de direction", colorPalette.red, "Entity") end end function updatePatrol(enemy) -- Vérifie si le timer de l'ennemi est inférieur à son délai initial if enemy.timer < enemy.initialDelay then enemy.timer = enemy.timer + 1 return end local delta = enemy.currentSpeed / FRAME_RATE -- Calcule le déplacement horizontal en fonction de la direction de l'ennemi local dx = enemy.direction * delta -- Vérifie si le chemin est bloqué dans la direction actuelle if not attemptMove(enemy, dx, 0) then -- Inverse la direction de l'ennemi en cas d'obstacle enemy.direction = enemy.direction * -1 addDebugMessage("Parasite change de direction à cause d'un obstacle", colorPalette.red, "Entity") return end -- Met à jour la position horizontale de l'ennemi enemy.x = enemy.x + dx -- Vérifie si l'ennemi a atteint la fin de sa patrouille if enemy.x >= enemy.patrolEnd then enemy.x = enemy.patrolEnd enemy.direction = -1 elseif enemy.x <= enemy.patrolStart then enemy.x = enemy.patrolStart enemy.direction = 1 end end function updateLinearMovement(wormHead) -- Calculer le déplacement par frame local delta = wormHead.currentSpeed / FRAME_RATE local dx = wormHead.direction * delta -- Vérifier si le ver touche un obstacle bloquant ou sort des limites if isPathBlocked(wormHead, dx, 0) or wormHead.x < 0 or wormHead.x > SCREEN_WIDTH or wormHead.y < 0 or wormHead.y > SCREEN_HEIGHT then despawnWorm(wormHead) return end -- Si le ver n'a pas de position cible, définir la prochaine position cible if not wormHead.targetX then wormHead.targetX = wormHead.x + (wormHead.direction * TILE_SIZE) end -- Déplacer la tête vers la position cible if wormHead.x < wormHead.targetX then wormHead.x = math.min(wormHead.x + delta, wormHead.targetX) elseif wormHead.x > wormHead.targetX then wormHead.x = math.max(wormHead.x - delta, wormHead.targetX) end -- Enregistrer la position actuelle dans l'historique table.insert(wormHead.positionHistory, {x = wormHead.x, y = wormHead.y}) -- Limiter la taille de l'historique pour éviter une utilisation excessive de mémoire local positionsPerTile = math.ceil(TILE_SIZE / delta) local maxHistorySize = wormHead.size * positionsPerTile + 1 if #wormHead.positionHistory > maxHistorySize then table.remove(wormHead.positionHistory, 1) end -- Mise à jour des segments en utilisant un tableau d'historique de positions for i, segment in ipairs(wormHead.segments) do -- Calculer le nombre de positions à retarder pour chaque segment local delay = i * positionsPerTile -- Calculer l'indice dans positionHistory local index = #wormHead.positionHistory - delay if index > 0 then local targetPosition = wormHead.positionHistory[index] segment.x = targetPosition.x segment.y = targetPosition.y else -- Si pas assez d'historique, placer le segment sur la tête segment.x = wormHead.x segment.y = wormHead.y end end -- Si la tête a atteint la position cible, définir une nouvelle position cible if wormHead.x == wormHead.targetX then wormHead.targetX = wormHead.x + (wormHead.direction * TILE_SIZE) end end function updateCyclicEmergence(enemy) -- Appliquer le délai initial si nécessaire if enemy.initialDelay > 0 then enemy.initialDelay = enemy.initialDelay - 1 return -- Attendre jusqu'à ce que le délai initial soit écoulé end -- Décrémenter le timer de l'ennemi enemy.timer = enemy.timer - 1 -- Changer d'état lorsque le timer atteint zéro if enemy.timer <= 0 then if enemy.state == "enfoncé" then -- State: Emerger enemy.state = "émerger" enemy.timer = FRAME_RATE * 1.5 enemy.spriteIndex = 2 elseif enemy.state == "émerger" then -- State: Emergé enemy.state = "émergé" enemy.timer = FRAME_RATE * 2.5 enemy.spriteIndex = 3 if collides(player, enemy) then handlePlayerDeath() addDebugMessage("Le joueur a été touché par le ver", colorPalette.red, "Entity") end elseif enemy.state == "émergé" then -- State: Disparaître enemy.state = "disparaître" enemy.timer = FRAME_RATE * 1 enemy.spriteIndex = 4 elseif enemy.state == "disparaître" then -- State: Enfoncé enemy.state = "enfoncé" enemy.timer = FRAME_RATE * 2 enemy.spriteIndex = 1 end end -- Gestion des mouvements en fonction de l'état -- local delta = enemy.currentSpeed / FRAME_RATE -- if enemy.state == "émerger" then -- -- Monter progressivement vers la position cible (émergence) -- enemy.y = math.max(enemy.y - delta, enemy.baseY - enemy.emergenceHeight) -- elseif enemy.state == "disparaître" then -- -- Descendre progressivement vers la position de base (disparition) -- enemy.y = math.min(enemy.y + delta, enemy.baseY) -- end end updateBehaviorFunctions = { randomMovement = updateRandomMovement, patrol = updatePatrol, linearMovement = updateLinearMovement, cyclicEmergence = updateCyclicEmergence } function updateCollectible(collectible) -- TODO: Implementer la logique de mise à jour des collectibles end function updateInteractable(interactable) -- TODO: Implementer la logique de mise à jour des objets interactifs end function updateSpecialTile(tile) -- TODO: Implementer la logique de mise à jour des tiles spéciales end function updateObstacle(obstacle) -- TODO: Implementer la logique de mise à jour des obstacles end updateFunctions = { enemy = updateEnemy, collectible = updateCollectible, interactable = updateInteractable, specialTile = updateSpecialTile, obstacle = updateObstacle } function drawEntities() for _, entity in ipairs(entities) do local spriteIndex = entity.spriteIndex or 1 spr(entity.sprites[spriteIndex], entity.x, entity.y, colorPalette.transparent) end end -- ============================= -- // --------------------------- -- // SECTION 6 : Système d'Effets -- // --------------------------- activeEffects = {} obstacleSpeedModifier = 1.0 function applyEffect(effectType) local effectConfig = effectTypes[effectType] if effectConfig then -- Vérifier si un effet de ce type est déjà actif local existingEffect = nil for _, effect in ipairs(activeEffects) do if effect.type == effectType then existingEffect = effect break end end if existingEffect then -- Mettre à jour la durée de l'effet existant existingEffect.duration = effectConfig.duration addDebugMessage("Effet mis à jour: " .. effectType .. " (" .. effectConfig.percentage .. "%) pendant " .. effectConfig.duration .. " frames", colorPalette.electricBlue, "Effect") else -- Ajouter un nouvel effet table.insert(activeEffects, {type = effectType, value = effectConfig.percentage, duration = effectConfig.duration}) addDebugMessage("Effet appliqué: " .. effectType .. " (" .. effectConfig.percentage .. "%) pendant " .. effectConfig.duration .. " frames", colorPalette.electricBlue, "Effect") end -- Mettre à jour la vitesse des entités après avoir appliqué l'effet updateEntitiesSpeed() else addDebugMessage("Type d'effet inconnu: " .. effectType, colorPalette.red, "Effect") end end function updateEffects() -- Réinitialiser le modificateur de vitesse de base local baseModifier = 1.0 + (player.prestigeLevel * 0.1) obstacleSpeedModifier = baseModifier -- Appliquer les effets actifs for _, effect in ipairs(activeEffects) do if effect.type == "speedUp" then obstacleSpeedModifier = obstacleSpeedModifier * (1 + effect.value / 100) elseif effect.type == "speedDown" then obstacleSpeedModifier = obstacleSpeedModifier * (1 - effect.value / 100) end end -- Mettre à jour la durée des effets et les supprimer s'ils sont expirés for i = #activeEffects, 1, -1 do local effect = activeEffects[i] effect.duration = effect.duration - 1 if effect.duration <= 0 then table.remove(activeEffects, i) addDebugMessage("Effet expiré: " .. effect.type, colorPalette.grey, "Effect") end end -- Mettre à jour la vitesse des entités après avoir mis à jour obstacleSpeedModifier updateEntitiesSpeed() end function updateEntitiesSpeed() for _, entity in ipairs(entities) do if entity.baseSpeed then entity.currentSpeed = entity.baseSpeed * obstacleSpeedModifier -- Optionnel : Mettre à jour delta si nécessaire if entity.delta then entity.delta = (entity.currentSpeed * (math.random(80, 120) / 100)) / FRAME_RATE end end end end -- ============================= -- // --------------------------- -- // SECTION 7 : Gestion des Collisions -- // --------------------------- function checkCollisions() local isCurrentlyColliding = false local collidedEntity = nil -- Détecte si il y a une collision avec une entité for _, entity in ipairs(entities) do if collides(player, entity) then isCurrentlyColliding = true collidedEntity = entity if entity.behavior == "cyclicEmergence" and entity.state == "émergé" then handlePlayerDeath() addDebugMessage("Collision fatale avec un ver émergé", colorPalette.red, "Collision") end break end end -- si il y a collision: Met à jour le flag de collision du joueur if isCurrentlyColliding and not player.isColliding then player.isColliding = true handleCollisions(collidedEntity) end -- Si aucune collision n'est trouvée: Réinitialiser le flag de collision if not isCurrentlyColliding then player.isColliding = false end end function handleCollisions(entity) if collisionHandlers[entity.type] then collisionHandlers[entity.type](entity) else addDebugMessage("Aucun gestionnaire de collision pour le type : " .. entity.type, colorPalette.red, "Collision") end end -- GESTIONNAIRES DE COLLISION function handleEnemyCollision(entity) if entity.behavior == "cyclicEmergence" and entity.state ~= "émergé" then -- Si l'entité est un ver en phase "émerger", "disparaître" ou "enfoncé", elle n'est pas dangereuse addDebugMessage("Collision avec un ennemi non dangereux : " .. entity.name, colorPalette.grey, "Collision") return end addDebugMessage("Collision avec un ennemi : " .. entity.name, colorPalette.red, "Collision") handlePlayerDeath() end function handleCollectibleCollection(entity) if entity.type == "collectible" and entity.name:find("fragment") then player.fragmentsCollected = player.fragmentsCollected + 1 addScoreForFragment() elseif entity.type == "collectible" and entity.subType == "bonus" then addScoreForCollectible() end -- Retirer le fragment de la carte for i, e in ipairs(entities) do if e == entity then table.remove(entities, i) break end end -- Active le portail de la transcendance si tous les fragments sont collectés if player.fragmentsCollected >= gameState.requiredFragments then for _, interactable in ipairs(entities) do if interactable.name == "portalTrans" then interactable.isActive = true addDebugMessage("Portail de la transcendance actif!", colorPalette.lightGreen, "Interactable") end end end -- Ajouter un message de debug addDebugMessage("Fragment collecté: " .. entity.name, colorPalette.yellow, "Collectible") end function handleInteractableCollision(entity) if entity.effect == "setSpawnPoint" then gameState.spawnX = entity.x gameState.spawnY = entity.y addDebugMessage("Point de spawn mis à jour à (" .. gameState.spawnX .. ", " .. gameState.spawnY .. ")", colorPalette.lightGreen, "Effect") elseif entity.name == "portalTrans" then if entity.isActive then addDebugMessage("Collision avec le portail de la transcendance", colorPalette.electricBlue, "Collision") local level = getCurrentLevel() if level.levelTag == "Final" then gameState.gameWon = true displayWinScreen() else _switchLevel() end else addDebugMessage("Le portail de la transcendance est inactif", colorPalette.red, "Effect") end elseif entity.name == "portalPrestige" then local level = getCurrentLevel() if level.levelTag == "Final" then entity.isActive = true end if entity.isActive and level.levelTag == "Final" then addDebugMessage("Collision avec le portail du recommencement", colorPalette.green, "Collision") _prestige() -- TODO: Placeholder, implementer le système de prestige else addDebugMessage("Le portail du recommencement est inactif", colorPalette.red, "Effect") end elseif entity.effect == "switchLevel" then addDebugMessage("Changement de niveau via la tuile de changement de niveau", colorPalette.green, "Effect") _switchLevel() elseif entity.effect == "speedUp" then -- Fanatique Fécaliste: Augmente la vitesse des obstacles applyEffect("speedUp") addDebugMessage("Fanatique Fécaliste a appliqué un buff de vitesse.", colorPalette.lightGreen, "Effect") -- Optionnel: Retirer ou désactiver l'entité fanatique après application entity.isActive = false elseif entity.effect == "speedDown" then -- Fanatique Transcendantaliste: Diminue la vitesse des obstacles applyEffect("speedDown") addDebugMessage("Fanatique Transcendantaliste a appliqué un debuff de vitesse.", colorPalette.lightGreen, "Effect") -- Optionnel: Retirer ou désactiver l'entité fanatique après application entity.isActive = false end end function applyTileEffect(tileType) if tileType then -- Vous pouvez implémenter des effets spécifiques ici -- Exemple : -- if tileType == "slippery" then -- player.speedMultiplier = player.speedMultiplier * 0.8 -- end end end function handleObstacleCollision(obstacle) local obstacleType = obstacle.obstacleType if obstacleCollisionHandlers[obstacleType] then obstacleCollisionHandlers[obstacleType](obstacle) else addDebugMessage("Aucun gestionnaire de collision pour l'obstacle de type : " .. obstacleType, colorPalette.red, "Collision") end end function handleVaporCollision(obstacle) addDebugMessage("Collision avec : " .. obstacle.name, colorPalette.white, "Collision") handlePlayerDeath() -- Déplacer le jet de vapeur vers la droite avec le modificateur de vitesse obstacle.x = obstacle.x + (obstacle.speed / FRAME_RATE) if obstacle.x > SCREEN_WIDTH then despawnEntity(obstacle) end end function handleRiverTileCollision(obstacle) addDebugMessage("Collision avec : " .. obstacle.name, colorPalette.lightGreen, "Collision") handlePlayerDeath() -- Déplacer la tuile de rivière vers le bas avec le modificateur de vitesse obstacle.y = obstacle.y + (obstacle.speed / FRAME_RATE) if obstacle.y > SCREEN_HEIGHT then despawnEntity(obstacle) end end function handleFloatingDebrisCollision(obstacle) -- Ici, on ne fait rien, car les débris flottants ne sont pas mortels addDebugMessage("Collision avec : " .. obstacle.name, colorPalette.darkGrey, "Collision") end obstacleCollisionHandlers = { vapor = handleVaporCollision, riverTile = handleRiverTileCollision, debris_floating = handleFloatingDebrisCollision } collisionHandlers = { enemy = handleEnemyCollision, collectible = handleCollectibleCollection, interactable = handleInteractableCollision, specialTile = applyTileEffect, obstacle = handleObstacleCollision } -- ============================= -- // --------------------------- -- // SECTION 8 : Gestion du joueur -- // --------------------------- function updatePlayer() -- Gestion des inputs du joueur if not player.isMoving then if btnp(2, 30, 10) then initiateMovement("left") end if btnp(3, 30, 10) then initiateMovement("right") end if btnp(0, 30, 10) then initiateMovement("up") end if btnp(1, 30, 10) then initiateMovement("down") end end local deltaSpeed = (PLAYER_SPEED * player.speedMultiplier) / FRAME_RATE local dx, dy = 0, 0 -- Calculer le déplacement horizontal vers la position cible if player.x < player.targetX then dx = math.min(deltaSpeed, player.targetX - player.x) elseif player.x > player.targetX then dx = -math.min(deltaSpeed, player.x - player.targetX) end -- Calculer le déplacement vertical vers la position cible if player.y < player.targetY then dy = math.min(deltaSpeed, player.targetY - player.y) elseif player.y > player.targetY then dy = -math.min(deltaSpeed, player.y - player.targetY) end -- Si un déplacement est nécessaire if dx ~= 0 or dy ~= 0 then -- Tenter de déplacer le joueur if not attemptMove(player, dx, dy) then -- Si le mouvement est bloqué, arrêter le déplacement player.targetX = player.x player.targetY = player.y player.isMoving = false addDebugMessage("Mouvement interrompu par un obstacle", colorPalette.red, "Collision") end end -- Arrêter le mouvement si on atteint la cible if player.x == player.targetX and player.y == player.targetY then player.isMoving = false -- Vérifier si le joueur se déplace vers le haut if dy < 0 then local currentTile = player.y addScoreForMovement(currentTile) end end -- Appliquer les effets de la tuile actuelle -- applyTileEffect(getTileType(player.x, player.y)) end function initiateMovement(direction) local dx, dy = 0, 0 if direction == "left" then dx = -TILE_SIZE elseif direction == "right" then dx = TILE_SIZE elseif direction == "up" then dy = -TILE_SIZE elseif direction == "down" then dy = TILE_SIZE end -- Vérifier si la nouvelle position cible est libre if not isPathBlocked(player, dx, dy) then player.targetX = player.x + dx player.targetY = player.y + dy player.isMoving = true end end function drawPlayer() spr(player.sprite, player.x, player.y, 0) end function handlePlayerDeath() if player.isInvincible then return end gameState.lives = gameState.lives - 1 if gameState.lives <= 0 then gameState.gameOver = true gameOver() else respawnPlayer() end end function respawnPlayer() if gameState.spawnX and gameState.spawnY then player.x = gameState.spawnX player.y = gameState.spawnY player.targetX = player.x player.targetY = player.y else -- Fallback to default local level = getCurrentLevel() player.x = level.playerStartX player.y = level.playerStartY player.targetX = player.x player.targetY = player.y end player.isMoving = false end -- ============================= -- // --------------------------- -- // SECTION 9 : Interface Utilisateur (UI) -- // ----------------=========== -- Positions de base pour chaque quadrant local UIPositions = { upperLeft = {x = 17, y = 10}, lowerLeft = {x = 17, y = SCREEN_HEIGHT - 15}, upperRight = {x = SCREEN_WIDTH - 55, y = 10}, lowerRight = {x = SCREEN_WIDTH - 67, y = SCREEN_HEIGHT - 17}, } -- Fonction principale de dessin de l'UI function drawUI() if not debugEnabled then drawUIUpperLeft() drawUILowerLeft() -- Affiche le score en bas à gauche drawUIUpperRight() drawUILowerRight() -- Affiche les vies et fragments en bas à droite end end -- Dessin du haut gauche (Prestige) function drawUIUpperLeft() local pos = UIPositions.upperLeft print("Prestige: " .. player.prestigeLevel, pos.x, pos.y, colorPalette.red, false, 1, true) end -- Dessin du bas gauche (Score) function drawUILowerLeft() local pos = UIPositions.lowerLeft -- Afficher le score local score = math.floor(gameState.score) print("Score: " .. score, pos.x, pos.y, colorPalette.yellow, false, 1, true) end -- Dessin du haut droit (Temps restant) function drawUIUpperRight() local pos = UIPositions.upperRight -- Afficher le temps restant local timeLeft = math.ceil(gameState.timeLeft) print("Temps: " .. timeLeft .. "s", pos.x, pos.y, colorPalette.yellow, false, 1, true) end -- Dessin du bas droit (Vies et Fragments) function drawUILowerRight() local pos = UIPositions.lowerRight -- Définir les constantes pour les tailles des sprites local spriteWidth = 8 -- Largeur des sprites en pixels local spriteHeight = 8 -- Hauteur des sprites en pixels (si nécessaire) -- Définir les variables d'espacement (facile à ajuster) local spacingBetweenLifeIconAndText = 5 -- Espacement entre l'icône des vies et le texte des vies local spacingBetweenLifeAndFragment = 5 -- Espacement entre la section des vies et l'icône des fragments -- Position de départ pour l'icône des vies local lifeIconX = pos.x local lifeIconY = pos.y -- Dessiner l'icône des vies spr(257, lifeIconX, lifeIconY, colorPalette.transparent) -- Texte des vies local lifeText = "x" .. gameState.lives local lifeTextX = lifeIconX + spriteWidth + spacingBetweenLifeIconAndText local lifeTextY = pos.y + 2 print(lifeText, lifeTextX, lifeTextY, colorPalette.red, false, 1, true) -- Calculer la largeur du texte des vies (4 pixels par caractère) local lifeTextWidth = #lifeText * 4 -- Position pour l'icône des fragments local fragmentIconX = lifeTextX + lifeTextWidth + spacingBetweenLifeAndFragment local fragmentIconY = pos.y -- Dessiner l'icône des fragments spr(323, fragmentIconX, fragmentIconY, colorPalette.transparent) -- Texte des fragments local fragmentsText = player.fragmentsCollected .. "/" .. gameState.requiredFragments local fragmentsTextX = fragmentIconX + spriteWidth + spacingBetweenLifeIconAndText local fragmentsTextY = pos.y + 2 print(fragmentsText, fragmentsTextX, fragmentsTextY, colorPalette.yellow, false, 1, true) end -- ============================= -- // --------------------------- -- // SECTION 10 : Système de Débogage -- // --------------------------- -- === CONFIGURATION DES CATEGORIES DE DEBUG === activeDebugCategories = { ["Effect"] = true, -- Active les messages liés aux buffs/debuffs ["Entity"] = true, -- Active les messages liés aux entités ["Spawner"] = true, -- Active les messages liés aux spawners ["Collision"] = true, ["Score"] = true, ["GameState"] = true, ["Player"] = true, -- Ajoutez d'autres catégories selon vos besoins } -- DEBUG VARIABLES debugMessages = {} debugEnabled = false -- Permet d'activer/desactiver le mode debug debugColor = colorPalette.yellow consoleLimit = 10 -- Augmenté pour afficher plus de messages -- DEBUG CONSOLE function addDebugMessage(message, color, category) if debugEnabled and activeDebugCategories[category] then table.insert(debugMessages, {text = message, duration = (FRAME_RATE * 5), color = color or debugColor}) if #debugMessages > consoleLimit then table.remove(debugMessages, 1) end end end function drawDebugConsole() local y = SCREEN_HEIGHT - (#debugMessages * 7) - 5 for i = #debugMessages, 1, -1 do local msg = debugMessages[i] print(msg.text, 10, y, msg.color, false, 0.5, true) y = y + 7 * 0.5 -- Ajuster en fonction de l'échelle -- Supprimer les messages de debug après un certain temps msg.duration = msg.duration - 1 if msg.duration <= 0 then table.remove(debugMessages, i) end end end -- DEBUG INPUTS function handleDebugInputs() -- Vérifie les touches spécifiques au mode debug uniquement si debugEnabled est true if debugEnabled then -- Changement de niveau avec la touche (TAB) if keyp(49) then _switchLevel() end -- Modifications du multiplicateur de vitesse par incréments de 0.1 avec les touches Q (-) et S (+) if keyp(19) then -- Touche S _updateSpeedMultiplier(1) elseif keyp(17) then -- Touche Q _updateSpeedMultiplier(0) end -- Ajouter un ennemi de debug avec la touche (E) if keyp(5) then _spawnDebugEnemy() end -- Ajuster le niveau de prestige avec la touche (P) if keyp(16) then _increasePrestige() end -- Mode invincible switch en pressant (I) if keyp(9) then player.isInvincible = not player.isInvincible addDebugMessage("Mode invincible : " .. tostring(player.isInvincible), colorPalette.yellow, "Player") end -- Switch au niveau dev ground avec la touche (G) if keyp(7) then gameState.currentLevel = 7 loadLevel() end -- Touche de toggle debug (D) if keyp(4) then debugEnabled = not debugEnabled addDebugMessage("Mode debug " .. (debugEnabled and "activé" or "désactivé"), colorPalette.lightGrey, "GameState") end else -- Touche de toggle debug (D) if keyp(4) then debugEnabled = not debugEnabled addDebugMessage("Mode debug " .. (debugEnabled and "activé" or "désactivé"), colorPalette.lightGrey, "GameState") end end end -- DEBUG UI function drawDebug() if debugEnabled then drawDebugConsole() drawDebugUI() end end function drawGeneralInfo(x, y, scale) print("Score: " .. gameState.score, x, y, debugColor, false, scale, true) y = y + 10 * scale print("Level: " .. levels[gameState.currentLevel].title, x, y, debugColor, false, scale, true) y = y + 10 * scale print("Prestige Lv: " .. player.prestigeLevel, x, y, debugColor, false, scale, true) end function drawPlayerInfo(x, y, scale) print("Player Pos:", x, y, debugColor, false, scale, true) y = y + 10 * scale print("(" .. math.floor(player.x) .. ", " .. math.floor(player.y) .. ")", x, y, debugColor, false, scale, true) y = y + 10 * scale print("Speed Mult.: " .. string.format("%.2f", player.speedMultiplier), x, y, debugColor, false, scale, true) y = y + 10 * scale print("Obst. Speed Mod: " .. string.format("%.2f", obstacleSpeedModifier), x, y, debugColor, false, scale, true) y = y + 10 * scale print("Lives: " .. gameState.lives, x, y, debugColor, false, scale, true) end function drawEffectsInfo(x, y, scale) print("Active efct.:", x, y, debugColor, false, scale, true) y = y + 10 * scale for _, effect in ipairs(activeEffects) do local effectColor = effect.type == "speedUp" and colorPalette.lightGreen or colorPalette.red print(effect.type .. " (" .. effect.value .. "%) - " .. effect.duration .. " frames", x, y, effectColor, false, scale, true) y = y + 10 * scale end end function drawEnemiesInfo(x, y, scale) print("Enemies sp.:", x, y, debugColor, false, scale, true) y = y + 10 * scale for _, entity in ipairs(entities) do if entity.type == "enemy" then -- Utiliser currentSpeed au lieu de speed print(entity.name .. ": " .. string.format("%.2f", entity.currentSpeed), x, y, debugColor, false, scale, true) y = y + 10 * scale end end end function drawDebugUI() local scale = 1 -- Informations Générales drawGeneralInfo(10, 10, scale) -- État du Joueur drawPlayerInfo(SCREEN_WIDTH - 80, 10, scale) -- Effets Actifs drawEffectsInfo(10, SCREEN_HEIGHT - 50, scale) -- Vitesse des Ennemis drawEnemiesInfo(SCREEN_WIDTH - 80, SCREEN_HEIGHT - 50, scale) end -- DEBUG UTILS function _increasePrestige() player.prestigeLevel = player.prestigeLevel + 1 addDebugMessage("Prestige level augmenté à : " .. player.prestigeLevel, colorPalette.yellow, "Player") end function _switchLevel() local timeLeft = gameState.timeLeft or 0 addScoreForTimeRemaining(timeLeft) gameState.currentLevel = gameState.currentLevel + 1 if gameState.currentLevel > #levels then gameState.currentLevel = #levels gameState.gameWon = true displayWinScreen() else loadLevel() end end function _prestige() -- Réinitialiser le niveau au début gameState.currentLevel = 1 -- Augmenter le niveau de prestige player.prestigeLevel = player.prestigeLevel + 1 addDebugMessage("Prestige level augmenté à : " .. player.prestigeLevel, colorPalette.yellow, "Effect") -- Charger le niveau avec les nouveaux paramètres de difficulté loadLevel() end function _spawnDebugEnemy() local debugEnemy = createEntity("mutant", 120, 68) -- Utiliser un type existant comme "mutant" if debugEnemy then debugEnemy.name = "debugEnemy" debugEnemy.baseSpeed = 0 debugEnemy.currentSpeed = 0 addDebugMessage("Debug enemy spawned!", colorPalette.lightGreen, "Entity") end end function _updateSpeedMultiplier(amount) if amount == 1 then player.speedMultiplier = player.speedMultiplier + 0.1 addDebugMessage("Speed Multiplier +0.1", colorPalette.lightGreen, "Player") elseif amount == 0 then player.speedMultiplier = player.speedMultiplier - 0.1 addDebugMessage("Speed Multiplier -0.1", colorPalette.red, "Player") end end -- ============================= -- // --------------------------- -- // SECTION 11 : Boucle principale -- // --------------------------- function TIC() cls(0) if gameState.isWinScreen then drawWinScreen() if keyp(18) then -- Touche R pour recommencer reset() end return end if gameState.isGameOver then drawGameOverScreen() if keyp(18) then -- Touche R pour recommencer reset() end return end handleDebugInputs() -- Mise à jour des éléments updateLevelTimer() -- Mise à jour du timer du niveau updateSpawners() -- Mise à jour des spawners updateEffects() -- Mise à jour des effets temporaires updateEntities() -- Mise à jour générique de toutes les entités updatePlayer() -- Mise à jour du joueur checkCollisions() -- Vérification des collisions -- Dessin de la frame drawMap() drawEntities() drawPlayer() drawUI() -- Appel à la fonction UI refactorisée -- Afficher la console de debug drawDebug() end -- ============================= -- // --------------------------- -- // SECTION 12 : Fonctions Utilitaires -- // --------------------------- -- UI Utils function drawIconWithText(iconSprite, iconX, iconY, text, textXOffset, textYOffset, textColor) spr(iconSprite, iconX, iconY, colorPalette.transparent) print(text, iconX + textXOffset, iconY + textYOffset, textColor, false, 1, true) end -- Gestion du score function addScoreForMovement(yCoord) -- Vérifier si la tuile en Y n'a pas été visitée et que le mouvement est vers le haut if not player.visitedTiles[yCoord] then player.visitedTiles[yCoord] = true local prestigeMultiplier = 1 + (player.prestigeLevel * 0.1) local scoreIncrease = 10 * prestigeMultiplier gameState.score = gameState.score + math.floor(scoreIncrease) addDebugMessage("Score augmenté de " .. math.floor(scoreIncrease) .. " pour un mouvement vers une nouvelle tuile en haut", colorPalette.lightGreen, "Score") end end function addScoreForTimeRemaining(timeLeft) local bonus = math.floor(timeLeft * 10) gameState.score = gameState.score + bonus addDebugMessage("Bonus de temps restant ajouté : " .. bonus .. " points", colorPalette.lightGreen, "Score") end function addScoreForFragment() local prestigeMultiplier = 1 + player.prestigeLevel * 0.1 local scoreIncrease = 100 * prestigeMultiplier gameState.score = gameState.score + math.floor(scoreIncrease) addDebugMessage("Score augmenté de " .. math.floor(scoreIncrease) .. " pour la collecte d'un fragment", colorPalette.yellow, "Score") if player.fragmentsCollected >= gameState.requiredFragments then local bonus = 500 * prestigeMultiplier gameState.score = gameState.score + math.floor(bonus) addDebugMessage("Bonus de " .. math.floor(bonus) .. " points pour avoir collecté tous les fragments", colorPalette.lightGreen, "Score") end end function addScoreForCollectible() gameState.score = gameState.score + 150 addDebugMessage("Score augmenté de 150 pour la collecte d'un collectible bonus", colorPalette.yellow, "Score") end -- Timers et gestion du temps function updateLevelTimer() if gameState.gameOver then return end local level = getCurrentLevel() -- Ignorer le timer pour les niveaux comme le Dev Ground if level.title == "Dev Ground" then return end -- Le reste de la fonction reste inchangé local deltaTime = 1 / FRAME_RATE if gameState.timeLeft > 0 then gameState.timeLeft = gameState.timeLeft - deltaTime end if gameState.timeLeft <= 0 then gameState.timeLeft = 0 handleTimeOut() end end -- Autres fonctions utilitaires function collides(entity1, entity2) return entity1.x < entity2.x + entity2.width and entity2.x < entity1.x + entity1.width and entity1.y < entity2.y + entity2.height and entity2.y < entity1.y + entity1.height end function isPathBlocked(entity, dx, dy) local futureX = entity.x + dx local futureY = entity.y + dy local futureX2 = futureX + entity.width - 1 local futureY2 = futureY + entity.height - 1 local tileXStart = math.floor(futureX / TILE_SIZE) local tileYStart = math.floor(futureY / TILE_SIZE) local tileXEnd = math.floor(futureX2 / TILE_SIZE) local tileYEnd = math.floor(futureY2 / TILE_SIZE) for ty = tileYStart, tileYEnd do for tx = tileXStart, tileXEnd do local tileX = tx + currentMapX local tileY = ty + currentMapY local tile = mget(tileX, tileY) if fget(tile, flagsConfig.BLOCKING_FLAG) then return true end end end return false end function attemptMove(entity, dx, dy) if isPathBlocked(entity, dx, dy) then return false -- Déplacement impossible else entity.x = entity.x + dx entity.y = entity.y + dy return true -- Déplacement réussi end end function getTileType(playerX, playerY) return "undefined" -- Valeur temporaire pour éviter les erreurs end function despawnWorm(wormHead) -- Supprimer chaque segment du worm for _, segment in ipairs(wormHead.segments) do for i, entity in ipairs(entities) do if entity == segment then table.remove(entities, i) break end end end -- Supprimer la tête du worm for i, entity in ipairs(entities) do if entity == wormHead then table.remove(entities, i) break end end end function despawnEntity(entity) for i, e in ipairs(entities) do if e == entity then table.remove(entities, i) entity.removed = true -- Marquer l'entité comme supprimée break end end end init()