From c1a3df2192c2caddac246cbd5414f5cebd17426f Mon Sep 17 00:00:00 2001 From: "Bob.Song" <605277374@qq.com> Date: Sat, 27 Sep 2025 17:53:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=AF=BC=E8=A1=A8=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Config/Binary/BaitConfigData.bytes | 5 + Config/Binary/BobberConfigData.bytes | 5 + Config/Binary/FeederConfigData.bytes | 2 + Config/Binary/FishConfigData.bytes | 6 + Config/Binary/HookConfigData.bytes | 3 + Config/Binary/LineConfigData.bytes | 3 + Config/Binary/LureConfigData.bytes | 5 + Config/Binary/ReelConfigData.bytes | Bin 0 -> 101 bytes Config/Binary/RingConfigData.bytes | 3 + Config/Binary/RodConfigData.bytes | 4 + Config/Binary/WeightConfigData.bytes | 2 + Config/Excel/BaitConfig.xlsx | Bin 0 -> 9867 bytes Config/Excel/BobberConfig.xlsx | Bin 0 -> 11779 bytes Config/Excel/FeederConfig.xlsx | Bin 0 -> 9692 bytes Config/Excel/FishConfig.xlsx | Bin 0 -> 11721 bytes Config/Excel/HookConfig.xlsx | Bin 0 -> 11687 bytes Config/Excel/LineConfig.xlsx | Bin 0 -> 11629 bytes Config/Excel/LureConfig.xlsx | Bin 0 -> 9999 bytes Config/Excel/ReelConfig.xlsx | Bin 0 -> 11647 bytes Config/Excel/RingConfig.xlsx | Bin 0 -> 9555 bytes Config/Excel/RodConfig.xlsx | Bin 0 -> 11912 bytes Config/Excel/Version.txt | 1 - Config/Excel/WeightConfig.xlsx | Bin 0 -> 11428 bytes Config/Json/Server/BaitConfigData.Json | 6 + Config/Json/Server/BobberConfigData.Json | 6 + Config/Json/Server/FeederConfigData.Json | 3 + Config/Json/Server/FishConfigData.Json | 6 + Config/Json/Server/HookConfigData.Json | 4 + Config/Json/Server/LineConfigData.Json | 4 + Config/Json/Server/LureConfigData.Json | 6 + Config/Json/Server/ReelConfigData.Json | 4 + Config/Json/Server/RingConfigData.Json | 4 + Config/Json/Server/RodConfigData.Json | 5 + Config/Json/Server/WeightConfigData.Json | 3 + .../Generate/ConfigTable/Entity/BaitConfig.cs | 101 ++ .../ConfigTable/Entity/BobberConfig.cs | 103 ++ .../ConfigTable/Entity/FeederConfig.cs | 101 ++ .../Generate/ConfigTable/Entity/FishConfig.cs | 105 ++ .../Generate/ConfigTable/Entity/HookConfig.cs | 103 ++ .../Generate/ConfigTable/Entity/LineConfig.cs | 103 ++ .../Generate/ConfigTable/Entity/LureConfig.cs | 103 ++ .../Generate/ConfigTable/Entity/ReelConfig.cs | 103 ++ .../Generate/ConfigTable/Entity/RingConfig.cs | 95 ++ .../Generate/ConfigTable/Entity/RodConfig.cs | 109 ++ .../ConfigTable/Entity/WeightConfig.cs | 99 ++ Server.sln.DotSettings.user | 1 + Tools/ConfigBuilder/ConfigBuilder.sln | 25 + .../ConfigBuilder.sln.DotSettings.user | 3 + Tools/ConfigBuilder/NBConfigBuilder/App.cs | 20 + .../NBConfigBuilder/Core/IPool.cs | 17 + .../Core/IntDictionaryConfig.cs | 25 + .../Core/MemoryStreamBuffer.cs | 77 ++ .../NBConfigBuilder/Core/OneToManyList.cs | 217 ++++ .../NBConfigBuilder/Core/Pool.cs | 72 ++ .../Core/Serialize/ASerialize.cs | 20 + .../Core/StringDictionaryConfig.cs | 25 + .../DynamicAssembly/DynamicAssembly.cs | 169 ++++ .../DynamicAssembly/DynamicConfigDataType.cs | 35 + .../DynamicAssembly/OneDynamicAssembly.cs | 69 ++ .../NBConfigBuilder/Exporter/ExcelExporter.cs | 950 ++++++++++++++++++ .../NBConfigBuilder/Exporter/ExcelHelper.cs | 47 + .../NBConfigBuilder/Exporter/ExcelTable.cs | 28 + .../NBConfigBuilder/Exporter/ExcelTemplate.cs | 98 ++ .../Exporter/ExcelWorksheets.cs | 22 + .../NBConfigBuilder/Exporter/ExportInfo.cs | 17 + .../NBConfigBuilder/Exporter/ExportType.cs | 24 + .../NBConfigBuilder/Exporter/VersionInfo.cs | 7 + .../NBConfigBuilder/Form1.Designer.cs | 265 +++++ Tools/ConfigBuilder/NBConfigBuilder/Form1.cs | 141 +++ .../ConfigBuilder/NBConfigBuilder/Form1.resx | 120 +++ Tools/ConfigBuilder/NBConfigBuilder/Log.cs | 97 ++ .../NBConfigBuilder/NBConfigBuilder.csproj | 25 + .../NBConfigBuilder.csproj.user | 8 + .../ConfigBuilder/NBConfigBuilder/NLog.config | 25 + .../ConfigBuilder/NBConfigBuilder/Program.cs | 25 + .../NBConfigBuilder/Utils/FileHelper.cs | 182 ++++ .../NBConfigBuilder/Utils/HashCodeHelper.cs | 228 +++++ .../NBConfigBuilder/Utils/TimeHelper.cs | 65 ++ .../Exporter/ExcelExporter.cs | 2 + 79 files changed, 4365 insertions(+), 1 deletion(-) create mode 100644 Config/Binary/BaitConfigData.bytes create mode 100644 Config/Binary/BobberConfigData.bytes create mode 100644 Config/Binary/FeederConfigData.bytes create mode 100644 Config/Binary/FishConfigData.bytes create mode 100644 Config/Binary/HookConfigData.bytes create mode 100644 Config/Binary/LineConfigData.bytes create mode 100644 Config/Binary/LureConfigData.bytes create mode 100644 Config/Binary/ReelConfigData.bytes create mode 100644 Config/Binary/RingConfigData.bytes create mode 100644 Config/Binary/RodConfigData.bytes create mode 100644 Config/Binary/WeightConfigData.bytes create mode 100644 Config/Excel/BaitConfig.xlsx create mode 100644 Config/Excel/BobberConfig.xlsx create mode 100644 Config/Excel/FeederConfig.xlsx create mode 100644 Config/Excel/FishConfig.xlsx create mode 100644 Config/Excel/HookConfig.xlsx create mode 100644 Config/Excel/LineConfig.xlsx create mode 100644 Config/Excel/LureConfig.xlsx create mode 100644 Config/Excel/ReelConfig.xlsx create mode 100644 Config/Excel/RingConfig.xlsx create mode 100644 Config/Excel/RodConfig.xlsx delete mode 100644 Config/Excel/Version.txt create mode 100644 Config/Excel/WeightConfig.xlsx create mode 100644 Config/Json/Server/BaitConfigData.Json create mode 100644 Config/Json/Server/BobberConfigData.Json create mode 100644 Config/Json/Server/FeederConfigData.Json create mode 100644 Config/Json/Server/FishConfigData.Json create mode 100644 Config/Json/Server/HookConfigData.Json create mode 100644 Config/Json/Server/LineConfigData.Json create mode 100644 Config/Json/Server/LureConfigData.Json create mode 100644 Config/Json/Server/ReelConfigData.Json create mode 100644 Config/Json/Server/RingConfigData.Json create mode 100644 Config/Json/Server/RodConfigData.Json create mode 100644 Config/Json/Server/WeightConfigData.Json create mode 100644 Entity/Generate/ConfigTable/Entity/BaitConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/BobberConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/FeederConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/FishConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/HookConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/LineConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/LureConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/ReelConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/RingConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/RodConfig.cs create mode 100644 Entity/Generate/ConfigTable/Entity/WeightConfig.cs create mode 100644 Tools/ConfigBuilder/ConfigBuilder.sln create mode 100644 Tools/ConfigBuilder/ConfigBuilder.sln.DotSettings.user create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/App.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Core/IPool.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Core/IntDictionaryConfig.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Core/MemoryStreamBuffer.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Core/OneToManyList.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Core/Pool.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Core/Serialize/ASerialize.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Core/StringDictionaryConfig.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/DynamicAssembly.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/DynamicConfigDataType.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/OneDynamicAssembly.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelExporter.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelHelper.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelTable.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelTemplate.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelWorksheets.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExportInfo.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExportType.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Exporter/VersionInfo.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Form1.Designer.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Form1.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Form1.resx create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Log.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/NBConfigBuilder.csproj create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/NBConfigBuilder.csproj.user create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/NLog.config create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Program.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Utils/FileHelper.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Utils/HashCodeHelper.cs create mode 100644 Tools/ConfigBuilder/NBConfigBuilder/Utils/TimeHelper.cs diff --git a/Config/Binary/BaitConfigData.bytes b/Config/Binary/BaitConfigData.bytes new file mode 100644 index 0000000..0bffa4c --- /dev/null +++ b/Config/Binary/BaitConfigData.bytes @@ -0,0 +1,5 @@ + + baits/worm_01/worm_01( + baits/fly/fly(x +'baits/black_leech/black_leech(x +baits/bread/bread(x \ No newline at end of file diff --git a/Config/Binary/BobberConfigData.bytes b/Config/Binary/BobberConfigData.bytes new file mode 100644 index 0000000..fd77b45 --- /dev/null +++ b/Config/Binary/BobberConfigData.bytes @@ -0,0 +1,5 @@ + +6*bobbers/expressfishing/bob_25003/bob_25003 (( +:0bobbers/expressfishing/bob_25162_25163/bob_25162 (( +:0bobbers/expressfishing/bob_25166_25167/bob_25166 (( +4*bobbers/expressfishing/bob_25001/bob_25001 (( \ No newline at end of file diff --git a/Config/Binary/FeederConfigData.bytes b/Config/Binary/FeederConfigData.bytes new file mode 100644 index 0000000..7512fc4 --- /dev/null +++ b/Config/Binary/FeederConfigData.bytes @@ -0,0 +1,2 @@ + +&6Feeders/Feeder 1/FeedTrash 1 d( \ No newline at end of file diff --git a/Config/Binary/FishConfigData.bytes b/Config/Binary/FishConfigData.bytes new file mode 100644 index 0000000..cfe7aeb --- /dev/null +++ b/Config/Binary/FishConfigData.bytes @@ -0,0 +1,6 @@ + +Burbot_B +(0"8 +£ CarpCommon_B (0"8 +ã CarpGrass_B (0"8 +ģ CarpCrucian_B (0"8 \ No newline at end of file diff --git a/Config/Binary/HookConfigData.bytes b/Config/Binary/HookConfigData.bytes new file mode 100644 index 0000000..66b9ce3 --- /dev/null +++ b/Config/Binary/HookConfigData.bytes @@ -0,0 +1,3 @@ + +:*.hooks/alliance/c_hook_20789_20794/c_hook_20789 0 +?*3hooks/berserk_hooks/triple_20569_20577/triple_20569 0 \ No newline at end of file diff --git a/Config/Binary/LineConfigData.bytes b/Config/Binary/LineConfigData.bytes new file mode 100644 index 0000000..1ede551 --- /dev/null +++ b/Config/Binary/LineConfigData.bytes @@ -0,0 +1,3 @@ + +(Lines/UFE Mono/UFE monoClear ((0 +4(rods/syberia/bolo_10021/bolo_10021_LB400 ((0 \ No newline at end of file diff --git a/Config/Binary/LureConfigData.bytes b/Config/Binary/LureConfigData.bytes new file mode 100644 index 0000000..656c402 --- /dev/null +++ b/Config/Binary/LureConfigData.bytes @@ -0,0 +1,5 @@ + +D$5lures/express_fishing/crankbaits_1/775/crankbaits_775* 20 +E$7lures/express_fishing/poppers_1/poppers_590/poppers_590* 20x +M$Clures/express_fishing/softplastic/ef_supergrab_6/softplastic_g_1622 20x +O$Elures/express_fishing/softplastic/ef_superminnow_6/softplastic_m_1634 20x \ No newline at end of file diff --git a/Config/Binary/ReelConfigData.bytes b/Config/Binary/ReelConfigData.bytes new file mode 100644 index 0000000000000000000000000000000000000000..4b7686faeb9d2322aa9d35bc64bcc0d0f7831bf7 GIT binary patch literal 101 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 N(E`4!(NxcR{|D$)Ofdie literal 0 HcmV?d00001 diff --git a/Config/Excel/FeederConfig.xlsx b/Config/Excel/FeederConfig.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..c253564fc3464f874a877be9850abb43a517afc4 GIT binary patch literal 9692 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 literal 0 HcmV?d00001 diff --git a/Config/Excel/FishConfig.xlsx b/Config/Excel/FishConfig.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..ae840be99beb215864730e8e9b46758ee5060c0b GIT binary patch literal 11721 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 XB_jd(D*(O$c>|&j9II@U&wBp{MBodp literal 0 HcmV?d00001 diff --git a/Config/Excel/LineConfig.xlsx b/Config/Excel/LineConfig.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b8dcd5cf1ea6fffb12ccc9fd231c579ab902a914 GIT binary patch literal 11629 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%0A; zrHPvi(QrwhH4_Dki#4)^iZ)C63$6-4SKK7fMRVYEn&{;T=u%?lW`!E*3c5^JHPyn3 z>(T)Z+O-qwFEfL!GVO2nzQEuosmi`Q>9BeOeKaU}cWxd=g#DW8ay*Nivaw}DxrPFH zB+5N$u~_nctO}ZM(+_wYrCyxP@8N)Ol)Z2uh2Icfx(bG(Ehm*j=X9K07H`vosL;o+ zO|XTGHfh$7LDs@u%ti&*c|9tb0O^V=lZ@?1bKS4nJ3k zF%Z5A`bz~I3VNKPQ**Z51tdwh$m80`2S-Vz#bd*N6>JMG>L~aoW-(08J3h;7G;2&| zj!!A`)jg5|ejWP-c&srQ5%CyeVO%?p$BCVzpg3%@Pl7)*zfz6Q#zwU&LKK{)-iw&D zpPN%VIq?BKQ1njlQr*OhgpW6%JmyH^>L^0+nk;wI9vo2-Vjk{PrN+@K)=dfPa_261xu>}dO11KXB|o3vQ>7> z9w$!)CJ%w6Ay>QS;eMHgx%Z!gi=jJ45Td_CsOUfn`1OCMNgY*}b)E(;0a3$iQU|tX zkZy0O(neJv74Ce{###BfSG1!w>hB>si=0UFf~Lu%frMWR6zi zTd+U_A{{`@%VecP65v0pQF*{QSDhGcOSV@ioymclh^vJ6IRj$p_J7nwVL%_!1`cM{ z&yQt1xSX3FLH{j-l3>s`_rGKqpCn5`ElmS}@L8e#0Y0-J6CfTF$O6@Flj=m;C#hH6Z@lXWy!xW8x4Y%AJ@I1CK;h=Y^B zz>$kgP)X@q24r#Fz+{!s@0+@<_VmCWT?OJ$%FLk~p^1qf9qln=IyD1QJOPy1dyf>X zJzX1#ZUV0jq#RBeie3J{_2vHrXRq~~!#)VcuOK+_LBF7y!f&ysbFqJ?xc@>|-m;S! zE$}LENqDd&F+EXPX2V*BbYYs`C%QW^gaLl&yr;h0j?|vyGXVe!0MmRmB(<fqx&o&S&)Q|0)P6C@lVU4WW~EWzz6Dz-hy{b(>pd9HdvfqIZ+29LAmcJElkAK zHHKwO0juR$aLt_{L;WLc5&V6xCKh&~<1rB;w{U0M5}=(I&n7N=*%Ejt*5k$0WY8c@ zXm+ra_Ip2x^>k&a-zM`X;BSGym_xfvTIG*?YaGGD>OufNcjen)p@1Eow}? zn(=ma5iLa6IHS(zaa6kl_>f~+qO;pO1qf9LB@w7psSviS@4RfMhBX{KS|V-zb7>dV zM6zu%`Lw(Pb64m5UT@(gj1oqNMTR+itl67d3FJ75n6oUyxamyJnbLCT+u;V&U}awg zB1(w4BeNX_riI}h_vjym$i5?=P43qAM}{?_?h@(Qu#wNGS4fno-f+4zi0B6%>uV^B zvGyma6ACM9U?xCqZgfODN?fdysAKw{#QA{XH=;YEvldzF8fv+DW~M!&?Z!Ql@9zo} zUS%mq%ZUXOcPJ_+9OEd;ft*?0ojo0<{cca4J+;4a8UH!S=WNC&;otqe`n%e{xQ)+8`<$Wo zM>__z)c@-A-+aC2>d%R0f7IbY1gSsjf6>nV*QB2)RlnQQqzyrpN>CvGUkd+3X$%yk z{)+t12l{-Ac^C z|7-sLbe=t^<_PNi->1j_oc#0i<3Bwn|6T3RY4Y!=bv{q55pY;A81!o81 literal 0 HcmV?d00001 diff --git a/Config/Excel/RingConfig.xlsx b/Config/Excel/RingConfig.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e457fc523129529f70050c280879edde139d5e14 GIT binary patch literal 9555 zcma)CWk6iXvc}!r9RdUh9tf_%gS$&`26wmM?iPY;2<}dBCwPG1gS!TKLvr`-E_dI3 zuYdHMnfa=^&)28xR9BU}G$a%P*i-u;w1CpP;9y{HA;7@U{t7d&wPkX(w#tkflY(SH>pk*6LbC@<;h@vaW1XPthKZG2rDR$| zHcR$LNbgmunNG z_BL;Rgs@Q^*z35%;;%rQrY@|+hXWx+t9%*6xQ4IOXk~%~tAnN1nFpBYf}#@(zV#*^ ziy0tixI3u%QL^AiqCi{0X`|ebc|xBu*=bJsh&TWU_**n=L?Bd8xdJB%WX+{H>S^51 zp(`6XXJY#}coy+!^MQ*S(7-JuL~BZVjj0+K5q{R1s~t4VQ5z0nHnR2FC{-S*00OPq z8Yldi9Csf#cMq>?SI;Dhoh2=;%kujLk|H}xiLak4;elLnU!)sqlp%C+5Q%qCf7p>W zm;h~gGD>T9LS2G%?hUQ#<)*wh!jDL^`N98A^#%~=Ed`=l7eqDsU#T_%*qc0Y?G>jd z-OY^kbp^UzWWXc2=I1(+^#>(bf5?fjH7$w5I{oxus)d8a^g69_TK=sp2cN6&(s5tg z82sNctE$qAO@w5FVO0bwFJkd+m)b@_?4@9o6=CH8!EhL>&Z+|~2VX*>7EJ1AKoPpe zRVD0U9b`;l84bC}v+|sxY<`tBk=nYOvZ{Q4muU^&7dUJS9|gaKXXE?nFccVq)$(>1 zRjCH7lLbrO-yY{}E}V}CamwVw{5US7WCXHg&0uhcMngT-4b71*KZhD zUq-=E9GjaZ-{-YNQ~*w~2MsaeVzdwzBpwUsIjXryPAtz#w-;2u~ zxb>WuE8*MvIfJtU{f$Em0#3q|f$MpBe`T_@Jpe_pqYdA!nSA7tTzGlDHwL<_=$ z61H1Hwn+BhwnjEWQ=cvQOk9gW$Ay46Gk>9SYIaT)K^Tb4D?E?2pP8--r_qL6Rr@JA z@twQ1@t&u-v&7YXhgk6qLy&!lzGuL-Rc4ayZI>S#z{%D}%sK7jzEw4!obGFB{?)$h zWE-I3vFcc~KK^IF`8FMW& zOG&CsOScTOEN-;VXHON6Mi29YJw4C=Y+)clw7OdTvMv7%K=}=D0oYqQn3h`(0lg$XsS zWTaB;PcHy}V(Jf7zatwhz-;TLN z!~td>E{ex3LJ}O4TR}@8Zd>BanElq6rW=lAG*Gw_fr8|6+LY`wStIgLsypjW7LB}AJO$D)B!PTBZi`wKcTowhWSGWa`0#N>(Qyz+fYO_tT!F5_qQXcrH$fCTPplo8jun`L<`K)2l?-!gn(LX20WoQjIs|VUyf1bTQ)+}HJ~=u&F@1XkVb~z|FOa-uX-H!Is;q;5mzG_5c-;jec5vcbh3e zmT#9%xv)eabPCjfLaK`Ax1lr)$9s*2`N1p{DsEOZswGtXym0uIgIY!hY;lc0=VeuN z3U^eJME|-DMY&!zf`JR8dVhbJQ>*7%@TYw6x)2xhFW#8A`X

%S~HBOY`+;bDg6x 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 literal 0 HcmV?d00001 diff --git a/Config/Json/Server/BaitConfigData.Json b/Config/Json/Server/BaitConfigData.Json new file mode 100644 index 0000000..a5d4897 --- /dev/null +++ b/Config/Json/Server/BaitConfigData.Json @@ -0,0 +1,6 @@ +{"List":[ +{"Id":500001,"Model":"baits/worm_01/worm_01","EfficacyBase":15,"Length":0,"Weight":250}, +{"Id":500002,"Model":"baits/fly/fly","EfficacyBase":15,"Length":0,"Weight":120}, +{"Id":500003,"Model":"baits/black_leech/black_leech","EfficacyBase":15,"Length":0,"Weight":120}, +{"Id":500004,"Model":"baits/bread/bread","EfficacyBase":15,"Length":0,"Weight":120} +]} diff --git a/Config/Json/Server/BobberConfigData.Json b/Config/Json/Server/BobberConfigData.Json new file mode 100644 index 0000000..6b660e3 --- /dev/null +++ b/Config/Json/Server/BobberConfigData.Json @@ -0,0 +1,6 @@ +{"List":[ +{"Id":300001,"Model":"bobbers/expressfishing/bob_25003/bob_25003","Type":2,"Weight":1,"Displacement":40,"NightLight":0}, +{"Id":300002,"Model":"bobbers/expressfishing/bob_25162_25163/bob_25162","Type":0,"Weight":1,"Displacement":40,"NightLight":0}, +{"Id":300003,"Model":"bobbers/expressfishing/bob_25166_25167/bob_25166","Type":0,"Weight":1,"Displacement":40,"NightLight":0}, +{"Id":300004,"Model":"bobbers/expressfishing/bob_25001/bob_25001","Type":0,"Weight":1,"Displacement":40,"NightLight":0} +]} diff --git a/Config/Json/Server/FeederConfigData.Json b/Config/Json/Server/FeederConfigData.Json new file mode 100644 index 0000000..f6c86fa --- /dev/null +++ b/Config/Json/Server/FeederConfigData.Json @@ -0,0 +1,3 @@ +{"List":[ +{"Id":900001,"Model":"Feeders/Feeder 1/FeedTrash 1","Type":0,"Capacity":100,"Weight":5} +]} diff --git a/Config/Json/Server/FishConfigData.Json b/Config/Json/Server/FishConfigData.Json new file mode 100644 index 0000000..9c79c1c --- /dev/null +++ b/Config/Json/Server/FishConfigData.Json @@ -0,0 +1,6 @@ +{"List":[ +{"Id":2200001,"Model":["Burbot_B"],"Type":0,"SpeciesName":10,"MinWeight":1,"MaxWeight":34,"Accept":2100001}, +{"Id":2200002,"Model":["CarpCommon_B"],"Type":0,"SpeciesName":11,"MinWeight":1,"MaxWeight":34,"Accept":2100001}, +{"Id":2200003,"Model":["CarpGrass_B"],"Type":0,"SpeciesName":14,"MinWeight":1,"MaxWeight":34,"Accept":2100001}, +{"Id":2200004,"Model":["CarpCrucian_B"],"Type":0,"SpeciesName":16,"MinWeight":1,"MaxWeight":34,"Accept":2100001} +]} diff --git a/Config/Json/Server/HookConfigData.Json b/Config/Json/Server/HookConfigData.Json new file mode 100644 index 0000000..61293c5 --- /dev/null +++ b/Config/Json/Server/HookConfigData.Json @@ -0,0 +1,4 @@ +{"List":[ +{"Id":700001,"Model":"hooks/alliance/c_hook_20789_20794/c_hook_20789","Type":1,"Zadzior":1,"Length":0,"Weight":1}, +{"Id":700002,"Model":"hooks/berserk_hooks/triple_20569_20577/triple_20569","Type":1,"Zadzior":1,"Length":0,"Weight":1} +]} diff --git a/Config/Json/Server/LineConfigData.Json b/Config/Json/Server/LineConfigData.Json new file mode 100644 index 0000000..7b62059 --- /dev/null +++ b/Config/Json/Server/LineConfigData.Json @@ -0,0 +1,4 @@ +{"List":[ +{"Id":400001,"Model":"Lines/UFE Mono/UFE monoClear","Type":0,"Length":7,"Strength":40,"Size":1}, +{"Id":400002,"Model":"rods/syberia/bolo_10021/bolo_10021_LB400","Type":0,"Length":5,"Strength":40,"Size":1} +]} diff --git a/Config/Json/Server/LureConfigData.Json b/Config/Json/Server/LureConfigData.Json new file mode 100644 index 0000000..709dffb --- /dev/null +++ b/Config/Json/Server/LureConfigData.Json @@ -0,0 +1,6 @@ +{"List":[ +{"Id":600001,"Model":"lures/express_fishing/crankbaits_1/775/crankbaits_775","Hook":[700102],"EfficacyBase":50,"Length":0,"Weight":250}, +{"Id":600002,"Model":"lures/express_fishing/poppers_1/poppers_590/poppers_590","Hook":[700102],"EfficacyBase":50,"Length":0,"Weight":120}, +{"Id":600003,"Model":"lures/express_fishing/softplastic/ef_supergrab_6/softplastic_g_1622","Hook":[],"EfficacyBase":50,"Length":0,"Weight":120}, +{"Id":600004,"Model":"lures/express_fishing/softplastic/ef_superminnow_6/softplastic_m_1634","Hook":[],"EfficacyBase":50,"Length":0,"Weight":120} +]} diff --git a/Config/Json/Server/ReelConfigData.Json b/Config/Json/Server/ReelConfigData.Json new file mode 100644 index 0000000..31d27e5 --- /dev/null +++ b/Config/Json/Server/ReelConfigData.Json @@ -0,0 +1,4 @@ +{"List":[ +{"Id":200001,"Model":"reels/syberia/spin_5002/spin_5002","Type":0,"GearRatio":[7.0],"Size":250,"Strength":40}, +{"Id":200002,"Model":"reels/syberia/spin_5036/spin_5036","Type":0,"GearRatio":[5.0],"Size":120,"Strength":40} +]} diff --git a/Config/Json/Server/RingConfigData.Json b/Config/Json/Server/RingConfigData.Json new file mode 100644 index 0000000..6ce7186 --- /dev/null +++ b/Config/Json/Server/RingConfigData.Json @@ -0,0 +1,4 @@ +{"List":[ +{"Id":1100001,"Model":"rod_rings/rumoi/rumoi_oxiline_spin"}, +{"Id":1100002,"Model":"rod_rings/smt/smt_pure_ceramic_bolo"} +]} diff --git a/Config/Json/Server/RodConfigData.Json b/Config/Json/Server/RodConfigData.Json new file mode 100644 index 0000000..d677526 --- /dev/null +++ b/Config/Json/Server/RodConfigData.Json @@ -0,0 +1,5 @@ +{"List":[ +{"Id":100001,"Model":"rods/syberia/tele_10037/tele_10037_t13","Type":1,"Ring":0,"Length":7,"Weight":250,"Strength":40,"MaxRange":67,"ConstructionType":0}, +{"Id":100002,"Model":"rods/syberia/bolo_10021/bolo_10021_LB400","Type":0,"Ring":1100002,"Length":5,"Weight":120,"Strength":40,"MaxRange":30,"ConstructionType":0}, +{"Id":100003,"Model":"rods/syberia/spin_10034/spin_10034_S60H","Type":0,"Ring":1100001,"Length":5,"Weight":120,"Strength":40,"MaxRange":30,"ConstructionType":0} +]} diff --git a/Config/Json/Server/WeightConfigData.Json b/Config/Json/Server/WeightConfigData.Json new file mode 100644 index 0000000..a356320 --- /dev/null +++ b/Config/Json/Server/WeightConfigData.Json @@ -0,0 +1,3 @@ +{"List":[ +{"Id":800001,"Model":"Weights/Weight2_5g","Type":0,"Weight":3} +]} diff --git a/Entity/Generate/ConfigTable/Entity/BaitConfig.cs b/Entity/Generate/ConfigTable/Entity/BaitConfig.cs new file mode 100644 index 0000000..54a1f1c --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/BaitConfig.cs @@ -0,0 +1,101 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class BaitConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static BaitConfigData _instance = null; + + public static BaitConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public BaitConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"BaitConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out BaitConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class BaitConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public uint EfficacyBase { get; set; } // 吸引力 + [ProtoMember(4)] + public uint Length { get; set; } // 长度(毫米) + [ProtoMember(5)] + public uint Weight { get; set; } // 重量(克) + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/BobberConfig.cs b/Entity/Generate/ConfigTable/Entity/BobberConfig.cs new file mode 100644 index 0000000..8afe79f --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/BobberConfig.cs @@ -0,0 +1,103 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class BobberConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static BobberConfigData _instance = null; + + public static BobberConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public BobberConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"BobberConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out BobberConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class BobberConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public uint Type { get; set; } // 类型 + [ProtoMember(4)] + public uint Weight { get; set; } // 重量(克) + [ProtoMember(5)] + public uint Displacement { get; set; } // 位移 + [ProtoMember(6)] + public uint NightLight { get; set; } // 是否夜光 + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/FeederConfig.cs b/Entity/Generate/ConfigTable/Entity/FeederConfig.cs new file mode 100644 index 0000000..7d092aa --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/FeederConfig.cs @@ -0,0 +1,101 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class FeederConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static FeederConfigData _instance = null; + + public static FeederConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public FeederConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"FeederConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out FeederConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class FeederConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public uint Type { get; set; } // 类型 + [ProtoMember(4)] + public uint Capacity { get; set; } // 能力 + [ProtoMember(5)] + public uint Weight { get; set; } // 重量(克) + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/FishConfig.cs b/Entity/Generate/ConfigTable/Entity/FishConfig.cs new file mode 100644 index 0000000..2ed0c08 --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/FishConfig.cs @@ -0,0 +1,105 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class FishConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static FishConfigData _instance = null; + + public static FishConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public FishConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"FishConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out FishConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class FishConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string[] Model { get; set; } = Array.Empty(); // 模型 + [ProtoMember(3)] + public uint Type { get; set; } // 类型 + [ProtoMember(4)] + public uint SpeciesName { get; set; } // 鱼类型Id + [ProtoMember(5)] + public uint MinWeight { get; set; } // 最小重量(克) + [ProtoMember(6)] + public uint MaxWeight { get; set; } // 最大重量(克) + [ProtoMember(7)] + public uint Accept { get; set; } // 接受饵 + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/HookConfig.cs b/Entity/Generate/ConfigTable/Entity/HookConfig.cs new file mode 100644 index 0000000..98722e1 --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/HookConfig.cs @@ -0,0 +1,103 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class HookConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static HookConfigData _instance = null; + + public static HookConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public HookConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"HookConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out HookConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class HookConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public uint Type { get; set; } // 类型 + [ProtoMember(4)] + public uint Zadzior { get; set; } // 长钉 + [ProtoMember(5)] + public uint Length { get; set; } // 长度(毫米) + [ProtoMember(6)] + public uint Weight { get; set; } // 重量(克) + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/LineConfig.cs b/Entity/Generate/ConfigTable/Entity/LineConfig.cs new file mode 100644 index 0000000..1399198 --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/LineConfig.cs @@ -0,0 +1,103 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class LineConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static LineConfigData _instance = null; + + public static LineConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public LineConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"LineConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out LineConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class LineConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public uint Type { get; set; } // 类型 + [ProtoMember(4)] + public uint Length { get; set; } // 长度(毫米) + [ProtoMember(5)] + public uint Strength { get; set; } // 强度 + [ProtoMember(6)] + public uint Size { get; set; } // 尺寸 + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/LureConfig.cs b/Entity/Generate/ConfigTable/Entity/LureConfig.cs new file mode 100644 index 0000000..764f5d2 --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/LureConfig.cs @@ -0,0 +1,103 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class LureConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static LureConfigData _instance = null; + + public static LureConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public LureConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"LureConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out LureConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class LureConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public uint[] Hook { get; set; } = Array.Empty(); // 勾 + [ProtoMember(4)] + public uint EfficacyBase { get; set; } // 吸引力 + [ProtoMember(5)] + public uint Length { get; set; } // 长度(毫米) + [ProtoMember(6)] + public uint Weight { get; set; } // 重量(克) + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/ReelConfig.cs b/Entity/Generate/ConfigTable/Entity/ReelConfig.cs new file mode 100644 index 0000000..16e0839 --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/ReelConfig.cs @@ -0,0 +1,103 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class ReelConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static ReelConfigData _instance = null; + + public static ReelConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public ReelConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"ReelConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out ReelConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class ReelConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public uint Type { get; set; } // 类型 + [ProtoMember(4)] + public float[] GearRatio { get; set; } = Array.Empty(); // 组件比 + [ProtoMember(5)] + public uint Size { get; set; } // 尺寸 + [ProtoMember(6)] + public uint Strength { get; set; } // 强度 + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/RingConfig.cs b/Entity/Generate/ConfigTable/Entity/RingConfig.cs new file mode 100644 index 0000000..a0e39c6 --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/RingConfig.cs @@ -0,0 +1,95 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class RingConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static RingConfigData _instance = null; + + public static RingConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public RingConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"RingConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out RingConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class RingConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/RodConfig.cs b/Entity/Generate/ConfigTable/Entity/RodConfig.cs new file mode 100644 index 0000000..2fabc5e --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/RodConfig.cs @@ -0,0 +1,109 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class RodConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static RodConfigData _instance = null; + + public static RodConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public RodConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"RodConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out RodConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class RodConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public uint Type { get; set; } // 类型 + [ProtoMember(4)] + public uint Ring { get; set; } // 导线圈 + [ProtoMember(5)] + public uint Length { get; set; } // 长度(毫米) + [ProtoMember(6)] + public uint Weight { get; set; } // 重量(克) + [ProtoMember(7)] + public uint Strength { get; set; } // 强度 + [ProtoMember(8)] + public uint MaxRange { get; set; } // 最大范围 + [ProtoMember(9)] + public uint ConstructionType { get; set; } // 结构类型 + } +} \ No newline at end of file diff --git a/Entity/Generate/ConfigTable/Entity/WeightConfig.cs b/Entity/Generate/ConfigTable/Entity/WeightConfig.cs new file mode 100644 index 0000000..c372e52 --- /dev/null +++ b/Entity/Generate/ConfigTable/Entity/WeightConfig.cs @@ -0,0 +1,99 @@ +using System; +using ProtoBuf; +using Fantasy; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Collections.Concurrent; +using Fantasy.ConfigTable; +using Fantasy.Serialize; +// ReSharper disable CollectionNeverUpdated.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#pragma warning disable CS0169 +#pragma warning disable CS8618 +#pragma warning disable CS8625 +#pragma warning disable CS8603 + +namespace Fantasy +{ + [ProtoContract] + public sealed partial class WeightConfigData : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List List { get; set; } = new List(); +#if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); +#else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); +#endif + private static WeightConfigData _instance = null; + + public static WeightConfigData Instance + { + get { return _instance ??= ConfigTableHelper.Load(); } + private set => _instance = value; + } + + public WeightConfig Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"WeightConfig not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out WeightConfig config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { +#if FANTASY_NET + _configs.TryAdd(config.Id, config); +#else + _configs.Add(config.Id, config); +#endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class WeightConfig : ASerialize, IProto + { + [ProtoMember(1)] + public uint Id { get; set; } // Id + [ProtoMember(2)] + public string Model { get; set; } // 模型 + [ProtoMember(3)] + public uint Type { get; set; } // 类型 + [ProtoMember(4)] + public uint Weight { get; set; } // 重量(克) + } +} \ No newline at end of file diff --git a/Server.sln.DotSettings.user b/Server.sln.DotSettings.user index 9a3aa0f..de40ef4 100644 --- a/Server.sln.DotSettings.user +++ b/Server.sln.DotSettings.user @@ -61,6 +61,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/Tools/ConfigBuilder/ConfigBuilder.sln b/Tools/ConfigBuilder/ConfigBuilder.sln new file mode 100644 index 0000000..ff5bae9 --- /dev/null +++ b/Tools/ConfigBuilder/ConfigBuilder.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36408.4 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NBConfigBuilder", "NBConfigBuilder\NBConfigBuilder.csproj", "{A6264F0E-870D-401D-B1BF-698695AC674C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A6264F0E-870D-401D-B1BF-698695AC674C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6264F0E-870D-401D-B1BF-698695AC674C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6264F0E-870D-401D-B1BF-698695AC674C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6264F0E-870D-401D-B1BF-698695AC674C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F223241C-FF74-4903-8F47-8A5A5A0FAC99} + EndGlobalSection +EndGlobal diff --git a/Tools/ConfigBuilder/ConfigBuilder.sln.DotSettings.user b/Tools/ConfigBuilder/ConfigBuilder.sln.DotSettings.user new file mode 100644 index 0000000..bc0585f --- /dev/null +++ b/Tools/ConfigBuilder/ConfigBuilder.sln.DotSettings.user @@ -0,0 +1,3 @@ + + ForceIncluded + ForceIncluded \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/App.cs b/Tools/ConfigBuilder/NBConfigBuilder/App.cs new file mode 100644 index 0000000..dde37af --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/App.cs @@ -0,0 +1,20 @@ +namespace NBConfigBuilder; + +// 添加配置类 +public class AppConfig +{ + public string ExcelPath { get; set; } + public string ClientPath { get; set; } + public string ClientJsonPath { get; set; } + public string ServerPath { get; set; } + public string ServerJsonPath { get; set; } + public bool GenClient { get; set; } + public bool GenServer { get; set; } + + public string ExcelVersionPath => Path.Combine(ExcelPath, "Version.txt"); +} + +public static class App +{ + public static AppConfig Config; +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Core/IPool.cs b/Tools/ConfigBuilder/NBConfigBuilder/Core/IPool.cs new file mode 100644 index 0000000..808a332 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Core/IPool.cs @@ -0,0 +1,17 @@ +namespace NBConfigBuilder; + +///

+/// 实现了这个接口代表支持对象池 +/// +public interface IPool +{ + /// + /// 是否从池里创建的 + /// + bool IsPool(); + /// + /// 设置是否从池里创建的 + /// + /// + void SetIsPool(bool isPool); +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Core/IntDictionaryConfig.cs b/Tools/ConfigBuilder/NBConfigBuilder/Core/IntDictionaryConfig.cs new file mode 100644 index 0000000..7d8a06a --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Core/IntDictionaryConfig.cs @@ -0,0 +1,25 @@ +namespace NBConfigBuilder; + +public partial class IntDictionaryConfig +{ + public Dictionary Dic; + public int this[int key] => GetValue(key); + + public bool TryGetValue(int key, out int value) + { + value = default; + + if (!Dic.ContainsKey(key)) + { + return false; + } + + value = Dic[key]; + return true; + } + + private int GetValue(int key) + { + return Dic.TryGetValue(key, out var value) ? value : 0; + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Core/MemoryStreamBuffer.cs b/Tools/ConfigBuilder/NBConfigBuilder/Core/MemoryStreamBuffer.cs new file mode 100644 index 0000000..ddc6998 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Core/MemoryStreamBuffer.cs @@ -0,0 +1,77 @@ +using System.Buffers; + +namespace NBConfigBuilder; + +public enum MemoryStreamBufferSource +{ + None = 0, + Pack = 1, + UnPack = 2, +} + +public sealed class MemoryStreamBuffer : MemoryStream, IBufferWriter +{ + public MemoryStreamBufferSource MemoryStreamBufferSource; + + public MemoryStreamBuffer() + { + } + + public MemoryStreamBuffer(MemoryStreamBufferSource memoryStreamBufferSource, int capacity) : base(capacity) + { + MemoryStreamBufferSource = memoryStreamBufferSource; + } + + public MemoryStreamBuffer(byte[] buffer) : base(buffer) + { + } + + public void Advance(int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException(nameof(count), count, "The value of 'count' cannot be negative."); + } + + var newLength = Position + count; + + if (newLength != Length) + { + SetLength(newLength); + } + + Position = newLength; + } + + public Memory GetMemory(int sizeHint = 0) + { + if (sizeHint < 0) + { + throw new ArgumentOutOfRangeException(nameof(sizeHint), sizeHint, + "The value of 'count' cannot be negative."); + } + + if (Length - Position <= sizeHint) + { + SetLength(Position + sizeHint); + } + + return new Memory(GetBuffer(), (int)Position, (int)(Length - Position)); + } + + public Span GetSpan(int sizeHint = 0) + { + if (sizeHint < 0) + { + throw new ArgumentOutOfRangeException(nameof(sizeHint), sizeHint, + "The value of 'count' cannot be negative."); + } + + if (Length - Position <= sizeHint) + { + SetLength(Position + sizeHint); + } + + return new Span(GetBuffer(), (int)Position, (int)(Length - Position)); + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Core/OneToManyList.cs b/Tools/ConfigBuilder/NBConfigBuilder/Core/OneToManyList.cs new file mode 100644 index 0000000..f6a1203 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Core/OneToManyList.cs @@ -0,0 +1,217 @@ +namespace NBConfigBuilder; + +/// +/// 可回收的、一对多关系的列表池。 +/// +/// 键的类型。 +/// 值的类型。 +public class OneToManyListPool : OneToManyList, IDisposable, IPool where TKey : notnull +{ + private bool _isPool; + private bool _isDispose; + + /// + /// 创建一个 一对多关系的列表池的实例。 + /// + /// 创建的实例。 + public static OneToManyListPool Create() + { + var list = Pool>.Rent(); + list._isDispose = false; + list._isPool = true; + return list; + } + + /// + /// 释放当前对象所占用的资源,并将对象回收到对象池中。 + /// + public void Dispose() + { + if (_isDispose) + { + return; + } + + _isDispose = true; + Clear(); + Pool>.Return(this); + } + + /// + /// 获取一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public bool IsPool() + { + return _isPool; + } + + /// + /// 设置一个值,该值指示当前实例是否为对象池中的实例。 + /// + /// + public void SetIsPool(bool isPool) + { + _isPool = isPool; + } +} + +/// +/// 一对多关系的列表字典。 +/// +/// 键的类型。 +/// 值的类型。 +public class OneToManyList : Dictionary> where TKey : notnull +{ + private readonly int _recyclingLimit = 120; + private static readonly List Empty = new List(); + private readonly Queue> _queue = new Queue>(); + + /// + /// 初始化一个新的 实例。 + /// + public OneToManyList() + { + } + + /// + /// 设置最大缓存数量 + /// + /// + /// 1:防止数据量过大、所以超过recyclingLimit的数据还是走GC. + /// 2:设置成0不控制数量,全部缓存 + /// + public OneToManyList(int recyclingLimit) + { + _recyclingLimit = recyclingLimit; + } + + /// + /// 判断给定的键和值是否存在于列表中。 + /// + /// 要搜索的键。 + /// 要搜索的值。 + /// 如果存在则为 ,否则为 + public bool Contains(TKey key, TValue value) + { + TryGetValue(key, out var list); + + return list != null && list.Contains(value); + } + + /// + /// 向列表中添加指定键和值。 + /// + /// 要添加值的键。 + /// 要添加的值。 + public void Add(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + list = Fetch(); + list.Add(value); + Add(key, list); + + return; + } + + list.Add(value); + } + + /// + /// 获取指定键对应的列表中的第一个值。 + /// + /// 要获取值的键。 + /// 键对应的列表中的第一个值。 + public TValue First(TKey key) + { + return !TryGetValue(key, out var list) ? default : list.FirstOrDefault(); + } + + /// + /// 从列表中移除指定键和值。 + /// + /// 要移除值的键。 + /// 要移除的值。 + /// 如果成功移除则为 ,否则为 + public bool RemoveValue(TKey key, TValue value) + { + if (!TryGetValue(key, out var list)) + { + return true; + } + + var isRemove = list.Remove(value); + + if (list.Count == 0) + { + isRemove = RemoveByKey(key); + } + + return isRemove; + } + + /// + /// 从列表中移除指定键及其关联的所有值。 + /// + /// 要移除的键。 + /// 如果成功移除则为 ,否则为 + public bool RemoveByKey(TKey key) + { + if (!TryGetValue(key, out var list)) + { + return false; + } + + Remove(key); + Recycle(list); + return true; + } + + /// + /// 获取指定键关联的所有值的列表。 + /// + /// 要获取值的键。 + /// 键关联的所有值的列表。 + public List GetValues(TKey key) + { + if (TryGetValue(key, out List list)) + { + return list; + } + + return Empty; + } + + /// + /// 清除字典中的所有键值对,并回收相关的值集合。 + /// + public new void Clear() + { + foreach (var keyValuePair in this) Recycle(keyValuePair.Value); + + base.Clear(); + } + + /// + /// 从空闲值集合队列中获取一个值集合,如果队列为空则创建一个新的值集合。 + /// + /// 从队列中获取的值集合。 + private List Fetch() + { + return _queue.Count <= 0 ? new List() : _queue.Dequeue(); + } + + /// + /// 回收一个不再使用的值集合到空闲值集合队列中。 + /// + /// 要回收的值集合。 + private void Recycle(List list) + { + list.Clear(); + + if (_recyclingLimit != 0 && _queue.Count > _recyclingLimit) return; + + _queue.Enqueue(list); + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Core/Pool.cs b/Tools/ConfigBuilder/NBConfigBuilder/Core/Pool.cs new file mode 100644 index 0000000..989ac54 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Core/Pool.cs @@ -0,0 +1,72 @@ +namespace NBConfigBuilder; + +/// +/// 静态的对象池系统,不支持多线程。 +/// +/// +public static class Pool where T : IPool, new() +{ + private static readonly Queue PoolQueue = new Queue(); + + /// + /// 池子里可用的数量 + /// + public static int Count => PoolQueue.Count; + + /// + /// 租借 + /// + /// + public static T Rent() + { + return PoolQueue.Count == 0 ? new T() : PoolQueue.Dequeue(); + } + + /// + /// 租借 + /// + /// 如果池子里没有,会先执行这个委托。 + /// + public static T Rent(Func generator) + { + return PoolQueue.Count == 0 ? generator() : PoolQueue.Dequeue(); + } + + /// + /// 返还 + /// + /// + public static void Return(T t) + { + if (t == null) + { + return; + } + + PoolQueue.Enqueue(t); + } + + /// + /// 返还 + /// + /// 返还的东西 + /// 返还后执行的委托 + public static void Return(T t, Action reset) + { + if (t == null) + { + return; + } + + reset(t); + PoolQueue.Enqueue(t); + } + + /// + /// 清空池子 + /// + public static void Clear() + { + PoolQueue.Clear(); + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Core/Serialize/ASerialize.cs b/Tools/ConfigBuilder/NBConfigBuilder/Core/Serialize/ASerialize.cs new file mode 100644 index 0000000..67e7671 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Core/Serialize/ASerialize.cs @@ -0,0 +1,20 @@ +using System.ComponentModel; + +namespace Fantasy.Serialize; + +public abstract class ASerialize : ISupportInitialize, IDisposable +{ + public virtual void Dispose() + { + } + + public virtual void BeginInit() + { + } + + public virtual void EndInit() + { + } + + public virtual void AfterDeserialization() => EndInit(); +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Core/StringDictionaryConfig.cs b/Tools/ConfigBuilder/NBConfigBuilder/Core/StringDictionaryConfig.cs new file mode 100644 index 0000000..bf8ae75 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Core/StringDictionaryConfig.cs @@ -0,0 +1,25 @@ +namespace NBConfigBuilder; + +public sealed partial class StringDictionaryConfig +{ + public Dictionary Dic; + public string this[int key] => GetValue(key); + + public bool TryGetValue(int key, out string value) + { + value = default; + + if (!Dic.ContainsKey(key)) + { + return false; + } + + value = Dic[key]; + return true; + } + + private string GetValue(int key) + { + return Dic.TryGetValue(key, out var value) ? value : null; + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/DynamicAssembly.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/DynamicAssembly.cs new file mode 100644 index 0000000..66a3272 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/DynamicAssembly.cs @@ -0,0 +1,169 @@ +using System.Reflection; +using Fantasy.Serialize; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using ProtoBuf; + +namespace NBConfigBuilder; + + +/// +/// 动态程序集类,用于加载动态生成的程序集并获取动态信息。 +/// +public static class DynamicAssembly +{ + private static void MetadataReference(out string assemblyName, out List metadataReferenceList) + { + AssemblyMetadata assemblyMetadata; + MetadataReference metadataReference; + var currentDomain = AppDomain.CurrentDomain; + assemblyName = Path.GetRandomFileName(); + var assemblyArray = currentDomain.GetAssemblies(); + metadataReferenceList = new List(); + + // 注册引用 + + foreach (var domainAssembly in assemblyArray) + { + if (string.IsNullOrEmpty(domainAssembly.Location)) + { + continue; + } + + assemblyMetadata = AssemblyMetadata.CreateFromFile(domainAssembly.Location); + metadataReference = assemblyMetadata.GetReference(); + metadataReferenceList.Add(metadataReference); + } + + // 添加Proto支持 + + assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(ProtoMemberAttribute).Assembly.Location); + metadataReference = assemblyMetadata.GetReference(); + metadataReferenceList.Add(metadataReference); + + // 添加Fantasy支持 + + assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(ASerialize).Assembly.Location); + metadataReference = assemblyMetadata.GetReference(); + metadataReferenceList.Add(metadataReference); + } + + /// + /// 加载指定路径下的动态程序集。 + /// + /// 程序集文件路径。 + /// 加载的动态程序集。 + public static Assembly Load(string path) + { + var fileList = new List(); + + // 找到所有需要加载的CS文件 + + foreach (string file in Directory.GetFiles(path)) + { + if (Path.GetExtension(file) != ".cs") + { + continue; + } + + fileList.Add(file); + } + + var syntaxTreeList = new List(); + + foreach (var file in fileList) + { + using var fileStream = new StreamReader(file); + var cSharp = CSharpSyntaxTree.ParseText(fileStream.ReadToEnd()); + syntaxTreeList.Add(cSharp); + } + + // 注册程序集 + MetadataReference(out var assemblyName, out var metadataReferenceList); + var compilation = CSharpCompilation.Create(assemblyName, syntaxTreeList, metadataReferenceList, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + using var ms = new MemoryStream(); + var result = compilation.Emit(ms); + if (!result.Success) + { + foreach (var resultDiagnostic in result.Diagnostics) + { + Log.Error(resultDiagnostic.GetMessage()); + } + + throw new Exception("failures"); + } + + ms.Seek(0, SeekOrigin.Begin); + return Assembly.Load(ms.ToArray()); + } + + /// + /// 获取动态程序集中指定表格的动态信息。 + /// + /// 动态程序集。 + /// 表格名称。 + /// 动态信息对象。 + public static DynamicConfigDataType GetDynamicInfo(Assembly dynamicAssembly, string tableName) + { + var dynamicConfigDataType = new DynamicConfigDataType + { + ConfigDataType = GetConfigType(dynamicAssembly, $"{tableName}Data"), + ConfigType = GetConfigType(dynamicAssembly, $"{tableName}") + }; + + dynamicConfigDataType.ConfigData = CreateInstance(dynamicConfigDataType.ConfigDataType); + + var listPropertyType = dynamicConfigDataType.ConfigDataType.GetProperty("List"); + + if (listPropertyType == null) + { + throw new Exception("No Property named Add was found"); + } + + dynamicConfigDataType.Obj = listPropertyType.GetValue(dynamicConfigDataType.ConfigData); + dynamicConfigDataType.Method = listPropertyType.PropertyType.GetMethod("Add"); + + if (dynamicConfigDataType.Method == null) + { + throw new Exception("No method named Add was found"); + } + + return dynamicConfigDataType; + } + + /// + /// 根据类型名称获取动态类型。 + /// + /// 动态程序集。 + /// 类型名称。 + /// 动态类型。 + private static Type GetConfigType(Assembly dynamicAssembly, string typeName) + { + var configType = dynamicAssembly.GetType($"Fantasy.{typeName}"); + + if (configType == null) + { + throw new FileNotFoundException($"Fantasy.{typeName} not found"); + } + + return configType; + // return dynamicAssembly.GetType($"Fantasy.{typeName}"); + } + + /// + /// 创建动态实例。 + /// + /// 动态类型。 + /// 动态实例。 + public static object CreateInstance(Type configType) + { + var config = Activator.CreateInstance(configType); + + if (config == null) + { + throw new Exception($"{configType.Name} is Activator.CreateInstance error"); + } + + return config; + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/DynamicConfigDataType.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/DynamicConfigDataType.cs new file mode 100644 index 0000000..0d808d0 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/DynamicConfigDataType.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Text; + +namespace NBConfigBuilder; + +/// +/// 动态配置数据类型类,用于存储动态配置数据的相关信息。 +/// +public class DynamicConfigDataType +{ + /// + /// 配置数据对象,继承自 AProto 基类。 + /// + public object ConfigData; + /// + /// 配置数据类型。 + /// + public Type ConfigDataType; + /// + /// 配置类型。 + /// + public Type ConfigType; + /// + /// 反射方法信息,用于调用特定方法。 + /// + public MethodInfo Method; + /// + /// 配置数据对象实例。 + /// + public object Obj; + /// + /// 用于生成 JSON 格式数据的字符串构建器。 + /// + public StringBuilder Json = new StringBuilder(); +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/OneDynamicAssembly.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/OneDynamicAssembly.cs new file mode 100644 index 0000000..03b7a99 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/DynamicAssembly/OneDynamicAssembly.cs @@ -0,0 +1,69 @@ +using System.Reflection; +using Fantasy.Serialize; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using ProtoBuf; + +namespace NBConfigBuilder; + +public class OneDynamicAssembly +{ + private readonly List _syntaxTreeList = new List(); + + public void Load(string file) + { + using var fileStream = new StreamReader(file); + var cSharp = CSharpSyntaxTree.ParseText(fileStream.ReadToEnd()); + _syntaxTreeList.Add(cSharp); + } + + public Assembly Assembly + { + get + { + AssemblyMetadata assemblyMetadata; + MetadataReference metadataReference; + var currentDomain = AppDomain.CurrentDomain; + var assemblyName = Path.GetRandomFileName(); + var assemblyArray = currentDomain.GetAssemblies(); + var metadataReferenceList = new List(); + // 注册引用 + foreach (var domainAssembly in assemblyArray) + { + if (string.IsNullOrEmpty(domainAssembly.Location)) + { + continue; + } + + assemblyMetadata = AssemblyMetadata.CreateFromFile(domainAssembly.Location); + metadataReference = assemblyMetadata.GetReference(); + metadataReferenceList.Add(metadataReference); + } + // 添加ProtoEntity支持 + assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(ASerialize).Assembly.Location); + metadataReference = assemblyMetadata.GetReference(); + metadataReferenceList.Add(metadataReference); + // 添加MessagePack支持 + assemblyMetadata = AssemblyMetadata.CreateFromFile(typeof(ProtoMemberAttribute).Assembly.Location); + metadataReference = assemblyMetadata.GetReference(); + metadataReferenceList.Add(metadataReference); + CSharpCompilation compilation = CSharpCompilation.Create(assemblyName, _syntaxTreeList, metadataReferenceList, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + using var ms = new MemoryStream(); + var result = compilation.Emit(ms); + + if (!result.Success) + { + foreach (var resultDiagnostic in result.Diagnostics) + { + Log.Error(resultDiagnostic.GetMessage()); + } + + throw new Exception("failures"); + } + + ms.Seek(0, SeekOrigin.Begin); + return Assembly.Load(ms.ToArray()); + } + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelExporter.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelExporter.cs new file mode 100644 index 0000000..b021e9f --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelExporter.cs @@ -0,0 +1,950 @@ +using System.Collections.Concurrent; +using System.Reflection; +using System.Runtime.Loader; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using Microsoft.VisualBasic.ApplicationServices; +using Newtonsoft.Json; +using OfficeOpenXml; +using static System.String; + +namespace NBConfigBuilder; + +using TableDictionary = SortedDictionary>; + +/// +/// Excel 数据导出器,用于从 Excel 文件导出数据到二进制格式和生成 C# 类文件。 +/// +public sealed class ExcelExporter +{ + public ExportType ExportType; + private readonly string _excelProgramPath; + private readonly string _versionFilePath; + + // private readonly string _excelClientFileDirectory; + // private readonly string _excelServerFileDirectory; + // private readonly string _excelServerJsonDirectory; + // private readonly string _excelClientJsonDirectory; + + public VersionInfo VersionInfo; // 存储 Excel 文件的版本信息。 + private readonly Regex _regexName = new Regex("^[a-zA-Z][a-zA-Z0-9_]*$"); // 用于验证 Excel 表名的正则表达式。 + private readonly HashSet _loadFiles = [".xlsx", ".xlsm", ".csv"]; // 加载的支持文件扩展名。 + + private readonly OneToManyList + _tables = new OneToManyList(); // 存储 Excel 表及其导出信息。 + + private readonly ConcurrentDictionary _excelTables = + new ConcurrentDictionary(); // 存储解析后的 Excel 表。 + + public readonly ConcurrentDictionary Worksheets = + new ConcurrentDictionary(); // 存储已加载的 Excel 工作表。 + + public readonly Dictionary IgnoreTable = new Dictionary(); // 存储以#开头的的表和路径 + + /// + /// 导表支持的数据类型集合。 + /// + public static readonly HashSet ColTypeSet = + [ + "", "0", "bool", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "string", + "IntDictionaryConfig", "StringDictionaryConfig", + "short[]", "int[]", "long[]", "float[]", "string[]", "uint[]" + ]; + + static ExcelExporter() + { + ExcelPackage.License.SetNonCommercialOrganization("Fantasy"); + } + + /// + /// 根据指定的 exportType 初始化 ExcelExporter 类的新实例。 + /// + /// 要执行的导出类型(AllExcel 或 AllExcelIncrement)。 + public ExcelExporter(ExportType exportType) + { + ExportType = exportType; + + if (App.Config.ExcelVersionPath == null || App.Config.ExcelVersionPath.Trim() == "") + { + Log.Info($"ExcelVersionFile Can not be empty!"); + return; + } + + if (App.Config.ExcelPath == null || App.Config.ExcelPath.Trim() == "") + { + Log.Info($"ExcelProgramPath Can not be empty!"); + return; + } + + _excelProgramPath = FileHelper.GetFullPath(App.Config.ExcelPath); + _versionFilePath = FileHelper.GetFullPath(App.Config.ExcelVersionPath); + + if (App.Config.GenClient) + { + if (App.Config.ClientJsonPath == null || + App.Config.ClientJsonPath.Trim() == "") + { + Log.Info($"ExcelClientJsonDirectory Can not be empty!"); + return; + } + + if (App.Config.ClientPath == null || + App.Config.ClientPath.Trim() == "") + { + Log.Info($"ExcelServerFileDirectory Can not be empty!"); + return; + } + } + + if (App.Config.GenServer) + { + if (App.Config.ServerPath == null || + App.Config.ServerPath.Trim() == "") + { + Log.Info($"ExcelServerFileDirectory Can not be empty!"); + return; + } + + if (App.Config.ServerJsonPath == null || + App.Config.ServerJsonPath.Trim() == "") + { + Log.Info($"ExcelServerJsonDirectory Can not be empty!"); + return; + } + } + + switch (ExportType) + { + case ExportType.AllExcelIncrement: + { + break; + } + case ExportType.AllExcel: + { + if (File.Exists(_versionFilePath)) + { + File.Delete(_versionFilePath); + } + + break; + } + } + + // SerializerManager.Initialize(); + } + + public void Run() + { + Find(); + Parsing(); + ExportToBinary(); + File.WriteAllText(_versionFilePath, JsonConvert.SerializeObject(VersionInfo)); + } + + /// + /// 查找配置文件 + /// + private void Find() + { + VersionInfo = File.Exists(_versionFilePath) + ? JsonConvert.DeserializeObject(File.ReadAllText(_versionFilePath)) + : new VersionInfo(); + + var dir = new DirectoryInfo(_excelProgramPath); + var excelFiles = dir.GetFiles("*", SearchOption.AllDirectories); + + if (excelFiles.Length <= 0) + { + return; + } + + foreach (var excelFile in excelFiles) + { + // 过滤掉非指定后缀的文件 + + if (!_loadFiles.Contains(excelFile.Extension)) + { + continue; + } + + var lastIndexOf = excelFile.Name.LastIndexOf(".", StringComparison.Ordinal); + + if (lastIndexOf < 0) + { + continue; + } + + var fullName = excelFile.FullName; + var excelName = excelFile.Name.Substring(0, lastIndexOf); + var path = fullName.Substring(0, fullName.Length - excelFile.Name.Length); + + // 过滤~开头文件 + + if (excelName.StartsWith("~", StringComparison.Ordinal)) + { + continue; + } + + // 如果文件名以#开头,那么这个文件夹下的所有文件都不导出 + + if (excelName.StartsWith("#", StringComparison.Ordinal)) + { + IgnoreTable.Add(excelName, fullName); + continue; + } + + // 如果文件夹名包含#,那么这个文件夹下的所有文件都不导出 + + if (path.Contains("#", StringComparison.Ordinal)) + { + continue; + } + + if (!_regexName.IsMatch(excelName)) + { + Log.Info($"{excelName} 配置文件名非法"); + continue; + } + + var exportInfo = new ExportInfo() + { + Name = excelName, FileInfo = excelFile + }; + + _tables.Add(excelName.Split('_')[0], exportInfo); + } + + var removeTables = new List(); + + foreach (var (tableName, tableList) in _tables) + { + var isNeedExport = false; + + foreach (var exportInfo in tableList) + { + var key = HashCodeHelper.ComputeHash64(exportInfo.Name); + var timer = TimeHelper.Transition(exportInfo.FileInfo.LastWriteTime); + + if (!isNeedExport) + { + if (VersionInfo.Tables.TryGetValue(key, out var lastWriteTime)) + { + isNeedExport = lastWriteTime != timer; + } + else + { + isNeedExport = true; + } + } + + VersionInfo.Tables[key] = timer; + } + + if (!isNeedExport) + { + removeTables.Add(tableName); + } + } + + foreach (var removeTable in removeTables) + { + _tables.Remove(removeTable); + } + + foreach (var (_, exportInfo) in _tables) + { + exportInfo.Sort((x, y) => Compare(x.Name, y.Name, StringComparison.Ordinal)); + } + } + + /// + /// 生成配置文件 + /// + private void Parsing() + { + var generateTasks = new List(); + + foreach (var (tableName, tableList) in _tables) + { + var task = Task.Run(() => + { + var writeToClassTask = new List(); + var excelTable = new ExcelTable(tableName); + + // 筛选需要导出的列 + foreach (var exportInfo in tableList) + { + try + { + var serverColInfoList = new List(); + var clientColInfoList = new List(); + var worksheet = LoadExcel(exportInfo.FileInfo.FullName, true); + + for (var col = 3; col <= worksheet.Columns.EndColumn; col++) + { + // 列名字第一个字符是#不参与导出 + + var colName = worksheet.GetCellValue(5, col); + if (colName.StartsWith("#", StringComparison.Ordinal)) + { + continue; + } + + // 数值列不参与导出 + + var numericalCol = worksheet.GetCellValue(3, col); + if (numericalCol != "" && numericalCol != "0") + { + continue; + } + + var serverType = worksheet.GetCellValue(1, col); + var clientType = worksheet.GetCellValue(2, col); + var isExportServer = !IsNullOrEmpty(serverType) && serverType != "0"; + var isExportClient = !IsNullOrEmpty(clientType) && clientType != "0"; + + if (!isExportServer && !isExportClient) + { + continue; + } + + if (isExportServer && isExportClient & serverType != clientType) + { + Log.Info( + $"配置表 {exportInfo.Name} {col} 列 [{colName}] 客户端类型 {clientType} 和 服务端类型 {serverType} 不一致"); + continue; + } + + if (!ColTypeSet.Contains(serverType) || !ColTypeSet.Contains(clientType)) + { + Log.Info( + $"配置表 {exportInfo.Name} {col} 列 [{colName}] 客户端类型 {clientType}, 服务端类型 {serverType} 不合法"); + continue; + } + + if (!_regexName.IsMatch(colName)) + { + Log.Info($"配置表 {exportInfo.Name} {col} 列 [{colName}] 列名非法"); + continue; + } + + serverColInfoList.Add(col); + + if (isExportClient) + { + clientColInfoList.Add(col); + } + } + + if (clientColInfoList.Count > 0) + { + excelTable.ClientColInfos.Add(exportInfo.FileInfo.FullName, clientColInfoList); + } + + if (serverColInfoList.Count > 0) + { + excelTable.ServerColInfos.Add(exportInfo.FileInfo.FullName, serverColInfoList); + } + } + catch (Exception e) + { + Log.Error($"Config : {tableName}, Name : {exportInfo.Name}, Error : {e}"); + } + } + + // 生成cs文件 + + if (App.Config.GenServer) + { + writeToClassTask.Add(Task.Run(() => + { + WriteToClass(excelTable.ServerColInfos, App.Config.ServerPath, true); + })); + } + + if (App.Config.GenClient) + { + writeToClassTask.Add(Task.Run(() => + { + WriteToClass(excelTable.ClientColInfos, App.Config.ClientPath, false); + })); + } + + Task.WaitAll(writeToClassTask.ToArray()); + _excelTables.TryAdd(tableName, excelTable); + }); + + generateTasks.Add(task); + } + + Task.WaitAll(generateTasks.ToArray()); + Console.WriteLine("build success==="); + } + + /// + /// 把数据和实体类转换二进制导出到文件中 + /// + private void ExportToBinary() + { + System.Reflection.Assembly dynamicServerAssembly = null; + System.Reflection.Assembly dynamicClientAssembly = null; + var exportToBinaryTasks = new List(); + + if (App.Config.GenServer && + Directory.Exists(App.Config.ServerPath)) + { + dynamicServerAssembly = DynamicAssembly.Load(App.Config.ServerPath); + } + + if (App.Config.GenClient && + Directory.Exists(App.Config.ClientPath)) + { + dynamicClientAssembly = DynamicAssembly.Load(App.Config.ClientPath); + } + + foreach (var (tableName, tableList) in _tables) + { + var task = Task.Run(() => + { + DynamicConfigDataType serverDynamicInfo = null; + DynamicConfigDataType clientDynamicInfo = null; + + var idCheck = new HashSet(); + var excelTable = _excelTables[tableName]; + var csName = Path.GetFileNameWithoutExtension(tableName); + + if (App.Config.GenServer) + { + var serverColInfoCount = excelTable.ServerColInfos.Sum(d => d.Value.Count); + serverDynamicInfo = serverColInfoCount == 0 + ? null + : DynamicAssembly.GetDynamicInfo(dynamicServerAssembly, csName); + } + + if (App.Config.GenClient) + { + var clientColInfoCount = excelTable.ClientColInfos.Sum(d => d.Value.Count); + clientDynamicInfo = clientColInfoCount == 0 + ? null + : DynamicAssembly.GetDynamicInfo(dynamicClientAssembly, csName); + } + + for (var i = 0; i < tableList.Count; i++) + { + var tableListName = tableList[i]; + + try + { + var fileInfoFullName = tableListName.FileInfo.FullName; + var excelWorksheet = LoadExcel(fileInfoFullName, false); + var rows = excelWorksheet.Dimension.Rows; + excelTable.ServerColInfos.TryGetValue(fileInfoFullName, out var serverCols); + excelTable.ClientColInfos.TryGetValue(fileInfoFullName, out var clientCols); + + for (var row = 7; row <= rows; row++) + { + if (excelWorksheet.GetCellValue(row, 1).StartsWith("#", StringComparison.Ordinal)) + { + continue; + } + + var id = excelWorksheet.GetCellValue(row, 3); + + if (idCheck.Contains(id)) + { + Log.Info($"{tableListName.Name} 存在重复Id {id} 行号 {row}"); + continue; + } + + idCheck.Add(id); + var isLast = row == rows && (i == tableList.Count - 1); + + if (App.Config.GenServer) + { + GenerateBinary(fileInfoFullName, excelWorksheet, serverDynamicInfo, serverCols, id, row, + isLast, true); + } + + if (App.Config.GenClient) + { + GenerateBinary(fileInfoFullName, excelWorksheet, clientDynamicInfo, clientCols, id, row, + isLast, false); + } + } + } + catch (Exception e) + { + Log.Error($"Table:{tableListName} error! \n{e}"); + throw; + } + } + + if (serverDynamicInfo?.ConfigData != null) + { + // var memoryStream = new MemoryStreamBuffer(); + // SerializerManager.GetSerializer(FantasySerializerType.ProtoBuf) + // .Serialize(serverDynamicInfo.ConfigData, memoryStream); + // if (!Directory.Exists(_excelServerBinaryDirectory)) + // { + // Directory.CreateDirectory(_excelServerBinaryDirectory); + // } + + // var asSpan = memoryStream.GetBuffer().AsSpan(0, (int)memoryStream.Position); + // File.WriteAllBytes(Path.Combine(_excelServerBinaryDirectory, $"{csName}Data.bytes"), + // asSpan.ToArray()); + + if (serverDynamicInfo.Json.Length > 0) + { + if (!Directory.Exists(App.Config.ServerJsonPath)) + { + Directory.CreateDirectory(App.Config.ServerJsonPath); + } + + using var sw = new StreamWriter(Path.Combine(App.Config.ServerJsonPath, $"{csName}Data.Json")); + sw.WriteLine("{\"List\":["); + sw.Write(serverDynamicInfo.Json.ToString()); + sw.WriteLine("]}"); + } + } + + if (clientDynamicInfo?.ConfigData != null) + { + // var memoryStream = new MemoryStreamBuffer(); + // SerializerManager.GetSerializer(FantasySerializerType.ProtoBuf) + // .Serialize(clientDynamicInfo.ConfigData, memoryStream); + // if (!Directory.Exists(_excelClientBinaryDirectory)) + // { + // Directory.CreateDirectory(_excelClientBinaryDirectory); + // } + // + // var asSpan = memoryStream.GetBuffer().AsSpan(0, (int)memoryStream.Position); + // File.WriteAllBytes(Path.Combine(_excelClientBinaryDirectory, $"{csName}Data.bytes"), + // asSpan.ToArray()); + + if (clientDynamicInfo.Json.Length > 0) + { + if (!Directory.Exists(App.Config.ClientJsonPath)) + { + Directory.CreateDirectory(App.Config.ClientJsonPath); + } + + using var sw = new StreamWriter(Path.Combine(App.Config.ClientJsonPath, $"{csName}Data.Json")); + sw.WriteLine("{\"List\":["); + sw.Write(clientDynamicInfo.Json.ToString()); + sw.WriteLine("]}"); + } + } + }); + exportToBinaryTasks.Add(task); + } + + Task.WaitAll(exportToBinaryTasks.ToArray()); + } + + private void GenerateBinary(string fileInfoFullName, ExcelWorksheet excelWorksheet, + DynamicConfigDataType dynamicInfo, List cols, string id, int row, bool isLast, bool isServer) + { + if (cols == null || IsNullOrEmpty(id) || cols.Count <= 0 || dynamicInfo?.ConfigType == null) + { + return; + } + + var config = DynamicAssembly.CreateInstance(dynamicInfo.ConfigType); + + for (var i = 0; i < cols.Count; i++) + { + string colType; + var colIndex = cols[i]; + var colName = excelWorksheet.GetCellValue(5, colIndex); + var value = excelWorksheet.GetCellValue(row, colIndex); + + if (isServer) + { + colType = excelWorksheet.GetCellValue(1, colIndex); + + if (IsNullOrEmpty(colType) || colType == "0") + { + colType = excelWorksheet.GetCellValue(2, colIndex); + } + } + else + { + colType = excelWorksheet.GetCellValue(2, colIndex); + } + + try + { + SetNewValue(dynamicInfo.ConfigType.GetProperty(colName), config, colType, value); + } + catch (Exception e) + { + Log.Error( + $"Error Table {fileInfoFullName} Col:{colName} colType:{colType} Row:{row} value:{value} {e}"); + throw; + } + } + + dynamicInfo.Method.Invoke(dynamicInfo.Obj, new object[] { config }); + + var json = JsonConvert.SerializeObject(config); + + if (isLast) + { + dynamicInfo.Json.AppendLine(json); + } + else + { + dynamicInfo.Json.AppendLine($"{json},"); + } + } + + + /// + /// 从 Excel 文件加载工作表并返回 ExcelWorksheet 对象。 + /// + /// 工作表的名称或文件路径。 + /// 是否将加载的工作表添加到缓存字典中。 + /// 表示 Excel 工作表的 ExcelWorksheet 对象。 + public ExcelWorksheet LoadExcel(string name, bool isAddToDic) + { + if (Worksheets.TryGetValue(name, out var worksheet)) + { + return worksheet; + } + + var workbookWorksheets = ExcelHelper.LoadExcel(name).Workbook.Worksheets; + worksheet = workbookWorksheets[0]; + + if (isAddToDic) + { + Worksheets.TryAdd(name, worksheet); + + foreach (var workbookWorksheet in workbookWorksheets) + { + try + { + var hash = HashCodeHelper.ComputeHash64(workbookWorksheet.Name); + VersionInfo.WorksheetNames.Add(hash); + } + catch (Exception e) + { + Console.WriteLine(e); + } + + Worksheets.TryAdd(workbookWorksheet.Name, workbookWorksheet); + } + } + + Log.Info(name); + return workbookWorksheets[0]; + } + + /// + /// 写入到cs + /// + /// + /// + /// + private void WriteToClass(TableDictionary colInfos, string exportPath, bool isServer) + { + if (colInfos.Count <= 0) + { + return; + } + + var index = 0; + var fileBuilder = new StringBuilder(); + var colNameSet = new HashSet(); + + if (colInfos.Count == 0) + { + return; + } + + var csName = Path.GetFileNameWithoutExtension(colInfos.First().Key)?.Split('_')[0]; + + foreach (var (tableName, cols) in colInfos) + { + if (cols == null || cols.Count == 0) + { + continue; + } + + var excelWorksheet = LoadExcel(tableName, false); + + foreach (var colIndex in cols) + { + var colName = excelWorksheet.GetCellValue(5, colIndex); + + if (colNameSet.Contains(colName)) + { + continue; + } + + colNameSet.Add(colName); + + string colType; + + if (isServer) + { + colType = excelWorksheet.GetCellValue(1, colIndex); + + if (IsNullOrEmpty(colType) || colType == "0") + { + colType = excelWorksheet.GetCellValue(2, colIndex); + } + } + else + { + colType = excelWorksheet.GetCellValue(2, colIndex); + } + + var remarks = excelWorksheet.GetCellValue(4, colIndex); + + // 解决不同平台换行符不一致的问题 + + switch (Environment.OSVersion.Platform) + { + case PlatformID.Win32NT: + case PlatformID.Win32S: + case PlatformID.Win32Windows: + case PlatformID.WinCE: + { + fileBuilder.Append($"\r\n\t\t[ProtoMember({++index})]\r\n"); + break; + } + default: + { + fileBuilder.Append($"\n\t\t[ProtoMember({++index})]\n"); + break; + } + } + + fileBuilder.Append( + IsArray(colType, out var t) + ? $"\t\tpublic {colType} {colName} {{ get; set; }} = Array.Empty<{t}>(); // {remarks}" + : $"\t\tpublic {colType} {colName} {{ get; set; }} // {remarks}"); + } + } + + var template = ExcelTemplate.Template; + + if (fileBuilder.Length > 0) + { + if (!Directory.Exists(exportPath)) + { + FileHelper.CreateDirectory(exportPath); + } + + var content = template.Replace("(namespace)", "Fantasy") + .Replace("(ConfigName)", csName) + .Replace("(Fields)", fileBuilder.ToString()); + File.WriteAllText(Path.Combine(exportPath, $"{csName}.cs"), content); + } + } + + private void SetNewValue(PropertyInfo propertyInfo, object config, string type, string value) + { + if (IsNullOrWhiteSpace(value)) + { + return; + } + + switch (type) + { + case "short": + { + propertyInfo.SetValue(config, Convert.ToInt16(value)); + return; + } + case "ushort": + { + propertyInfo.SetValue(config, Convert.ToUInt16(value)); + return; + } + case "uint": + { + propertyInfo.SetValue(config, Convert.ToUInt32(value)); + return; + } + case "int": + { + propertyInfo.SetValue(config, Convert.ToInt32(value)); + return; + } + case "decimal": + { + propertyInfo.SetValue(config, Convert.ToDecimal(value)); + return; + } + case "string": + { + try + { + propertyInfo.SetValue(config, value); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + + return; + } + case "bool": + { + // 空字符串 + + value = value.ToLower(); + + if (IsNullOrEmpty(value)) + { + propertyInfo.SetValue(config, false); + } + else if (bool.TryParse(value, out bool b)) + { + propertyInfo.SetValue(config, b); + } + else if (int.TryParse(value, out int v)) + { + propertyInfo.SetValue(config, v != 0); + } + else + { + propertyInfo.SetValue(config, false); + } + + return; + } + case "ulong": + { + propertyInfo.SetValue(config, Convert.ToUInt64(value)); + return; + } + case "long": + { + propertyInfo.SetValue(config, Convert.ToInt64(value)); + return; + } + case "double": + { + propertyInfo.SetValue(config, Convert.ToDouble(value)); + return; + } + case "float": + { + propertyInfo.SetValue(config, Convert.ToSingle(value)); + return; + } + case "int32[]": + case "int[]": + { + if (value != "0") + { + propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToInt32(d)).ToArray()); + } + + return; + } + case "uint[]": + { + if (value != "0") + { + propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToUInt32(d)).ToArray()); + } + + return; + } + case "long[]": + { + if (value != "0") + { + propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToInt64(d)).ToArray()); + } + + return; + } + case "double[]": + { + if (value != "0") + { + propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToDouble(d)).ToArray()); + } + + return; + } + case "string[]": + { + if (value == "0") + { + return; + } + + var list = value.Split(",").ToArray(); + + for (var i = 0; i < list.Length; i++) + { + list[i] = list[i].Replace("\"", ""); + } + + propertyInfo.SetValue(config, value.Split(",").ToArray()); + + return; + } + case "float[]": + { + if (value != "0") + { + propertyInfo.SetValue(config, value.Split(",").Select(d => Convert.ToSingle(d)).ToArray()); + } + + return; + } + case "IntDictionaryConfig": + { + if (value.Trim() == "" || value.Trim() == "{}") + { + propertyInfo.SetValue(config, null); + return; + } + + var attr = new IntDictionaryConfig { Dic = JsonConvert.DeserializeObject>(value) }; + + propertyInfo.SetValue(config, attr); + + return; + } + case "StringDictionaryConfig": + { + if (value.Trim() == "" || value.Trim() == "{}") + { + propertyInfo.SetValue(config, null); + return; + } + + var attr = new StringDictionaryConfig + { Dic = JsonConvert.DeserializeObject>(value) }; + + propertyInfo.SetValue(config, attr); + + return; + } + default: + throw new NotSupportedException($"不支持此类型: {type}"); + } + } + + private bool IsArray(string type, out string t) + { + t = null; + var index = type.IndexOf("[]", StringComparison.Ordinal); + + if (index >= 0) + { + t = type.Remove(index, 2); + } + + return index >= 0; + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelHelper.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelHelper.cs new file mode 100644 index 0000000..3ef64d4 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelHelper.cs @@ -0,0 +1,47 @@ +using OfficeOpenXml; + +namespace NBConfigBuilder; + +/// +/// 提供操作 Excel 文件的辅助方法。 +/// +public static class ExcelHelper +{ + /// + /// 加载 Excel 文件并返回 ExcelPackage 实例。 + /// + /// Excel 文件的路径。 + /// ExcelPackage 实例。 + public static ExcelPackage LoadExcel(string name) + { + return new ExcelPackage(name); + } + + /// + /// 获取指定工作表中指定行列位置的单元格值。 + /// + /// Excel 工作表。 + /// 行索引。 + /// 列索引。 + /// 单元格值。 + public static string GetCellValue(this ExcelWorksheet sheet, int row, int column) + { + ExcelRange cell = sheet.Cells[row, column]; + + try + { + if (cell.Value == null) + { + return ""; + } + + string s = cell.GetValue(); + + return s.Trim(); + } + catch (Exception e) + { + throw new Exception($"Rows {row} Columns {column} Content {cell.Text} {e}"); + } + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelTable.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelTable.cs new file mode 100644 index 0000000..20f0558 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelTable.cs @@ -0,0 +1,28 @@ +namespace NBConfigBuilder; + +/// +/// Excel表格类,用于存储表格的名称和列信息。 +/// +public sealed class ExcelTable +{ + /// + /// 表格的名称。 + /// + public readonly string Name; + /// + /// 客户端列信息,使用排序字典存储列名和列索引列表。 + /// + public readonly SortedDictionary> ClientColInfos = new(); + /// + /// 服务器端列信息,使用排序字典存储列名和列索引列表。 + /// + public readonly SortedDictionary> ServerColInfos = new(); + /// + /// 构造函数,初始化Excel表格对象并设置表格名称。 + /// + /// 表格名称。 + public ExcelTable(string name) + { + Name = name; + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelTemplate.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelTemplate.cs new file mode 100644 index 0000000..c3f67ff --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelTemplate.cs @@ -0,0 +1,98 @@ +namespace NBConfigBuilder; + +public static class ExcelTemplate +{ + public static readonly string Template = """ + using System; + using ProtoBuf; + using Fantasy; + using System.Linq; + using System.Reflection; + using System.Collections.Generic; + using System.Collections.Concurrent; + using Fantasy.ConfigTable; + using Fantasy.Serialize; + // ReSharper disable CollectionNeverUpdated.Global + // ReSharper disable UnusedAutoPropertyAccessor.Global + #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + #pragma warning disable CS0169 + #pragma warning disable CS8618 + #pragma warning disable CS8625 + #pragma warning disable CS8603 + + namespace (namespace) + { + [ProtoContract] + public sealed partial class (ConfigName)Data : ASerialize, IConfigTable, IProto + { + [ProtoMember(1)] + public List<(ConfigName)> List { get; set; } = new List<(ConfigName)>(); + #if FANTASY_NET + [ProtoIgnore] + private readonly ConcurrentDictionary _configs = new ConcurrentDictionary(); + #else + [ProtoIgnore] + private readonly Dictionary _configs = new Dictionary(); + #endif + private static (ConfigName)Data _instance = null; + + public static (ConfigName)Data Instance + { + get { return _instance ??= ConfigTableHelper.Load<(ConfigName)Data>(); } + private set => _instance = value; + } + + public (ConfigName) Get(uint id, bool check = true) + { + if (_configs.ContainsKey(id)) + { + return _configs[id]; + } + + if (check) + { + throw new Exception($"(ConfigName) not find {id} Id"); + } + + return null; + } + public bool TryGet(uint id, out (ConfigName) config) + { + config = null; + + if (!_configs.ContainsKey(id)) + { + return false; + } + + config = _configs[id]; + return true; + } + public override void AfterDeserialization() + { + foreach (var config in List) + { + #if FANTASY_NET + _configs.TryAdd(config.Id, config); + #else + _configs.Add(config.Id, config); + #endif + config.AfterDeserialization(); + } + + EndInit(); + } + + public override void Dispose() + { + Instance = null; + } + } + + [ProtoContract] + public sealed partial class (ConfigName) : ASerialize, IProto + {(Fields) + } + } + """; +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelWorksheets.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelWorksheets.cs new file mode 100644 index 0000000..cf4277a --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExcelWorksheets.cs @@ -0,0 +1,22 @@ +using OfficeOpenXml; + +namespace NBConfigBuilder; + +public sealed class ExcelWorksheets(ExcelExporter excelExporter) +{ + public bool TryGetValue(string worksheetName, out ExcelWorksheet excelWorksheet) + { + if (excelExporter.Worksheets.TryGetValue(worksheetName, out excelWorksheet)) + { + return true; + } + + var computeHash64 = HashCodeHelper.ComputeHash64(worksheetName); + if (!excelExporter.VersionInfo.WorksheetNames.Contains(computeHash64)) + { + Log.Info($"{worksheetName} is not exist!"); + } + + return false; + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExportInfo.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExportInfo.cs new file mode 100644 index 0000000..d9d7b91 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExportInfo.cs @@ -0,0 +1,17 @@ +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. +namespace NBConfigBuilder; + +/// +/// 导出信息类,用于存储导出操作的名称和文件信息。 +/// +public class ExportInfo +{ + /// + /// 导出操作的名称。 + /// + public string Name; + /// + /// 导出操作生成的文件信息。 + /// + public FileInfo FileInfo; +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExportType.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExportType.cs new file mode 100644 index 0000000..348a9e6 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/ExportType.cs @@ -0,0 +1,24 @@ +namespace NBConfigBuilder; + +/// +/// 导出类型枚举,用于标识不同类型的导出操作。 +/// +public enum ExportType +{ + /// + /// 无导出类型。 + /// + None = 0, + /// + /// 所有数据的增量导出Excel类型。 + /// + AllExcelIncrement = 1, + /// + /// 所有数据的全量导出Excel类型。 + /// + AllExcel = 2, + /// + /// 导出类型枚举的最大值,一定要放在最后。 + /// + Max, +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Exporter/VersionInfo.cs b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/VersionInfo.cs new file mode 100644 index 0000000..c04cd9f --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Exporter/VersionInfo.cs @@ -0,0 +1,7 @@ +namespace NBConfigBuilder; + +public class VersionInfo +{ + public SortedSet WorksheetNames = []; + public SortedDictionary Tables = new(); +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Form1.Designer.cs b/Tools/ConfigBuilder/NBConfigBuilder/Form1.Designer.cs new file mode 100644 index 0000000..b1a50ee --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Form1.Designer.cs @@ -0,0 +1,265 @@ +namespace NBConfigBuilder +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + textBoxExcelPath = new TextBox(); + buttonSelectExcelPath = new Button(); + label1 = new Label(); + textBoxClientGenPath = new TextBox(); + buttonSelectClientPath = new Button(); + checkBoxGenClient = new CheckBox(); + label2 = new Label(); + textBoxServerGenPath = new TextBox(); + label3 = new Label(); + checkBoxGenServer = new CheckBox(); + buttonSelectServerPath = new Button(); + buttonRun = new Button(); + textBoxClientGenJsonPath = new TextBox(); + label4 = new Label(); + buttonSelectClientJsonPath = new Button(); + textBoxServerGenJsonPath = new TextBox(); + label5 = new Label(); + buttonSelectServerJsonPath = new Button(); + SuspendLayout(); + // + // textBoxExcelPath + // + textBoxExcelPath.Location = new Point(84, 13); + textBoxExcelPath.Name = "textBoxExcelPath"; + textBoxExcelPath.Size = new Size(322, 23); + textBoxExcelPath.TabIndex = 0; + // + // buttonSelectExcelPath + // + buttonSelectExcelPath.Location = new Point(412, 13); + buttonSelectExcelPath.Name = "buttonSelectExcelPath"; + buttonSelectExcelPath.Size = new Size(75, 23); + buttonSelectExcelPath.TabIndex = 1; + buttonSelectExcelPath.Text = "选择"; + buttonSelectExcelPath.UseVisualStyleBackColor = true; + buttonSelectExcelPath.Click += buttonSelectExcelPath_Click; + // + // label1 + // + label1.AutoSize = true; + label1.Location = new Point(7, 17); + label1.Name = "label1"; + label1.Size = new Size(68, 17); + label1.TabIndex = 2; + label1.Text = "配置表路径"; + // + // textBoxClientGenPath + // + textBoxClientGenPath.Location = new Point(84, 42); + textBoxClientGenPath.Name = "textBoxClientGenPath"; + textBoxClientGenPath.Size = new Size(318, 23); + textBoxClientGenPath.TabIndex = 3; + // + // buttonSelectClientPath + // + buttonSelectClientPath.Location = new Point(412, 42); + buttonSelectClientPath.Name = "buttonSelectClientPath"; + buttonSelectClientPath.Size = new Size(75, 23); + buttonSelectClientPath.TabIndex = 4; + buttonSelectClientPath.Text = "选择"; + buttonSelectClientPath.UseVisualStyleBackColor = true; + buttonSelectClientPath.Click += buttonSelectClientPath_Click; + // + // checkBoxGenClient + // + checkBoxGenClient.AutoSize = true; + checkBoxGenClient.Checked = true; + checkBoxGenClient.CheckState = CheckState.Checked; + checkBoxGenClient.Location = new Point(495, 44); + checkBoxGenClient.Name = "checkBoxGenClient"; + checkBoxGenClient.Size = new Size(87, 21); + checkBoxGenClient.TabIndex = 5; + checkBoxGenClient.Text = "生产服务端"; + checkBoxGenClient.UseVisualStyleBackColor = true; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new Point(7, 47); + label2.Name = "label2"; + label2.Size = new Size(68, 17); + label2.TabIndex = 6; + label2.Text = "客户端代码"; + // + // textBoxServerGenPath + // + textBoxServerGenPath.Location = new Point(84, 100); + textBoxServerGenPath.Name = "textBoxServerGenPath"; + textBoxServerGenPath.Size = new Size(318, 23); + textBoxServerGenPath.TabIndex = 7; + // + // label3 + // + label3.AutoSize = true; + label3.Location = new Point(7, 103); + label3.Name = "label3"; + label3.Size = new Size(68, 17); + label3.TabIndex = 8; + label3.Text = "服务端代码"; + // + // checkBoxGenServer + // + checkBoxGenServer.AutoSize = true; + checkBoxGenServer.Checked = true; + checkBoxGenServer.CheckState = CheckState.Checked; + checkBoxGenServer.Location = new Point(495, 16); + checkBoxGenServer.Name = "checkBoxGenServer"; + checkBoxGenServer.Size = new Size(87, 21); + checkBoxGenServer.TabIndex = 9; + checkBoxGenServer.Text = "生产客户端"; + checkBoxGenServer.UseVisualStyleBackColor = true; + // + // buttonSelectServerPath + // + buttonSelectServerPath.Location = new Point(412, 100); + buttonSelectServerPath.Name = "buttonSelectServerPath"; + buttonSelectServerPath.Size = new Size(75, 23); + buttonSelectServerPath.TabIndex = 10; + buttonSelectServerPath.Text = "选择"; + buttonSelectServerPath.UseVisualStyleBackColor = true; + buttonSelectServerPath.Click += buttonSelectServerPath_Click; + // + // buttonRun + // + buttonRun.Location = new Point(495, 71); + buttonRun.Name = "buttonRun"; + buttonRun.Size = new Size(80, 81); + buttonRun.TabIndex = 12; + buttonRun.Text = "执行"; + buttonRun.UseVisualStyleBackColor = true; + buttonRun.Click += buttonRun_Click; + // + // textBoxClientGenJsonPath + // + textBoxClientGenJsonPath.Location = new Point(84, 71); + textBoxClientGenJsonPath.Name = "textBoxClientGenJsonPath"; + textBoxClientGenJsonPath.Size = new Size(318, 23); + textBoxClientGenJsonPath.TabIndex = 13; + // + // label4 + // + label4.AutoSize = true; + label4.Location = new Point(7, 77); + label4.Name = "label4"; + label4.Size = new Size(68, 17); + label4.TabIndex = 14; + label4.Text = "客户端配置"; + // + // buttonSelectClientJsonPath + // + buttonSelectClientJsonPath.Location = new Point(412, 71); + buttonSelectClientJsonPath.Name = "buttonSelectClientJsonPath"; + buttonSelectClientJsonPath.Size = new Size(75, 23); + buttonSelectClientJsonPath.TabIndex = 15; + buttonSelectClientJsonPath.Text = "选择"; + buttonSelectClientJsonPath.UseVisualStyleBackColor = true; + buttonSelectClientJsonPath.Click += buttonSelectClientJsonPath_Click; + // + // textBoxServerGenJsonPath + // + textBoxServerGenJsonPath.Location = new Point(85, 129); + textBoxServerGenJsonPath.Name = "textBoxServerGenJsonPath"; + textBoxServerGenJsonPath.Size = new Size(317, 23); + textBoxServerGenJsonPath.TabIndex = 16; + // + // label5 + // + label5.AutoSize = true; + label5.Location = new Point(7, 132); + label5.Name = "label5"; + label5.Size = new Size(68, 17); + label5.TabIndex = 17; + label5.Text = "服务端配置"; + // + // buttonSelectServerJsonPath + // + buttonSelectServerJsonPath.Location = new Point(412, 129); + buttonSelectServerJsonPath.Name = "buttonSelectServerJsonPath"; + buttonSelectServerJsonPath.Size = new Size(75, 23); + buttonSelectServerJsonPath.TabIndex = 18; + buttonSelectServerJsonPath.Text = "选择"; + buttonSelectServerJsonPath.UseVisualStyleBackColor = true; + buttonSelectServerJsonPath.Click += buttonSelectServerJsonPath_Click; + // + // Form1 + // + AutoScaleDimensions = new SizeF(7F, 17F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(585, 161); + Controls.Add(buttonSelectServerJsonPath); + Controls.Add(label5); + Controls.Add(textBoxServerGenJsonPath); + Controls.Add(buttonSelectClientJsonPath); + Controls.Add(label4); + Controls.Add(textBoxClientGenJsonPath); + Controls.Add(buttonRun); + Controls.Add(buttonSelectServerPath); + Controls.Add(checkBoxGenServer); + Controls.Add(label3); + Controls.Add(textBoxServerGenPath); + Controls.Add(label2); + Controls.Add(checkBoxGenClient); + Controls.Add(buttonSelectClientPath); + Controls.Add(textBoxClientGenPath); + Controls.Add(label1); + Controls.Add(buttonSelectExcelPath); + Controls.Add(textBoxExcelPath); + Name = "Form1"; + Text = "配置生成导出"; + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private TextBox textBoxExcelPath; + private Button buttonSelectExcelPath; + private Label label1; + private TextBox textBoxClientGenPath; + private Button buttonSelectClientPath; + private CheckBox checkBoxGenClient; + private Label label2; + private TextBox textBoxServerGenPath; + private Label label3; + private CheckBox checkBoxGenServer; + private Button buttonSelectServerPath; + private Button buttonRun; + private TextBox textBoxClientGenJsonPath; + private Label label4; + private Button buttonSelectClientJsonPath; + private TextBox textBoxServerGenJsonPath; + private Label label5; + private Button buttonSelectServerJsonPath; + } +} diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Form1.cs b/Tools/ConfigBuilder/NBConfigBuilder/Form1.cs new file mode 100644 index 0000000..c41e3c9 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Form1.cs @@ -0,0 +1,141 @@ +using System.Text.Json; + +namespace NBConfigBuilder +{ + public partial class Form1 : Form + { + // 配置文件路径 + private readonly string configPath = + Path.Combine(Path.GetDirectoryName(Application.ExecutablePath) ?? string.Empty, "config.json"); + + public Form1() + { + InitializeComponent(); + // 设置窗口大小不可变 + this.FormBorderStyle = FormBorderStyle.FixedSingle; + this.MaximizeBox = false; + + // 加载保存的配置 + LoadConfig(); + } + + private void buttonRun_Click(object sender, EventArgs e) + { + // 保存当前配置 + SaveConfig(); + new ExcelExporter(ExportType.AllExcel).Run(); + } + + private void buttonSelectExcelPath_Click(object sender, EventArgs e) + { + using (FolderBrowserDialog folderDlg = new FolderBrowserDialog()) + { + folderDlg.Description = "请选择配置表路径"; + if (folderDlg.ShowDialog() == DialogResult.OK) + { + textBoxExcelPath.Text = folderDlg.SelectedPath; + } + } + } + + private void buttonSelectClientJsonPath_Click(object sender, EventArgs e) + { + using (FolderBrowserDialog folderDlg = new FolderBrowserDialog()) + { + folderDlg.Description = @"选择客户端json保存目录"; + if (folderDlg.ShowDialog() == DialogResult.OK) + { + textBoxClientGenJsonPath.Text = folderDlg.SelectedPath; + } + } + } + + private void buttonSelectServerJsonPath_Click(object sender, EventArgs e) + { + using (FolderBrowserDialog folderDlg = new FolderBrowserDialog()) + { + folderDlg.Description = @"选择服务端json保存目录"; + if (folderDlg.ShowDialog() == DialogResult.OK) + { + textBoxServerGenJsonPath.Text = folderDlg.SelectedPath; + } + } + } + + private void buttonSelectClientPath_Click(object sender, EventArgs e) + { + using (FolderBrowserDialog folderDlg = new FolderBrowserDialog()) + { + folderDlg.Description = @"选择客户端代码保存目录"; + if (folderDlg.ShowDialog() == DialogResult.OK) + { + textBoxClientGenPath.Text = folderDlg.SelectedPath; + } + } + } + + private void buttonSelectServerPath_Click(object sender, EventArgs e) + { + using (FolderBrowserDialog folderDlg = new FolderBrowserDialog()) + { + folderDlg.Description = @"选择服务端代码保存目录"; + if (folderDlg.ShowDialog() == DialogResult.OK) + { + textBoxServerGenPath.Text = folderDlg.SelectedPath; + } + } + } + + // 添加保存和加载配置的方法 + private void SaveConfig() + { + var config = new AppConfig + { + ExcelPath = textBoxExcelPath.Text, + ClientPath = textBoxClientGenPath.Text, + ClientJsonPath = textBoxClientGenJsonPath.Text, + ServerPath = textBoxServerGenPath.Text, + ServerJsonPath = textBoxServerGenJsonPath.Text, + GenClient = checkBoxGenClient.Checked, + GenServer = checkBoxGenServer.Checked, + }; + App.Config = config; + try + { + string json = JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(configPath, json); + } + catch (Exception ex) + { + MessageBox.Show($"保存配置失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + private void LoadConfig() + { + if (!File.Exists(configPath)) return; + + try + { + string json = File.ReadAllText(configPath); + var config = JsonSerializer.Deserialize(json); + + if (config != null) + { + App.Config = config; + textBoxExcelPath.Text = config.ExcelPath ?? ""; + textBoxClientGenPath.Text = config.ClientPath ?? ""; + textBoxServerGenPath.Text = config.ServerPath ?? ""; + checkBoxGenClient.Checked = config.GenClient; + checkBoxGenServer.Checked = config.GenServer; + textBoxClientGenJsonPath.Text = config.ClientJsonPath ?? ""; + textBoxServerGenJsonPath.Text = config.ServerJsonPath ?? ""; + } + } + catch (Exception ex) + { + MessageBox.Show($"加载配置失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Form1.resx b/Tools/ConfigBuilder/NBConfigBuilder/Form1.resx new file mode 100644 index 0000000..8b2ff64 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Log.cs b/Tools/ConfigBuilder/NBConfigBuilder/Log.cs new file mode 100644 index 0000000..6da4ad0 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Log.cs @@ -0,0 +1,97 @@ +using NLog; +using System; +using System.Diagnostics; + +namespace NBConfigBuilder +{ + public static class Log + { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + static Log() + { + } + + /// + /// 记录信息级别的日志消息。 + /// + /// 日志消息。 + public static void Info(string msg) + { + logger.Info(msg); + } + + /// + /// 记录错误级别的日志消息,并附带调用栈信息。 + /// + /// 日志消息。 + public static void Error(string msg) + { + var st = new StackTrace(1, true); + logger.Error($"{msg}\n{st}"); + } + + /// + /// 记录异常的错误级别的日志消息,并附带调用栈信息。 + /// + /// 异常对象。 + public static void Error(Exception e) + { + if (e.Data.Contains("StackTrace")) + { + logger.Error($"{e.Data["StackTrace"]}\n{e}"); + return; + } + + var str = e.ToString(); + logger.Error(str); + } + + /// + /// 记录信息级别的格式化日志消息。 + /// + /// 日志消息模板。 + /// 格式化参数。 + public static void Info(string message, params object[] args) + { + logger.Info(message, args); + } + + /// + /// 记录调试级别的日志消息。 + /// + /// 日志消息。 + public static void Debug(string msg) + { + logger.Debug(msg); + } + + /// + /// 记录警告级别的日志消息。 + /// + /// 日志消息。 + public static void Warn(string msg) + { + logger.Warn(msg); + } + + /// + /// 记录严重错误级别的日志消息。 + /// + /// 日志消息。 + public static void Fatal(string msg) + { + logger.Fatal(msg); + } + + /// + /// 记录异常的错误级别的日志消息。 + /// + /// 异常对象。 + /// 附加消息。 + public static void Error(Exception e, string message) + { + logger.Error(e, message); + } + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/NBConfigBuilder.csproj b/Tools/ConfigBuilder/NBConfigBuilder/NBConfigBuilder.csproj new file mode 100644 index 0000000..dea1755 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/NBConfigBuilder.csproj @@ -0,0 +1,25 @@ + + + + WinExe + net8.0-windows + disable + true + enable + true + + + + + + + + + + + + + Always + + + \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/NBConfigBuilder.csproj.user b/Tools/ConfigBuilder/NBConfigBuilder/NBConfigBuilder.csproj.user new file mode 100644 index 0000000..7814ea2 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/NBConfigBuilder.csproj.user @@ -0,0 +1,8 @@ + + + + + Form + + + diff --git a/Tools/ConfigBuilder/NBConfigBuilder/NLog.config b/Tools/ConfigBuilder/NBConfigBuilder/NLog.config new file mode 100644 index 0000000..4bd1557 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/NLog.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Program.cs b/Tools/ConfigBuilder/NBConfigBuilder/Program.cs new file mode 100644 index 0000000..a3718e5 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Program.cs @@ -0,0 +1,25 @@ +using NLog; + +namespace NBConfigBuilder +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // 配置NLog + var logger = LogManager.GetCurrentClassLogger(); + logger.Info("Application started"); + + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); + + logger.Info("Application closing"); + } + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Utils/FileHelper.cs b/Tools/ConfigBuilder/NBConfigBuilder/Utils/FileHelper.cs new file mode 100644 index 0000000..db846ad --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Utils/FileHelper.cs @@ -0,0 +1,182 @@ +using System.Text; + +namespace NBConfigBuilder; + +/// +/// 文件操作助手类,提供了各种文件操作方法。 +/// +public static partial class FileHelper +{ + /// + /// 获取相对路径的完整路径。 + /// + /// 相对路径。 + /// 完整路径。 + public static string GetFullPath(string relativePath) + { + return Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePath)); + } + + /// + /// 获取相对路径的完整路径。 + /// + /// 相对于指定的目录的相对路径。 + /// 指定的目录 + /// 完整路径。 + public static string GetFullPath(string relativePath, string srcDir) + { + return Path.GetFullPath(Path.Combine(srcDir, relativePath)); + } + + /// + /// 获取相对路径的的文本信息。 + /// + /// + /// + public static async Task GetTextByRelativePath(string relativePath) + { + var fullPath = GetFullPath(relativePath); + return await File.ReadAllTextAsync(fullPath, Encoding.UTF8); + } + + /// + /// 获取绝对路径的的文本信息。 + /// + /// + /// + public static async Task GetText(string fullPath) + { + return await File.ReadAllTextAsync(fullPath, Encoding.UTF8); + } + + /// + /// 根据文件夹路径创建文件夹,如果文件夹不存在会自动创建文件夹。 + /// + /// + public static void CreateDirectory(string directoryPath) + { + if (directoryPath.LastIndexOf('/') != directoryPath.Length - 1) + { + directoryPath += "/"; + } + + var directoriesByFilePath = GetDirectoriesByFilePath(directoryPath); + + foreach (var dir in directoriesByFilePath) + { + if (Directory.Exists(dir)) + { + continue; + } + + Directory.CreateDirectory(dir); + } + } + + /// + /// 将文件复制到目标路径,如果目标目录不存在会自动创建目录。 + /// + /// 源文件路径。 + /// 目标文件路径。 + /// 是否覆盖已存在的目标文件。 + public static void Copy(string sourceFile, string destinationFile, bool overwrite) + { + CreateDirectory(destinationFile); + File.Copy(sourceFile, destinationFile, overwrite); + } + + /// + /// 获取文件路径内的所有文件夹路径。 + /// + /// 文件路径。 + /// 文件夹路径列表。 + public static IEnumerable GetDirectoriesByFilePath(string filePath) + { + var dir = ""; + var fileDirectories = filePath.Split('/'); + + for (var i = 0; i < fileDirectories.Length - 1; i++) + { + dir = $"{dir}{fileDirectories[i]}/"; + yield return dir; + } + + if (fileDirectories.Length == 1) + { + yield return filePath; + } + } + + /// + /// 将文件夹内的所有内容复制到目标位置。 + /// + /// 源文件夹路径。 + /// 目标文件夹路径。 + /// 是否覆盖已存在的文件。 + public static void CopyDirectory(string sourceDirectory, string destinationDirectory, bool overwrite) + { + // 创建目标文件夹 + + if (!Directory.Exists(destinationDirectory)) + { + Directory.CreateDirectory(destinationDirectory); + } + + // 获取当前文件夹中的所有文件 + + var files = Directory.GetFiles(sourceDirectory); + + // 拷贝文件到目标文件夹 + + foreach (var file in files) + { + var fileName = Path.GetFileName(file); + var destinationPath = Path.Combine(destinationDirectory, fileName); + File.Copy(file, destinationPath, overwrite); + } + + // 获取源文件夹中的所有子文件夹 + + var directories = Directory.GetDirectories(sourceDirectory); + + // 递归方式拷贝文件夹 + + foreach (var directory in directories) + { + var directoryName = Path.GetFileName(directory); + var destinationPath = Path.Combine(destinationDirectory, directoryName); + CopyDirectory(directory, destinationPath, overwrite); + } + } + + /// + /// 获取目录下的所有文件 + /// + /// 文件夹路径。 + /// 需要查找的文件通配符 + /// 查找的类型 + /// + public static string[] GetDirectoryFile(string folderPath, string searchPattern, SearchOption searchOption) + { + return Directory.GetFiles(folderPath, searchPattern, searchOption); + } + + /// + /// 清空文件夹内的所有文件。 + /// + /// 文件夹路径。 + public static void ClearDirectoryFile(string folderPath) + { + if (!Directory.Exists(folderPath)) + { + return; + } + + var files = Directory.GetFiles(folderPath); + + foreach (var file in files) + { + File.Delete(file); + } + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Utils/HashCodeHelper.cs b/Tools/ConfigBuilder/NBConfigBuilder/Utils/HashCodeHelper.cs new file mode 100644 index 0000000..92094c7 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Utils/HashCodeHelper.cs @@ -0,0 +1,228 @@ +using System.Security.Cryptography; +using System.Text; + +namespace NBConfigBuilder; + +/// +/// HashCode算法帮助类 +/// +public static partial class HashCodeHelper +{ + private static readonly SHA256 Sha256Hash = SHA256.Create(); + + /// + /// 计算两个字符串的HashCode + /// + /// + /// + /// + public static int GetHashCode(string a, string b) + { + var hash = 17; + hash = hash * 31 + a.GetHashCode(); + hash = hash * 31 + b.GetHashCode(); + return hash; + } +#if FANTASY_NET || !FANTASY_WEBGL + /// + /// 使用bkdr算法生成一个long的值 + /// + /// + /// + public static unsafe long GetBKDRHashCode(string str) + { + ulong hash = 0; + // 如果要修改这个种子、建议选择一个质数来做种子 + const uint seed = 13131; // 31 131 1313 13131 131313 etc.. + fixed (char* p = str) + { + for (var i = 0; i < str.Length; i++) + { + var c = p[i]; + var high = (byte)(c >> 8); + var low = (byte)(c & byte.MaxValue); + hash = hash * seed + high; + hash = hash * seed + low; + } + } + + return (long)hash; + } + + /// + /// 使用MurmurHash3算法生成一个uint的值 + /// + /// + /// + public static unsafe uint MurmurHash3(string str) + { + const uint seed = 0xc58f1a7b; + uint hash = seed; + uint c1 = 0xcc9e2d51; + uint c2 = 0x1b873593; + + fixed (char* p = str) + { + var current = p; + + for (var i = 0; i < str.Length; i++) + { + var k1 = (uint)(*current); + k1 *= c1; + k1 = (k1 << 15) | (k1 >> (32 - 15)); + k1 *= c2; + + hash ^= k1; + hash = (hash << 13) | (hash >> (32 - 13)); + hash = hash * 5 + 0xe6546b64; + + current++; + } + } + + hash ^= (uint)str.Length; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + return hash; + } + + /// + /// 使用MurmurHash3算法生成一个long的值 + /// + /// + /// + public static unsafe long ComputeHash64(string str) + { + const ulong seed = 0xc58f1a7bc58f1a7bUL; // 64-bit seed + var hash = seed; + var c1 = 0x87c37b91114253d5UL; + var c2 = 0x4cf5ad432745937fUL; + + fixed (char* p = str) + { + var current = p; + + for (var i = 0; i < str.Length; i++) + { + var k1 = (ulong)(*current); + k1 *= c1; + k1 = (k1 << 31) | (k1 >> (64 - 31)); + k1 *= c2; + + hash ^= k1; + hash = (hash << 27) | (hash >> (64 - 27)); + hash = hash * 5 + 0x52dce729; + + current++; + } + } + + hash ^= (ulong)str.Length; + hash ^= hash >> 33; + hash *= 0xff51afd7ed558ccdUL; + hash ^= hash >> 33; + hash *= 0xc4ceb9fe1a85ec53UL; + hash ^= hash >> 33; + return (long)hash; + } +#endif +#if FANTASY_WEBGL + /// + /// 使用bkdr算法生成一个long的值 + /// + /// + /// + public static long GetBKDRHashCode(string str) + { + long hash = 0; + // 如果要修改这个种子、建议选择一个质数来做种子 + const uint seed = 13131; // 31 131 1313 13131 131313 etc.. + foreach (var c in str) + { + var high = (byte)(c >> 8); + var low = (byte)(c & byte.MaxValue); + hash = hash * seed + high; + hash = hash * seed + low; + } + return hash; + } + /// + /// 使用MurmurHash3算法生成一个uint的值 + /// + /// + /// + public static uint MurmurHash3(string str) + { + const uint seed = 0xc58f1a7b; + uint hash = seed; + uint c1 = 0xcc9e2d51; + uint c2 = 0x1b873593; + + foreach (var t in str) + { + var k1 = (uint)(t); + k1 *= c1; + k1 = (k1 << 15) | (k1 >> (32 - 15)); + k1 *= c2; + + hash ^= k1; + hash = (hash << 13) | (hash >> (32 - 13)); + hash = hash * 5 + 0xe6546b64; + } + + hash ^= (uint)str.Length; + hash ^= hash >> 16; + hash *= 0x85ebca6b; + hash ^= hash >> 13; + hash *= 0xc2b2ae35; + hash ^= hash >> 16; + return hash; + } + + /// + /// 使用MurmurHash3算法生成一个long的值 + /// + /// + /// + public static long ComputeHash64(string str) + { + const ulong seed = 0xc58f1a7bc58f1a7bUL; // 64-bit seed + var hash = seed; + var c1 = 0x87c37b91114253d5UL; + var c2 = 0x4cf5ad432745937fUL; + + foreach (var t in str) + { + var k1 = (ulong)(t); + k1 *= c1; + k1 = (k1 << 31) | (k1 >> (64 - 31)); + k1 *= c2; + + hash ^= k1; + hash = (hash << 27) | (hash >> (64 - 27)); + hash = hash * 5 + 0x52dce729; + } + + hash ^= (ulong)str.Length; + hash ^= hash >> 33; + hash *= 0xff51afd7ed558ccdUL; + hash ^= hash >> 33; + hash *= 0xc4ceb9fe1a85ec53UL; + hash ^= hash >> 33; + return (long)hash; + } +#endif + /// + /// 根据字符串计算一个Hash值 + /// + /// + /// + public static int ComputeSha256HashAsInt(string rawData) + { + var bytes = Sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData)); + return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]; + } +} \ No newline at end of file diff --git a/Tools/ConfigBuilder/NBConfigBuilder/Utils/TimeHelper.cs b/Tools/ConfigBuilder/NBConfigBuilder/Utils/TimeHelper.cs new file mode 100644 index 0000000..8630260 --- /dev/null +++ b/Tools/ConfigBuilder/NBConfigBuilder/Utils/TimeHelper.cs @@ -0,0 +1,65 @@ +namespace NBConfigBuilder; + +/// +/// 提供与时间相关的帮助方法。 +/// +public static partial class TimeHelper +{ + /// + /// 一小时的毫秒值。 + /// + public const long Hour = 3600000; + + /// + /// 一分钟的毫秒值。 + /// + public const long Minute = 60000; + + /// + /// 一天的毫秒值。 + /// + public const long OneDay = 86400000; + + // 1970年1月1日的Ticks + private const long Epoch = 621355968000000000L; + /// + /// 获取当前时间的毫秒数,从1970年1月1日开始计算。 + /// + public static long Now => (DateTime.UtcNow.Ticks - Epoch) / 10000; + + /// + /// 根据时间获取时间戳 + /// + public static long Transition(DateTime dateTime) + { + return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000; + } + + /// + /// 根据时间获取 时间戳 + /// + public static long TransitionToSeconds(DateTime dateTime) + { + return (dateTime.ToUniversalTime().Ticks - Epoch) / 10000000; + } + + /// + /// 将毫秒数转换为日期时间。 + /// + /// 要转换的毫秒数。 + /// 转换后的日期时间。 + public static DateTime Transition(this long timeStamp) + { + return new DateTime(Epoch + timeStamp * 10000, DateTimeKind.Utc).ToUniversalTime(); + } + + /// + /// 将毫秒数转换为本地时间的日期时间。 + /// + /// 要转换的毫秒数。 + /// 转换后的本地时间的日期时间。 + public static DateTime TransitionLocal(this long timeStamp) + { + return new DateTime(Epoch + timeStamp * 10000, DateTimeKind.Utc).ToLocalTime(); + } +} \ No newline at end of file diff --git a/Tools/SourceCode/Fantasy.Tools.ConfigTable/Exporter/ExcelExporter.cs b/Tools/SourceCode/Fantasy.Tools.ConfigTable/Exporter/ExcelExporter.cs index 00aa813..f11f6b0 100644 --- a/Tools/SourceCode/Fantasy.Tools.ConfigTable/Exporter/ExcelExporter.cs +++ b/Tools/SourceCode/Fantasy.Tools.ConfigTable/Exporter/ExcelExporter.cs @@ -664,6 +664,7 @@ public sealed class ExcelExporter Task.WaitAll(exportToBinaryTasks.ToArray()); } + private void GenerateBinary(string fileInfoFullName, ExcelWorksheet excelWorksheet, DynamicConfigDataType dynamicInfo, List cols, string id, int row, bool isLast, bool isServer) { if (cols == null || IsNullOrEmpty(id) || cols.Count <= 0 || dynamicInfo?.ConfigType == null) @@ -718,6 +719,7 @@ public sealed class ExcelExporter dynamicInfo.Json.AppendLine($"{json},"); } } + /// /// 从 Excel 文件加载工作表并返回 ExcelWorksheet 对象。 ///