From 21a58e4b26def92655f9eb4c4da63603509596da Mon Sep 17 00:00:00 2001 From: _Redstone_c_ <2824517378@qq.com> Date: Tue, 1 Dec 2020 15:59:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=A8=A1=E7=BB=84=E6=96=B0?= =?UTF-8?q?=E5=BB=BA/=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 15 ++ ModSupport.uplugin | 34 +++ README.md | 5 + Resources/ButtonIcon_40x.png | Bin 0 -> 9212 bytes Resources/CreateMod_128x.png | Bin 0 -> 9780 bytes Resources/CreateMod_16x.png | Bin 0 -> 2149 bytes Resources/CreateMod_48x.png | Bin 0 -> 3266 bytes Resources/CreateMod_64x.png | Bin 0 -> 4567 bytes Resources/Icon128.png | Bin 0 -> 12699 bytes Resources/ModPackageCofnig.json | 61 +++++ Resources/PackageMod_128x.png | Bin 0 -> 6324 bytes Resources/PackageMod_16x.png | Bin 0 -> 2055 bytes Resources/PackageMod_48x.png | Bin 0 -> 2163 bytes Resources/PackageMod_64x.png | Bin 0 -> 2915 bytes Source/ModSupport/ModSupport.Build.cs | 53 +++++ Source/ModSupport/Private/ModInfo.cpp | 3 + Source/ModSupport/Private/ModSupport.cpp | 20 ++ Source/ModSupport/Private/ModSupportLog.cpp | 3 + Source/ModSupport/Public/ModInfo.h | 67 ++++++ Source/ModSupport/Public/ModSupport.h | 15 ++ Source/ModSupport/Public/ModSupportLog.h | 6 + .../ModSupportEditor.Build.cs | 60 +++++ .../ModSupportEditor/Private/ModCreator.cpp | 64 +++++ .../ModSupportEditor/Private/ModPackager.cpp | 221 ++++++++++++++++++ .../Private/ModPluginWizardDefinition.cpp | 201 ++++++++++++++++ .../Private/ModSupportEditor.cpp | 96 ++++++++ .../Private/ModSupportEditorCommands.cpp | 51 ++++ .../Private/ModSupportEditorLog.cpp | 4 + .../Private/ModSupportEditorStyle.cpp | 72 ++++++ Source/ModSupportEditor/Public/ModCreator.h | 33 +++ Source/ModSupportEditor/Public/ModPackager.h | 49 ++++ .../Public/ModPluginWizardDefinition.h | 70 ++++++ .../Public/ModSupportEditor.h | 30 +++ .../Public/ModSupportEditorCommands.h | 27 +++ .../Public/ModSupportEditorLog.h | 6 + .../Public/ModSupportEditorStyle.h | 31 +++ Templates/BaseTemplate/Resources/Icon128.png | Bin 0 -> 13381 bytes 37 files changed, 1297 insertions(+) create mode 100644 .gitignore create mode 100644 ModSupport.uplugin create mode 100644 README.md create mode 100644 Resources/ButtonIcon_40x.png create mode 100644 Resources/CreateMod_128x.png create mode 100644 Resources/CreateMod_16x.png create mode 100644 Resources/CreateMod_48x.png create mode 100644 Resources/CreateMod_64x.png create mode 100644 Resources/Icon128.png create mode 100644 Resources/ModPackageCofnig.json create mode 100644 Resources/PackageMod_128x.png create mode 100644 Resources/PackageMod_16x.png create mode 100644 Resources/PackageMod_48x.png create mode 100644 Resources/PackageMod_64x.png create mode 100644 Source/ModSupport/ModSupport.Build.cs create mode 100644 Source/ModSupport/Private/ModInfo.cpp create mode 100644 Source/ModSupport/Private/ModSupport.cpp create mode 100644 Source/ModSupport/Private/ModSupportLog.cpp create mode 100644 Source/ModSupport/Public/ModInfo.h create mode 100644 Source/ModSupport/Public/ModSupport.h create mode 100644 Source/ModSupport/Public/ModSupportLog.h create mode 100644 Source/ModSupportEditor/ModSupportEditor.Build.cs create mode 100644 Source/ModSupportEditor/Private/ModCreator.cpp create mode 100644 Source/ModSupportEditor/Private/ModPackager.cpp create mode 100644 Source/ModSupportEditor/Private/ModPluginWizardDefinition.cpp create mode 100644 Source/ModSupportEditor/Private/ModSupportEditor.cpp create mode 100644 Source/ModSupportEditor/Private/ModSupportEditorCommands.cpp create mode 100644 Source/ModSupportEditor/Private/ModSupportEditorLog.cpp create mode 100644 Source/ModSupportEditor/Private/ModSupportEditorStyle.cpp create mode 100644 Source/ModSupportEditor/Public/ModCreator.h create mode 100644 Source/ModSupportEditor/Public/ModPackager.h create mode 100644 Source/ModSupportEditor/Public/ModPluginWizardDefinition.h create mode 100644 Source/ModSupportEditor/Public/ModSupportEditor.h create mode 100644 Source/ModSupportEditor/Public/ModSupportEditorCommands.h create mode 100644 Source/ModSupportEditor/Public/ModSupportEditorLog.h create mode 100644 Source/ModSupportEditor/Public/ModSupportEditorStyle.h create mode 100644 Templates/BaseTemplate/Resources/Icon128.png diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93e6106 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +Binaries +DerivedDataCache +Intermediate +Saved +Build +.vscode +.vs +*.VC.db +*.opensdf +*.opendb +*.sdf +*.sln +*.suo +*.xcodeproj +*.xcworkspace diff --git a/ModSupport.uplugin b/ModSupport.uplugin new file mode 100644 index 0000000..a822df8 --- /dev/null +++ b/ModSupport.uplugin @@ -0,0 +1,34 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Mod Support", + "Description": "", + "Category": "Other", + "CreatedBy": "_Redstone_c_", + "CreatedByURL": "", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "ModSupport", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + }, + { + "Name": "ModSupportEditor", + "Type": "Editor", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "PluginBrowser", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e10c93 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +## UE4 Plugin: ModSupport +[ModSupport](http://git.myredstone.top/summary/UE4-Plugins!ModSupport.git) 是一个辅助制作 UE4 游戏模块化模组的插件,借鉴 [UGCExample](https://github.com/EpicGames/UGCExample/tree/a30eb37ddd71c6b958000af35506085d263e6934) 的思路管理和组织模组,配合 [HotPatcher](https://github.com/EpicGames/UGCExample/tree/a30eb37ddd71c6b958000af35506085d263e6934) 进行打包来实现单 Pak 包模组,可以在发布版的 UE4 上运行而无需源码。 + +目前支持的 UE4 版本:4.25.4 +前置插件:[HotPatcher](https://github.com/EpicGames/UGCExample/tree/a30eb37ddd71c6b958000af35506085d263e6934) diff --git a/Resources/ButtonIcon_40x.png b/Resources/ButtonIcon_40x.png new file mode 100644 index 0000000000000000000000000000000000000000..2e8bbfd4648ddbadd87d366494464ee7e2deb267 GIT binary patch literal 9212 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�TyPy#GCNqSjB1N2_LbMX!)}?MeMP z|Jc|!En-(XnIv#ePyq7Txc?!!a@JDmY{KbdRfwNA~H7^z-OANFr-j&qV-JP}w zc~1Ogs#KHxDi6t!@0o6k*1x(lXezOlj0zPoEfz#|jy!tx&`~*I#nMS<5k%9ARQ=%L zz^&fSW-ZH=d#f7@arfm@aDNDV)w>doHqnj!7^P<(h2I><86t)9A_SpuIlvX8j|;U%TPh)xr3Kp5?{YXGC|}|_)6W^xRObuG z$`=C7cC9=#6v80J(kbnH6ehr>=GMEod?>-=&jdT0mt(^K=0E&SweaQRz3j+zI* zd;*H5SDqx+tN24wft&_b+C-4KH4OZaD`sz|P(W|DN*X8(ORC}vAtMg*NjcBufxyxj zO@!#wY*)Ctr#zaokIpz7ig(b|8Lh+cf~D0@wA#~A!mtozjAaI}=oUM;d+6wkd{u&M z&@^md%Rl`f-WSZnKyFaW>K`o0Q_KyrZDRbsK*un=ErC(!R?{%BZ9l>TyP7W4cgb%#8tA*y)MA+B5&HDxo z4IQUGkVyl%BZf1;VE?8*Td%UBfrU056DFv*O)X6uCXy9>1Y(*+EqwjO)j&X(*i$g4Hmeq-n2HClE%OH{MA zlVcs%OsKDyR~5{en&>no-0Y z85!a2w+)#j8lE3JNk4qf|C0A&{)O?I>#Ug@mFM~HGq$gD^U>bJIY!vu@dnE4Um}pr zBsH5n%e#A$Ykduc2T_ZN!mthInbs@o zC4Th5!%A`SdHj&0G?Y4T|NHjNGD9(C;b~gNJX;*KGH8F;){dv+^GTq!UGvrku?ea4 z#V`dWBO0_Rf&u^3m!NWEUtKeBS+Wi!WgC^Xm$p6=*bN1h#l1rqj(-rsG{aOn~ z2dLMh`%74VJng@mEB}*V8Hybinb*bh!hM?UJ7^UcNP1__4Y2bd`3PxsPIQbHK zK6o~`VWy}k3hYx*ti1+-*8^$2L&^(so&IEJOKr>r>&>;{iYWioDD#)|#LZDsHi2a- z#LF)4p5nV=cK82Y5`@lozZFzx3K~Mq@jst-iE=9RpME-|Yke8U{p|R>Fg!!hd{TIx z!X6nrTN^~03DVb?GdI1`OvBcWs5+0pVqtkpf1!BquCl2Je(D@MMuk@C!z=TTy80`* zKbr~rR{fxi^yC*<+1LSoyN!{rsWEO=nL5C4hTfL6 zJE0#m4MhL#`mQZ2to}q|Mg8V#n$ASqFFNnGYP>N^snQbA)fZ%;cmsW!Y*OP;QvE}t z1Cmtit8x;Hr0ORt~nT0uf5$X2hVNdpLm>6k71abS>J+AruYa6J9$7V(*;z#r7P(!N(&c_>+PkpInM z`-*P3t~%~Ngnz!9Zr`9=1jnBrG$^NC+~zC(G~lE3khxPQva3M6+T6>+w2eJ{ItY+V zr@s|nW05T5bL7~JHwSdJ1slCP)jR}u^Ufozw@B!@Ol@H#u^D_Y?-}(&OVyvQD&GoI#cYBL7HR(fbXScO{QJ6^lj-S(`~=@a?6LBlR2RyGpzp!W zuh*GgFZFWKG@eUWQf2LLHSXnZ{!{q}C}YWdx8am}H_&QfwapdJA8J0768FsniL@J_ zsiZ`(fPt@|HGW<<=~oBKMtt*o9`gqNJ^vXyi~^LnQ0a|w7u^A6SmDK|*TcUY+~~pYc0S z31A~ZJd%r?;)avGpVuFFj|=E%3l=wDp<=q->;10bgHb}wT5OLNxEwHzOC7#%%V+ zY6WT_2yx7Y8%UW-{H|R}M*c6k*~QwsE9_4X?~J)-Ml#aVg94bZ`4T120i8r%)J?H7 z`J_C}>;wsH7(2m6imb`EW#<14Nd4a_s`sGN7m!nNtq)&4n%ch@7@#PpDqA6K9`rxq C3h0Xf literal 0 HcmV?d00001 diff --git a/Resources/CreateMod_16x.png b/Resources/CreateMod_16x.png new file mode 100644 index 0000000000000000000000000000000000000000..f61daf00e39fec016e050c8f8f386dc42357285c GIT binary patch literal 2149 zcmbVO2~ZPf6b^JiMX9KGw&>0>gIG;=H^GD~1d9nrv{B@cAf>YGZeUB2jk`+{z_VJZ zQWU{@6?MdxvCe=8MT(%J7V$nPiUXpxt%^m^dZMCqmm{Mcd%3fl{rCUB|9kIy|9fw; zB4S3EyW1Eykx1mO4cA2SZx7+=@5=v z*L{TIv`K;ybP_4Cn=Cw9Bnq5jw_tcG$pJ}ZGG$hQcPkHp07a<4*vUFrXHk_$e0s`$QPhcWB46vJwW)`)pz;0fY9}CkE2y{cZR23L37zFgX2tZ9U zBp{c-VjPC0fI=xjM{5O4$%RntbA;SKZN$zGk; zs?`w;ZJ><&0vj1N70`yL<%m))7fU5bx4SwWsx`A5X2waaMg{WWkx&$Y$`nd}r2$up zWr)AO*iWfYh%pk8ils0jl@kgju0*7L`!zJ4CL}@Me&T=bpUF^s5n{%-<`GI$$R0GD zV)=7T?=7Ava$fJqNCDjmgJQT)2r3X4GJqt&-f8NsBhW)@OCfpEf5?j-7)u*C8^(~q z$$Yf_L*_xe`;btyJxZXzitJu{rNH~(eAf_$j&{X=I2s;l=DQrjw=+*@`fnnU^B-DG zaI}ApDKEk3ndVuusMI>rQeO5`w4y#o{JlPP88|q*ZbL|T*)a_;E2417&~#v%7HL@u z4aMgpL)CE7pvL=&el70=?Lg{s`@1`>TQ~HOM^nOcC%x4WP$?^w1q3u?p{MHXd0%(3 zZ2Qw|ne8!}Wup9wr1(%pzvNqOp$A&bSJr31gDoBP+Gq&$ivb>jBR| zzf;YdO0VpxYAaf~s-bPu%Pdj9tdc?gMO{1FX3u%BFCfFpv?mrX2Em-+b6vYm4lgGr z=TA7}o3pxLKuvr@m`~lvFwHm3Ps;3J_vb-de%K|sx_j-VV=XtnI_)!T$vZwbmQTPT?MrnTMAQkc_kOMjr%hxbKH^a)&T8+dx5_mJ?fb-xY>Fl?yN_| zAo0L!lXq-9D;pp5Vs8>rI(<&lIM>9cRqHPll_bWEy|uU8S3mY?YgDKG4*O+f#iE2l zD)wh5QNOD*v*qjn)6Gc%*1Owpyd1GN7Ro<3qKHUVb~PiHRD1F}PI}&bv}?BWu-2W{ zmFElWYlnNShPj8{Nwc=r7k#o&^};3O6n^Wfy5-wZ*6$)Kmo+(qL0iVBxO}cV*EaqR z>EC%czUqFX)0{?yCGF(4J24(}BVx*Q4UbNH*#Pk3hKjgqpWtUlT=G;4Iv?8xoO^IM ztIKC>R7_f1;k(XDTrTA(^JTY3FRv~B6svY>cM6S(X-;UX-gmP!c;1Hvn`$!sDKh{7 literal 0 HcmV?d00001 diff --git a/Resources/CreateMod_48x.png b/Resources/CreateMod_48x.png new file mode 100644 index 0000000000000000000000000000000000000000..a9580565590866bdbfed2263c8b4187957d12905 GIT binary patch literal 3266 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 literal 0 HcmV?d00001 diff --git a/Resources/CreateMod_64x.png b/Resources/CreateMod_64x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4278cdc59a9941f3ee96cd2da456e0c78879161 GIT binary patch literal 4567 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 a;7#~yJh_ha>A&A^fRdb=Y>l)<=>Gxy=2LS3 literal 0 HcmV?d00001 diff --git a/Resources/ModPackageCofnig.json b/Resources/ModPackageCofnig.json new file mode 100644 index 0000000..c7cf9dc --- /dev/null +++ b/Resources/ModPackageCofnig.json @@ -0,0 +1,61 @@ +{ + "bByBaseVersion": false, + "baseVersion": + { + "filePath": "" + }, + "versionId": "%%%PluginName%%%", + "assetIncludeFilters": [ + { + "path": "%%%PluginContentDir%%%" + } + ], + "assetIgnoreFilters": [], + "bForceSkipContent": true, + "forceSkipContentRules": [ + { + "path": "/Engine/Editor" + }, + { + "path": "/Engine/VREditor" + } + ], + "forceSkipAssets": [], + "bIncludeHasRefAssetsOnly": false, + "bAnalysisFilterDependencies": false, + "bRecursiveWidgetTree": false, + "assetRegistryDependencyTypes": [], + "includeSpecifyAssets": [], + "bIncludeAssetRegistry": false, + "bIncludeGlobalShaderCache": false, + "bIncludeShaderBytecode": false, + "bIncludeEngineIni": false, + "bIncludePluginIni": false, + "bIncludeProjectIni": false, + "bEnableExternFilesDiff": false, + "ignoreDeletionModulesAsset": [], + "addExternAssetsToPlatform": [], + "bEnableChunk": false, + "chunkInfos": [], + "bCookPatchAssets": false, + "pakCommandOptions": [], + "replacePakCommandTexts": [], + "unrealPakOptions": [ + "-compress", + "-compressionformats=Zlib" + ], + "pakTargetPlatforms": [ + "%%%TargetPlatform%%%" + ], + "bCustomPakNameRegular": true, + "pakNameRegular": "{VERSION}", + "bSaveDeletedAssetsToNewReleaseJson": false, + "bSavePakList": false, + "bSaveDiffAnalysis": false, + "bSaveAssetRelatedInfo": false, + "bSavePatchConfig": false, + "savePath": + { + "path": "%%%OutputDirectory%%%" + } +} \ No newline at end of file diff --git a/Resources/PackageMod_128x.png b/Resources/PackageMod_128x.png new file mode 100644 index 0000000000000000000000000000000000000000..422111382234f20c04cd98610de93c06cf41b023 GIT binary patch literal 6324 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 literal 0 HcmV?d00001 diff --git a/Resources/PackageMod_16x.png b/Resources/PackageMod_16x.png new file mode 100644 index 0000000000000000000000000000000000000000..cbe6e06ab6c066d19a8e2d3c9e2502398dc84ffe GIT binary patch literal 2055 zcmbVNYfuwc6kZ;sXra7xe4ut`iGm%I-DE=`tKpTT5Fv;pBPc2^n++@^yD_^6VPsmB zR$#R4K(SWogGWV+byO;7p_SrvYJb#DM=gB_v{lh61)W;uQA_Xg$Y{sD+}X|Ez4yD{ zIp4YGoK&QxCi>5uKNEr=e?yWk9lQg@$JYn^KJ)8130^ZCNo#osdMZSGJfMBGb0O%7 z`HVSJ$TX%96la%_7A}XD726#E4M8hniyb6YKnrjVoy)KqWT^c#0y7p3k`Zadj1DcG z$0U_FX;Vq6nJOuu)D|Q*23}E200KKLkZ`fx#_~k52ASX`z*w9{5qJV36ljom(IA{@ zOoO$YlZI6?OiE#x0*+G414ki}A#51VGt(LCj390J3!u*gW} zaD{>f0iI4uushteEI$<{5HPfubf9t>CQ6zBS|~TpQRuWy7`IR;ZKLfpEARj-cViuS zoWSvU+}}{Umme|!LTfa-Z9LK!yWMSq7xY_zjH!S;63v@k4jN6Td9Kh&(fTdG%`j1o zgU~u@QsA6sjECBV!l~p{7-; z$Y^zxl%g%TREg8kQni(elHzhJsibiVSCEr(!G0Y_6^cnPz2EXb_pfp?pa@CZWAlim zDP|9m#PHx;T~ozlqBl>CYz#b+Fa$}7g`hzwF#~7|GBwRSb_6DAi}Gkd`VV@4w)Y)r=l8cC3Y8|`jF%`)m5V%QC@Uo^OOitTZq%6r6CA$< zdu2!Rp@zL9f7_m*E< zevQ(VUHrONV{R;KT9vtQFudoNny~T3jq{Sjub*5oVD7>{TC=kt``H7Z&KWxuQMf*@ zIkUPvC}``w7^3D@Et-7%gm3>c9MWl9hVP69UDDI*REV@BDdk-={TwskK^NU8X#w9jjR zDE14jOzs|Vl_)OwZhEoPbo8^4uegJ%XpoIP8+ucAD#Tk!5G`_AX>6P>2IzJWXW zscf7oq;%fsZtil`-tth$qq&Ji0?U0rWPfOi^(z=u)%C~Wb81G0o1;AX12W?baFB=h z+2dQo8$4H91B0ynvv!Snv|McP>9QH*uC^4<1+UXC-5$DW>B5dHKIX`6hST}4ZlmEU zUBz(yo>}OV7@R3-4|~3JvwALmKG;*|y}zfXXtEW`w(1Vruts>i9{g66KqAc9O_f~rzIc!}w4y`{mDD_WF z-RSDwe49V`UaNh5Rbz9N#1(P*%{8^}|8ee6zIXgLhi;BSL8Tk`$m+WRUBP)(b3U%< Q7XPXZ`c&PKgiYK31RdJj=>Px# literal 0 HcmV?d00001 diff --git a/Resources/PackageMod_48x.png b/Resources/PackageMod_48x.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca1ebdda3d7588a43035aa00d4361c33a67ab5e GIT binary patch literal 2163 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 z z%-P~1Z!nF!Vn+4s!lk8#^uYqlzT`oe{(>IRXDStur@Tb>mmfY$bu|5$C9=9hdM+H;Bmd-@`^u5Pnm%2fVbWp8^_cb~Wt#-(Ryh#v8PH zb0EcjyVCfUgSfsgfHCHMdN)5d@ZK3ArqL%XTRi9uH{J2P-y4vBY#1`a?*X)r1e*2;3NW{vXxg!G4+sR*Hs z0pjN*WjlB literal 0 HcmV?d00001 diff --git a/Source/ModSupport/ModSupport.Build.cs b/Source/ModSupport/ModSupport.Build.cs new file mode 100644 index 0000000..4a99027 --- /dev/null +++ b/Source/ModSupport/ModSupport.Build.cs @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class ModSupport : ModuleRules +{ + public ModSupport(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Source/ModSupport/Private/ModInfo.cpp b/Source/ModSupport/Private/ModInfo.cpp new file mode 100644 index 0000000..6927568 --- /dev/null +++ b/Source/ModSupport/Private/ModInfo.cpp @@ -0,0 +1,3 @@ +#include "ModInfo.h" + +#include "Interfaces/IPluginManager.h" diff --git a/Source/ModSupport/Private/ModSupport.cpp b/Source/ModSupport/Private/ModSupport.cpp new file mode 100644 index 0000000..876e7e5 --- /dev/null +++ b/Source/ModSupport/Private/ModSupport.cpp @@ -0,0 +1,20 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModSupport.h" + +#define LOCTEXT_NAMESPACE "FModSupportModule" + +void FModSupportModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module +} + +void FModSupportModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FModSupportModule, ModSupport) \ No newline at end of file diff --git a/Source/ModSupport/Private/ModSupportLog.cpp b/Source/ModSupport/Private/ModSupportLog.cpp new file mode 100644 index 0000000..8b307b4 --- /dev/null +++ b/Source/ModSupport/Private/ModSupportLog.cpp @@ -0,0 +1,3 @@ +#include "ModSupportLog.h" + +DEFINE_LOG_CATEGORY(LogModSupport); diff --git a/Source/ModSupport/Public/ModInfo.h b/Source/ModSupport/Public/ModInfo.h new file mode 100644 index 0000000..30b6e46 --- /dev/null +++ b/Source/ModSupport/Public/ModInfo.h @@ -0,0 +1,67 @@ +#pragma once + +#include "CoreMinimal.h" +#include "ModInfo.generated.h" + +USTRUCT(BlueprintType, Category = "ModSupport|ModInfo") +struct MODSUPPORT_API FModInfo +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString Name; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString ContentDir; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString VirtualMountPoint; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + int32 Version; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString VersionName; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString FriendlyName; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString Description; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString Category; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString CreatedBy; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString CreatedByURL; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString DocsURL; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString MarketplaceURL; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString SupportURL; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString EngineVersion; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + FString ParentPluginName; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + bool bIsBetaVersion; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + bool bIsExperimentalVersion; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + bool bIsHidden; + + UPROPERTY(BlueprintReadOnly, Category = "ModSupport|ModInfo") + TArray PluginsRequire; +}; diff --git a/Source/ModSupport/Public/ModSupport.h b/Source/ModSupport/Public/ModSupport.h new file mode 100644 index 0000000..3fb56cd --- /dev/null +++ b/Source/ModSupport/Public/ModSupport.h @@ -0,0 +1,15 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FModSupportModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; diff --git a/Source/ModSupport/Public/ModSupportLog.h b/Source/ModSupport/Public/ModSupportLog.h new file mode 100644 index 0000000..2f5a232 --- /dev/null +++ b/Source/ModSupport/Public/ModSupportLog.h @@ -0,0 +1,6 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +MODSUPPORT_API DECLARE_LOG_CATEGORY_EXTERN(LogModSupport, Log, All); diff --git a/Source/ModSupportEditor/ModSupportEditor.Build.cs b/Source/ModSupportEditor/ModSupportEditor.Build.cs new file mode 100644 index 0000000..e8ca401 --- /dev/null +++ b/Source/ModSupportEditor/ModSupportEditor.Build.cs @@ -0,0 +1,60 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class ModSupportEditor : ModuleRules +{ + public ModSupportEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "ModSupport", + "PluginBrowser", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Projects", + "InputCore", + "UnrealEd", + "LevelEditor", + "CoreUObject", + "Engine", + "PluginBrowser", + "Slate", + "SlateCore", + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/Source/ModSupportEditor/Private/ModCreator.cpp b/Source/ModSupportEditor/Private/ModCreator.cpp new file mode 100644 index 0000000..bb6927d --- /dev/null +++ b/Source/ModSupportEditor/Private/ModCreator.cpp @@ -0,0 +1,64 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModCreator.h" + +#include "ModPluginWizardDefinition.h" +#include "Widgets/Docking/SDockTab.h" + +// This depends on the Plugin Browser module to work correctly... +#include "IPluginBrowser.h" + + + +#define LOCTEXT_NAMESPACE "FModCreator" + +const FName FModCreator::ModSupportEditorPluginCreatorName("ModCreator"); + +FModCreator::FModCreator() +{ + RegisterTabSpawner(); +} + +FModCreator::~FModCreator() +{ + UnregisterTabSpawner(); +} + +void FModCreator::OpenNewPluginWizard(bool bSuppressErrors) const +{ + if (IPluginBrowser::IsAvailable()) + { + FGlobalTabmanager::Get()->InvokeTab(ModSupportEditorPluginCreatorName); + } + else if (!bSuppressErrors) + { + FMessageDialog::Open(EAppMsgType::Ok, + LOCTEXT("PluginBrowserDisabled", "Creating a game mod requires the use of the Plugin Browser, but it is currently disabled.")); + } +} + +void FModCreator::RegisterTabSpawner() +{ + FTabSpawnerEntry& Spawner = FGlobalTabmanager::Get()->RegisterNomadTabSpawner(ModSupportEditorPluginCreatorName, + FOnSpawnTab::CreateRaw(this, &FModCreator::HandleSpawnPluginTab)); + + // Set a default size for this tab + FVector2D DefaultSize(800.0f, 500.0f); + FTabManager::RegisterDefaultTabWindowSize(ModSupportEditorPluginCreatorName, DefaultSize); + + Spawner.SetDisplayName(LOCTEXT("NewModTabHeader", "Create New Mod Package")); + Spawner.SetMenuType(ETabSpawnerMenuType::Hidden); +} + +void FModCreator::UnregisterTabSpawner() +{ + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ModSupportEditorPluginCreatorName); +} + +TSharedRef FModCreator::HandleSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs) +{ + check(IPluginBrowser::IsAvailable()); + return IPluginBrowser::Get().SpawnPluginCreatorTab(SpawnTabArgs, MakeShared()); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ModSupportEditor/Private/ModPackager.cpp b/Source/ModSupportEditor/Private/ModPackager.cpp new file mode 100644 index 0000000..5af5867 --- /dev/null +++ b/Source/ModSupportEditor/Private/ModPackager.cpp @@ -0,0 +1,221 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModPackager.h" +#include "ModSupportEditor.h" +#include "ModSupportEditorCommands.h" +#include "ModSupportEditorStyle.h" +#include "ModSupportEditorLog.h" +#include "Editor.h" +#include "Widgets/SWindow.h" +#include "Widgets/SWidget.h" +#include "Interfaces/IPluginManager.h" +#include "Developer/DesktopPlatform/Public/DesktopPlatformModule.h" +#include "Editor/UATHelper/Public/IUATHelperModule.h" +#include "Editor/MainFrame/Public/Interfaces/IMainFrameModule.h" + +#include "FileHelpers.h" +#include "Misc/FileHelper.h" +#include "Misc/PackageName.h" + +#define LOCTEXT_NAMESPACE "ModPackager" + +FModPackager::FModPackager() +{ +} + +FModPackager::~FModPackager() +{ +} + +void FModPackager::OpenPluginPackager(TSharedRef Plugin) +{ + IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); + + FString DefaultDirectory = FPaths::ConvertRelativePathToFull(Plugin->GetBaseDir()); + FString OutputDirectory; + + // Prompt the user to save all dirty packages. We'll ensure that if any packages from the mod that the user wants to + // package are dirty that they will not be able to save them. + + if (!IsAllContentSaved(Plugin)) + { + FEditorFileUtils::SaveDirtyPackages( true, true, true); + } + + if (IsAllContentSaved(Plugin)) + { + void* ParentWindowWindowHandle = nullptr; + IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked(TEXT("MainFrame")); + const TSharedPtr& MainFrameParentWindow = MainFrameModule.GetParentWindow(); + if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid()) + { + ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle(); + } + + if (DesktopPlatform->OpenDirectoryDialog(ParentWindowWindowHandle, LOCTEXT("SelectOutputFolderTitle", "Select Mod output directory:").ToString(), DefaultDirectory, OutputDirectory)) + { + PackagePlugin(Plugin, OutputDirectory); + } + } + else + { + FText PackageModError = FText::Format(LOCTEXT("PackageModError_UnsavedContent", "You must save all assets in {0} before you can share it."), + FText::FromString(Plugin->GetName())); + + FMessageDialog::Open(EAppMsgType::Ok, PackageModError); + } +} + +bool FModPackager::IsAllContentSaved(TSharedRef Plugin) +{ + bool bAllContentSaved = true; + + TArray UnsavedPackages; + FEditorFileUtils::GetDirtyContentPackages(UnsavedPackages); + FEditorFileUtils::GetDirtyWorldPackages(UnsavedPackages); + + if (UnsavedPackages.Num() > 0) + { + FString PluginBaseDir = Plugin->GetBaseDir(); + + for (UPackage* Package : UnsavedPackages) + { + FString PackageFilename; + if (FPackageName::TryConvertLongPackageNameToFilename(Package->GetName(), PackageFilename)) + { + if (PackageFilename.Find(PluginBaseDir) == 0) + { + bAllContentSaved = false; + break; + } + } + } + } + + return bAllContentSaved; +} + +void FModPackager::PackagePlugin(TSharedRef Plugin, const FString& OutputDirectory) +{ + FString PackageCofnig; + FString PackageCofnigTemplate; + PackageCofnigTemplate = IPluginManager::Get().FindPlugin(TEXT("ModSupport"))->GetBaseDir() / TEXT("Resources") / TEXT("ModPackageCofnig.json"); + + if (!FFileHelper::LoadFileToString(PackageCofnig, *PackageCofnigTemplate)) + { + UE_LOG(LogModSupportEditor, Error, TEXT("Failed to load configuration template")); + return; + } + + PackageCofnig = PackageCofnig.Replace(TEXT("%%%PluginName%%%"), *Plugin->GetName()); + PackageCofnig = PackageCofnig.Replace(TEXT("%%%PluginContentDir%%%"), *Plugin->GetMountedAssetPath()); + +#if PLATFORM_WINDOWS + PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("WindowsNoEditor")); +#elif PLATFORM_MAC + PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("MacNoEditor")); +#elif PLATFORM_LINUX + PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("LinuxNoEditor")); +#else + PackageCofnig = PackageCofnig.Replace(TEXT("%%%TargetPlatform%%%"), TEXT("AllDesktop")); +#endif + + PackageCofnig = PackageCofnig.Replace(TEXT("%%%OutputDirectory%%%"), *OutputDirectory); + + FString PackageCofnigSavePath; + PackageCofnigSavePath = FPaths::ProjectSavedDir() / TEXT("ModInfo") / TEXT("ModPackageCofnig.json"); + + if (!FFileHelper::SaveStringToFile(PackageCofnig, *PackageCofnigSavePath)) + { + UE_LOG(LogModSupportEditor, Error, TEXT("Failed to save configuration")); + return; + } + +#if PLATFORM_WINDOWS + PackageCofnigSavePath = PackageCofnigSavePath.Replace(TEXT("/"), TEXT("\\")); +#endif + + FText OptTitle = LOCTEXT("PackageCofnigDialog", "Saved packaging configuration file"); + FText Message = FText::FromString(PackageCofnigSavePath); + FMessageDialog::Open(EAppMsgType::Ok, Message, &OptTitle); + UE_LOG(LogModSupportEditor, Display, TEXT("Saved packaging configuration file to %s"), *PackageCofnigSavePath); +} + +void FModPackager::FindAvailableGameMods(TArray>& OutAvailableGameMods) +{ + OutAvailableGameMods.Empty(); + + for (TSharedRef Plugin : IPluginManager::Get().GetDiscoveredPlugins()) + { + if (Plugin->GetLoadedFrom() == EPluginLoadedFrom::Project && Plugin->GetType() == EPluginType::Mod) + { + UE_LOG(LogModSupportEditor, Display, TEXT("Adding %s"), *Plugin->GetName()); + OutAvailableGameMods.AddUnique(Plugin); + } + } +} + +void FModPackager::GeneratePackagerMenuContent_Internal(class FMenuBuilder& MenuBuilder, const TArray>& Commands) +{ + for (TSharedPtr Command : Commands) + { + MenuBuilder.AddMenuEntry(Command, NAME_None, TAttribute(), TAttribute(), FSlateIcon(FModSupportEditorStyle::GetStyleSetName(), "ModSupportEditor.Folder")); + } +} + +void FModPackager::GeneratePackagerMenuContent(class FMenuBuilder& MenuBuilder) +{ + TArray> AvailableGameMods; + FindAvailableGameMods(AvailableGameMods); + + TArray> Commands; + + GeneratePackagerMenuContent_Internal(MenuBuilder, ModCommands); +} + +TSharedRef FModPackager::GeneratePackagerComboButtonContent() +{ + // Regenerate the game mod commands + TArray> AvailableGameMods; + FindAvailableGameMods(AvailableGameMods); + + GetAvailableModCommands(AvailableGameMods); + + // Regenerate the action list + TSharedPtr GameModActionsList = MakeShareable(new FUICommandList); + + for (int32 Index = 0; Index < ModCommands.Num(); ++Index) + { + GameModActionsList->MapAction( + ModCommands[Index], + FExecuteAction::CreateRaw(this, &FModPackager::OpenPluginPackager, AvailableGameMods[Index]), + FCanExecuteAction() + ); + } + + // Show the drop down menu + const bool bShouldCloseWindowAfterMenuSelection = true; + FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GameModActionsList); + + MenuBuilder.BeginSection(NAME_None, LOCTEXT("PackageMod", "Share...")); + { + GeneratePackagerMenuContent_Internal(MenuBuilder, ModCommands); + } + MenuBuilder.EndSection(); + + return MenuBuilder.MakeWidget(); +} + +void FModPackager::GetAvailableModCommands(const TArray>& AvailableMod) +{ + if (ModCommands.Num() > 0) + { + // Unregister UI Commands + FModSupportEditorCommands::Get().UnregisterModCommands(ModCommands); + } + ModCommands.Empty(AvailableMod.Num()); + + ModCommands = FModSupportEditorCommands::Get().RegisterModCommands(AvailableMod); +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ModSupportEditor/Private/ModPluginWizardDefinition.cpp b/Source/ModSupportEditor/Private/ModPluginWizardDefinition.cpp new file mode 100644 index 0000000..ee0592e --- /dev/null +++ b/Source/ModSupportEditor/Private/ModPluginWizardDefinition.cpp @@ -0,0 +1,201 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModPluginWizardDefinition.h" +#include "ContentBrowserModule.h" +#include "EngineAnalytics.h" +#include "Interfaces/IPluginManager.h" +#include "IContentBrowserSingleton.h" +#include "Algo/Transform.h" +#include "SlateBasics.h" +#include "SourceCodeNavigation.h" + +#define LOCTEXT_NAMESPACE "SimpleModPluginWizard" + +FModPluginWizardDefinition::FModPluginWizardDefinition() +{ + PluginBaseDir = IPluginManager::Get().FindPlugin(TEXT("ModSupport"))->GetBaseDir(); + BackingTemplate = MakeShareable(new FPluginTemplateDescription(FText(), FText(), TEXT("BaseTemplate"), true, EHostType::Runtime)); + BackingTemplatePath = PluginBaseDir / TEXT("Templates") / BackingTemplate->OnDiskPath; +} + +const TArray>& FModPluginWizardDefinition::GetTemplatesSource() const +{ + return TemplateDefinitions; +} + +void FModPluginWizardDefinition::OnTemplateSelectionChanged(TArray> InSelectedItems, ESelectInfo::Type SelectInfo) +{ + SelectedTemplates = InSelectedItems; +} + +TArray> FModPluginWizardDefinition::GetSelectedTemplates() const +{ + TArray> SelectedTemplatePtrs; + + for (TSharedRef Ref : SelectedTemplates) + { + SelectedTemplatePtrs.Add(Ref); + } + + return SelectedTemplatePtrs; +} + +void FModPluginWizardDefinition::ClearTemplateSelection() +{ + SelectedTemplates.Empty(); +} + +bool FModPluginWizardDefinition::HasValidTemplateSelection() const +{ + // A mod should be created even if no templates are actually selected + return true; +} + +bool FModPluginWizardDefinition::CanContainContent() const +{ + bool bHasContent = SelectedTemplates.Num() == 0; // if no templates are selected, by default it is a content mod + + if (!bHasContent) + { + for (TSharedPtr Template : SelectedTemplates) + { + // If at least one module can contain content, it's a content mod. Otherwise, it's a pure code mod. + if (Template->bCanContainContent) + { + bHasContent = true; + break; + } + } + } + + return bHasContent; +} + +bool FModPluginWizardDefinition::HasModules() const +{ + return false; +} + +bool FModPluginWizardDefinition::IsMod() const +{ + return true; +} + +void FModPluginWizardDefinition::OnShowOnStartupCheckboxChanged(ECheckBoxState CheckBoxState) +{ +} + +ECheckBoxState FModPluginWizardDefinition::GetShowOnStartupCheckBoxState() const +{ + return ECheckBoxState(); +} + +FText FModPluginWizardDefinition::GetInstructions() const +{ + return LOCTEXT("CreateNewModPanel", "Give your new Mod package a name and Click 'Create Mod' to make a new content only Mod package."); +} + +TSharedPtr FModPluginWizardDefinition::GetCustomHeaderWidget() +{ + if ( !CustomHeaderWidget.IsValid() ) + { + FString IconPath; + GetPluginIconPath(IconPath); + + const FName BrushName(*IconPath); + const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + if ((Size.X > 0) && (Size.Y > 0)) + { + IconBrush = MakeShareable(new FSlateDynamicImageBrush(BrushName, FVector2D(Size.X, Size.Y))); + } + + CustomHeaderWidget = SNew(SHorizontalBox) + // Header image + + SHorizontalBox::Slot() + .AutoWidth() + .Padding(4.0f) + [ + SNew(SBox) + .WidthOverride(80.0f) + .HeightOverride(80.0f) + [ + SNew(SImage) + .Image(IconBrush.IsValid() ? IconBrush.Get() : nullptr) + ] + ]; + } + + return CustomHeaderWidget; +} + +bool FModPluginWizardDefinition::GetPluginIconPath(FString& OutIconPath) const +{ + // Replace this file with your own 128x128 image if desired. + OutIconPath = BackingTemplatePath / TEXT("Resources/Icon128.png"); + return false; +} + +bool FModPluginWizardDefinition::GetTemplateIconPath(TSharedRef InTemplate, FString& OutIconPath) const +{ + FString TemplateName = InTemplate->Name.ToString(); + + OutIconPath = PluginBaseDir / TEXT("Resources"); + + if (TemplateToIconMap.Contains(TemplateName)) + { + OutIconPath /= TemplateToIconMap[TemplateName]; + } + else + { + // Couldn't find a suitable icon to use for this template, so use the default one instead + OutIconPath /= TEXT("Icon128.png"); + } + + return false; +} + +FString FModPluginWizardDefinition::GetPluginFolderPath() const +{ + return BackingTemplatePath; +} + +EHostType::Type FModPluginWizardDefinition::GetPluginModuleDescriptor() const +{ + return BackingTemplate->ModuleDescriptorType; +} + +ELoadingPhase::Type FModPluginWizardDefinition::GetPluginLoadingPhase() const +{ + return BackingTemplate->LoadingPhase; +} + +TArray FModPluginWizardDefinition::GetFoldersForSelection() const +{ + TArray SelectedFolders; + SelectedFolders.Add(BackingTemplatePath); // This will always be a part of the mod plugin + + for (TSharedPtr Template : SelectedTemplates) + { + SelectedFolders.AddUnique(PluginBaseDir / TEXT("Templates") / Template->OnDiskPath); + } + + return SelectedFolders; +} + +void FModPluginWizardDefinition::PluginCreated(const FString& PluginName, bool bWasSuccessful) const +{ + // Override Category to Mod + if (bWasSuccessful) + { + TSharedPtr Plugin = IPluginManager::Get().FindPlugin(PluginName); + if (Plugin != nullptr) + { + FPluginDescriptor Desc = Plugin->GetDescriptor(); + Desc.Category = "Mod"; + FText UpdateFailureText; + Plugin->UpdateDescriptor(Desc, UpdateFailureText); + } + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/ModSupportEditor/Private/ModSupportEditor.cpp b/Source/ModSupportEditor/Private/ModSupportEditor.cpp new file mode 100644 index 0000000..16031c7 --- /dev/null +++ b/Source/ModSupportEditor/Private/ModSupportEditor.cpp @@ -0,0 +1,96 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModSupportEditor.h" + +#include "ModCreator.h" +#include "ModPackager.h" +#include "Misc/MessageDialog.h" +#include "ModSupportEditorStyle.h" +#include "ModSupportEditorCommands.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" + +#include "LevelEditor.h" + +#define LOCTEXT_NAMESPACE "FModSupportEditorModule" + +void FModSupportEditorModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + + ModCreator = MakeShared(); + ModPackager = MakeShared(); + + FModSupportEditorStyle::Initialize(); + FModSupportEditorStyle::ReloadTextures(); + + FModSupportEditorCommands::Register(); + + PluginCommands = MakeShareable(new FUICommandList); + + PluginCommands->MapAction( + FModSupportEditorCommands::Get().CreateModAction, + FExecuteAction::CreateRaw(this, &FModSupportEditorModule::CreateModButtonClicked), + FCanExecuteAction() + ); + + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + // Add commands + { + FName MenuSection = "FileProject"; + FName ToolbarSection = "Misc"; + + // Add creator button to the toolbar + { + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension(ToolbarSection, EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FModSupportEditorModule::AddModCreatorToolbarExtension)); + + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + } + + // Add packager button to the toolbar + { + TSharedPtr ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension(ToolbarSection, EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FModSupportEditorModule::AddModPackagerToolbarExtension)); + + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + } + } +} + +void FModSupportEditorModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + + FModSupportEditorStyle::Shutdown(); + FModSupportEditorCommands::Unregister(); +} + +void FModSupportEditorModule::CreateModButtonClicked() +{ + if (ModCreator.IsValid()) + { + ModCreator->OpenNewPluginWizard(); + } +} + +void FModSupportEditorModule::AddModCreatorToolbarExtension(FToolBarBuilder& Builder) +{ + Builder.AddToolBarButton(FModSupportEditorCommands::Get().CreateModAction); +} + +void FModSupportEditorModule::AddModPackagerToolbarExtension(FToolBarBuilder& Builder) +{ + FModPackager* Packager = ModPackager.Get(); + + Builder.AddComboButton(FUIAction(), + FOnGetContent::CreateSP(Packager, &FModPackager::GeneratePackagerComboButtonContent), + LOCTEXT("PackageMod_Label", "Package Mod"), + LOCTEXT("PackageMod_Tooltip", "Share and distribute Mod"), + FSlateIcon(FModSupportEditorStyle::GetStyleSetName(), "ModSupportEditor.PackageModAction") + ); +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FModSupportEditorModule, ModSupportEditor) \ No newline at end of file diff --git a/Source/ModSupportEditor/Private/ModSupportEditorCommands.cpp b/Source/ModSupportEditor/Private/ModSupportEditorCommands.cpp new file mode 100644 index 0000000..291b3cc --- /dev/null +++ b/Source/ModSupportEditor/Private/ModSupportEditorCommands.cpp @@ -0,0 +1,51 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModSupportEditorCommands.h" +#include "Interfaces/IPluginManager.h" + +#define LOCTEXT_NAMESPACE "FModSupportEditorModule" + +void FModSupportEditorCommands::RegisterCommands() +{ + UI_COMMAND(CreateModAction, "Create Mod", "Create a new Mod package in a mod plugin", EUserInterfaceActionType::Button, FInputGesture()); + UI_COMMAND(PackageModAction, "Package Mod", "Share and distribute your Mod", EUserInterfaceActionType::Button, FInputGesture()); +} + +TArray> FModSupportEditorCommands::RegisterModCommands(const TArray>& ModList) const +{ + TArray> AvailableModActions; + AvailableModActions.Reserve(ModList.Num()); + + FModSupportEditorCommands* MutableThis = const_cast(this); + + for (int32 Index = 0; Index < ModList.Num(); ++Index) + { + AvailableModActions.Add(TSharedPtr()); + TSharedRef Mod = ModList[Index]; + + FString CommandName = "ModEditor_" + Mod->GetName(); + + FUICommandInfo::MakeCommandInfo(MutableThis->AsShared(), + AvailableModActions[Index], + FName(*CommandName), + FText::FromString(Mod->GetName()), + FText::FromString(Mod->GetBaseDir()), + FSlateIcon(), + EUserInterfaceActionType::Button, + FInputGesture()); + } + + return AvailableModActions; +} + +void FModSupportEditorCommands::UnregisterModCommands(TArray>& UICommands) const +{ + FModSupportEditorCommands* MutableThis = const_cast(this); + + for (TSharedPtr Command : UICommands) + { + FUICommandInfo::UnregisterCommandInfo(MutableThis->AsShared(), Command.ToSharedRef()); + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Source/ModSupportEditor/Private/ModSupportEditorLog.cpp b/Source/ModSupportEditor/Private/ModSupportEditorLog.cpp new file mode 100644 index 0000000..db8cb15 --- /dev/null +++ b/Source/ModSupportEditor/Private/ModSupportEditorLog.cpp @@ -0,0 +1,4 @@ +#include "ModSupportEditorLog.h" + + +DEFINE_LOG_CATEGORY(LogModSupportEditor); diff --git a/Source/ModSupportEditor/Private/ModSupportEditorStyle.cpp b/Source/ModSupportEditor/Private/ModSupportEditorStyle.cpp new file mode 100644 index 0000000..fe0de69 --- /dev/null +++ b/Source/ModSupportEditor/Private/ModSupportEditorStyle.cpp @@ -0,0 +1,72 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "ModSupportEditorStyle.h" +#include "ModSupportEditor.h" +#include "Framework/Application/SlateApplication.h" +#include "Styling/SlateStyleRegistry.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" + +TSharedPtr< FSlateStyleSet > FModSupportEditorStyle::StyleInstance = NULL; + +void FModSupportEditorStyle::Initialize() +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} + +void FModSupportEditorStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +FName FModSupportEditorStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("ModSupportEditorStyle")); + return StyleSetName; +} + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +const FVector2D Icon16x16(16.0f, 16.0f); +const FVector2D Icon20x20(20.0f, 20.0f); +const FVector2D Icon40x40(40.0f, 40.0f); + +TSharedRef< FSlateStyleSet > FModSupportEditorStyle::Create() +{ + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("ModSupportEditorStyle")); + Style->SetContentRoot(IPluginManager::Get().FindPlugin("ModSupport")->GetBaseDir() / TEXT("Resources")); + + Style->Set("ModSupportEditor.PackageModAction", new IMAGE_BRUSH(TEXT("PackageMod_64x"), Icon40x40)); + Style->Set("ModSupportEditor.CreateModAction", new IMAGE_BRUSH(TEXT("CreateMod_64x"), Icon40x40)); + + return Style; +} + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef OTF_FONT + +void FModSupportEditorStyle::ReloadTextures() +{ + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } +} + +const ISlateStyle& FModSupportEditorStyle::Get() +{ + return *StyleInstance; +} diff --git a/Source/ModSupportEditor/Public/ModCreator.h b/Source/ModSupportEditor/Public/ModCreator.h new file mode 100644 index 0000000..b361675 --- /dev/null +++ b/Source/ModSupportEditor/Public/ModCreator.h @@ -0,0 +1,33 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +class FModSupportPluginWizardDefinition; +class SDockTab; + +class FModCreator : public TSharedFromThis +{ +public: + + FModCreator(); + ~FModCreator(); + + /** + * Opens the mod creator wizard. + * @param bSuppressErrors If false, a dialog will be shown if the wizard cannot be opened for whatever reason + */ + void OpenNewPluginWizard(bool bSuppressErrors = false) const; + + /** The name to use when creating the tab for the tab spawner */ + static const FName ModSupportEditorPluginCreatorName; + +private: + /** Registers a nomad tab spawner that will create the mod wizard */ + void RegisterTabSpawner(); + + /** Unregisters the nomad tab spawner */ + void UnregisterTabSpawner(); + + /** Spawns the tab that hosts the mod creator wizard widget */ + TSharedRef HandleSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs); +}; \ No newline at end of file diff --git a/Source/ModSupportEditor/Public/ModPackager.h b/Source/ModSupportEditor/Public/ModPackager.h new file mode 100644 index 0000000..073b2ef --- /dev/null +++ b/Source/ModSupportEditor/Public/ModPackager.h @@ -0,0 +1,49 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +struct FModSupportCommand +{ + TSharedPtr PluginInfo; + TSharedPtr CommandInfo; +}; + +class FModPackager : public TSharedFromThis +{ +public: + FModPackager(); + ~FModPackager(); + + void OpenPluginPackager(TSharedRef Plugin); + + void PackagePlugin(TSharedRef Plugin, const FString& OutputDirectory); + + /** Generates submenu content for the plugin packager command */ + void GeneratePackagerMenuContent(class FMenuBuilder& MenuBuilder); + + /** Generates the menu content for the plugin packager toolbar button */ + TSharedRef GeneratePackagerComboButtonContent(); + +private: + /** Gets all available game mod plugin packages */ + void FindAvailableGameMods(TArray>& OutAvailableGameMods); + + /** Gets all available game mod plugins and registers command info for them */ + void GetAvailableModCommands(const TArray>& AvailableMod); + + /** Generates menu content for the supplied set of commands */ + void GeneratePackagerMenuContent_Internal(class FMenuBuilder& MenuBuilder, const TArray>& Commands); + + /** + * Checks if a plugin has any unsaved content + * + * @param Plugin The plugin to check for unsaved content + * @return True if all mod content has been saved, false otherwise + */ + bool IsAllContentSaved(TSharedRef Plugin); + +private: + TArray> ModCommands; +}; diff --git a/Source/ModSupportEditor/Public/ModPluginWizardDefinition.h b/Source/ModSupportEditor/Public/ModPluginWizardDefinition.h new file mode 100644 index 0000000..dd14bca --- /dev/null +++ b/Source/ModSupportEditor/Public/ModPluginWizardDefinition.h @@ -0,0 +1,70 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +// Depends on code from the plugin browser to work correctly +#include "IPluginWizardDefinition.h" + +class FModPluginWizardDefinition : public IPluginWizardDefinition +{ +public: + FModPluginWizardDefinition(); + + // Begin IPluginWizardDefinition interface + virtual const TArray>& GetTemplatesSource() const override; + virtual void OnTemplateSelectionChanged(TArray> InSelectedItems, ESelectInfo::Type SelectInfo) override; + virtual TArray> GetSelectedTemplates() const override; + virtual void ClearTemplateSelection() override; + virtual bool HasValidTemplateSelection() const override; + + virtual ESelectionMode::Type GetSelectionMode() const override { return ESelectionMode::Multi; } + virtual bool AllowsEnginePlugins() const override { return false; } + virtual bool CanShowOnStartup() const override { return true; } + virtual bool CanContainContent() const override; + virtual bool HasModules() const override; + virtual bool IsMod() const override; + virtual void OnShowOnStartupCheckboxChanged(ECheckBoxState CheckBoxState) override; + virtual ECheckBoxState GetShowOnStartupCheckBoxState() const override; + virtual TSharedPtr GetCustomHeaderWidget() override; + virtual FText GetInstructions() const override; + + virtual bool GetPluginIconPath(FString& OutIconPath) const override; + virtual EHostType::Type GetPluginModuleDescriptor() const override; + virtual ELoadingPhase::Type GetPluginLoadingPhase() const override; + virtual bool GetTemplateIconPath(TSharedRef InTemplate, FString& OutIconPath) const override; + virtual FString GetPluginFolderPath() const override; + virtual TArray GetFoldersForSelection() const override; + virtual void PluginCreated(const FString& PluginName, bool bWasSuccessful) const override; + // End IPluginWizardDefinition interface + +private: + /** The available templates for the mod. They should function as mixins to the backing template */ + TArray> TemplateDefinitions; + + /** The content that will be used when creating the mod */ + TArray> SelectedTemplates; + + /** The base directory of this plugin. Used for accessing the templates used to create mods */ + FString PluginBaseDir; + + /** + * The path to the template that ultimately serves as the template that the mod will be based on. It's not intended to be + * selected directly, but rather other templates will act as mixins to define what content will exist in the plugin. + */ + FString BackingTemplatePath; + + /** The backing template definition for the mod. This should never be directly selectable */ + TSharedPtr BackingTemplate; + + /** The base code template definition. Can be directly selectable to create an "empty" code mod, but should be included with any code mod selection */ + TSharedPtr BaseCodeTemplate; + + /** Maps a specific template to a specific icon file */ + TMap TemplateToIconMap; + + /** Brush used for drawing the custom header widget */ + TSharedPtr IconBrush; + + /** Custom header widget */ + TSharedPtr CustomHeaderWidget; +}; \ No newline at end of file diff --git a/Source/ModSupportEditor/Public/ModSupportEditor.h b/Source/ModSupportEditor/Public/ModSupportEditor.h new file mode 100644 index 0000000..d49370b --- /dev/null +++ b/Source/ModSupportEditor/Public/ModSupportEditor.h @@ -0,0 +1,30 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FModSupportEditorModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + // When the Create Button is clicked + void CreateModButtonClicked(); + + /** Adds the plugin creator as a new toolbar button */ + void AddModCreatorToolbarExtension(FToolBarBuilder& Builder); + + /** Adds the plugin packager as a new toolbar button */ + void AddModPackagerToolbarExtension(FToolBarBuilder& Builder); + +private: + + TSharedPtr ModCreator; + TSharedPtr ModPackager; + TSharedPtr PluginCommands; +}; diff --git a/Source/ModSupportEditor/Public/ModSupportEditorCommands.h b/Source/ModSupportEditor/Public/ModSupportEditorCommands.h new file mode 100644 index 0000000..247bf51 --- /dev/null +++ b/Source/ModSupportEditor/Public/ModSupportEditorCommands.h @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "ModSupportEditorStyle.h" + +class FModSupportEditorCommands : public TCommands +{ +public: + + FModSupportEditorCommands() + : TCommands(TEXT("ModSupportEditor"), NSLOCTEXT("Contexts", "ModSupportEditor", "ModSupportEditor Plugin"), NAME_None, FModSupportEditorStyle::GetStyleSetName()) + { + } + + // TCommands<> interface + virtual void RegisterCommands() override; + + TArray> RegisterModCommands(const TArray>& ModList) const; + void UnregisterModCommands(TArray>& UICommands) const; + +public: + TSharedPtr< FUICommandInfo > CreateModAction; + TSharedPtr< FUICommandInfo > PackageModAction; +}; \ No newline at end of file diff --git a/Source/ModSupportEditor/Public/ModSupportEditorLog.h b/Source/ModSupportEditor/Public/ModSupportEditorLog.h new file mode 100644 index 0000000..2e68e5a --- /dev/null +++ b/Source/ModSupportEditor/Public/ModSupportEditorLog.h @@ -0,0 +1,6 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Logging/LogMacros.h" + +MODSUPPORTEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(LogModSupportEditor, Log, All); diff --git a/Source/ModSupportEditor/Public/ModSupportEditorStyle.h b/Source/ModSupportEditor/Public/ModSupportEditorStyle.h new file mode 100644 index 0000000..e0fd50d --- /dev/null +++ b/Source/ModSupportEditor/Public/ModSupportEditorStyle.h @@ -0,0 +1,31 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +class FModSupportEditorStyle +{ +public: + + static void Initialize(); + + static void Shutdown(); + + /** reloads textures used by slate renderer */ + static void ReloadTextures(); + + /** @return The Slate style set for the Shooter game */ + static const ISlateStyle& Get(); + + static FName GetStyleSetName(); + +private: + + static TSharedRef< class FSlateStyleSet > Create(); + +private: + + static TSharedPtr< class FSlateStyleSet > StyleInstance; +}; \ No newline at end of file diff --git a/Templates/BaseTemplate/Resources/Icon128.png b/Templates/BaseTemplate/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..85b65e75e0fe36738aebb756886c764eaf6b6ad9 GIT binary patch literal 13381 zcma)CWmMfv*S!~acXuhp-QC^Yt++$+ixe;J#oZ}R(Mxf6cXxLyAJ32P|GTm#$(rP3 zB{S#j*=L_bswhb#BM=|}003lJ840z|Bj~>d2lKh_6*3(K00^{wG<4k6OufjQU7f6K z>@CUMy`3$|EWg`W0RZ1u)-!bziT=1se$*i_0&jm4#aKDP>UB*>THqu}dUEF$kw#ip zk+Wj}dDbqs;2J(|jR)UgKU@(P`ZR1EdG-JG$(Ug>^kpx!HfUT*N)zj42~e~V6=!I!V6_g9Rm&)Ad!&*y|p??O-9eWctd z#_Tu3-E}2)-Cqxh_Zm)kUv&-RG9nJNrF3s&Cw~6Z4HXP~tsB$c4%~Tk?Itju_vBx` zu`DI_bJ0IsPBAxdANBBB>k=;grNH`fjwsxjYaeY?ng4V^`?@POm&V@}ilbF#zi_u2 z3LgDu$8@pLvi_>5{pZ%z;Dk7YSLi0q_~3Hm&8usSSDoCoIiNqv+k2mLc5337U%QU{ zqT_*a9njrvwCW^$6Ru$iJB#dQ8MGd29g(mWw}#-A3%N)gAVj^Vpj!)sLw{6K7=ZQ! zTj3^-PT*WdyYPZot3Ym!u@LKR2uNvC_0y^Y7-~=`7RtJhPj>_;h5k=DLD@ zUoaFii|QPo|De!(FxnFo`mn;TZy005UnoSJudsT z(4W2h-*ne=;0kYH_3P#mZ|}l;n^X646Lronufd!z8VU}1FW&zCp~+Kd}oZ@TEsWLb^>W* z>Iari$2R_oT3L93SxFxpx$HQ)@y?C156Y7qI9JZ*eQV&kMqHk%TExdJ#CT0Z`S69? zLBq+w|e_ctfftt+ROb=xpu;7I&DMY2gMpp%tYoem8e*4fcUK z**!!VhB=11gGKmX&c9duOju)T!e(=g=^a63 zsn?dTOIqhD#$<1CZJYO7kIK0C9QNB_rhgZGP+C4tXRw{DBI?v7QrLucOF-3JEcKpIrX0pRc zh2Oai06#q1*A)1A%NhT37axw#jBM*4{tzv4^2GSk@z67jr>Azr+TO;`^O?V=#LTj> z2^-$ULYfo|PaWTTTG&q`n)YThCqus?Z4wrtwWuka;{v1MaRB1_`?{K0#6YEMN$(UAkxeJCX=bnBQUU={aEYbuAU+gns6kmxGDc3|5j7p>vOuN zs`{t6XU!o5cAVT!w0-M!$i$Rl;hM)Zn6iPmDY>~lKk76LL08HHw>d*4Kem#29bRY1 zMw>-Q4xEVdd^ptII@E10MTKf&No_(AlBd_Di!qe|qE0g0lFrC?a8e#GR)!$Ufqel1 zHviBwaFSu!32)T>_3#5}Y3E~APK69zpansxj6u%?%vDJKknfY+*LXTllrA;X!F!?J&F_~u~6S$L~|-N}&0Xb2?l1VtMy zP`QXwrF?<^#t_=%r!yWbeRK{qRRgp={DTeYvWZ)OS3ED*EQbruHc-1U?t>T#i)5#i zT23*-3o;Rl7*+j}4A5K#uQyp0F1?Bv9-tXi%WTENwh*eK zA;rTGu|s|a(XZD{I0OwMFw#D>YxL$R8$b?2T3Cj{xe!ZaU_f1il0#X zBYLO~epCiQ(@x%lCzgr335dT0bLwlP?3>tRF@-==x4jy92O*?jh$OS`H&Xhky+$dM zZS8w?jABuxx!`D+X3@S@ZXh&t5)9tM9m zo?L~q*_+oLDhQ^^ro!iShk-JQ7NQEVtt=%iF|y0rC~Rz!G>c-O9lQ&yNG=ON9&B)THh ziECgJ$hdW}pLy6*N@zbM=eNty@Zh*L;G2kWQg9K#!1e}$coHT`QWuIjhu`eqy=e}< zNhCjEMS8*Jn_%+c_MZ1O4>H0_XHuyxG%x@5oXZJ!T?w6MOLME`5>P-y23d+)8BN$k zE*L@U0|=}Ks}T{PomhJGlYfdgHz7441h~gxls6>-^pEaZ$6gUPgP2Tj5o#n^^PXm1 z^@psBMf5uReG880eMl*3!csu3zlx*tJWi*rLW1r;J{$d}M%-R1q>lZ0% zs209qg%m!gD!7Wlie7Io;3((wGiI|u6JEA>NGVXR@*Di^*pu~3;(Lhv?*erBuSw&msJjoavA7_(IjTO?ZNiet`Fqcw%CoXi0oR{hW?$bCN*=2p$URdE|$RoAi6=kMp9l7bP^#6uwgq zWVk#KY@~nRE_RBS0gtATf0@9rLV?UhAx>}X4*dnCr#QW$c9HxIC>BLqBn{GEk~4?| z4NhiPqls|{wF=%k6-YyqPCO!Dgxrr@zA=k?z{pqMx#_AcTTccXi+w2>j{Oj`!}cJG z&u)r7C41nRNa8qB6CXF+WA`n7X*j&l6OASUEA}?rFFCGKoK^twqhtg2TkemNAB0q) z`edqKW-djjRfdvc42NaHSa6S&gRh-8S2ID;R>?4u)0;Sm&kcGRXm4j&98E9=ve0A| z=|X`)73YTJu+MNINm+UB5BkMWREtp>7PKQGmM$(UNY23bsr4kbfPT8n-30l*8$DnT}8UO#IZg zLNP)VikkN5ahrkiu_~21s=So$8$rlZ4nP7*q>};T9m+z=eLa|1Ua=LjOf-~32oFFj z!(&$Bc}aVbI2&hAe9Vb=K3iQhz7iDk&}Mv{&XKqff|@A-|JIByv@R=Jf~YNjhL1)_ z;$Qo?7CQ7XD5pABua|p*DMQJ`A$$qjR@}03f97fE4u_ma7l$*UjEre=B+<`AGb*H+KX!RuOJ_9v;Ww{IRlqP<~l5 zr*5hpf)=qS=VtrcG(tm&*9WQta0sEFC$b!Fs`+W~Nhract)s5w?R*X-WX2WCZ4H>f@cy45}&+tElcquqz} z`hwAlONH`w9|JzPb9*gB?6p*saC~jo&isg0ogm6>qi=KNZ+rgHzQ4JxYYy<^4pcE^ zDhBO~xl9#{UycMWzENq4{+KCS8fgKcSxTGI^q~XC`GUx`(Ojwp$9EEuB=NFV3~j>> zXkBQpVB`{+@w7&yr3H;(OE4RD=Mk=Hr4&ao*C7K^Oaw}Z2#&WZJ8th>-<}vaNE3(@ zqKVavix_#AD9cL=4fCL*+SaRUd^73#aB&MlSekHtYDG_oH2PTj|E+(33It|SF{&Z* zVovj`cOEot;SV5Mm6y)hM%+ZwZ8IH+O+c#1j*RW}8Z2S*rsu{Q`m8v23|L`t4mgjd zo8x)(NYZKk4xjsLD|QdJ?psFlP;?Sm-!b@2G6zdYL>U^Q$H8SN@eF1^KSw5-ZiWDk zBrLef?pr6r@fJ=h$j;;-=~t)#YdUnElyS*O%l8jLS;0GGO_};wfjQY9_+S+sqPdIe z?tM}wITV+p2#SslxNEUE0i&Y7(2LP);wD(o&^{JH1S3cZ;Wqq>5YY4uvEnsOu*p5Z zM;@}m8H$z`1dcTbN%Kz29re5j%S4~8V#lDJesFH>i=QU%nsJT78>U)2jJhu9dj|^< zMFx>tSrEQqc;xjhPe)qs8hfL=Z+K7)6~~8Pm!?8{#eA+S|4;ENNiF7Y&_@9Hw(&#D zq<-Ww!exufexom^?%RyJSpg(>il`e?wzo8Ijv@wolB-OaJNzqB6e#Poq862I({BV}b zfz!^{MrU}fw}!S$aJ213Y9!_QSNNWM+7z%jRpyDjS9wbmPDoZQi`wy3D>V=8g{|(% z!f1dat?8-HqzfBtc6Xj$f-*^knstmXUB;x|;9=!1;(WrqYvSTAYyDkInWuv-Pf=DF z%OgcGXrn%7cse6>xlV;wveOp0ae}PM&^Mh6r>wDW$)z&RRmD0&JH$b{CYDqyMN!70 zIL>DyUQ({DNs6EBQPkbY?ekIt%O>&@xfYWHA~e`I4{p)bD|pjG$wm*CiBg0ANV$%V z!;pAibJXG6K$UrSB0xvN&eA)>yYM#~yy%jin9v#0Z{U7q0F%r)nS57%8)FfMz;R@l zLUE9a?Vkkw%D(HRVHlP~-;$GQmef8tItxMJ?;-{UhS6usxA}e^m?ll7vzT-`ZI6Q{ zMmDG4*Qd`pPa8SRV!~NSwmX^@(dCEvK{+UzwEYkrAS!#b;z_1m1b3@Q3C^P6*sI}! zCMUZT9j7y6n~DcLink_*^^;O0BHR0wKc_sGb*#*d|r>dE&uMj*!?Z$8K^KUQvME!3E zEDtD9Q$mUyh{H*+wWd4m;SbMOkvW2_o1{Ycp80q?)osBT0@=Jb7!ZLxqCUNAq8~g* z8q){#iO|UXp2@8HLP6I;?^j#GyK&ci?<(D};T5xSzE9I~o0Hqp0Vu*{3sS$ER=eac zFl13x{@yE*>6jolT2J5sg!xB=kKv1mu0I@3^O0|`|IPwOSm;lak@jxTVcIOCLGWJ? ziL=*slc;*RsafWjmEwN}hc59F+ep=bmrKTeemi1ee?&$jD2FaWsaT=i+w_7ev<#$qQLXdbGv)RE191k-Qi`EGKGu6i?keOSygq9C zW@Rt{Y@eZ=O{1-$D3=TD`lvGaZ-~9DmYHGYUM7@iu%5P_1H+}x)eM?UZQ)_G9G1n@*sLw^o=|tn z$j#=sN8%y*W+DGp34JlwSWBd^35~9J?U?Y+H~D=1+*`-{Me0GMyvniAKI+&nH}fSY zF5eV@Lw+bm!`(?}aD467(tn$MvPvu4S^9VQZibP$Xam733ijJa3Yj{BraFB(aP}S_ zLMVHIfh0}={*H&;k4v%7gOtz0e)fl_M^_%-m7OcH{HT1VS?PXOPZY)aL}l}2A-y7T zwp37af-D5(n&mk6HZ>QY1&ZN5Myet4`C_NZzXrji`rqk*0A55&+AV^{yu`Eivj!e$#P(6)_N^A82b>-D&BK}MVV5zmhOdW0U?y{ zEbtkL7w0h*;pPbmT`BMb}BKjzvs)J5DMYw+n&3_nf`x941Tm}Xf$wwYt zeRY*({xJAWX!lFz(_6GLGc$$L%9)biOVOZ@AT zXgJP9B~GscD9Xq&22xgM#%YA*{6btgW%Q7}T6UEy+7LOYVMpL4h?mcX0d?zUrTI_~ z@&l`$1jMB2n-Z7TDrRd+HtHpEO$)?jLw^w;imB%Qet-x84ct*Bga|DJb=_g5iHJ$} zw)Yg&B88Fdj$!$F3kLk{vMRp1eS*(|Mw~yq#_Y2*MVB93_e*aghZMt&K|>dEiyxSj4)22AyEpj|-&yp(Vg%zSbP-=3Xr zcYjD&ytJVzCd%1UvChv99c>y)aALh{WMzlHYF{dqeyEq?pS7sgUU|F2CzXcpSLEc| zKiP+?+WLgm$F|F0M8$8G(y5}I+02S3V*pQ%e-%xnqPTbBienhMj*N<%ouf~aO{k!D z+Ot8}I8qZ2ho2i&7T$T7SqS_&B{Tk_Xic<-L83ZtaK>8fHPaao)It%GqAe52U(H(9RO^vYuCILd zyXHc$f1atsQjp3$?ofZApTgIbna`BEcmTKsGty$dsH=HPf0QuC$;OcGS9X-YRG!m? zO)m(`c%cioe15k@_JeVgppK>tSXdH4u!)ya4Fy=ly>sAC;gFGB)gel)&9%*SJeh*r z>3+Kx(0Z2+etMjw>Mp*siB1 zx4$AJx}gT8CYbCAa%bmF zW?MUyi~<1cWNV1Aup&p?d}%9Hs%XcXyt#aBc$o|C2Z1C6Mv-P|jA(N<8j&Yn`zRPj zzg+WPr_16}1?;u$ToWE9jzUagjobUg>QJT15m&jhF#IC8#&Xe^cOSuZC8^A6;jx=1 zuv<^+W>K3;V>c+}jDf;QkU+q|no|+Q<}zzMEz*&h^R zdbZnZODShJ>FJ!S%iHg%?9a*{jG4T&1mNSpEw8gQ>9YskSw`0l0AQm0uK~Xo3wwTc z!n(^UO2Y0zV*#Os1XK8EPz(yi%g-pf(-?-lLMSX%m5c;xIyfG4k)jz>R#CFAK`6_R z#}_LTkCgBh)cRhDDEC6jBoOY}>wJfu*6oK2&`5YJCSVW{66Sw6`3bCI6*iEN_-;M0 zbO~Go=DBSvBLaE=nIS_+0{{`2ct|qLP)eF?1MTC&<{}X%}!jR5Z_!J zAOZOe9*{?z zWQW|6HvTd067t|B762Rq!VHaafL?SLKfyV_JIj0zyQ-XAlF;C1Dn9}40{f^K& zKmPfC?a5K$2xA6(ji-9G2?6A&cEE$h0Fxm@m$Cfql?_(oB|}3pvE3wf^UNHIvna%T zMYTpviNwM#27n&uuth)zjjU%snTTQvDLM(tfEiHY>GgTG02h}b1Qr!%%!+*xZIEHK zzKfPg$^SWpnx=GiSVRWEA}jZMm9Qe_8Ajl?G`Nl#*oe=50QA@&(WHthDA(ZF_#HZY zTP2^y^p?(w`pw(hdu2F@Rxe~*wvzK}@3suTy$+~?enU*+l6`<=-#aIj$&dz>fMaM3 z@3tJ25^>ai`7nftGsbO~IRNm3Fo~jCHiQ9w;d&+qbkS@DUUoFJKfIjEH$9xM{_^LY z6!N;r>Tuq6d%NBp{04%26aC|K zwU+dAu2bX5)?rMlIKSHe>P<*>8^sjCa;OHJK~5c)538@Sva$vbTi9}pFyoN9s;a7H z)g<}t4Nc~o;5i_-J4An4c#UFwS^&vbAO|kK??jfa&6X;2-wI6TCj{^EEL3VU8)2%AfwyjRoI&c4mLSRA*id zytB7#3h-Ip&eM+qXxS}Q0&`~xn zxY(4OjP*E?&A&g%?KH8-Q9X0$-oCyvtE{XgIW1)-@w7Bh;TNddSS%qCs;;J%c!s>q z5LNL0YW(54>7h-kh~=Ph*WkF$*V2N0>CCfNt=}?&DtISNO+k@05Jy^iu(wC2`5xe^ zmdIZKMktCXsGcy;FyvAj175XT=VW~GI-cp@y?_7od-~e$2ZGhdUL&__8kMABh26op zN4+YyhcjKv+kV4BoWZ7Z{Ig6(Aa5z=}JHgN1^k10RH!WKxhX9_}-?W6ce)=+iW$pupm8;q|M#4GuI7AAs$hn z!33t$ONGKd2>|iT;~>7Fv)iT4ba=bPrSE*YzsJ8f@1=)%E%j}|k$|#z?6w;3Jzk7+ z&*yD;%%{t)RZ6UGp?vVkm4GrcV}R@@fL3Zsva_&Eao8=`LraYJESdl8fmVP9iH5-V zT0B@=Sd=xGS=EyU3Je)PcJ|)~tW)8>CFJDfFFxe>K3;ezzRXe{+{L(Gv_+dP!lSTk zH7%x&IIJ|^%Q*9Yn=;WlQY-P%=QXPCjxMO?26dy53W<4d1=Z1@(iul)@P;(du6Sc{ zzfvr>8&9mG5bN273#N^or!B0%Rl!&>TJl|9&0sf=*yV}Sn|-|?{OQ#F&J9qI=+WXZb_d!Epo_+! z*?Hu4bO@aPfksSU&r#VPgUwffQDHH~KN!n!I2O*!4Llq8$xYqS-7Va`_2BpdR;|~>=Dpz)WsuKLc$vid>H9vQ{PR|q-TjjB%=x96Rzs%u z;xrufxBT3vo8S)DN`fjK0WgA)zUJv#m%s09VVqD2mQFmORd>Oq*SAfh+pxDxE{88M zq#x4wB7~}q?;E&`U}wN5E;{{uFDS8jiArH;6!hKsLWZWP5azlQRAIIS4#M83xbw*5 za5!nI+3%X3mPBP4!-b#9jhHHyUnF?_SVAUDR_1nK!;Yqxn|Rnk=)xb@nf}mrpffdH zNF-%6{UD{sN?QjQQ-KX}q9q;XD>Q$Oee{IDt~oIV&Z=4bm4{B-|Mf4* z${|HZ?JFA&R97-RJu{dQjcm7LVPw7{Mf0lRsT@$?7xl@L+Os3{Vn_sGIBjk^k~6S+qF2~7ZcmKRfZK49kC8rn0 z(`LXIF|6+G@1E6dk1Cp)f&Ry8Ov1vsDd~++M@75Ke{T1DrGFMoO{pLPh=lxx?)Q?E zpInHv#aJy5d_5@9JsSe8`*)FQnaZ$}OuYcK{{7Hv@BoL^rb^FzdL|~N^_I(5wxZ*y zy238Y(X<=$IAKR7H6g&=1@SSr>mFV4-?^#LtB2J7=JOT!e#;X=D6&RKP<;HBZL!Sq z^72V#?Z0XTrS*2NqGrc+pK80sntiFYd~$N1c`H`UN8T@GPSf{1;MLR5C921EyY}2~ z5Sqmu29%e#WvdJN#Oaeie|W`Q)Hf=%ozAOUQq_OrB$nX>fK*KP0t>&6bvRlC2M2po zK$urWGg|`5C7=R7D!jNXt*pK>V=)Mkq?sb5>uw~}w zK#2`YP;PE+4ILYR;~pP^WgxS>qTrKU7B|y`_{t}1RBuYzM=mw! zxGs!snQ}Dl81NdYB&aQ7$-u2;o|<^M{=WkYFy`W z3$~tpS4}BSBv)t+EvbnID-`535uv|z^$*|ka>@UgoqO%gt! z<~b2@$Rwuc*jjJ_eOJC{1ZDg#aX{Ff z*uIt?tcu1whxvfQAH#M}8s?ZnPh9}RAa?Q>CY#KIc@8pmhDw>RQonEj_=gyT#tFpt z9PNQv;zL1pIT9-Mazha^5lzkUUz8(bLv*guhyKIRM3Cqcxx(7z&TSs21A5K&$Ni5$ zHO6|7+zt{JjwyWN7)ON*S6SEjiyBX}HZPwg6K%#{*gzAF!YBQVD>rlOC zW(woPcd=*5Mf!Qmq6%9}s?y8ukBv_6TWf<*IxV}uHkl=X_w!tUp;W5Z+Ab_eK+L3M~fjc6%>bN;j3tt#+MG-EqqT)X`a8s3zpK3E(lHbg{TyPgUoQvZi)>-c)**O8HZiM5{c~U^QuG z86~XICAoBc-cs_(b{Qm!Dk>^+c-^Fo+C8fU%=VmTFIo-7NG*fI%@pq@6dDwg~mcgxcZoX1` zp1JEuOuy?*QQqU_EoukZa1o3bzSHz|#-q>iO#=lLwe~@7~Dkx&)0V25#)*MNIIawDko%`bt0ev@FeCnMTh2wQeaEBa#BFZvm{Qc#$ zwo?i~QLwj1RWri%6L`8<%U*5VT~qaHvTupJelST@QLR6%b&XPZH^M3HF zR+oI*th1c5cA*Xv10xO)?|HFi+V=grpj2tH;Bze7-Rc_*kF{%BxdIm)s zK7T2A>Fh(KE<1bFDF-KxRB|SZy*>~(N=D=KSg_5@bd{HvkHivhW`a%9s9sHTn*cw) zqk>mmT-uN821y=e))JGFdP%--1&rSFYMZgcwR?*h`?z8h&pm+s6i-;39~(sY^SESB5AO(vE4OTPhMfS zxZob#OA2+S??l3VQnng?Hntld)nT(b=l7TUkczUE>B8=0Jh#oR_qh}X?c?*6rpl(9 zq{PJJABec=_~!Hgn$N7*wvYnUn^2-xZEB5Yj+)Es2J?wvw5Yl0ePZI`&!)X$E|4&5 zg=_=mk&&K3DX#>)U${m5%#Ya~1tDQm%js?eChw1D!x7=(H6JcNf@mqmviT=JN$u^^ zXV#?(`bM@^)%W@rF?tV{)Ne{7QsHPT2-0;ll~H$b)#-6*$=uw0-_q$TXUI09 zY<*mWOD;|xL8gnzfWpB2cDBwaKF8YhLZ zug{URIUMtY&wZa?<-OAvK2BtAV=_h~`_L$*2ol3Ug1x#-WppZ3{3SGR*MXadGc-A= z&Bo47FDOX->4Zi~)XK`r+O$f3>GJY!&SV7i)Kmfbi{@o%g)2m{jp|x<>G8RN)gMl! z0fj-2YvP=YzZS4)k|I%&koKyz7y|vZU5Wo5yW27gd4M1X0`y6@e$;P$Qo_1aPoJ|df_ zd4gtS@Oz^B9hanP^3c$bl-;Mv?8o(EcO_{+;|9`S`hg`f*?Q~w!}OE)R;_T#^UO~Z zZ1B5uAnyzt^JT@S3Mc#XoXE6gwYplT&fw!|H*U=2j3ekmRz^ml`qG6n;?{>aw$Y%H z(;g9zFd_XLs=Tc1bU{JEC(l}>lZpTJtCCUoCb7%mR0|W2AGnqCDU3xUo7y8EfUUwQ z87FO#SE060OO-zsjt5CUgQk;m{u8Bhg*0Bymf2ku6p(N~XA}_j4hWM;e3ui)Hx9vY z+%A}wLesOhu)2GByB+uY%rDon`8;+`3uV*^kJ^;lz0MPJbJ&)%asqxp{w4v7{}|mx zhC$XUEBNLc6|um=^6{DfR8Pc2_Bgbx(>w*=)EfLLs4?m!*z~(i0f3=RN~o3>x%%q3 zj&!M`n)w0tfGgjIWrb)QMy;^VR963MD$Fs?nqy0|?<3QBN}4g(A4_h?D@2eq)o?&T z{zz-juX&OJMKTi-kR%|}{bEfro87vG1RI;0BUwRIQ&UjrAFW4!6t=-9BLslqx3CA6 z>I`#)?`}Y2R+Rw?8t;8uLl=Gi#;(403pJF|K6FH+cAqNN^mM)9CqPT zKZ~=M9{miDi5x-sM5^sk=4R*N(!^-1c0@1@ppt5s>A8T&ziaPHR5Q*GM^g&Wky$_n z%pGaoLu6Eer^9NUR<*wB=lFdO{GhQF5drM-Oc;D|NV>hb89E-rt#5E^lrJwo`($BQ zq@nLq&;!6hnb6zgdWFOzhS_7;56M6{6F}Em@_vzhX!zfy^G|vG8~7