From 4261cdface9d97293f198f59b6d7d3ca55caeeb5 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Mon, 17 Apr 2023 22:43:07 -0500 Subject: [PATCH] Add migration guide --- docs/.vitepress/config.js | 7 ++ docs/guide/getting-started/setup.md | 4 +- docs/guide/getting-started/updating.md | 6 +- docs/guide/migrations/0-6.md | 124 ++++++++++++++++++++ docs/guide/migrations/persistence-error.png | Bin 0 -> 13060 bytes 5 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 docs/guide/migrations/0-6.md create mode 100644 docs/guide/migrations/persistence-error.png diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index d81a2c99..646e8d56 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -86,6 +86,13 @@ module.exports = { { text: "Dynamic Layers", link: "/guide/advanced-concepts/dynamic-layers" }, { text: "Nodes", link: "/guide/advanced-concepts/nodes" } ] + }, + { + text: "Migrations", + collapsed: true, + items: [ + { text: "0.5.X to 0.6.0", link: "/guide/migrations/0-6" } + ] } ], "/api/": generateAPISidebar() diff --git a/docs/guide/getting-started/setup.md b/docs/guide/getting-started/setup.md index 03255b00..b99305b0 100644 --- a/docs/guide/getting-started/setup.md +++ b/docs/guide/getting-started/setup.md @@ -12,14 +12,14 @@ For local development, you will need the following tools: Create a new project from the [Profectus repository](https://github.com/profectus-engine/Profectus) by clicking the "Use this template" button. Then, clone the repository locally using the provided link. ::: info -The template repository allows easy creation of multiple projects from one repository. However, updating an existing project to a newer version of Profectus can be challenging. Consider [updating Profectus](https://chat.openai.com/updating.md) _before_ starting development to avoid issues with unrelated histories. +The template repository allows easy creation of multiple projects from one repository. However, updating an existing project to a newer version of Profectus can be challenging. Consider [updating Profectus](./updating.md) _before_ starting development to avoid issues with unrelated histories. ::: It's recommended to create a new Git branch for development, allowing you to push changes without affecting the live build. The GitHub workflow will automatically rebuild the page when you push to the `main` branch. Next, install Profectus' dependencies by running `npm install`. Run `npm run serve` to start a local server hosting your project. The site will automatically reload as you modify files. -Also, follow the steps to [update Profectus](https://chat.openai.com/updating.md) before starting to make future updates easier without worrying about unrelated histories. +Also, follow the steps to [update Profectus](./updating.md) before starting to make future updates easier without worrying about unrelated histories. ### Deploying diff --git a/docs/guide/getting-started/updating.md b/docs/guide/getting-started/updating.md index 9cc00155..0ec2cb67 100644 --- a/docs/guide/getting-started/updating.md +++ b/docs/guide/getting-started/updating.md @@ -2,7 +2,7 @@ ## Github -Due to Profectus being a template repository, your projects do not share a git history with Profectus. In order to update changes, you will need to run the following: +Due to Profectus being a template repository, your projects do not share a git history with Profectus. To update changes, you will need to run the following: ```bash git remote add template https://github.com/profectus-engine/Profectus @@ -14,8 +14,8 @@ The first command only has to be performed once. The third command may require y ## Replit -The sidebar has a tab labelled "Version Control", which you can use to merge all changes made to Profectus into your project. Unfortunately, replit does not have a merge tool so this process may irrecoverably erase changes you've made - I'd recommend making a backup first. +The sidebar has a tab labeled "Version Control", which you can use to merge all changes made to Profectus into your project. Unfortunately, Replit does not have a merge tool so this process may irrecoverably erase changes you've made - I'd recommend making a backup first. ## Glitch -Unfortunately glitch does not provide any method by which to update a project from a github repository. If you've only changed things in the data folder you may consider creating a new project, importing the current version of Profectus, and then placing your data folder in the new project. +Unfortunately, Glitch does not provide any method by which to update a project from a Github repository. If you've only changed things in the data folder you may consider creating a new project, importing the current version of Profectus, and then placing your data folder in the new project. diff --git a/docs/guide/migrations/0-6.md b/docs/guide/migrations/0-6.md new file mode 100644 index 00000000..d6a2ce2a --- /dev/null +++ b/docs/guide/migrations/0-6.md @@ -0,0 +1,124 @@ +# Migrating to Profectus 0.6 + +In addition to the usual steps for [Updating Profectus](../getting-started/updating), this update has many large or breaking changes. This guide will go over the additional steps you'll want to do after updating Profectus. + +## Fixing save data + +This update includes a major change to how save data is collected and stored. The change makes save data smaller and fixes issues that can arise, causing persistent values to be reset to their default values. Unfortunately, this change will require the developer to effectively mark which uses of persistent values are supposed to be included in the save data, and which uses are just a reference. Let's walk through an example: + +```ts +const flowers = createResource(0, "moly"); +const job = createJob(name, () => ({ + /** snip **/ + resource: flowers +})); +/** snip **/ +return { + /** snip **/ + flowers, + job +} +``` + +This would store the same persistent data in two locations - `flowers.flowers` and `flowers.job.resource`. We can mark the latter usage as a reference by wrapping it in the [noPersist](../../api/modules/game/persistence#nopersist) utility, so it'd look like `resource: noPersist(flowers)`. Otherwise, you'll get an error in the console when the layer is loaded: + +![Persistence Error](./persistence-error.png) + +You can use these errors in the console to determine where you have save data redundancy that needs to be corrected. It's recommended to just run the app and use those errors as a guide, rather than trying to determine any redundancies by hand. + +In addition to getting non-persistent refs from your persistent refs, you may also need to wrap entire features that contain persistent refs within them. For example, in Kronos there are 7 layers with "Job" features, and they're collected into a dictionary in the main layer. That would make the persistent state appear in both layers, but you can wrap that dictionary into a `noPersist` call to skip it during serialization, thus making it so that it'll only use the jobs inside their respective layers. Here's what that looks like in Kronos: + +```ts +const jobs = noPersist({ + flowers: flowers.job, + distill: distill.job, + study: study.job, + experiments: experiments.job, + generators: generators.job, + breeding: breeding.job, + rituals: rituals.job +}) as Record; +``` + +This step will take longer depending on how you've structured your project. You can use [this commit](https://github.com/thepaperpilot/Kronos/commit/6e8bfc1a78df0a7957de06bacdabf87c688b917c) to see all the changes it took for Kronos, which is structured so that similar features all used a utility function that made it so only a few places needed to be changed. + +## Breaking feature changes + +Several features had some breaking changes this update. Here are a couple more minor fixes: + +- Buyables have been renamed to repeatables. This should be fixable by just replacing every instance of `Buyable` with `Repeatable`. +- Achievements and Milestones have been merged. Any existing achievements should have `small: true` added to their options, and any `createMilestone` calls replaced with `createAchievement`. + +In addition, there are a couple changes which have a more significant impact on your code: Requirements, Formulas, and Modifiers. + +### Requirements + +Many features no longer take a `cost` and `resource` property, but instead take a `requirements` property, which can be one or more `Requirement` objects. This'll make it easier to support features requiring multiple currencies or having other conditions. To update an existing cost requirement, you can simply wrap your existing cost function and resource property like so: + +```ts +requirements: createCostRequirement(() => ({ + cost: () => Decimal.pow(priceRatio, unref(machines.amount)), + resource: generators.energeia, +})) +``` + +You can read more about requirements and their capabilities in [this guide page](../important-concepts/requirements). + +### Formulas + +Formulas are a new feature that allow for scaling cost or effect functions to be inverted or integrated without the developer needing to code anything besides the original formula. They can make it much easier to support things like "buy max" functionalities, and make conversions much easier to read and write. + +Any cost requirements can now accept a formula instead of a cost function. The formula system can then handle finding how many purchases can be made at once. To continue the example above, here's how it'd be rewritten: + +```ts +requirements: createCostRequirement(() => ({ + cost: Formula.variable(machines.amount).pow_base(priceRatio), + resource: generators.energeia, +})) +``` + +Conversions work slightly differently. Their scaling function system has been replaced with a `formula` property that takes a lambda - it'll give you the input formula variable, representing the base resource, as a parameter, and you then return a formula that represents the amount of the gain resource you could convert for. For example, if previously you had code like this: + +```ts +scaling: addSoftcap(createPolynomialScaling(10, 0.5), 1e100, 0.5) +``` + +then you can now write this: + +```ts +formula: x => x.div(10).sqrt().step(1e100, f => f.sqrt()) +``` + +You can read more about formulas and their capabilities in [this guide page](../important-concepts/formulas). + +### Modifiers + +Modifiers now display negative effects in red. It currently assumes any value that reduces the result is negative, and the output being less than the base means is a negative outcome. However, for some modifiers this may be the opposite of what you want - for example, a cool down being reduced below it's base is a positive effect. You should set the `smallerIsBetter` property to `true` for those modifiers. This property also exists when making collapsible modifier sections. + +Modifiers have renamed their `revert` property to `invert` so they match the terms formulas use. If you've created a custom modifier you'll need to update that. + +## Fixing visibility changes + +Visibility properties now work with booleans, which has had a lot of implications. + +The `showIf` util is no longer useful and has been removed - you can now simply return the boolean value itself. In fact, if you were previously passing a computer boolean into showIf, then now you can just use the computed ref directly, lowering overhead. Here's an example: + +```ts +visibility() { + return showIf(spellExpMilestone.earned.value); +} +``` + +This code can now be simplified to this: + +```ts +visibility: spellExpMilestone.earned +``` + +Be aware that using the computed ref directly instead of a function can cause a circular dependency issue. If you experience one while simplifying a visibility property, you'll need to resolve those or continue to use a function, returning the value of the computed ref. + +### Custom Components + +If you made any custom features with their own Vue component, you'll need to update it to support booleans for visibility values. That means replacing **ALL** equality checks for specific visibilities with calls to [isVisible](../../api/modules/features/feature#isvisible) and [isHidden](../../api/modules/features/feature#ishidden). + +While updating your component, you may also need to cast the component to [GenericComponent](../../api/modules/features/feature#genericcomponent). diff --git a/docs/guide/migrations/persistence-error.png b/docs/guide/migrations/persistence-error.png new file mode 100644 index 0000000000000000000000000000000000000000..e001e6412b4e84d1baf1753437f297b6904ef90d GIT binary patch literal 13060 zcmY+rWmua**S1?qvEpqhPD_E}7Cd-y3+@m~u>wVc2Q9_j-HN-rOCUHwiv@Qn?)vq8 z_Vexi?jQHuvyNkCj`=mSuJc+Gq5_h^eNFcI#fulXaP*q4e7onZ9@dNRg8i z*Kh;>&A@fi>~}tXez^#FBldD{2vgBS4o-sg`j<|PRJMy-wAd?r@-JFSm>(+NvrMtr zQ=Ea*zWEnVHU4@lVWJcG`DYfkS9kOcwH03u9=PCVfVF=ZorHfhc|YcbF|BP_g-`k{{|%RV z$m0LoO^mNX&;NhMgwfIUQVjK^-DNw>afCPBn)~0cgd2gI{e7Coe-*6wP0 z2+kUZ+ic#ydenOV-gs3hORBRid!K+ZZ-wu&lQ03zgyW2huQaz9fHGrUoV@`D{iykL zpkR-d213>7pJm2L3zhh9EQ(rYrn7mS|`T8!v zRv`W^C5UpG--u|b`Sr>9hvXuc3KN&MIxMM~42E(8|Nfckqc{*HC}7S3lcq)HprlkY z@utJ8Qt5A4xNa@^Iny-ADAx@w(N@nFSw}+M*JL+k3uxNfhMIRE@UD@QL*JZV4wNI; zDSS*vDV(`E?{J&M$-+~VpI`7LhQD2m_G-;noj*or>0mWVkLus)yr8>eem^=VgH|5g zfLkv%|B%Tt$Ewp({o0JZ-hlX6%H4C{@Ox#&mulG+i)v}gs6leXO;l|wWA!?)CO7@b4D95uBYdm8LU19l5>xPHsOOKWH1$KqmcNl-xi~Fok z6TNPYX^GBbjGkZRBmCT~>}aG%^n*CpUy-KbCv2Q`pq(Mtg_}GPo_ky6MLoaP-4EaN zf9Mq-Q69P(``)@E5<~PutVVS{>&dm;?bU}r?Tl&t4P@ z_1HUzOidZ3ISGOH^s~i7VPp*pLj+bDW^k}ybAac|Cbd=~*HU=2NBrq)lx7`P3-#SElFZb1&or7I zZ0|H<7>>0Vi&@h;MSoniNPf+2npEN_kA5|$>gjhA?bkxwM@*F1T7=vGcp4I?YD#c& zg77pwoH&ES!iU>1Hrnx1uX1;r-&)bXerP-QZ4{KXjR~sW%9L_&OtmfCaK#wtx&%RBNOHy+nB?}_Fy$vc%)X$aPwT#8*%(J<#2KtH(|b+Gz}AGN@r|k zXBWC7`t2j4*zRhdIqQ+-2BCX3tnI`kpLymOC6%H4;Q?T;SBs>>Ex;SHSTp(L69L#| zubmjG)wONkVkcYn#GRxm^yWCC+591Tw5$DPk7=?Tw_eKPLpV01%srZh&$D1R30#0>Hz_Z3~Zl24s z(2;IT1^6`0`B8LGYvkA2lF{$^8;8|tzua+{vR*+1WzgXsl@+6sc~xA3-R4Vu7qft*_CJzvirE$Vu&tp%!$ z1tQwN^`O()W#Cu7=K7FfN7#9JS|FN2#`dXwyHBvXK`D(_V6lhYi*Yp-YpvFrIN-YE z@px2+6puZ9^`S$EM%gflSfrtZ3CT^k-m%`o#D`oCIHTyFHKo8`aB8b~fUeDImE&~s zj(B7&f2vGM^H5&C>0pX9=7J@a8t7&jHT`+Jd~bmD<1KN3s4C>ZVs#2wPx#SO8zp*5LKKUTF1iUu(GsQkaC0o7-8Z&~mj5eOhv@2oDk-d5=^BWH)gF05d z4(T88T95Nc=gEXLvvKHAjPkDEJqy-~b}wP^aJq}Fn_V+UrNVc0=;jR3voOup$v$_| z2S!xUv;l2z^B4@DThov!&*wgTRdRoBKJKhw0~w+o#t-k5%_H}52!w=g50x|TAThv~_F zG~QIZkg@|am~gqu#MMnnDsb+3YG`!L#(Fz=#Yh*{e_8L?W9?##C2COSbBt$K#FDtf zAF0ILCzW?R^DCH`KSxzWGzRJgak07)^+rfqAo zadLFJegaE-c|=-wyJ_DL>dcuXH*lbg;ka=^Q3+8Doc|j#>uJmCT6TZOce?O#Ed%)w zf1>E}*vH4YTKQbaRd_b-^CbPC9Ed<9`_8Z21TJt57J|#tjcVLAXn2D&O{faO$-;fj zhNs)!=JFLZspLUl%~?ayGly1*u7qbp@tBCi2sdd7ng zO#Bw{Tb=%m+ zqLvh=ADtvek)o0#04H}-x0z_)XLeHJbt5KR;HkBIZeKi}UyXdVUViC8c3czmfECg5 zLrZ=BVn@A(WpK0y!(zWm{ckIfQNUHl0}A?GVaf3&gqAr$=`X}_XuG$XVZi=?=MS~^ z)putNsxP{uMe3J<(Q5`;jb_onTRf{?JNB|TRFh4qNmB@T>QR>c_rz^p|L-l1b zHpf&q06!G+^m3s$W?5<$zb%>-GKX4xmcGDZAxY0R`YG#`#^+)GMot&u!CZ#3rW6;X zt(x<>!?-N!!ITXH;T8jaoQyO2zH}7i78CqP4{iKIgS$IK8i@=SXo{n<>J<{UvlEdH zX6BCIf4UxG*6Ji()RsT~n7TNw$?+lTr0oloSHwK|6>}_7PHJEz-5<>gy!OkXGSY=| ziAKnS#X=_OM*M^;!+FbvK^mg^9x&XQn>lmB;*zwj-ExAB5gy!vihkL~k)mIVXhF&dZ z2UPg!EYaZ0)MR7J0oDe7iCN(?D>LXg- z+H!$S&XE{;%Z_eF3u{y4z70P+#k|`&&h=+h@&Vl@=XI6U5#ze8_*Do1xv0NE556gA6I_@p8vF)1RSTk;Y()49D^l)o7MqLsip0PGg zI!iruuo<+;QT>|`V+JYIEF3OTE$vIn%Qmb%``KHkG@p(rzsY+RIG6QOeFH8Xdep@z&l%#mR}`k~S1 z$-@~wY-63H zeu1?Z*~m+&+ac>1+ves*R$Q_9pY)dLXD|J2`XK@y4DU-N7E0leOtc-9vkn*h!o*h$r-vnlyt&LlYWxX6^iVK+~IoV*cT&#vqHdk zMoBCdguuZ}m!YtWGTuZWDbve^Wg%Z96WW5P(8`Uy1Zu(uGe+^bs=T}I3)B9d(-Ba5 zrhgD1nE%qW@Th1ID7IeyA#o)6IAP6`lB~$n_5<;kR<54$`*23mkcvC)0edJ)Az~>~ z&4dS{hUsU8zdSmkgj8q5QFzV802ujg3>KJ+d)>}KXFR7bhq#D-PT5yoA%NdtJhd+1 ziBWtVX7Z`mLI^uT(e9US5 zTp1U4bNLMubtUh+V@-|gr#4BD_Z6sv_n3y0*fTlorRUm3yAHZZtb1OWIsXdn<;R!w z;nq0=A6?wmT>xPY4|`%oH>(P*lJrF$@2R1mdt~AS9Uu{msD-(0><8#{0Acb%L(^rr zvi7s7mupXJGegjN@n1>Z$mXw7gJV<-%9%H&u9<`5CcuVc9G)ie7V@7v&FL5C%R%Qk z7VoztXEA(4n(scs{Ch=6aUYPBwjKB9a_l1GX0&RB4*eJouv>e}GPavs6VA1WnbmBYk;HQoKcqmP# zU>F$%NVKK18MBYxnw#{GDwWcAox%cPd#Zcl8p-vju!*}qw=tO+;-ueq;{8NKT+gYI zxx5~ECAsSf`QL4Rv!}m>8^lj3-ic8P8G7T8R!kqsz={Sz!h^w1tHow|JMO5w@f|!^ zDJ>js@1st72uzC7h&xWk8^9cIs(wqd@j}iQ4D;5*!c?&>-HhezNdnFmmVj?b&T&P;m$e9wlQ0l)dICvT3$`A7d^dIy}y5Y0V;vS+o2Kv(oN4y`t z48CeAFq5Rw#qo3KzA`j5CULIcO5NHKbw|g2zAIS>i{xQo{Vz6Pqz03l{tsCA9~|IB zY^|dAUoxQf|3?dEc3oi@C9$`_s4c6m%K0We_N=R(N`k#|Vh?>5FD&m%Eg~zA^NiM6 zw1+?({X!l0#DkvSrV`W{L>iN)#l+iz=2CoGZkIJrZSVUtvcq3P$^?x{oA*i1tZ9@; z{~U2$vEHb={EtX4HHbe`NY00pWEt53mYoiAx9~~XWQuo9zOA+9$32u3Sl8Hwf{qiE zELTI^voR)BM1y#!4Bak0T+@LK9UzK>>_gVxB#WH3pJ`0nd-IhhsG4jKP*(54ZzS|8 z-S-b$=+5)?y|JWmeF~MMh~Pctbh^9Ss?uN4oL6B9Z^cOp9}Z_mG0zo3rGT_#l_U2z zSs&_kp2nk%i0)uMm4{d(db4Slt!-K5>MX)?WMFbh;Z;qEi9f2LfKx4(4KMo#x_}&B z3Pge9zS8s2EPI<#f5=@v>IyN}NF|wD#{wZOs`+~IEgG?}5b?n<)7dcB-N7sWCoA7u z=T|}8ZO@=Q^?3Hp6XA%;Q`xzLRd3Di=NYVXsfKhg9WKD;(Xwzw6~5O_5%HawIRQdu zbP=3DT+MNOAhV2kMoSs6C~Zzi4$^4i%3xrQJ7#psnoVlN@|2^Y z>AYKU@aVd?R>sa zESO%2xEW&cXt}(nkGPi%a%R3^W@CXF422PF1w8DBoPyPr$)H;<9q%RX*KeC7WF6kI z7=-9Aw!80;$f&Nrb<5Pw^iu^>Dtht2+$OvA8$ahT1K58#14@ui=|-QQ8*-W90$fp; zhP(phHxjQv^V*PL;z@F2p*{GkyeLnq+iJh74c+){+h(j1S6cJOZ0CiUcI|$t0>5JX zBjWD1JrzNs@0dS!*1L@N{w&`urTN${5w){md0JM$Sb2x?3qc`_jg^_EBXyXxHMY(b z*PB^sGDwi3-E|`2IV#%rxw})(d0EJeYL1eX=uy10k0+toVB88Bw9-g9;cuF*Xl+@` z<5mN|imlNy@V5Ipr_HTZ5ICLm)elf_^QEb{gvk>sor%n9VxR!?a6JfL-p7?Btjz{e z8%W--q*YHg+aC9n*iT7v;Xx^%MOs}{-75KymPSr;V~D+N5+Y#<;w$uR_P=f@N!e~k zKZwlSq}$AmoS4N59?p9r#%6af-lg3l+aT_PbtcU>ZZU5IE1eN&`5AFwxYttKniK9= z*!Q&X;cLZs;wNh-@Sq8F&7{L(@~+A?dlLH(b4+1{_n~3aA1FFkxVOl42i&m=MAkB~ zLB4{B^8rTGl@%b53eLL{xZInA>xGpqH4kIuYhRKBQr=gc+iRi?B>^ZdvCBcd7b z4$Eu2(`(IZOMG~z`SUlfvn;+WoQk7Xv%zUD`tak9FD|i#!~A1=f58^pHa~IhfRQv- z0_Oo!X~!MrB6~tnq|1%asqXR*zcyVibO-+KhhBGVoA(laY{aXqbe+`tkjs&jJ17Rp zGPJG}^(-g!8NX^PQ4i&(iZQS71W=9$g;n621MP(?4%pdRyY~pH&<09j!`zSqcyLV@ zFwc1t4t(0Xnc2fXeh-q#e z;kbv+4hC#(j(1^uveMe*W7T*>rkIo^#E8v%Z?vC$xFri-U$;Kn8^5Vt%0dG-)b_6Q`NOX65W*Fn~e7lEi~9> z37SP$teM#N@6kxpm%AN~-7N9Rq*b9M6d{e*ZNVA) z{II=`*SNylHv|+PMAZXTpWgVwa9%57@FC**6oa%825+3@qLJp_ReU?7`OKq#13%SX zD+F;4Z{GsBztR>2w+R1JY$&*P@E9I;e_y3S{YuBb#fp1_u=4q&aBj3I$2A({r4k#<cJ(qq`53u0&iFpmc*TC=lS#Qxiu1V+VAFXC^L(kt*IK~L`)?og6Q(_ zrF`!`F0ycqI(r~M_{){d>n&A+rL+0}6rhICd`<5j7Y9h6oYs}QweX)sKrh!S&M1Uw zZU-PZqTDobA4yQJL0MYNYqvvMqYo8;2rS|PxI`J#sSaL^;O zUp?nV?*Ui+8dQzAy2MV(-c`h90^9cP4oSHgP+p7SZhjX|?K&kw@9zhS{LEzJ!1GO9 z3Yem%Ac5;AO7@FCDZaeQD}wU|B2UNB8QI6r2gD!^5lr11(^_?WwBw`%nU&RJwGN&E z-{NyTf-kh3hfFeobm;OQ8q|S#TC;r;?koPjbjd$4Q?4QjK>WbeZR@A}gNbh(JRteP0rdOXRE^p>!Iq7@OAo(LgBiv?0 z_={tSJO1?_eAf3Plknm^Yc=_ggnLg-+g1G*28XL!O5T<3QPr1K?Z12b&S=47J|1s7 z2a6ip<8{$aohbqWlo77gJ)^*99lkdb(>G^yP?ZR1LBnN`N;!hM(*N}Y?6&Q)IdJ+_ z>?USso`FH~pJ^CZQ%tE;yE8OHRZumh4Nf&iq~WBxoWfTIJo0~r++OJgP1_QHNlg*l zd&W6by$1XNJxtRGwYx2&uZb$)s?+8VGty*xSnJkck;uJ-k_R5u1=j*|)iaHtU+GMm z=9n48(@oRd<$Xe`qT{vGyr|Qq7D8LH0~~6ZzoqNk%LOQUTS~|@LXT7Qh9nGE2+EkN zWtc!;jsBXJnLnxlsw}8^={XL%bf&{N-ekCxNjTQ--DaAk=v8SB)MEZV>kYOf`4VNe zEcXcX4c)K)e$q$(Ucc>>C3A!)YmgjU$OnZ)mf~OT%RV>-`&MRf9k?#>rkY*ZPM}M(D`fJ94K0gU)fSYhX9fsKXS}gC z-L4LF^~aMFPLzENT^>W5DR+MzP#o8NCx*~~-|oW^e;-Y)YWHatSdL$U3`J>U@xL|A z8}a5Q(lIJjJ&M`byx)tS__JItGnO+%2|H44odxdcZrN1)uG3*jJb34rZxKpV+UJlE zY=Ffc-j@!8XhHQWTn|0|D{C00?YTTmN=rVzFA2BmMYmSeTaQM^|G)0&mOI@2|I%lTO$l)N z-YJKsJBm46R{~Bz`rBP$h(Hrwiqnc`GQD<0kP5+4Fc&Jhy5m?+p~?Ks!@RZf$nwD*sg-hBU8Kfx-;fh~HujdmHvKCeNNU$Eo;q|R2~aGB*q zs1bkU^~7!FT?$+8Wm>h@DU3f}g+@JG1w;?Bbt6z_ z^(JVIbonMJmy^j0(9t5!)eZ2nI@N2-AEQO&l5jC$C+T-qUfpxtHV^zL;mWM13epk@ z{<|1!JA+CRayY`xb49el`D=e+M1NYLH|$tSL~pOsE{5~h&`rMjO%d?Y;)u`bqsY^Y zczqK8UVOH;fJ%aA^OSMLQ0|lPwg@*-Qt7?1i6*+s%yxENl=COYpnx1FJk5DU8BS#k zSkMMn5-3-IS|U0eLFAfac5d~sS?RX8@9njFS-$+}#K}U8I`$`D6ug~s&v~EtqwQum zhNL!TsIs2XBSYkD;dif5MzwM}L*uvpk6S(C-ze zRf4)i)98TBBo>$rP@4g;Bk`BWnJ&y?dN`y|3wPSmQUCbS>DT`DRZv}N*4i9kX`z28KZ zyYykhk_$RXaxL#Tc}1=XH03d>O})`0vr>Lq?t6G0Ygv3eX%?<_jd`I~t2eF8b*(LlRS3y`?qxufd(Lcgxl ziB<2zf+BGG=kQiDz(=4{e*a|pj`-$F%!)Ks(g{Gf_-s(#)1C>>FheJA?tc+u*HlGSpJ=lL+C>gj1G zVB6yKt~2GKD41(wkIeH5e5ph)A`%c8mga@ZmysH9;@T06f8FYO+<8r6+fwzu^AS5V z>)Pu&%!LKYue6hEqt7g_t^B6t`e+%05pnx&OM%Y5t%GDrgUZV5E4*x8$~|w$e2OYT zCS1ROR7P7;WJ;JJm^7-F93H}MPEskte{Jl=A`s%2Tsc%U9Vc;VJ2xW(^eh} zLA{CHnEjcf@KK+zw?jpgLI>=ekD1gCK{u;&(hus+ErwaC5p&RLoJ)z}YHF~%{v?P@ zC|=}vy;thH4!^kPaePE4DwlbVRy##PRO_a3Ka(@^oRrR6p;H8u_S7ZhgC2c#I|X_O z;edMQ-$&>=gQ@u1GdgaB0D0&h4DTLd0CmILKUGv5H6Pa&Vrrp9-@ZKUUB=11>v6+_lB zjs*6x>?1P@oOW0x%8-doCh+dso|M`@G@di|!Tb>NW@*t1V1&2rCI)UI6zwv4EMZO=kp`nH^N#5QtZ%xj$o z+L8tO4=u6>=or;#|de*qcjQ3n!N*FPwl$?!(L^ z=vDcRoXl^L-35xK9wJd4FQMSRD3qu#rmGozJ|6`=ktk)}`WXkGHPD&E9Zus(QmFl6 zoR%h5#N(os&m2>X{W(cB5?`eUSwQZxJ`r^@rdh2Fzs6DGr^L*S0}Jfa=GJKl)w;QaqrE^u*qW_? zB3Nv6Llt5}l0~}zfiLP4DtR3Kki8muNzP1iE63+!p$IbPqs!VZAMh@tNvzJ308Xf5c;|K$85TBmEbIVF#Q*y)|3ETXcA#Xv5a zYENk@z*uK6V3~fyx08?wabV{Vr>b9kETPYH@{?DR9wejN?@cdqN{Qs=VxX6CV}A5> zHi}E;6WCs<;XLpi-T(fYyD^8^q6wf@6bmD4BU2ensk2>V3GA4Qc54KA@YD8)u|DH% zYu@O2a(cD~G5w*puNh8vm5UhR29OkUPB=0bu0bu|g-~bhKMF3q8|CkPrU~pykp^tq_SZ$2s<_3ble2*Mt}x&Bd~}MD{@uCp z+iX0OFj0JG2Ah8=KA#LaRQsG57AN}Xv+l~!8dhN`Cd^ZH8yBjH{DFBseoaVW55(Kx zT(1#)_jJ_F8s8zX3zSDB8T}&sdpyzn(Q9twa=nbHpV$HWWR$ioZsNx4vav`zHYmyn z`sPFAlaOu84PM_ZF4D~o+j}@u-Wk=H0rx8y8Hedr~6a_tE^h;NgxAzh)6 zGZW4xOcCUytwkfb7SRg9Ga}S0_H?Z;{Hoz_y9TluPwy`vqjL;OEo=!pJFysQ9SF4O_vk6r;ka_S z<~L08p|Ahl!^2PU_K42!^s2JxHiq1u_)0!1mt-svPU%6N-_0l-x>NG+P^QqGPtp)d zB>{L=Q=n47ocuUGM!m+*i!X#UUj0m5cWlh+^Wg_0s8&<%s}ENCzuoqIe)tLvWLZfB z=l0_cG1v2qVXWh4HPc3*0A47(PE&nrHkaBNR4N?0-M^g+fUvwS5=bxY;T{=sB`VIx z)qZW!nmNYZBfV+Oeh6EBe&pK^PRbf#?e&VHgh!`X>IIvSkmVR-o#>&#sH%RV2@GKh z8J^)!e{T*GfyakyfJ@!$>MgKf5V10M`z6J=BVUo%^TG!s!5lhpJ(&5EbOoUW#dg(uJxBi8) zP436VL6vb_Dn6ZQvCfhR|JQ)I^cw~x><3)lT+?xz6ijvHz#OC*>KC{40geH?k2ESc zG7j|q71Yi7jrU+}_PH1DrE?^Ae~z=`ImQForgvjehNWfL$P?G+*Y_k^NTjxINkotw zxP0NLB6m<+z+6?EJ$kY;`JItfKmES^74I?!8|;+#s`CI3nvu2ddUCo-QSzd21FqX; z*Lmg?nmNEw<(eEplEB`qk>~^*22qjUiTKN;2s+ueSfK(MP+&Js>Ju%0jYETg^$9k_mp0NCB39d0?5 zG83XVgMTJ^V3S5nR}W=KZvAp@l_d||C(Vhc>i}=+_R>f*r!AS-e-A!rC-)YWUh&2< zJmgq|#$?Dq)JVTotPKXP+@Fz|yD^@j9|Um!cyUJ1$V*~51)F-wdlX(p#b0J84x%6> z%YY`=2}taAxE(QxALVN~S6FRA^@C# zpc-EBOKU(|beoMQq2SHUEir`e_!uQ|>e-1j@i^1AE2^^|@boZPHyMm~!m&@G$XMap zE(d`yk06JfW5A)8*x|;^ydq0$nT`8oUJ_fA3WUKN;mFWA{~hJf8CNS&!a4hJ{|Fdc z+lO&tPTcYJ0OfBKXs*N;rpB1pW-oPpkDqHOgCc*Z-OF`3j&HX(e&VUvu|wMyU@jqQ z6Wbr8nOq#fZS7Bv19!UOTiIddlK%iUO5-M_R^g0MNQZEf?3jS%G7ikvANEvmO&rNv zUeg=50p2LO-5^q0`y)y*H^}h7xt{|?iZqiDwnMTuzaARt*FUeB(ZU?;u|baygdw_A^ffbqy4&$T8f zR+?nk#+^2ziY!w!~sc~K*H^xX>_zlSKK;tc(Ra__8O;*Fds7H}s2jQs`TZW~M# z`&YqruDXunljg_}GRC{rll2dm(Fn(*I(jB{lNZ`o*w-X0lh*HsQlCHaClQ@3D~z^p zCTxUgX;-(!dUpUHqkPpdrs`_agad^dt6vr7D!#-I3@Y7BA3$rImb1e`c&WqU9g5U& z4Y4=WaaRCBk~Hr$nmP+v9Deegs^S};oqppkrFOhRZ-K!b#!3Y9iUS94!Ggu>kBk9?{Rudq*kXOj zKv6j9c6qyn&+to<*NAXil0p9O+tu2X+hUZ@Xr@6gW}?v7s>?mO`7DNP}SWUzuDV{p^4rKb8)2gvQ#BwqfcheJN z$I#{V(EU<3sKD4+nxJe=-Yd8NLkd=edWMH03Ayc)Oh^ko;h5!fh}uC)knfD z=g->SB!e4g2dS`l9NMuYg})@TOWu(p$z*Z_^v}pys8V?_DK-0r*W|fj-WM&W6cpqr zN?3(K=IqmVgqUs4fBJnpRn2>9fr}^;syf0^hhj6yXxEtqNOgJp+M9lSR;Ry*;*S=} zF>9SvooYBe9x{liEVuot4uGpPQA8#NT3m2+K`7kUyqrO{Mww zSL_`PuD+yzPlw%0?DY)dP{L?lu|4m_X(4$Uk-!~Y2a--4Ph{*8N z%|)Z{O0nj8$a-7x$Lv|37)Y%j(5G$Rkg}Q<+oQSz{lSFyePs@OsKil^L8k>JOVF%h zz2o)C!3EW`3AYFwHnp$A^g}4r2zd!C_*|2-JV|r0YwdTz?Csv8(rUg+T_f6xx(if{ z7zcU&=xBj$`mLa#+G+`Ye$WeJfTQChinP6NPTO1xf*@eKb!h!yJqn^(S4052^RAx) z@VPQDu|`I!Glx-gjz6Ii3IQvboSyx);9Nb>po-AOqYLEz@gy6+@oI%`cq>lZm7XW{ z>jdV;qcWiQ2lxZ!+4AqNd)M}}e>JqzDj%9(2hYk!9DXqbhRXpIuPf8SeMvie!y4yi z(F~Q!#4Z0@G$9uva`w&fzX)--UcR#8tl_`q6c20v8!no9j^aW8@5>FfuCeTYEb~8A w7kU1a|L>GM|Nm8*@xN#PFEshG>lve&;i8vQ@dWbk<;n{=DUf8vm#@G64+22-g8%>k literal 0 HcmV?d00001