k^Uh-^=
zVsQZj#70qe+DcSzwi`t`Rt&_6fqDelS)zg%l*Hn@S(G;!-N6+`mn@=YB_RG6A`8cK
zmC+Rtk<#1&ojZ^R8l404_%P6C&f>jzVmm4ysoU`ljI2e-60+*vYotw4Fadi$_^7Q3
zGjPdU(p~bM87}#fV|(#-^%5XzeaUkbWe0}4N)tnb=EM|(g(1f=od{Rm0Ut<|mm@+8
z`NCpwyH&W!&cwx2r75Dk9@q=C@~3*W))UZr;9W6Ti%@$nLjT1zOhS;njRHI=6O%M6
zmNzI^mnXnW91>;BGq4PUgA~auMhr|916z!Wl(gBYZ$Wqs=Q~B58hU8zbRTewM<$@~aN{o&45rp=Bd!C$!s6Op2~xqY=KlD97i^
zZ@9}|Er{)Q2ijU7J7;zqIWy7(+H7(!i6axPV*jT=%EspFRvp8>eG;YC?mbD4ljK85w}Ksf|wuiDIEB^)@9H1qeEo8C364K6sEdkUswgyu^OlLes*
z?w~!RU@`Mq#NbA=D*&yN6I}sp3EG*w)>mLUXh}rA7IFuzOCb*u5MxG`#l8{2>juZhwgXn&rQCKS
zh|cYUJ*h50j_gT`tq?W&Ss%H|LIq{-LK(4lp+iNsYzn2)9l_O`6w8Krv{T=ik9na?
zXTl%<$oHx%xLoPK##a?u4@$81V&ElPJ+iLOS7105H;R>1lS=~GHLDGICVu>`OR&Gf
z&xUl6b*o0oL{@~Fxq}p$27Y8~MC<(4_wN=I%h1iVa936b7g`769rEdqFHZfv7`(e4
z_YtH{cAUFtft_L=NEU7)HKG#gv1igF_aE}B;R9GxXcV?uZ#Wqmfg|9+52@|qslIna
z<=%%VPuE|&LCy*5l0oJWVjky`-vEPTS{dC7D3%BLfUD~%t0FCT!cK_=
za;GRml%&A#B6^XBu$l8QAK}0bl8wcNPOh9Y4T%oVE&5by}8(iQ*cj#
z7O*}3Yt&n@Y{5zdvK{hYME}b)6gX9dv}*>>}RP0LHp0O=0dy$uLn
z;0%zG;eKPAV{1hJ$Q~H^)rm`f6XCi4vQB96fD+Ff1^@m}bkYJXI{xs>H1kIAFdQAr
zSEIcrTsV=27em=$IAJv>x^wLOf`s8hYl$f1whE;Fc>&A@%=`Yt=(%(^hyX4;R9S}B|P;zwQpk8tk@pqt!6XA!z|0;jGzNGP{YTbe;
zww{LsqqGjo2EO>hMGguLii;B@Tt_;L9a$kdA>pt3uh?OrUKPQ%>hNSVFOKDtc+EHl>7P?9ruRtyVCx#VH207~^RzK6Z4SY|^FR1YKf57FA>hBiTY
zA4l76%n`I)oN-_u%9A+o4T#rzrj_9YpjtL1_6Mm_2a_55=+;bktD5
zcN6zL&0tKiJPdSj6@Odt)8F&e7`U6iFIbQF0?ZX$jjC6K6uTM8ij;wb8has92~xd~
z($s_j#6V*`{JcuhAIi|)E`)2j+6jUCh>I6E7ms|22AzdLhhGe2tHW|hJ_nvec{Scq
zY{Vv9hOy&ZL{M366>&a`a*QZrdy-?g3?3s!g`-pmXVFX>ZLbZ7&!e`(#E$%&?P(6l
zbB0zegTDf;YIJPh25Z$ZaGv{jWMhq-rTb~MUL%wA^c7VMKR!WAr&z+X=;=kCBdAHM
z*5sfTX1>Cktf3~6Fms361Xreeur3h=<@T3WUwhwll0?HC!tp^UBJ8nmmH*@cV01pr
zwI}>doKf%<5nim%5OaE@$s`k15M-n`Rbumlhe9kE4qeg8
z7wxmWV%LvdH^mZy@c6*Agu`#hREdq2SE0O*CL_w9z%TL->3p{hoAP~4sNm*YX!#l6
z5Iy1B|Ewt31c4(c4+rZdGzPTvbYXD^DqCAnnbj?JE=xm(1yC%b?`TtWKGa=lCe(aL6GiyfYE*W{*37^ISep7nM`)FU=tUCBg
z5HT*v1?PFU{F>b3@7B-K!}kFmedY819^)(r#^vSnRByJKsis{x88$ka`iz3J9hNAt
zB0cMZD6b%yr*+1YEmkZYE`IQ|JPs~%3FS*W@h>2b`}Udi{J9OfNr-_dWRjmlbJZj#
z^6Cdrt(xR#aAl0^5ydhc75(DJz${NiM}A00YT2U)p3iu1F~&`7c8`FMh>LRqMRb;oDy1}111ei&jy_7NR4eW)Wh_wa88lv{KoMaSlFvtNG#TyyuG0ZuPe;s#VQWcfP9jrN+
zrXBeh0x6dqOY>Eokf0Madnu>dDgL13AAz(NRGJ(nCBFrxcXvHmw?0C5*~yT!HbS}dm4DxVl(DMnSinXVL;CB@)3UjN_L?V0lrr?cIn8cJwZAb
z;Uk`JBAkB^Um{IGXnh1DB6cN~Xsd8!%Q5H?@k=(t*g
zrorG4j&NMG9$0B5KSS+3EN(2=e_GuAN5LA65znRQD~QllrHC<9F-m5L&}V@724HYL
z!z%Z8qF7B)#iH@*P;CP-N^<@y`24J#U2+0f&=HA^Y`dualVfVf``c^2@nUGYULF=b*KmW@2H^p5ZyaFPZL;kf|9
z@es!$G$Pn9sNxbHZ0AL8Fezr`J`+Da)pyLId=0*QR(H4uL|UM{UFgWaV{-7~0o;k&
zU(oq;=Pd&gH#2y48m6H@EQB95P!DNM_=j)_!>{Y{_zNDmvHoZByo09K0K#t}xIpMS
zC%jl~Zbki1)cVJ%{yEfl8oC^6d}7a34!#=xNj$!i)uQ-r5B~`{a<={1OtL{U!++*%
zO4w{?PGWo0%YWSSxn|R8ju^jELagT4-f;p{S@>x@50RWB
zv3!R8;C0z>1pRSCWKuiZA-;^_4y7w#IDm@qt(YN~jMbkzk(l<~8eWB&g@1=d3;))L
zvwqgU*+CxD_yD|SU9cZ~Ev&;(A6=aKw*QhYT!7XvtWT&+%p@ibofYNNa2~C&oN4^r
z5smzw95JIy9+W@962Tw~i!FNaa8Ep(;SVtcLNPdRyYmsEJ$eJ=1TiPVNy?zr8AwHZ
z2&8?$^i&k%kDLU7BS_@<8PJY|fy#QlE{*0Jt9
zCWn1`Gpk#%3`{0PLk#*BGK%GWUhF!Vpl7tNtXo87)6*XdGW}lb@nT2OV^T{Ti
zAXPj!2Rc7t3DD1=MA!Ft?Pd4`2#Qs+5zt}x84gleHe4bQFcd9TTiTJ|3{GUfxdw)iYSG;`-jmaxFi1HN-Y=tQ#vAxeK
zW+9)IZx*xKMgCy!Kkh|w3Kg;Ph;oA_Q!Wb1tPip1
z;5}%f?{)Qmi0v%~4La1JjRs+}!yj5@{jLW834GgB9c`##K6?fEg?z{lEI5H3^u#)pr?2n|dQ?|vOSwJY4BqV<`f
zf%xz@QNVi^8j#Kp81`J~5Dfc+D!mA;2DEDl`KZ(QaD`fk=ijp&xDwSryb4v}#Lz5)2&n^+f-lb-omC7DV|RUOwS_myjQ+%x2s?6>6q=
z_`5)mb#ec*=u3QucXJ}}%!8&b*gTq0|BrzSzpPR>gul<5j?D9jR_=cY8Fg<9#|?gT
zZtZ@N_L1Q1IACZ%IlPN(_1Do%lR#>?TrD7A34ayYfy)Q!E09*Mt17Lg+-!7Z@|(V$
z{_DEkUkm>e76B`n4xiZuE@u#71Nvg>#Pye{{I6=8`H*r}g-~a(kO2H$${&S$3|CbP
zK=y%(+)e}xvFLWja1KZvUk0#n0z_H(6Fj(2fTORyt>6xPPhhdZcN$k@jbfSUX=HG)
zdI-e!G%u#_r~8WAl3*Pw7CX{>Meez$gclsCVsLGm7~6|%oES`QatD)p#6YUem2+N7
zlUF9YbK1R$aARZJ-8msI4Be^pfp-uj`;(9zb5H3*A9
zQ&F+3Ohr!qUKq{9;DZ-Y39}DFr&!YQl9LSF(ujcz_VHTYXvD~2uLtS-TinbMP0Cp5
z4EYWbkXyT`5{9r)47AcP#0W<^+8&}&`8m*ffcX3w)+f>f=QLei7FwXafJLOTisg~<
zbbFxFu2SKUiNX9q;QgG3TyB1cj=q~!*`J#hzITvGHv83
zI)mb$a|ZW*iFn26PMJIsePnFmCbbwIM#dJ1jg%%s!sKk*$MgCD{Wu}mHZTvZrKM(QeKK_*Wvw}Z2^XE$b
zT*{xd{CU_+Tf77G)w8fbcHjkKk4ruuHi7MtxCSHI*XPUhPQl^7G=$3u$45->md{HQ
z9C#B}P*$k__@X=Zyja%do_XF?*0EAfMaY(Crt1Sa&-js37b{3f!f`-<-=I6T%bkT`
zy0`SV;%%cgBLY$JnE#5|4MXTA{VvFQ+YPrT%{{lbKv?!Gk6XTB7I~emZN=taLecoal&YxHeVYST-0dM<0*ZkDzw4~Kgz7HVMO}|)jhg=
zu)t3HZ(IKZ_{!4RD6}=a512!|vSXw|ZF~vefl+brelO#`3wI&pn~`q0gQiFE%Fr$6
z*UKB=#}$->ZD>xEGGHVy1n-g6-JA`b3JDLi%k98J3j9;KLxl&+RG5bE+0y4`4KCB*
zIt_kJgZni2um+E7uuFp%HF!;f$t9dDo1(!?4Hjx}hX(J`;Qbo>M-7HFxKHEv+ZudU
zgS{GjUxPy$r0>(yN7CSY4c?(arv{6(_G)X)zf{d7Yw%Acks1{?sGx~y^NZB((AwXj
z!3cg(EB{)9&uZ`?4c@E4Mh#YL(5UexYUM%=M(8fQjWy{|h(NnTEtiaek@o*p@KC)v
zPR7?&7@>>vdemntb21Ox#j02t;8x~f6|5X^6H4{0j#c9yeQKEmJeB5aP^Gw9$}6oc
z^HjU)mRD7K@*7nwzGErnc|E0)hp>c;lvjE7c*^s4d1|Fdf2U_xRhh?CmseLSdF!gR
z4y&bgo@!5NgJ(>K)zYTYDrtG0cMN`=RJ~eSFO^EZhN3!WT|>UtTj$l7iTrjj24%z$
zjBP+79}}NQT{7};SYQc|#?+C~w4-wo>WaqH;d=|CKH0R`{|7!sZ9T`54y{$!w^xH>
z>-qWk8RY+cM3=_jMGf|8uzuHQ{T$2Ly7kWW1Bd_k>FW=??0)8STG7SwfBqK|@f}OH
z;s=LYcYEsVs;jp)E?T%%(-#ee-z^SX3|d!8wEpn&*owR&H~oe(V*z9Qca^r9TA*2m
zL;a~_1ZT_0U=_gKk2)0x^)6I@r#Ziheiv!`3iZPpuyksZkx^E$6W%7uMk?p6LkN}!
z3XBbo#v!V}ZzV_JT)Z9q7ILJoi?=h5!WHs%=FvFbE^QR9h_|x>M@Op4OA&9E0bDPH
z5&5ks?T3ek%eK4jx~qH#rokTCr19dF%4{wNr+0W1?ha1x>1bS$+V0pWoZ7ApxJvl!
z^BdLQltzA2JKv+0>9@7z#H%XXOk`R5HvW@?2ri3ocHly~;dToNM|S=(qOJB_#Buew
z)<-7tTh}I(E8!B(-_f8R52!pFi?a3`SNctF3ALx+vf4;HPzH{I2@S`2BK&{!(Ft)(
zD352tLcq+VdYxX!^vmF`!bOhDjH#b)h+ziQWsJvn)scE3o9U#XV{xf^mRfFPhN5_O
z1?{dPU5T^>J{rfzEJ~vB1{3+zB*@5-6tNSNH
zr*|s$BCb1sqtS^jPo5gf5_C&gOorNz^kA}$c$vs73oXoYZCqb+eNqY0Y{VkzDvhjC
z!WtAAu@+d5%%l?CxHvWrKZqQcnK)?diz}I)sAq|olL2i8t@xeFbjE|;*#h12#$?#-kRN!TJUNynz8BZ0FH~h!
z3EcFQIOxPUmQ-$G`h}^CzVl;(@mh+VO!FOG#%4iVhPG@>TUEbJkZCd#?uUMJeO80L
zlwlo#etZp1uuZgNpm=edPEEL!j@5H{;`UxdOR|mSSXmckl#xtNpkn^xE
zrcjrlV+lJ@HtLzttFMnOiOJCT%g|xDObZj3DKoAxf%Z1mBtq{V_y`HOTkHcpg1Q(T
zi@_S`QjL5sr()lvLN2M9=6apQ02!TxUbV3}YQyaY^mU
z7SM+rI)Zn>eo{M%O(}^eZ$+v{B0F;|1w0~=Eueka3jDW_9z*&O(s3lB5QDH+kKgFg
z(K^4px|;D^aana;gC~;b&fTcOf=EeFW!F2>(ISPOlG4Uc?&SArbo
zzB#JhqEgs>w0k-VHy&$I3EZJ5+(O_Af%~G*|HMzKWd6IxPfOUQ^$u5E8I%gS%;Ky}
zu92)J)>T_2Rh3p(-Q`)oZj-yN>`u0W_(Y>s+dcCJis;YzkS01y%BZ;N8<<%ZnZAG28rc}j;V%?t7U7pXh-xJoyti`_quq)##3NX6aR9-CYsrM9DVU3E>88PFY;_9jf
zjCC8U@zgYUklUEIZo}##S3&-Qg&C^86tU7B#L#qB?D2YQ>&UCD@OmKb$*iWdn*U9b
zMGRD{Bq&D>yLBXA3>{*{4W7#4imGZ<7L!R#W5s24wG~x6eO^y-Z7HJ7-NmKeox5-g
zt@cz^Gg9TL-BsnStF7U2@4`_n>S{fWRZ_83x`QI+8Q@P<-r`Xe6EPmOW305kYF{c>L|-xAm+-aU-HOEnv-yppf9dP6OSg^s;s*ifr3OF7ijQ%_5YMTT$~`v$G}
z7gkRr?aA|by^sW{A*tZQh9Zc(sutG;5flr=x*D`Xop(8I62NOA6qW>7sE2xc_Y``(
ze4ApE^KFK~Z~QEES2>#zg)OSY$yE*{8zk)dJq?nlrU(LbG?123j#WqP?DcM_mWVEF
zA2@P)cKCMg^mrqje9@`%Uxz=p&3d8Y*NPH(V;ec^>70*2y2YxEHKanq)5pfX#9_%g
z?H-7iMjqK;aGx;J;(v1gFGzs?CdN}Kh{s@M$$#rd>O!9UxH<6YC?6d|QY=X^;YHx1
zV?&A?zX`vOA*Leu3#2sUqvJw~4=MIr3Vd`7NU>lV{KabE3Fad;As-#fQQWt5BH|n1
z39dtGL!RInq%P#6<2Z`%Dv_=LPjD|1Lu}LpXuA=27RYA+CRtHN-VAsN$&UOWU?fH(
zSTzHuH}C}SLfVUbbc_@oYf%hVZNt3LhTsp8evLfA5Yqd|6MP%#Q{)MLf@G$fj#-SQ
zAYQT}p8@y=(#^<6$4nGUU7Z8j08cOuKOLz?o?s!;UgQZjYWOC=XYfr7zP^AL@bj#P
z&?a;XXlKFyMcxirigXP5a=_=1Xx$0+ECdApEMVbRaHoVk!RAHyTFw@{cn54pBKoHR
zhmZ~dPq6z|@PqtWz}#%KMcxj01?eH=(-z}2L!z-30@i5xdcdPvp5O^B{}SN)TAm=i
zQKr710&ZP`aX^#ceMp2Scv#C5>_;Nrt^n@NQS*&}=A{@9c!CR&s13nwTD~6eBrbOQ
zfTzD}G=xNJL=gAxJf98tCgQz8w6WXK7m0W%0elfDc`Ni2aJmEYM&1s%3<+1^tOSta
zRU`5QDgGP;eS#EwQl22ikCZ1!v7dyo2o52gM*e^Qql4c8{-EBYX;I7uo%|d;zTw0~
z)X_cGw?=p3rKPtw)>K<}dAtoc!gFUYn4dA*>ZvWOE3c~EnLB$!(eioOv#kvhT(Ht=
zI3&5V_jnp+-+r4ZacOBogQsRk^&Tq-)HdYK_IYcUG?Z0(YDydC)l`*v>l*4Rqi@&Z|2z4~^BDjD
literal 0
HcmV?d00001
diff --git a/Projects/UOContent/Compression/ZstdArchive.cs b/Projects/UOContent/Compression/ZstdArchive.cs
index 08246902c..72cc56e94 100755
--- a/Projects/UOContent/Compression/ZstdArchive.cs
+++ b/Projects/UOContent/Compression/ZstdArchive.cs
@@ -23,7 +23,7 @@ namespace Server.Compression
StartInfo = new ProcessStartInfo
{
FileName = _pathToZstd,
- Arguments = $"--no-progress -d \"{fileNamePath}\" -o \"${tempTarArchive}\""
+ Arguments = $"-q -d \"{fileNamePath}\" -o \"${tempTarArchive}\""
}
};
@@ -70,7 +70,7 @@ namespace Server.Compression
StartInfo = new ProcessStartInfo
{
FileName = Path.Combine(_pathToZstd, "zstd.exe"),
- Arguments = $"--no-progress -10 \"{tempTarArchive}\" -o \"{destinationArchiveFileName}\""
+ Arguments = $"-q -10 \"{tempTarArchive}\" -o \"{destinationArchiveFileName}\""
}
};
diff --git a/THIRD-PARTY-NOTICES b/THIRD-PARTY-NOTICES
index 76fdea608..68a241499 100644
--- a/THIRD-PARTY-NOTICES
+++ b/THIRD-PARTY-NOTICES
@@ -84,3 +84,34 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
+
+License notice for wepoll
+---------------------------
+wepoll - epoll for Windows
+https://github.com/piscisaureus/wepoll
+
+Copyright 2012-2020, Bert Belder
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/version.json b/version.json
index 629bc3bab..dd101024d 100644
--- a/version.json
+++ b/version.json
@@ -1,4 +1,4 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "0.8.2"
+ "version": "0.8.3"
}
From 8e4d5bb16949f81aa2e5acb71c2c549070c221cc Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Sun, 3 Oct 2021 19:19:26 -0700
Subject: [PATCH 27/42] fix: Fixes movement issue (#814)
* Fixes wrong item list used for item detection
---
Projects/Server/TileData.cs | 1 +
Projects/UOContent/Engines/Pathing/Movement.cs | 13 +++++++------
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/Projects/Server/TileData.cs b/Projects/Server/TileData.cs
index a566368c7..6e2b66aaf 100644
--- a/Projects/Server/TileData.cs
+++ b/Projects/Server/TileData.cs
@@ -150,6 +150,7 @@ namespace Server
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this[TileFlag.Impassable | TileFlag.Surface];
+ set => this[TileFlag.Impassable | TileFlag.Surface] = value;
}
[CommandProperty(AccessLevel.GameMaster, AccessLevel.Administrator)]
diff --git a/Projects/UOContent/Engines/Pathing/Movement.cs b/Projects/UOContent/Engines/Pathing/Movement.cs
index b5b3665ad..8ce683ad9 100644
--- a/Projects/UOContent/Engines/Pathing/Movement.cs
+++ b/Projects/UOContent/Engines/Pathing/Movement.cs
@@ -212,10 +212,14 @@ namespace Server.Movement
continue;
}
- if (item.AtWorldPoint(xStart, yStart) || sectorStartIsForward && item.AtWorldPoint(xForward, yForward))
+ if (item.AtWorldPoint(xStart, yStart))
{
itemsStart.Add(item);
}
+ else if (sectorStartIsForward && item.AtWorldPoint(xForward, yForward))
+ {
+ itemsForward.Add(item);
+ }
}
if (checkMobs)
@@ -619,10 +623,9 @@ namespace Server.Movement
{
var tile = staticTiles[i];
var id = TileData.ItemTable[tile.ID & TileData.MaxItemValue];
-
var calcTop = tile.Z + id.CalcHeight;
- if (isSet && calcTop < zCenter || !id.Surface && !(m.CanSwim && id.Wet) || loc.Z < calcTop)
+ if (isSet && calcTop < zCenter || loc.Z < calcTop || !id.Surface && !(m.CanSwim && id.Wet))
{
continue;
}
@@ -648,12 +651,10 @@ namespace Server.Movement
for (var i = 0; i < itemList.Count; ++i)
{
var item = itemList[i];
-
var id = item.ItemData;
-
var calcTop = item.Z + id.CalcHeight;
- if (isSet && calcTop < zCenter || !id.Surface && !(m.CanSwim && id.Wet) || loc.Z < calcTop)
+ if (isSet && calcTop < zCenter || loc.Z < calcTop || !id.Surface && !(m.CanSwim && id.Wet))
{
continue;
}
From 4893094bb4465b527cadb295695ae925dc25f886 Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Sun, 3 Oct 2021 22:58:46 -0700
Subject: [PATCH 28/42] fix: Fixes pooled timers orphaning each other (#809)
---
Projects/Server/Timer/Timer.DelayCall.cs | 61 +++++++++-----------
Projects/Server/Timer/Timer.Pool.cs | 11 +++-
Projects/Server/Timer/Timer.TimerWheel.cs | 7 ++-
Projects/Server/Timer/Timer.cs | 6 +-
Projects/Server/Timer/TimerExecutionToken.cs | 9 ++-
Projects/Server/World/World.cs | 2 +-
Projects/UOContent/World Saves/AutoSave.cs | 2 +-
7 files changed, 55 insertions(+), 43 deletions(-)
diff --git a/Projects/Server/Timer/Timer.DelayCall.cs b/Projects/Server/Timer/Timer.DelayCall.cs
index 81fb9d5b6..5d98180bb 100644
--- a/Projects/Server/Timer/Timer.DelayCall.cs
+++ b/Projects/Server/Timer/Timer.DelayCall.cs
@@ -71,7 +71,7 @@ namespace Server
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
{
DelayCallTimer t = DelayCallTimer.GetTimer(delay, interval, count, callback);
- t._selfReturn = true;
+ t._returnOnDetach = true;
t.Start();
#if DEBUG_TIMERS
@@ -115,7 +115,7 @@ namespace Server
public sealed class DelayCallTimer : Timer, INotifyCompletion
{
- internal bool _selfReturn;
+ internal bool _returnOnDetach;
#if DEBUG_TIMERS
internal bool _allowFinalization;
#endif
@@ -143,17 +143,7 @@ namespace Server
_continuation?.Invoke();
}
- public override void Stop()
- {
- base.Stop();
-
- if (_selfReturn)
- {
- Return();
- }
- }
-
- internal void Return()
+ internal override void OnDetach()
{
if (Running)
{
@@ -161,39 +151,42 @@ namespace Server
return;
}
- Version++; // Increment the version so if this is called from OnTick() and another timer is started, we don't have a problem
-
-#if DEBUG_TIMERS
- _stackTraces.Remove(GetHashCode());
-#endif
-
- if (_poolCount >= _poolCapacity)
+ if (_returnOnDetach)
{
-#if DEBUG_TIMERS
- logger.Warning($"DelayCallTimer pool reached maximum of {_poolCapacity} timers");
- _allowFinalization = true;
-#endif
- return;
- }
+ // Increment the version so if this is called from OnTick() and another timer is started, we don't have a problem
+ // Version++;
- _continuation = null;
- ReturnToPool(1, this, this);
+#if DEBUG_TIMERS
+ _stackTraces.Remove(GetHashCode());
+#endif
+
+ if (_poolCount >= _poolCapacity)
+ {
+#if DEBUG_TIMERS
+ logger.Warning($"DelayCallTimer pool reached maximum of {_poolCapacity} timers");
+ _allowFinalization = true;
+#endif
+ return;
+ }
+
+ _continuation = null;
+ _returnOnDetach = false;
+ ReturnToPool(1, this, this);
+ }
}
public static DelayCallTimer GetTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
{
- if (_poolHead != null)
+ var timer = GetFromPool();
+ if (timer != null)
{
- _poolCount--;
#if DEBUG_TIMERS
- logger.Information($"Pool count: {_poolCount} / {_poolCapacity}");
+ logger.Information($"Getting from pool: ({_poolCount} / {_poolCapacity})");
#endif
- var timer = GetFromPool();
-
timer.Init(delay, interval, count);
timer._continuation = callback;
- timer._selfReturn = false;
+ timer._returnOnDetach = false;
#if DEBUG_TIMERS
timer._allowFinalization = false;
#endif
diff --git a/Projects/Server/Timer/Timer.Pool.cs b/Projects/Server/Timer/Timer.Pool.cs
index a7db9c273..cb92fc18f 100644
--- a/Projects/Server/Timer/Timer.Pool.cs
+++ b/Projects/Server/Timer/Timer.Pool.cs
@@ -62,22 +62,29 @@ namespace Server
_poolHead = head;
_poolCount += amount;
#if DEBUG_TIMERS
- logger.Information($"Pool count: {_poolCount} / {_poolCapacity}");
+ logger.Information($"Returning to pool. ({_poolCount} / {_poolCapacity})");
#endif
}
private static DelayCallTimer GetFromPool()
{
+ if (_poolHead == null)
+ {
+ return null;
+ }
+
var timer = _poolHead;
_poolHead = _poolHead._nextTimer as DelayCallTimer;
timer.Detach();
+ _poolCount--;
+
return timer;
}
internal static void RefillPool(int amount, out DelayCallTimer head, out DelayCallTimer tail)
{
#if DEBUG_TIMERS
- logger.Information($"Filling pool with {amount} timers.");
+ logger.Information($"Filling pool with {amount} timers.");
#endif
head = null;
diff --git a/Projects/Server/Timer/Timer.TimerWheel.cs b/Projects/Server/Timer/Timer.TimerWheel.cs
index 5389eb557..f8e4e451c 100644
--- a/Projects/Server/Timer/Timer.TimerWheel.cs
+++ b/Projects/Server/Timer/Timer.TimerWheel.cs
@@ -113,6 +113,11 @@ namespace Server
}
}
+ if (!timer.Running)
+ {
+ timer.OnDetach();
+ }
+
timer = next;
} while (timer != null);
}
@@ -150,7 +155,7 @@ namespace Server
private static void AddTimer(Timer timer, long delay)
{
#if DEBUG_TIMERS
- var originalDelay = delay
+ var originalDelay = delay;
#endif
delay = Math.Max(0, delay);
diff --git a/Projects/Server/Timer/Timer.cs b/Projects/Server/Timer/Timer.cs
index 5ed8ac3d9..786d1c86c 100644
--- a/Projects/Server/Timer/Timer.cs
+++ b/Projects/Server/Timer/Timer.cs
@@ -116,8 +116,8 @@ namespace Server
Running = false;
Version++;
- var prof = GetProfile();
+ var prof = GetProfile();
if (prof != null)
{
prof.Stopped++;
@@ -152,5 +152,9 @@ namespace Server
_nextTimer = null;
_prevTimer = null;
}
+
+ internal virtual void OnDetach()
+ {
+ }
}
}
diff --git a/Projects/Server/Timer/TimerExecutionToken.cs b/Projects/Server/Timer/TimerExecutionToken.cs
index 9535e726e..9e9992e3b 100644
--- a/Projects/Server/Timer/TimerExecutionToken.cs
+++ b/Projects/Server/Timer/TimerExecutionToken.cs
@@ -50,9 +50,12 @@ namespace Server
public void Cancel()
{
- _timer?.Stop();
- _timer?.Return();
- _timer = null;
+ if (_timer != null)
+ {
+ _timer._returnOnDetach = true;
+ _timer?.Stop();
+ _timer = null;
+ }
this = default;
}
diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs
index 53968cce2..142b554a2 100644
--- a/Projects/Server/World/World.cs
+++ b/Projects/Server/World/World.cs
@@ -132,7 +132,7 @@ namespace Server
public static WorldState WorldState { get; private set; }
public static bool Saving => WorldState == WorldState.Saving;
- public static bool Running => WorldState == WorldState.Running;
+ public static bool Running => WorldState is not WorldState.Loading and not WorldState.Initial;
public static bool Loading => WorldState == WorldState.Loading;
public static Dictionary Mobiles { get; private set; }
diff --git a/Projects/UOContent/World Saves/AutoSave.cs b/Projects/UOContent/World Saves/AutoSave.cs
index a97446d89..96051e6a1 100644
--- a/Projects/UOContent/World Saves/AutoSave.cs
+++ b/Projects/UOContent/World Saves/AutoSave.cs
@@ -77,7 +77,7 @@ namespace Server.Saves
return;
}
- if (SavesEnabled && !AutoRestart.Restarting && !Core.Closing && World.Running)
+ if (SavesEnabled && !AutoRestart.Restarting && !Core.Closing && !World.Saving)
{
if (Warning > TimeSpan.Zero)
{
From 5725fba3733c1ee2f15cc1b202ca014d2b098d23 Mon Sep 17 00:00:00 2001
From: IAmDanielDinner <38978615+SpeedyDevil@users.noreply.github.com>
Date: Mon, 4 Oct 2021 08:04:06 +0200
Subject: [PATCH 29/42] fix: Adds missing ML Abilities (#799)
---
.../Items/Weapons/Abilities/ArmorPierce.cs | 13 +-
.../Items/Weapons/Abilities/Bladeweave.cs | 106 +++++++++
.../Items/Weapons/Abilities/Block.cs | 13 +-
.../Items/Weapons/Abilities/CrushingBlow.cs | 18 ++
.../Items/Weapons/Abilities/DefenseMastery.cs | 16 +-
.../Items/Weapons/Abilities/Disarm.cs | 44 ++--
.../Items/Weapons/Abilities/Dismount.cs | 11 +-
.../Items/Weapons/Abilities/DoubleShot.cs | 15 +-
.../Items/Weapons/Abilities/DualWield.cs | 20 +-
.../Items/Weapons/Abilities/Feint.cs | 19 +-
.../Items/Weapons/Abilities/ForceArrow.cs | 164 ++++++++++++++
.../Items/Weapons/Abilities/ForceOfNature.cs | 111 ++++++++++
.../Weapons/Abilities/FrenziedWhirlwind.cs | 14 +-
.../Weapons/Abilities/InfectiousStrike.cs | 2 +-
.../Items/Weapons/Abilities/LightningArrow.cs | 58 +++++
.../Items/Weapons/Abilities/NerveStrike.cs | 13 +-
.../Items/Weapons/Abilities/ParalyzingBlow.cs | 20 +-
.../Items/Weapons/Abilities/PsychicAttack.cs | 93 ++++++++
.../Items/Weapons/Abilities/RidingSwipe.cs | 14 +-
.../Items/Weapons/Abilities/SerpentArrow.cs | 46 ++++
.../Items/Weapons/Abilities/ShadowStrike.cs | 4 +
.../Items/Weapons/Abilities/TalonStrike.cs | 12 +-
.../Items/Weapons/Abilities/WeaponAbility.cs | 109 +++++++--
.../Weapons/Abilities/WhirlwindAttack.cs | 2 +-
.../UOContent/Items/Weapons/BaseWeapon.cs | 26 ++-
.../Items/Weapons/Staves/GnarledStaff.cs | 2 +-
Projects/UOContent/Misc/BuffIcons.cs | 209 ++++++++++++++----
27 files changed, 931 insertions(+), 243 deletions(-)
create mode 100644 Projects/UOContent/Items/Weapons/Abilities/Bladeweave.cs
create mode 100644 Projects/UOContent/Items/Weapons/Abilities/ForceArrow.cs
create mode 100644 Projects/UOContent/Items/Weapons/Abilities/ForceOfNature.cs
create mode 100644 Projects/UOContent/Items/Weapons/Abilities/LightningArrow.cs
create mode 100644 Projects/UOContent/Items/Weapons/Abilities/PsychicAttack.cs
create mode 100644 Projects/UOContent/Items/Weapons/Abilities/SerpentArrow.cs
diff --git a/Projects/UOContent/Items/Weapons/Abilities/ArmorPierce.cs b/Projects/UOContent/Items/Weapons/Abilities/ArmorPierce.cs
index 448de8b64..1d3cb2507 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/ArmorPierce.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/ArmorPierce.cs
@@ -10,18 +10,7 @@ namespace Server.Items
public override double DamageScalar => 1.5;
public override bool RequiresSE => true;
-
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
- from.SendLocalizedMessage(1063347, "50");
- return false;
- }
-
- return base.CheckSkills(from);
- }
+ public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
diff --git a/Projects/UOContent/Items/Weapons/Abilities/Bladeweave.cs b/Projects/UOContent/Items/Weapons/Abilities/Bladeweave.cs
new file mode 100644
index 000000000..48e69f012
--- /dev/null
+++ b/Projects/UOContent/Items/Weapons/Abilities/Bladeweave.cs
@@ -0,0 +1,106 @@
+using Server.Mobiles;
+using System;
+using System.Collections.Generic;
+
+namespace Server.Items
+{
+ public class Bladeweave : WeaponAbility
+ {
+ private class BladeWeaveRedirect
+ {
+ public readonly WeaponAbility NewAbility;
+ public readonly int ClilocEntry;
+
+ public BladeWeaveRedirect(WeaponAbility ability, int cliloc)
+ {
+ NewAbility = ability;
+ ClilocEntry = cliloc;
+ }
+ }
+
+ private static readonly Dictionary _newAttack = new();
+
+ public static bool BladeWeaving(Mobile attacker, out WeaponAbility a)
+ {
+ if (_newAttack.TryGetValue(attacker, out var bwr))
+ {
+ a = bwr.NewAbility;
+ return true;
+ }
+
+ a = null;
+ return false;
+ }
+
+ public override int BaseMana => 30;
+
+ public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
+ {
+ if (!Validate(attacker) || !CheckMana(attacker, false))
+ {
+ return false;
+ }
+
+ // Bladeweave is only from 90 fighting and tactics skill, so you need only to check bushido/ninjitsu value over 50 to perform block and feint.
+ var requiredBushido = Math.Max(attacker.Skills.Bushido.Value, attacker.Skills.Ninjitsu.Value);
+
+ var ran = Utility.Random(requiredBushido >= 50 ? 9 : 7);
+
+ var newAttack = _newAttack[attacker] = GetBladeWeaveRedirect(ran);
+
+ return newAttack.NewAbility.OnBeforeSwing(attacker, defender);
+ }
+
+ private static BladeWeaveRedirect GetBladeWeaveRedirect(int number) =>
+ number switch
+ {
+ 0 => new BladeWeaveRedirect(ArmorIgnore, 1028838),
+ 1 => new BladeWeaveRedirect(BleedAttack, 1028839),
+ 2 => new BladeWeaveRedirect(ConcussionBlow, 1028840),
+ 3 => new BladeWeaveRedirect(CrushingBlow, 1028841),
+ 4 => new BladeWeaveRedirect(DoubleStrike, 1028844),
+ 5 => new BladeWeaveRedirect(MortalStrike, 1028846),
+ 6 => new BladeWeaveRedirect(ParalyzingBlow, 1028848),
+ 7 => new BladeWeaveRedirect(Block, 1028853),
+ _ => new BladeWeaveRedirect(Feint, 1028857) //8
+ };
+
+ public override bool OnBeforeDamage(Mobile attacker, Mobile defender) =>
+ _newAttack.TryGetValue(attacker, out var bwr)
+ ? bwr.NewAbility.OnBeforeDamage(attacker, defender)
+ : base.OnBeforeDamage(attacker, defender);
+
+ public override void OnHit(Mobile attacker, Mobile defender, int damage)
+ {
+ if (CheckMana(attacker, false))
+ {
+ if (_newAttack.TryGetValue(attacker, out var bwr))
+ {
+ attacker.SendLocalizedMessage(1072841, $"#{bwr.ClilocEntry}");
+ bwr.NewAbility.OnHit(attacker, defender, damage);
+ }
+ else
+ {
+ base.OnHit(attacker, defender, damage);
+ }
+
+ _newAttack.Remove(attacker);
+ ClearCurrentAbility(attacker);
+ }
+ }
+
+ public override void OnMiss(Mobile attacker, Mobile defender)
+ {
+ if (_newAttack.TryGetValue(attacker, out var bwr))
+ {
+ bwr.NewAbility.OnMiss(attacker, defender);
+ }
+ else
+ {
+ base.OnMiss(attacker, defender);
+ }
+
+ _newAttack.Remove(attacker);
+ }
+ }
+}
diff --git a/Projects/UOContent/Items/Weapons/Abilities/Block.cs b/Projects/UOContent/Items/Weapons/Abilities/Block.cs
index 811d71dd6..8ace4d3bd 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/Block.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/Block.cs
@@ -11,18 +11,7 @@ namespace Server.Items
private static readonly Dictionary _table = new();
public override int BaseMana => 30;
-
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
- from.SendLocalizedMessage(1063347, "50");
- return false;
- }
-
- return base.CheckSkills(from);
- }
+ public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
diff --git a/Projects/UOContent/Items/Weapons/Abilities/CrushingBlow.cs b/Projects/UOContent/Items/Weapons/Abilities/CrushingBlow.cs
index a172b82ee..0dc5d9a60 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/CrushingBlow.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/CrushingBlow.cs
@@ -8,6 +8,24 @@ namespace Server.Items
public override int BaseMana => 25;
public override double DamageScalar => 1.5;
+ public virtual double GetRequiredTactics(Mobile from)
+ {
+ if (from.Weapon is BaseWeapon weapon)
+ {
+ if (weapon.PrimaryAbility == this)
+ {
+ return 30.0;
+ }
+
+ if (weapon.SecondaryAbility == this)
+ {
+ return 60.0;
+ }
+ }
+
+ return 200.0;
+ }
+
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
if (!Validate(attacker) || !CheckMana(attacker, true))
diff --git a/Projects/UOContent/Items/Weapons/Abilities/DefenseMastery.cs b/Projects/UOContent/Items/Weapons/Abilities/DefenseMastery.cs
index 174951364..ee6ce5792 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/DefenseMastery.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/DefenseMastery.cs
@@ -12,18 +12,7 @@ namespace Server.Items
private static readonly Dictionary _table = new();
public override int BaseMana => 30;
-
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
- from.SendLocalizedMessage(1063347, "50");
- return false;
- }
-
- return base.CheckSkills(from);
- }
+ public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
@@ -40,8 +29,7 @@ namespace Server.Items
var modifier =
(int)(30.0 *
- ((Math.Max(attacker.Skills.Bushido.Value, attacker.Skills.Ninjitsu.Value) -
- 50.0) / 70.0));
+ ((Math.Max(attacker.Skills.Bushido.Value, attacker.Skills.Ninjitsu.Value) - 50.0) / 70.0));
if (_table.TryGetValue(attacker, out var info))
{
diff --git a/Projects/UOContent/Items/Weapons/Abilities/Disarm.cs b/Projects/UOContent/Items/Weapons/Abilities/Disarm.cs
index e8a6c9c1f..f3ede2c1d 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/Disarm.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/Disarm.cs
@@ -12,33 +12,25 @@ namespace Server.Items
public override int BaseMana => 20;
- // No longer active in pub21:
- /*public override bool CheckSkills( Mobile from )
+ public override bool RequiresTactics(Mobile from) => from.Weapon is not BaseWeapon { Skill: SkillName.Wrestling };
+
+ // Disarm is special. Doesnt need tactics when wresling and tactics need is lower than fighting skill.
+ public virtual double GetRequiredTactics(Mobile from)
{
- if (!base.CheckSkills( from ))
- return false;
-
- if (!(from.Weapon is Fists))
- return true;
-
- Skill skill = from.Skills.ArmsLore;
-
- if (skill?.Base >= 80.0)
- return true;
-
- from.SendLocalizedMessage( 1061812 ); // You lack the required skill in armslore to perform that attack!
-
- return false;
- }*/
-
- public override bool RequiresTactics(Mobile from)
- {
- if (!(from.Weapon is BaseWeapon weapon))
+ if (from.Weapon is BaseWeapon weapon)
{
- return false;
+ if (weapon.PrimaryAbility == this)
+ {
+ return 30.0;
+ }
+
+ if (weapon.SecondaryAbility == this)
+ {
+ return 60.0;
+ }
}
- return weapon.Skill != SkillName.Wrestling;
+ return 200.0;
}
public override void OnHit(Mobile attacker, Mobile defender, int damage)
@@ -52,7 +44,7 @@ namespace Server.Items
var toDisarm = defender.FindItemOnLayer(Layer.OneHanded);
- if (toDisarm?.Movable == false)
+ if (toDisarm?.Movable != false)
{
toDisarm = defender.FindItemOnLayer(Layer.TwoHanded);
}
@@ -63,7 +55,7 @@ namespace Server.Items
{
attacker.SendLocalizedMessage(1004001); // You cannot disarm your opponent.
}
- else if (!Core.ML && toDisarm == null || toDisarm is BaseShield || toDisarm is Spellbook)
+ else if (!Core.ML && toDisarm == null || toDisarm is BaseShield or Spellbook)
{
attacker.SendLocalizedMessage(1060849); // Your target is already unarmed!
}
@@ -77,6 +69,8 @@ namespace Server.Items
pack.DropItem(toDisarm);
+ BuffInfo.AddBuff(defender, new BuffInfo(BuffIcon.NoRearm, 1075637, BlockEquipDuration, defender));
+
BaseWeapon.BlockEquip(defender, BlockEquipDuration);
}
}
diff --git a/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs b/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs
index 8512bf0db..903803d19 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/Dismount.cs
@@ -44,8 +44,8 @@ namespace Server.Items
return;
}
- if (attacker.Mounted && (!(attacker.Weapon is Lance) || !(defender.Weapon is Lance))
- ) // TODO: Should there be a message here?
+ // TODO: Should there be a message here?
+ if (attacker.Mounted && (attacker.Weapon is not Lance || defender.Weapon is not Lance))
{
return;
}
@@ -101,12 +101,9 @@ namespace Server.Items
{
playerMobile.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, true);
}
- else if (Core.ML && attacker is BaseCreature bc)
+ else if (Core.ML && attacker is BaseCreature { ControlMaster: PlayerMobile pm })
{
- if (bc.ControlMaster is PlayerMobile pm)
- {
- pm.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, false);
- }
+ pm.SetMountBlock(BlockMountType.DismountRecovery, RemountDelay, false);
}
if (!attacker.Mounted)
diff --git a/Projects/UOContent/Items/Weapons/Abilities/DoubleShot.cs b/Projects/UOContent/Items/Weapons/Abilities/DoubleShot.cs
index a97ad27f0..8f01dea14 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/DoubleShot.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/DoubleShot.cs
@@ -1,3 +1,5 @@
+using System;
+
namespace Server.Items
{
///
@@ -7,17 +9,8 @@ namespace Server.Items
{
public override int BaseMana => 30;
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
- from.SendLocalizedMessage(1063347, "50");
- return false;
- }
-
- return base.CheckSkills(from);
- }
+ public override bool RequiresTactics(Mobile from) => false;
+ public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
diff --git a/Projects/UOContent/Items/Weapons/Abilities/DualWield.cs b/Projects/UOContent/Items/Weapons/Abilities/DualWield.cs
index 76d2597ff..c8ea1b1af 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/DualWield.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/DualWield.cs
@@ -12,17 +12,8 @@ namespace Server.Items
public override int BaseMana => 30;
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Ninjitsu) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
- from.SendLocalizedMessage(1063352, "50");
- return false;
- }
-
- return base.CheckSkills(from);
- }
+ public override bool RequiresSecondarySkill(Mobile from) => true;
+ public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Ninjitsu;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
@@ -44,8 +35,8 @@ namespace Server.Items
timer = new DualWieldTimer(
attacker,
- (int)(20.0 + 3.0 * (attacker.Skills.Ninjitsu.Value - 50.0) / 7.0)
- ); // 20-50 % increase
+ (int)(20.0 + 3.0 * (attacker.Skills.Ninjitsu.Value - 50.0) / 7.0) // 20-50 % increase
+ );
timer.Start();
Registry.Add(attacker, timer);
@@ -55,8 +46,7 @@ namespace Server.Items
{
private readonly Mobile m_Owner;
- public DualWieldTimer(Mobile owner, int bonusSwingSpeed)
- : base(TimeSpan.FromSeconds(6.0))
+ public DualWieldTimer(Mobile owner, int bonusSwingSpeed) : base(TimeSpan.FromSeconds(6.0))
{
m_Owner = owner;
BonusSwingSpeed = bonusSwingSpeed;
diff --git a/Projects/UOContent/Items/Weapons/Abilities/Feint.cs b/Projects/UOContent/Items/Weapons/Abilities/Feint.cs
index 70f32fc43..20c738a77 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/Feint.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/Feint.cs
@@ -11,18 +11,7 @@ namespace Server.Items
public static Dictionary Registry { get; } = new();
public override int BaseMana => 30;
-
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
- from.SendLocalizedMessage(1063347, "50");
- return false;
- }
-
- return base.CheckSkills(from);
- }
+ public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
@@ -46,10 +35,8 @@ namespace Server.Items
timer = new FeintTimer(
defender,
- (int)(20.0 + 3.0 * (Math.Max(
- attacker.Skills.Ninjitsu.Value,
- attacker.Skills.Bushido.Value
- ) - 50.0) / 7.0)
+ (int)(20.0 + 3.0 *
+ (Math.Max(attacker.Skills.Ninjitsu.Value, attacker.Skills.Bushido.Value) - 50.0) / 7.0)
); // 20-50 % decrease
timer.Start();
diff --git a/Projects/UOContent/Items/Weapons/Abilities/ForceArrow.cs b/Projects/UOContent/Items/Weapons/Abilities/ForceArrow.cs
new file mode 100644
index 000000000..d8b5c9add
--- /dev/null
+++ b/Projects/UOContent/Items/Weapons/Abilities/ForceArrow.cs
@@ -0,0 +1,164 @@
+using Server.Spells;
+using System;
+using System.Collections.Generic;
+
+namespace Server.Items
+{
+ public class ForceArrow : WeaponAbility
+ {
+ public override int BaseMana => 20;
+
+ public override bool RequiresTactics(Mobile from) => false;
+
+ public override void OnHit(Mobile attacker, Mobile defender, int damage)
+ {
+ if (!Validate(attacker) || !CheckMana(attacker, true))
+ {
+ return;
+ }
+
+ ClearCurrentAbility(attacker);
+
+ attacker.SendLocalizedMessage(1074381); // You fire an arrow of pure force.
+ defender.SendLocalizedMessage(1074382); // You are struck by a force arrow!
+
+ if (0.4 > Utility.RandomDouble())
+ {
+ defender.Combatant = null;
+ defender.Warmode = false;
+ }
+
+ ForceArrowInfo info = GetInfo(attacker, defender);
+
+ if (info == null)
+ {
+ BeginForceArrow(attacker, defender);
+ }
+ else if (info.Timer.Running)
+ {
+ info.Timer.IncreaseExpiration();
+
+ BuffInfo.RemoveBuff(defender, BuffIcon.ForceArrow);
+ BuffInfo.AddBuff(defender, new BuffInfo(BuffIcon.ForceArrow, 1151285, 1151286, info.DefenseChanceMalus.ToString()));
+ }
+
+ if (defender.Spell is Spell spell && spell.IsCasting)
+ {
+ spell.Disturb(DisturbType.Hurt, false, true);
+ }
+ }
+
+ private static readonly Dictionary> _table = new Dictionary>();
+
+ public static void BeginForceArrow(Mobile attacker, Mobile defender)
+ {
+ ForceArrowInfo info = new ForceArrowInfo(attacker, defender);
+ info.Timer = new ForceArrowTimer(info);
+
+ if (_table.TryGetValue(attacker, out var list))
+ {
+ list.Add(info);
+ }
+ else
+ {
+ _table.Add(attacker, new List() { info });
+ }
+
+ BuffInfo.AddBuff(defender, new BuffInfo(BuffIcon.ForceArrow, 1151285, 1151286, info.DefenseChanceMalus.ToString()));
+ }
+
+ public static void EndForceArrow(ForceArrowInfo info)
+ {
+ if (info == null)
+ {
+ return;
+ }
+
+ Mobile attacker = info.Attacker;
+
+ if (_table.TryGetValue(attacker, out var list) && list.Remove(info) && list.Count == 0)
+ {
+ _table.Remove(attacker);
+ }
+
+ BuffInfo.RemoveBuff(info.Defender, BuffIcon.ForceArrow);
+ }
+
+ public static bool HasForceArrow(Mobile attacker, Mobile defender)
+ {
+ if (_table.TryGetValue(attacker, out var list))
+ {
+ foreach (ForceArrowInfo info in list)
+ {
+ if (info.Defender == defender)
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public static ForceArrowInfo GetInfo(Mobile attacker, Mobile defender)
+ {
+ if (_table.TryGetValue(attacker,out var list))
+ {
+ foreach (ForceArrowInfo info in list)
+ {
+ if (info.Defender == defender)
+ {
+ return info;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public class ForceArrowInfo
+ {
+ public Mobile Attacker { get; }
+ public Mobile Defender { get; }
+ public ForceArrowTimer Timer { get; set; }
+ public int DefenseChanceMalus { get; set; }
+
+ public ForceArrowInfo(Mobile attacker, Mobile defender)
+ {
+ Attacker = attacker;
+ Defender = defender;
+ DefenseChanceMalus = 10;
+ }
+ }
+
+ public class ForceArrowTimer : Timer
+ {
+ private readonly ForceArrowInfo _info;
+ private DateTime _expires;
+
+ public ForceArrowTimer(ForceArrowInfo info)
+ : base(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1))
+ {
+ _info = info;
+ _expires = Core.Now + TimeSpan.FromSeconds(10);
+
+ Start();
+ }
+
+ protected override void OnTick()
+ {
+ if (_expires < Core.Now)
+ {
+ Stop();
+ EndForceArrow(_info);
+ }
+ }
+
+ public void IncreaseExpiration()
+ {
+ _expires += TimeSpan.FromSeconds(2);
+ _info.DefenseChanceMalus += 5;
+ }
+ }
+ }
+}
diff --git a/Projects/UOContent/Items/Weapons/Abilities/ForceOfNature.cs b/Projects/UOContent/Items/Weapons/Abilities/ForceOfNature.cs
new file mode 100644
index 000000000..17d4ffc8a
--- /dev/null
+++ b/Projects/UOContent/Items/Weapons/Abilities/ForceOfNature.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Collections.Generic;
+
+namespace Server.Items
+{
+ public class ForceOfNature : WeaponAbility
+ {
+ public override int BaseMana => 35;
+
+ public override void OnHit(Mobile attacker, Mobile defender, int damage)
+ {
+ if (!Validate(attacker) || !CheckMana(attacker, true))
+ {
+ return;
+ }
+
+ ClearCurrentAbility(attacker);
+
+ attacker.SendLocalizedMessage(1074374); // You attack your enemy with the force of nature!
+ defender.SendLocalizedMessage(1074375); // You are assaulted with great force!
+
+ defender.PlaySound(0x22F);
+ defender.FixedParticles(0x36CB, 1, 9, 9911, 67, 5, EffectLayer.Head);
+ defender.FixedParticles(0x374A, 1, 17, 9502, 1108, 4, (EffectLayer)255);
+
+ Remove(attacker);
+
+ ForceOfNatureTimer t = new ForceOfNatureTimer(attacker, defender);
+ t.Start();
+
+ _table[attacker] = t;
+ }
+
+ private static readonly Dictionary _table = new Dictionary();
+
+ public static void Remove(Mobile m)
+ {
+ if (_table.Remove(m, out var timer))
+ {
+ timer.Stop();
+ }
+ }
+
+ public static void OnHit(Mobile from, Mobile target)
+ {
+ if (_table.TryGetValue(from,out var t))
+ {
+ t.Hits++;
+ t.LastHit = Core.Now;
+
+ if (t.Hits % 12 == 0)
+ {
+ int duration = target.Skills[SkillName.MagicResist].Value >= 90.0 ? 1 : 2;
+ target.Paralyze(TimeSpan.FromSeconds(duration));
+
+ target.FixedEffect(0x376A, 9, 32);
+ target.PlaySound(0x204);
+
+ t.Hits = 0;
+
+ from.SendLocalizedMessage(1004013); // You successfully stun your opponent!
+ target.SendLocalizedMessage(1004014); // You have been stunned!
+ }
+ }
+ }
+
+ public static double GetDamageScalar(Mobile from, Mobile target)
+ {
+ if (_table.TryGetValue(from, out var t) && t.Target == target)
+ {
+ var bonus = Math.Min(100, Math.Max(50, from.Str - 50));
+ return (100.0 + bonus) / 100.0;
+ }
+
+ return 1.0;
+ }
+
+ private class ForceOfNatureTimer : Timer
+ {
+ public Mobile Target { get; }
+ public Mobile From { get; }
+ public int Hits { get; set; }
+ public DateTime LastHit { get; set; }
+
+ public ForceOfNatureTimer(Mobile from, Mobile target)
+ : base(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1))
+ {
+ Target = target;
+ From = from;
+ Hits = 1;
+ LastHit = Core.Now;
+ }
+
+ protected override void OnTick()
+ {
+ if (!From.Alive || !Target.Alive || Target.Map != From.Map || Target.GetDistanceToSqrt(From.Location) > 10 || LastHit + TimeSpan.FromSeconds(20) < Core.Now || Index > 36)
+ {
+ Remove(From);
+ return;
+ }
+
+ if (Index == 1)
+ {
+ int damage = Utility.RandomMinMax(15, 35);
+
+ AOS.Damage(From, From, damage, false, 0, 0, 0, 0, 0, 0, 100, false, false, false);
+ }
+ }
+ }
+ }
+}
diff --git a/Projects/UOContent/Items/Weapons/Abilities/FrenziedWhirlwind.cs b/Projects/UOContent/Items/Weapons/Abilities/FrenziedWhirlwind.cs
index a523b7a1b..f6b2c6167 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/FrenziedWhirlwind.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/FrenziedWhirlwind.cs
@@ -12,18 +12,8 @@ namespace Server.Items
public override int BaseMana => 30;
public static Dictionary Registry { get; } = new();
-
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Ninjitsu) < 50.0 && GetSkill(from, SkillName.Bushido) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
- from.SendLocalizedMessage(1063347, "50");
- return false;
- }
-
- return base.CheckSkills(from);
- }
+ public override bool RequiresTactics(Mobile from) => false;
+ public override bool RequiresSecondarySkill(Mobile from) => true;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
diff --git a/Projects/UOContent/Items/Weapons/Abilities/InfectiousStrike.cs b/Projects/UOContent/Items/Weapons/Abilities/InfectiousStrike.cs
index ce155ce09..05b0e9969 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/InfectiousStrike.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/InfectiousStrike.cs
@@ -30,7 +30,7 @@ namespace Server.Items
ClearCurrentAbility(attacker);
- if (!(attacker.Weapon is BaseWeapon weapon))
+ if (attacker.Weapon is not BaseWeapon weapon)
{
return;
}
diff --git a/Projects/UOContent/Items/Weapons/Abilities/LightningArrow.cs b/Projects/UOContent/Items/Weapons/Abilities/LightningArrow.cs
new file mode 100644
index 000000000..750b0e4cf
--- /dev/null
+++ b/Projects/UOContent/Items/Weapons/Abilities/LightningArrow.cs
@@ -0,0 +1,58 @@
+using Server.Spells;
+using System;
+using System.Collections.Generic;
+
+namespace Server.Items
+{
+ public class LightningArrow : WeaponAbility
+ {
+ public override int BaseMana => 20;
+
+ //TODO - add ConsumeAmmo on weaponabilities
+ // public override bool ConsumeAmmo => false;
+
+ public override void OnHit(Mobile attacker, Mobile defender, int damage)
+ {
+ if (!Validate(attacker))
+ {
+ return;
+ }
+
+ ClearCurrentAbility(attacker);
+
+ Map map = attacker.Map;
+
+ if (map == null || attacker.Weapon is not BaseWeapon weapon || !CheckMana(attacker, true))
+ {
+ return;
+ }
+
+ List targets = new List();
+ IPooledEnumerable eable = defender.GetMobilesInRange(5);
+
+ foreach (Mobile m in eable)
+ {
+ if (m != defender && m != attacker && SpellHelper.ValidIndirectTarget(attacker, m) && m?.Deleted == false &&
+ m.Map == attacker.Map && m.Alive && attacker.CanSee(m) && attacker.CanBeHarmful(m) &&
+ attacker.InRange(m, weapon.MaxRange) && attacker.InLOS(m))
+ {
+ targets.Add(m);
+ }
+ }
+
+ eable.Free();
+ defender.BoltEffect(0);
+
+ var mobilesLeft = Math.Min(targets.Count, 2);
+ while (mobilesLeft-- > 0)
+ {
+ var index = Utility.Random(targets.Count);
+ var m = targets[index];
+ targets.RemoveAt(index);
+
+ m.BoltEffect(0);
+ AOS.Damage(m, attacker, Utility.RandomMinMax(29, 40), 0, 0, 0, 0, 100);
+ }
+ }
+ }
+}
diff --git a/Projects/UOContent/Items/Weapons/Abilities/NerveStrike.cs b/Projects/UOContent/Items/Weapons/Abilities/NerveStrike.cs
index cead5302b..11eb81352 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/NerveStrike.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/NerveStrike.cs
@@ -9,17 +9,8 @@ namespace Server.Items
{
public override int BaseMana => 30;
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Bushido) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
- from.SendLocalizedMessage(1070768, "50");
- return false;
- }
-
- return base.CheckSkills(from);
- }
+ public override bool RequiresSecondarySkill(Mobile from) => true;
+ public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Bushido;
public override bool OnBeforeSwing(Mobile attacker, Mobile defender)
{
diff --git a/Projects/UOContent/Items/Weapons/Abilities/ParalyzingBlow.cs b/Projects/UOContent/Items/Weapons/Abilities/ParalyzingBlow.cs
index 9c8184ebd..43f985df1 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/ParalyzingBlow.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/ParalyzingBlow.cs
@@ -17,25 +17,7 @@ namespace Server.Items
public override int BaseMana => 30;
- // No longer active in pub21:
- /*public override bool CheckSkills( Mobile from )
- {
- if (!base.CheckSkills( from ))
- return false;
-
- if (!(from.Weapon is Fists))
- return true;
-
- Skill skill = from.Skills.Anatomy;
-
- if (skill?.Base >= 80.0)
- return true;
-
- from.SendLocalizedMessage( 1061811 ); // You lack the required anatomy skill to perform that attack!
-
- return false;
- }*/
-
+ // When using Wrestling, tactics isnt needed.
public override bool RequiresTactics(Mobile from) =>
!(from.Weapon is BaseWeapon weapon && weapon.Skill == SkillName.Wrestling);
diff --git a/Projects/UOContent/Items/Weapons/Abilities/PsychicAttack.cs b/Projects/UOContent/Items/Weapons/Abilities/PsychicAttack.cs
new file mode 100644
index 000000000..c38d387dd
--- /dev/null
+++ b/Projects/UOContent/Items/Weapons/Abilities/PsychicAttack.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+
+namespace Server.Items
+{
+ public class PsychicAttack : WeaponAbility
+ {
+ public static Dictionary Registry { get; } = new();
+ public override int BaseMana => 30;
+
+ public override void OnHit(Mobile attacker, Mobile defender, int damage)
+ {
+ if (!Validate(attacker) || !CheckMana(attacker, true))
+ {
+ return;
+ }
+
+ ClearCurrentAbility(attacker);
+
+ attacker.SendLocalizedMessage(1074383); // Your shot sends forth a wave of psychic energy.
+ defender.SendLocalizedMessage(1074384); // Your mind is attacked by psychic force!
+
+ defender.FixedParticles(0x3789, 10, 25, 5032, EffectLayer.Head);
+ defender.PlaySound(0x1F8);
+
+ if (Registry.TryGetValue(defender, out var timer))
+ {
+ if (!timer.DoneIncrease)
+ {
+ timer.SpellDamageMalus *= 2;
+ timer.ManaCostMalus *= 2;
+ }
+ }
+ else
+ {
+ timer = new PsychicAttackTimer(defender);
+ timer.Start();
+ Registry.Add(defender, timer);
+ }
+
+ BuffInfo.RemoveBuff(defender, BuffIcon.PsychicAttack);
+
+ string args = $"{timer.SpellDamageMalus}\t{timer.ManaCostMalus}";
+ BuffInfo.AddBuff(defender, new BuffInfo(BuffIcon.PsychicAttack, 1151296, 1151297, args));
+ }
+
+ public static void RemoveEffects(Mobile defender)
+ {
+ if (defender == null)
+ {
+ return;
+ }
+
+ BuffInfo.RemoveBuff(defender, BuffIcon.PsychicAttack);
+
+ Registry.Remove(defender);
+
+ defender.SendLocalizedMessage(1150292); // You recover from the effects of the psychic attack.
+ }
+
+ public class PsychicAttackTimer : Timer
+ {
+ private readonly Mobile _defender;
+ private int _spellDamageMalus;
+ private int _manaCostMalus;
+
+ public int SpellDamageMalus
+ {
+ get => _spellDamageMalus;
+ set { _spellDamageMalus = value; DoneIncrease = true; }
+ }
+ public int ManaCostMalus
+ {
+ get => _manaCostMalus;
+ set { _manaCostMalus = value; DoneIncrease = true; }
+ }
+ public bool DoneIncrease { get; private set; }
+
+ public PsychicAttackTimer(Mobile defender) : base(TimeSpan.FromSeconds(10))
+ {
+ _defender = defender;
+ _spellDamageMalus = 15;
+ _manaCostMalus = 15;
+ DoneIncrease = false;
+ }
+
+ protected override void OnTick()
+ {
+ RemoveEffects(_defender);
+ }
+ }
+ }
+}
diff --git a/Projects/UOContent/Items/Weapons/Abilities/RidingSwipe.cs b/Projects/UOContent/Items/Weapons/Abilities/RidingSwipe.cs
index bd880c6b9..cf2be4a65 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/RidingSwipe.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/RidingSwipe.cs
@@ -13,18 +13,8 @@ namespace Server.Items
public override int BaseMana => 30;
public override bool RequiresSE => true;
-
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Bushido) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Bushido skill to perform that attack!
- from.SendLocalizedMessage(1070768, "50");
- return false;
- }
-
- return base.CheckSkills(from);
- }
+ public override bool RequiresSecondarySkill(Mobile from) => true;
+ public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Bushido;
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
diff --git a/Projects/UOContent/Items/Weapons/Abilities/SerpentArrow.cs b/Projects/UOContent/Items/Weapons/Abilities/SerpentArrow.cs
new file mode 100644
index 000000000..138dc25bf
--- /dev/null
+++ b/Projects/UOContent/Items/Weapons/Abilities/SerpentArrow.cs
@@ -0,0 +1,46 @@
+namespace Server.Items
+{
+ public class SerpentArrow : WeaponAbility
+ {
+ public override int BaseMana => 25;
+
+ // Serpent arrow is used only as secondary ability on "Elven composite longbow" so needed skill is always 60, but for sure we will use GetRequiredSecondarySkill
+ public override bool RequiresSecondarySkill(Mobile from) => true;
+ public override double GetRequiredSecondarySkill(Mobile from) => 60;
+ public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Poisoning;
+
+ public override void OnHit(Mobile attacker, Mobile defender, int damage)
+ {
+ if (!Validate(attacker) || !CheckMana(attacker, true))
+ {
+ return;
+ }
+
+ ClearCurrentAbility(attacker);
+
+ defender.SendLocalizedMessage(1112369); // You have been poisoned by a lethal arrow!
+
+ int level;
+
+ if (attacker.InRange(defender, 2))
+ {
+ level = attacker.Skills.Poisoning.Fixed / 2 switch
+ {
+ >= 1000 => 3,
+ > 850 => 2,
+ > 650 => 1,
+ _ => 0
+ };
+ }
+ else
+ {
+ level = 0;
+ }
+
+ defender.ApplyPoison(attacker, Poison.GetPoison(level));
+
+ defender.FixedParticles(0x374A, 10, 15, 5021, EffectLayer.Waist);
+ defender.PlaySound(0x474);
+ }
+ }
+}
diff --git a/Projects/UOContent/Items/Weapons/Abilities/ShadowStrike.cs b/Projects/UOContent/Items/Weapons/Abilities/ShadowStrike.cs
index a43bf94f2..29d6e5eaa 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/ShadowStrike.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/ShadowStrike.cs
@@ -12,6 +12,10 @@ namespace Server.Items
public override bool RequiresTactics(Mobile from) => false;
+ public override bool RequiresSecondarySkill(Mobile from) => true;
+ public override double GetRequiredSecondarySkill(Mobile from) => 80;
+ public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Stealth;
+
public override bool CheckSkills(Mobile from)
{
if (!base.CheckSkills(from))
diff --git a/Projects/UOContent/Items/Weapons/Abilities/TalonStrike.cs b/Projects/UOContent/Items/Weapons/Abilities/TalonStrike.cs
index 9741fdcb6..13963d11d 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/TalonStrike.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/TalonStrike.cs
@@ -13,17 +13,9 @@ namespace Server.Items
public override int BaseMana => 30;
public override double DamageScalar => 1.2;
- public override bool CheckSkills(Mobile from)
- {
- if (GetSkill(from, SkillName.Ninjitsu) < 50.0)
- {
- // You need ~1_SKILL_REQUIREMENT~ Ninjitsu skill to perform that attack!
- from.SendLocalizedMessage(1063352, "50");
- return false;
- }
+ public override bool RequiresSecondarySkill(Mobile from) => true;
+ public override SkillName GetSecondarySkillName(Mobile from) => SkillName.Ninjitsu;
- return base.CheckSkills(from);
- }
public override void OnHit(Mobile attacker, Mobile defender, int damage)
{
diff --git a/Projects/UOContent/Items/Weapons/Abilities/WeaponAbility.cs b/Projects/UOContent/Items/Weapons/Abilities/WeaponAbility.cs
index dff1d95c6..9ad7e996f 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/WeaponAbility.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/WeaponAbility.cs
@@ -38,12 +38,12 @@ namespace Server.Items
new DualWield(),
new DoubleShot(),
new ArmorPierce(),
- null,
- null,
- null,
- null,
- null,
- null,
+ new Bladeweave(),
+ new ForceArrow(),
+ new LightningArrow(),
+ new PsychicAttack(),
+ new SerpentArrow(),
+ new ForceOfNature(),
new Disrobe()
};
@@ -109,16 +109,60 @@ namespace Server.Items
public virtual bool RequiresTactics(Mobile from) => true;
+ public virtual bool RequiresSecondarySkill(Mobile from) => false;
+
+ ///
+ /// Return primary skill for ability. Default 70/90
+ ///
+ ///
+ ///
public virtual double GetRequiredSkill(Mobile from)
{
if (from.Weapon is BaseWeapon weapon)
{
- if (weapon.PrimaryAbility == this)
+ if (weapon.PrimaryAbility == this || weapon.PrimaryAbility == Bladeweave)
{
return 70.0;
}
- if (weapon.SecondaryAbility == this)
+ if (weapon.SecondaryAbility == this || weapon.SecondaryAbility == Bladeweave)
+ {
+ return 90.0;
+ }
+ }
+
+ return 200.0;
+ }
+
+ ///
+ /// Returns secondary skill for Ability. Default 50.
+ ///
+ ///
+ ///
+ public virtual double GetRequiredSecondarySkill(Mobile from) => 50.0;
+
+ ///
+ /// Return secondary skillName. Default bushido/ninjitsu
+ ///
+ ///
+ ///
+ public virtual SkillName GetSecondarySkillName(Mobile from) => from.Skills[SkillName.Ninjitsu].Base > from.Skills[SkillName.Bushido].Base ? SkillName.Ninjitsu : SkillName.Bushido;
+
+ ///
+ /// Returns tactics needed for Ability. Default 70/90.
+ ///
+ ///
+ ///
+ public virtual double GetRequiredTactics(Mobile from)
+ {
+ if (from.Weapon is BaseWeapon weapon)
+ {
+ if (weapon.PrimaryAbility == this || weapon.PrimaryAbility == Bladeweave)
+ {
+ return 70.0;
+ }
+
+ if (weapon.SecondaryAbility == this || weapon.SecondaryAbility == Bladeweave)
{
return 90.0;
}
@@ -184,13 +228,32 @@ namespace Server.Items
var reqSkill = GetRequiredSkill(from);
var reqTactics = Core.ML && RequiresTactics(from);
- if (Core.ML && reqTactics && from.Skills.Tactics.Base < reqSkill)
+ if (reqTactics && from.Skills.Tactics.Base < GetRequiredTactics(from))
{
// You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
from.SendLocalizedMessage(1079308, reqSkill.ToString());
return false;
}
+ var reqSecondarySkill = GetRequiredSecondarySkill(from);
+ SkillName secondarySkill = GetSecondarySkillName(from);
+
+ if (RequiresSecondarySkill(from) && from.Skills[secondarySkill].Base < reqSecondarySkill)
+ {
+ int loc = GetSkillLocalization(secondarySkill);
+
+ if (loc == 1060184)
+ {
+ from.SendLocalizedMessage(loc);
+ }
+ else
+ {
+ from.SendLocalizedMessage(loc, reqSecondarySkill.ToString());
+ }
+
+ return false;
+ }
+
if (skill?.Base >= reqSkill)
{
return true;
@@ -207,19 +270,22 @@ namespace Server.Items
}
/* */
- if (reqTactics)
- {
- // You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
- from.SendLocalizedMessage(1079308, reqSkill.ToString());
- }
- else
- {
- // You need ~1_SKILL_REQUIREMENT~ weapon skill to perform that attack
- from.SendLocalizedMessage(1060182, reqSkill.ToString());
- }
+ // You need ~1_SKILL_REQUIREMENT~ weapon skill to perform that attack
+ from.SendLocalizedMessage(1060182, reqSkill.ToString());
return false;
}
+ private int GetSkillLocalization(SkillName skill)
+ {
+ return skill switch
+ {
+ SkillName.Bushido => 1063347, // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
+ SkillName.Ninjitsu => 1063347, // You need ~1_SKILL_REQUIREMENT~ Bushido or Ninjitsu skill to perform that attack!
+ SkillName.Poisoning => 1060184, // You lack the required poisoning to perform that attack
+ _ => 1157351 // You need ~1_SKILL_REQUIREMENT~ weapon and tactics skill to perform that attack
+ };
+ }
+
public virtual bool CheckSkills(Mobile from) => CheckWeaponSkill(from);
@@ -420,10 +486,7 @@ namespace Server.Items
{
private readonly Mobile m_Mobile;
- public WeaponAbilityTimer(Mobile from) : base(TimeSpan.FromSeconds(3.0))
- {
- m_Mobile = from;
- }
+ public WeaponAbilityTimer(Mobile from) : base(TimeSpan.FromSeconds(3.0)) => m_Mobile = from;
protected override void OnTick()
{
diff --git a/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs b/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs
index 316ae4c53..32496a2a7 100644
--- a/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs
+++ b/Projects/UOContent/Items/Weapons/Abilities/WhirlwindAttack.cs
@@ -28,7 +28,7 @@ namespace Server.Items
return;
}
- if (!(attacker.Weapon is BaseWeapon weapon))
+ if (attacker.Weapon is not BaseWeapon weapon)
{
return;
}
diff --git a/Projects/UOContent/Items/Weapons/BaseWeapon.cs b/Projects/UOContent/Items/Weapons/BaseWeapon.cs
index aef60a032..b50b97f32 100644
--- a/Projects/UOContent/Items/Weapons/BaseWeapon.cs
+++ b/Projects/UOContent/Items/Weapons/BaseWeapon.cs
@@ -1024,6 +1024,7 @@ namespace Server.Items
}
ImmolatingWeaponSpell.StopImmolating(this);
+ ForceOfNature.Remove(m);
m.CheckStatTimers();
@@ -1157,6 +1158,13 @@ namespace Server.Items
bonus = AosAttributes.GetValue(defender, AosAttribute.DefendChance);
+ var info = ForceArrow.GetInfo(attacker, defender);
+
+ if (info != null && info.Defender == defender)
+ {
+ bonus -= info.DefenseChanceMalus;
+ }
+
if (DivineFurySpell.UnderEffect(defender))
{
bonus -= 20; // defender loses 20% bonus when they're under divine fury
@@ -1662,6 +1670,8 @@ namespace Server.Items
percentageBonus += (int)(move.GetDamageScalar(attacker, defender) * 100) - 100;
}
+ percentageBonus += (int)(ForceOfNature.GetDamageScalar(attacker, defender) * 100) - 100;
+
percentageBonus += (int)(damageBonus * 100) - 100;
var cs = CheckSlayers(attacker, defender);
@@ -1864,7 +1874,10 @@ namespace Server.Items
move = null;
}
- var ignoreArmor = a is ArmorIgnore || move?.IgnoreArmor(attacker) == true;
+ var ignoreArmor = a is ArmorIgnore ||
+ move?.IgnoreArmor(attacker) == true ||
+ Bladeweave.BladeWeaving(attacker, out var bladeweavingAbi) &&
+ bladeweavingAbi is ArmorIgnore;
var damageGiven = AOS.Damage(
defender,
@@ -1981,8 +1994,8 @@ namespace Server.Items
mobile.LocalOverheadMessage(
MessageType.Regular,
0x3B2,
- 1061121
- ); // Your equipment is severely damaged.
+ 1061121 // Your equipment is severely damaged.
+ );
}
}
else
@@ -2101,6 +2114,8 @@ namespace Server.Items
a?.OnHit(attacker, defender, damage);
move?.OnHit(attacker, defender, damage);
+ ForceOfNature.OnHit(attacker, defender);
+
if (defender is IHonorTarget it)
{
it.ReceivedHonorContext?.OnTargetHit(attacker);
@@ -2142,6 +2157,11 @@ namespace Server.Items
// SDI bonus
damageBonus += AosAttributes.GetValue(attacker, AosAttribute.SpellDamage);
+ if(PsychicAttack.Registry.TryGetValue(attacker,out var timer))
+ {
+ damageBonus -= timer.SpellDamageMalus;
+ }
+
var context = TransformationSpellHelper.GetContext(attacker);
if (context?.Spell is ReaperFormSpell spell)
diff --git a/Projects/UOContent/Items/Weapons/Staves/GnarledStaff.cs b/Projects/UOContent/Items/Weapons/Staves/GnarledStaff.cs
index aaa4d6e19..8ca5f06a6 100644
--- a/Projects/UOContent/Items/Weapons/Staves/GnarledStaff.cs
+++ b/Projects/UOContent/Items/Weapons/Staves/GnarledStaff.cs
@@ -8,7 +8,7 @@ namespace Server.Items
public GnarledStaff() : base(0x13F8) => Weight = 3.0;
public override WeaponAbility PrimaryAbility => WeaponAbility.ConcussionBlow;
- public override WeaponAbility SecondaryAbility => WeaponAbility.ParalyzingBlow;
+ public override WeaponAbility SecondaryAbility => WeaponAbility.ForceOfNature;
public override int AosStrengthReq => 20;
public override int AosMinDamage => 15;
diff --git a/Projects/UOContent/Misc/BuffIcons.cs b/Projects/UOContent/Misc/BuffIcons.cs
index 7008ed1cd..0c9e48340 100644
--- a/Projects/UOContent/Misc/BuffIcons.cs
+++ b/Projects/UOContent/Misc/BuffIcons.cs
@@ -228,57 +228,190 @@ namespace Server
{
DismountPrevention = 0x3E9,
NoRearm = 0x3EA,
-
- // Currently, no 0x3EB or 0x3EC
- NightSight = 0x3ED, // *
+ //Currently, no 0x3EB or 0x3EC
+ NightSight = 0x3ED, //*
DeathStrike,
EvilOmen,
- UnknownStandingSwirl, // Which is healing throttle & Stamina throttle?
- UnknownKneelingSword,
- DivineFury, // *
- EnemyOfOne, // *
- HidingAndOrStealth, // *
- ActiveMeditation, // *
- BloodOathCaster, // *
- BloodOathCurse, // *
- CorpseSkin, // *
- Mindrot, // *
- PainSpike, // *
+ HonoredDebuff,
+ AchievePerfection,
+ DivineFury, //*
+ EnemyOfOne, //*
+ HidingAndOrStealth, //*
+ ActiveMeditation, //*
+ BloodOathCaster, //*
+ BloodOathCurse, //*
+ CorpseSkin, //*
+ Mindrot, //*
+ PainSpike, //*
Strangle,
- GiftOfRenewal, // *
- AttuneWeapon, // *
- Thunderstorm, // *
- EssenceOfWind, // *
- EtherealVoyage, // *
- GiftOfLife, // *
- ArcaneEmpowerment, // *
+ GiftOfRenewal, //*
+ AttuneWeapon, //*
+ Thunderstorm, //*
+ EssenceOfWind, //*
+ EtherealVoyage, //*
+ GiftOfLife, //*
+ ArcaneEmpowerment, //*
MortalStrike,
- ReactiveArmor, // *
- Protection, // *
+ ReactiveArmor, //*
+ Protection, //*
ArchProtection,
- MagicReflection, // *
- Incognito, // *
+ MagicReflection, //*
+ Incognito, //*
Disguised,
AnimalForm,
Polymorph,
- Invisibility, // *
- Paralyze, // *
+ Invisibility, //*
+ Paralyze, //*
Poison,
Bleed,
- Clumsy, // *
- FeebleMind, // *
- Weaken, // *
- Curse, // *
+ Clumsy, //*
+ FeebleMind, //*
+ Weaken, //*
+ Curse, //*
MassCurse,
- Agility, // *
- Cunning, // *
- Strength, // *
- Bless, // *
+ Agility, //*
+ Cunning, //*
+ Strength, //*
+ Bless, //*
Sleep,
StoneForm,
SpellPlague,
- SpellTrigger,
- NetherBolt,
- Fly
+ Berserk,
+ MassSleep,
+ Fly,
+ Inspire,
+ Invigorate,
+ Resilience,
+ Perseverance,
+ TribulationTarget,
+ DespairTarget,
+ FishPie = 0x426,
+ HitLowerAttack,
+ HitLowerDefense,
+ DualWield,
+ Block,
+ DefenseMastery,
+ DespairCaster,
+ Healing,
+ SpellFocusingBuff,
+ SpellFocusingDebuff,
+ RageFocusingDebuff,
+ RageFocusingBuff,
+ Warding,
+ TribulationCaster,
+ ForceArrow,
+ Disarm,
+ Surge,
+ Feint,
+ TalonStrike,
+ PsychicAttack,
+ ConsecrateWeapon,
+ GrapesOfWrath,
+ EnemyOfOneDebuff,
+ HorrificBeast,
+ LichForm,
+ VampiricEmbrace,
+ CurseWeapon,
+ ReaperForm,
+ ImmolatingWeapon,
+ Enchant,
+ HonorableExecution,
+ Confidence,
+ Evasion,
+ CounterAttack,
+ LightningStrike,
+ MomentumStrike,
+ OrangePetals,
+ RoseOfTrinsic,
+ PoisonImmunity,
+ Veterinary,
+ Perfection,
+ Honored,
+ ManaPhase,
+ FanDancerFanFire,
+ Rage,
+ Webbing,
+ MedusaStone,
+ TrueFear,
+ AuraOfNausea,
+ HowlOfCacophony,
+ GazeDespair,
+ HiryuPhysicalResistance,
+ RuneBeetleCorruption,
+ BloodwormAnemia,
+ RotwormBloodDisease,
+ SkillUseDelay,
+ FactionStatLoss,
+ HeatOfBattleStatus,
+ CriminalStatus,
+ ArmorPierce,
+ SplinteringEffect,
+ SwingSpeedDebuff,
+ WraithForm,
+ CityTradeDeal = 0x466,
+ HumilityDebuff = 0x467,
+ Spirituality,
+ Humility,
+ // Skill Masteries
+ Rampage,
+ Stagger, // Debuff
+ Toughness,
+ Thrust,
+ Pierce, // Debuff
+ PlayingTheOdds,
+ FocusedEye,
+ Onslaught, // Debuff
+ ElementalFury,
+ ElementalFuryDebuff, // Debuff
+ CalledShot,
+ Knockout,
+ SavingThrow,
+ Conduit,
+ EtherealBurst,
+ MysticWeapon,
+ ManaShield,
+ AnticipateHit,
+ Warcry,
+ Shadow,
+ WhiteTigerForm,
+ Bodyguard,
+ HeightenedSenses,
+ Tolerance,
+ DeathRay,
+ DeathRayDebuff,
+ Intuition,
+ EnchantedSummoning,
+ ShieldBash,
+ Whispering,
+ CombatTraining,
+ InjectedStrikeDebuff,
+ InjectedStrike,
+ UnknownTomato,
+ PlayingTheOddsDebuff,
+ DragonTurtleDebuff,
+ Boarding,
+ Potency,
+ ThrustDebuff,
+ FistsOfFury, // 1169
+ BarrabHemolymphConcentrate,
+ JukariBurnPoiltice,
+ KurakAmbushersEssence,
+ BarakoDraftOfMight,
+ UraliTranceTonic,
+ SakkhraProphylaxis, // 1175
+ Sparks,
+ Swarm,
+ BoneBreaker,
+ Unknown2,
+ SwarmImmune,
+ BoneBreakerImmune,
+ UnknownGoblin,
+ UnknownRedDrop,
+ UnknownStar,
+ FeintDebuff,
+ CaddelliteInfused,
+ PotionGloriousFortune,
+ MysticalPolymorphTotem,
+ UnknownDebuff,
}
}
From da31153b2054e8e9e7a513c07954e4a24a04be26 Mon Sep 17 00:00:00 2001
From: IAmDanielDinner <38978615+SpeedyDevil@users.noreply.github.com>
Date: Mon, 4 Oct 2021 22:40:45 +0200
Subject: [PATCH 30/42] fix: Movement issue (#816)
---
Projects/UOContent/Engines/Pathing/Movement.cs | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/Projects/UOContent/Engines/Pathing/Movement.cs b/Projects/UOContent/Engines/Pathing/Movement.cs
index 8ce683ad9..1b733bea3 100644
--- a/Projects/UOContent/Engines/Pathing/Movement.cs
+++ b/Projects/UOContent/Engines/Pathing/Movement.cs
@@ -475,14 +475,9 @@ namespace Server.Movement
return true;
}
- if (!item.Movable)
- {
- continue;
- }
-
var notWater = !itemData.Wet;
- if (!itemData.Surface && itemData.Impassable && (!canSwim || notWater) || cantWalk && notWater)
+ if (item.Movable || !itemData.Surface && itemData.Impassable && (!canSwim || notWater) || cantWalk && notWater)
{
continue;
}
From a55e271a69113722d72e2b9864086d1174d4f2fa Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Tue, 5 Oct 2021 08:54:44 -0700
Subject: [PATCH 31/42] fix: Cleans up file system paths and archiving (#815)
* Makes EnsureDirectory properly work for relative and absolute paths
* Adds a `PathUtility.GetFullPath` which returns full paths for relative paths to `Core.BaseDirectory`. If the path is absolute, it will return as-is.
* Moves EnsureDirectory to `PathUtility`. So `ScriptsHandler.EnsureDirectory` and `AssemblyHandler.EnsureDirectory` are now `PathUtility.EnsureDirectory`
* Fixes crash guard so that it copies accounts properly.
* Changes world save and auto archive to use a random folder name inside of the temp folder.
---
Projects/Server/AssemblyHandler.cs | 22 --
.../Serialization/GenericPersistence.cs | 4 +-
Projects/Server/Text/StringHelpers.cs | 22 ++
Projects/Server/Timer/Timer.TimerWheel.cs | 2 +-
Projects/Server/Utilities/PathUtility.cs | 67 ++++
Projects/Server/Utilities/Utility.cs | 22 --
Projects/Server/World/EntityPersistence.cs | 2 +-
Projects/Server/World/World.cs | 21 +-
.../UOContent.Tests/Fixtures/ServerFixture.cs | 3 +-
.../Accounting/Account.Migrations.cs | 101 ++++++
Projects/UOContent/Accounting/Account.cs | 92 -----
Projects/UOContent/Compression/TarArchive.cs | 18 +-
Projects/UOContent/Compression/ZstdArchive.cs | 14 +-
.../Commands/ConvertPremiumSpawners.cs | 2 +-
.../Commands/ExportSpawnersCommand.cs | 2 +-
Projects/UOContent/Misc/CrashGuard.cs | 76 ++---
Projects/UOContent/World Saves/AutoArchive.cs | 318 +++++++++++-------
17 files changed, 447 insertions(+), 341 deletions(-)
create mode 100644 Projects/Server/Utilities/PathUtility.cs
create mode 100644 Projects/UOContent/Accounting/Account.Migrations.cs
diff --git a/Projects/Server/AssemblyHandler.cs b/Projects/Server/AssemblyHandler.cs
index 00e8d7e53..4cb69f497 100644
--- a/Projects/Server/AssemblyHandler.cs
+++ b/Projects/Server/AssemblyHandler.cs
@@ -173,28 +173,6 @@ namespace Server
return null;
}
-
- public static string EnsureDirectory(string dir)
- {
- var path = Path.Combine(Core.BaseDirectory, dir);
- Directory.CreateDirectory(path);
-
- return path;
- }
-
- public static void EnsureDirectory(FileInfo fi)
- {
- var dir = fi.DirectoryName;
- if (dir != null)
- {
- Directory.CreateDirectory(dir);
- }
- }
-
- public static void EnsureDirectory(DirectoryInfo di)
- {
- Directory.CreateDirectory(di.FullName);
- }
}
public class TypeCache
diff --git a/Projects/Server/Serialization/GenericPersistence.cs b/Projects/Server/Serialization/GenericPersistence.cs
index 2912664b7..eb878303c 100644
--- a/Projects/Server/Serialization/GenericPersistence.cs
+++ b/Projects/Server/Serialization/GenericPersistence.cs
@@ -41,7 +41,7 @@ namespace Server
{
var path = Path.Combine(savePath, name);
- AssemblyHandler.EnsureDirectory(path);
+ PathUtility.EnsureDirectory(path);
string binPath = Path.Combine(path, $"{name}.bin");
using var bin = new BinaryFileWriter(binPath, true);
@@ -54,7 +54,7 @@ namespace Server
{
var path = Path.Combine(savePath, name);
- AssemblyHandler.EnsureDirectory(path);
+ PathUtility.EnsureDirectory(path);
string binPath = Path.Combine(path, $"{name}.bin");
diff --git a/Projects/Server/Text/StringHelpers.cs b/Projects/Server/Text/StringHelpers.cs
index 7e05497c2..cf97b6a7c 100644
--- a/Projects/Server/Text/StringHelpers.cs
+++ b/Projects/Server/Text/StringHelpers.cs
@@ -169,6 +169,28 @@ namespace Server
return str;
}
+ public static string TrimMultiline(this string str, string lineSeparator = "\n")
+ {
+ var parts = str.Split(lineSeparator);
+ for (var i = 0; i < parts.Length; i++)
+ {
+ parts[i] = parts[i].Trim();
+ }
+
+ return string.Join(lineSeparator, parts);
+ }
+
+ public static string IndentMultiline(this string str, string indent = "\t", string lineSeparator = "\n")
+ {
+ var parts = str.Split(lineSeparator);
+ for (var i = 0; i < parts.Length; i++)
+ {
+ parts[i] = $"{indent}{parts[i]}";
+ }
+
+ return string.Join(lineSeparator, parts);
+ }
+
public static List Wrap(this string value, int perLine, int maxLines)
{
if ((value = value?.Trim() ?? "").Length <= 0)
diff --git a/Projects/Server/Timer/Timer.TimerWheel.cs b/Projects/Server/Timer/Timer.TimerWheel.cs
index f8e4e451c..5e9d66334 100644
--- a/Projects/Server/Timer/Timer.TimerWheel.cs
+++ b/Projects/Server/Timer/Timer.TimerWheel.cs
@@ -242,7 +242,7 @@ namespace Server
}
#if DEBUG_TIMERS
- tw.WriteLine("\nStack Traces:");
+ tw.WriteLine($"{Environment.NewLine}Stack Traces:");
foreach (var kvp in DelayCallTimer._stackTraces)
{
tw.WriteLine(kvp.Value);
diff --git a/Projects/Server/Utilities/PathUtility.cs b/Projects/Server/Utilities/PathUtility.cs
new file mode 100644
index 000000000..d2206143d
--- /dev/null
+++ b/Projects/Server/Utilities/PathUtility.cs
@@ -0,0 +1,67 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2021 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: PathUtility.cs *
+ * *
+ * This program is free software: you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation, either version 3 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see . *
+ *************************************************************************/
+
+using System;
+using System.IO;
+using Server.Text;
+
+namespace Server
+{
+ public static class PathUtility
+ {
+ public static string EnsureDirectory(string dir)
+ {
+ var path = GetFullPath(dir, Core.BaseDirectory);
+ Directory.CreateDirectory(path);
+
+ return path;
+ }
+
+ public static void EnsureDirectory(this FileInfo fi)
+ {
+ var dir = GetFullPath(fi.DirectoryName, Core.BaseDirectory);
+ if (dir != null)
+ {
+ Directory.CreateDirectory(dir);
+ }
+ }
+
+ public static void EnsureDirectory(this DirectoryInfo di)
+ {
+ var file = GetFullPath(di.FullName, Core.BaseDirectory);
+ Directory.CreateDirectory(file);
+ }
+
+ public static string GetFullPath(string relativeOrAbsolutePath) =>
+ GetFullPath(relativeOrAbsolutePath, Core.BaseDirectory);
+
+ public static string GetFullPath(string relativeOrAbsolutePath, string basePath) =>
+ relativeOrAbsolutePath switch
+ {
+ null => null,
+ "" => basePath,
+ _ => Path.IsPathRooted(relativeOrAbsolutePath)
+ ? relativeOrAbsolutePath
+ : Path.GetFullPath(relativeOrAbsolutePath, basePath)
+ };
+
+ public static string EnsureRandomPath(string basePath)
+ {
+ Span bytes = stackalloc byte[8];
+ Utility.RandomBytes(bytes);
+ return EnsureDirectory(Path.Combine(basePath, bytes.ToHexString()));
+ }
+ }
+}
diff --git a/Projects/Server/Utilities/Utility.cs b/Projects/Server/Utilities/Utility.cs
index 706fd09e5..286e488e7 100644
--- a/Projects/Server/Utilities/Utility.cs
+++ b/Projects/Server/Utilities/Utility.cs
@@ -1331,28 +1331,6 @@ namespace Server
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T Max(T val, T max) where T : IComparable => val.CompareTo(max) > 0 ? val : max;
- public static string TrimMultiline(this string str, string lineSeparator = "\n")
- {
- var parts = str.Split(lineSeparator);
- for (var i = 0; i < parts.Length; i++)
- {
- parts[i] = parts[i].Trim();
- }
-
- return string.Join(lineSeparator, parts);
- }
-
- public static string IndentMultiline(this string str, string indent = "\t", string lineSeparator = "\n")
- {
- var parts = str.Split(lineSeparator);
- for (var i = 0; i < parts.Length; i++)
- {
- parts[i] = $"{indent}{parts[i]}";
- }
-
- return string.Join(lineSeparator, parts);
- }
-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Tidy(this List list) where T : ISerializable
{
diff --git a/Projects/Server/World/EntityPersistence.cs b/Projects/Server/World/EntityPersistence.cs
index 4ba5b68c2..bf2f415df 100644
--- a/Projects/Server/World/EntityPersistence.cs
+++ b/Projects/Server/World/EntityPersistence.cs
@@ -40,7 +40,7 @@ namespace Server
var path = Path.Combine(savePath, typeName);
- AssemblyHandler.EnsureDirectory(path);
+ PathUtility.EnsureDirectory(path);
string idxPath = Path.Combine(path, $"{typeName}.idx");
string tdbPath = Path.Combine(path, $"{typeName}.tdb");
diff --git a/Projects/Server/World/World.cs b/Projects/Server/World/World.cs
index 142b554a2..02b3013d5 100644
--- a/Projects/Server/World/World.cs
+++ b/Projects/Server/World/World.cs
@@ -46,7 +46,6 @@ namespace Server
private static readonly ConcurrentQueue- _decayQueue = new();
private static string _tempSavePath; // Path to the temporary folder for the save
- private static string _savePath; // Path to "Saves" folder
private static bool _enableSaveStats;
public const bool DirtyTrackingEnabled = false;
@@ -130,6 +129,8 @@ namespace Server
internal static List MobileTypes { get; } = new();
internal static List GuildTypes { get; } = new();
+ public static string SavePath { get; private set; }
+
public static WorldState WorldState { get; private set; }
public static bool Saving => WorldState == WorldState.Saving;
public static bool Running => WorldState is not WorldState.Loading and not WorldState.Initial;
@@ -141,10 +142,12 @@ namespace Server
public static void Configure()
{
- var tempSavePath = ServerConfiguration.GetOrUpdateSetting("world.tempSavePath", "temp");
- _tempSavePath = Path.Combine(Core.BaseDirectory, tempSavePath);
+ var tempSavePath = ServerConfiguration.GetSetting("world.tempSavePath", "temp");
+ _tempSavePath = PathUtility.GetFullPath(tempSavePath);
+
var savePath = ServerConfiguration.GetOrUpdateSetting("world.savePath", "Saves");
- _savePath = Path.Combine(Core.BaseDirectory, savePath);
+ SavePath = PathUtility.GetFullPath(savePath);
+
_enableSaveStats = ServerConfiguration.GetOrUpdateSetting("world.enableSaveStats", false);
// Mobiles & Items
@@ -256,7 +259,7 @@ namespace Server
logger.Information("Loading world");
var watch = Stopwatch.StartNew();
- Persistence.Load(_savePath);
+ Persistence.Load(SavePath);
EventSink.InvokeWorldLoad();
ProcessSafetyQueues();
@@ -344,7 +347,7 @@ namespace Server
var timestamp = Utility.GetTimeStamp();
var saveStatsPath = Path.Combine(Core.BaseDirectory, $"Logs/Saves/Save-Stats-{timestamp}.log");
- AssemblyHandler.EnsureDirectory(saveStatsPath);
+ PathUtility.EnsureDirectory(saveStatsPath);
using var op = new StreamWriter(saveStatsPath, true);
@@ -388,7 +391,7 @@ namespace Server
{
Exception exception = null;
- var tempPath = Path.Combine(_tempSavePath, Utility.GetTimeStamp());
+ var tempPath = PathUtility.EnsureRandomPath(_tempSavePath);
try
{
@@ -417,8 +420,8 @@ namespace Server
{
try
{
- EventSink.InvokeWorldSavePostSnapshot(_savePath, tempPath);
- Directory.Move(tempPath, _savePath);
+ EventSink.InvokeWorldSavePostSnapshot(SavePath, tempPath);
+ Directory.Move(tempPath, SavePath);
}
catch (Exception ex)
{
diff --git a/Projects/UOContent.Tests/Fixtures/ServerFixture.cs b/Projects/UOContent.Tests/Fixtures/ServerFixture.cs
index ab391505b..a5cbdca8e 100644
--- a/Projects/UOContent.Tests/Fixtures/ServerFixture.cs
+++ b/Projects/UOContent.Tests/Fixtures/ServerFixture.cs
@@ -1,4 +1,5 @@
using System;
+using System.Reflection;
using Server.Misc;
namespace Server.Tests
@@ -8,8 +9,8 @@ namespace Server.Tests
// Global setup
static ServerFixture()
{
+ Core.Assembly = Assembly.GetExecutingAssembly();
Core.LoopContext = new EventLoopContext();
-
Core.Expansion = Expansion.EJ;
// Load Configurations
diff --git a/Projects/UOContent/Accounting/Account.Migrations.cs b/Projects/UOContent/Accounting/Account.Migrations.cs
new file mode 100644
index 000000000..16b61efa1
--- /dev/null
+++ b/Projects/UOContent/Accounting/Account.Migrations.cs
@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using Server.Accounting.Security;
+
+namespace Server.Accounting
+{
+ public partial class Account
+ {
+ private void MigrateFrom(V3Content content)
+ {
+ _username = content.Username;
+ _passwordAlgorithm = content.PasswordAlgorithm;
+ _password = content.Password;
+ _accessLevel = content.AccessLevel;
+ _flags = content.Flags;
+ Created = content.Created;
+ _lastLogin = content.LastLogin;
+ _totalGold = content.TotalGold;
+ _totalPlat = content.TotalPlat;
+ _mobiles = content.Mobiles;
+ _comments = content.Comments;
+ _tags = content.Tags;
+ _loginIPs = content.LoginIPs;
+ _ipRestrictions = content.IpRestrictions;
+ _totalGameTime = content.TotalGameTime;
+ _email = content.Email;
+ }
+
+ private void Deserialize(IGenericReader reader, int version)
+ {
+ if (version != 2)
+ {
+ // Due to a bug where we were not versioning at all, reset so we don't have an issue deserializing
+ reader.Seek(0, SeekOrigin.Begin);
+ }
+
+ _username = reader.ReadString();
+ _passwordAlgorithm = version < 2 ? (PasswordProtectionAlgorithm)reader.ReadInt() : reader.ReadEnum();
+ _password = reader.ReadString();
+ _accessLevel = version < 2 ? (AccessLevel)reader.ReadInt() : reader.ReadEnum();
+ _flags = reader.ReadInt();
+ Created = reader.ReadDateTime();
+ _lastLogin = reader.ReadDateTime();
+
+ _totalGold = reader.ReadInt();
+ _totalPlat = reader.ReadInt();
+
+ var length = reader.ReadInt();
+ _mobiles = new Mobile[length];
+ for (int i = 0; i < length; i++)
+ {
+ _mobiles[i] = reader.ReadEntity();
+ }
+
+ length = reader.ReadInt();
+ _comments = length > 0 ? new List(length) : null;
+ for (int i = 0; i < length; i++)
+ {
+ _comments!.Add(new AccountComment(reader));
+ }
+
+ length = reader.ReadInt();
+ _tags = length > 0 ? new List(length) : null;
+ for (int i = 0; i < length; i++)
+ {
+ _tags!.Add(new AccountTag(reader));
+ }
+
+ length = reader.ReadInt();
+ _loginIPs = new IPAddress[length];
+ for (int i = 0; i < length; i++)
+ {
+ if (version < 2)
+ {
+ if (IPAddress.TryParse(reader.ReadString(), out var address))
+ {
+ _loginIPs[i] = Utility.Intern(address);
+ }
+ }
+ else
+ {
+ _loginIPs[i] = reader.ReadIPAddress();
+ }
+ }
+
+ length = reader.ReadInt();
+ _ipRestrictions = new string[length];
+ for (int i = 0; i < length; i++)
+ {
+ _ipRestrictions[i] = reader.ReadString();
+ }
+
+ _totalGameTime = reader.ReadTimeSpan();
+
+ _email = reader.ReadString();
+
+ Timer.StartTimer(AfterDeserialization);
+ }
+ }
+}
diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs
index 5e2306f8d..43a1f8fb8 100644
--- a/Projects/UOContent/Accounting/Account.cs
+++ b/Projects/UOContent/Accounting/Account.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Net;
using System.Xml;
using Server.Accounting.Security;
@@ -357,97 +356,6 @@ namespace Server.Accounting
}
}
- private void MigrateFrom(V3Content content)
- {
- _username = content.Username;
- _passwordAlgorithm = content.PasswordAlgorithm;
- _password = content.Password;
- _accessLevel = content.AccessLevel;
- _flags = content.Flags;
- Created = content.Created;
- _lastLogin = content.LastLogin;
- _totalGold = content.TotalGold;
- _totalPlat = content.TotalPlat;
- _mobiles = content.Mobiles;
- _comments = content.Comments;
- _tags = content.Tags;
- _loginIPs = content.LoginIPs;
- _ipRestrictions = content.IpRestrictions;
- _totalGameTime = content.TotalGameTime;
- _email = content.Email;
- }
-
- private void Deserialize(IGenericReader reader, int version)
- {
- if (version != 2)
- {
- // Due to a bug where we were not versioning at all, reset so we don't have an issue deserializing
- reader.Seek(0, SeekOrigin.Begin);
- }
-
- _username = reader.ReadString();
- _passwordAlgorithm = version < 2 ? (PasswordProtectionAlgorithm)reader.ReadInt() : reader.ReadEnum();
- _password = reader.ReadString();
- _accessLevel = version < 2 ? (AccessLevel)reader.ReadInt() : reader.ReadEnum();
- _flags = reader.ReadInt();
- Created = reader.ReadDateTime();
- _lastLogin = reader.ReadDateTime();
-
- _totalGold = reader.ReadInt();
- _totalPlat = reader.ReadInt();
-
- var length = reader.ReadInt();
- _mobiles = new Mobile[length];
- for (int i = 0; i < length; i++)
- {
- _mobiles[i] = reader.ReadEntity();
- }
-
- length = reader.ReadInt();
- _comments = length > 0 ? new List(length) : null;
- for (int i = 0; i < length; i++)
- {
- _comments!.Add(new AccountComment(reader));
- }
-
- length = reader.ReadInt();
- _tags = length > 0 ? new List(length) : null;
- for (int i = 0; i < length; i++)
- {
- _tags!.Add(new AccountTag(reader));
- }
-
- length = reader.ReadInt();
- _loginIPs = new IPAddress[length];
- for (int i = 0; i < length; i++)
- {
- if (version < 2)
- {
- if (IPAddress.TryParse(reader.ReadString(), out var address))
- {
- _loginIPs[i] = Utility.Intern(address);
- }
- }
- else
- {
- _loginIPs[i] = reader.ReadIPAddress();
- }
- }
-
- length = reader.ReadInt();
- _ipRestrictions = new string[length];
- for (int i = 0; i < length; i++)
- {
- _ipRestrictions[i] = reader.ReadString();
- }
-
- _totalGameTime = reader.ReadTimeSpan();
-
- _email = reader.ReadString();
-
- Timer.StartTimer(AfterDeserialization);
- }
-
///
/// Deletes the account, all characters of the account, and all houses of those characters
///
diff --git a/Projects/UOContent/Compression/TarArchive.cs b/Projects/UOContent/Compression/TarArchive.cs
index 3bc024a52..0e5889e4a 100755
--- a/Projects/UOContent/Compression/TarArchive.cs
+++ b/Projects/UOContent/Compression/TarArchive.cs
@@ -50,8 +50,7 @@ namespace Server.Compression
private static string DownloadTarForWindows()
{
- var tempDir = Path.Combine(Core.BaseDirectory, "temp");
- AssemblyHandler.EnsureDirectory("temp");
+ var tempDir = PathUtility.EnsureRandomPath(Path.GetTempPath());
var libarchiveFile = Path.Combine(tempDir, "libarchive.zip");
using WebClient wc = new WebClient();
@@ -82,29 +81,28 @@ namespace Server.Compression
}
public static bool CreateFromPaths(
- List paths,
+ IEnumerable paths,
string destinationArchiveFileName,
+ string relativeTo,
string compressCommand = null,
string compressionProgramPath = null
)
{
_pathToTar ??= GetPathToTar();
- AssemblyHandler.EnsureDirectory(new FileInfo(destinationArchiveFileName));
- var di = new DirectoryInfo(paths[0]);
- var directory = di.Parent!.FullName;
+ new FileInfo(destinationArchiveFileName).EnsureDirectory();
using var builder = new ValueStringBuilder();
- for (var i = 0; i < paths.Count; i++)
+ var i = 0;
+ foreach (var path in paths)
{
- var path = paths[i];
- builder.Append($"{(i > 0 ? " " : "")}\"{Path.GetRelativePath(directory, path)}\"");
+ builder.Append($"{(i++ > 0 ? " " : "")}\"{Path.GetRelativePath(relativeTo, path)}\"");
}
var pathsToCompress = builder.ToString();
var tarFlags = compressCommand == null ? "-acf" : "-cf";
var useExternalCompression = compressCommand != null ? $"--use-compress-program \"{compressCommand}\" " : "";
- var arguments = $"{useExternalCompression}{tarFlags} \"{destinationArchiveFileName}\" -C \"{directory}\" {pathsToCompress}";
+ var arguments = $"{useExternalCompression}{tarFlags} \"{destinationArchiveFileName}\" -C \"{relativeTo}\" {pathsToCompress}";
return RunTar(arguments, compressionProgramPath) == 0;
}
diff --git a/Projects/UOContent/Compression/ZstdArchive.cs b/Projects/UOContent/Compression/ZstdArchive.cs
index 72cc56e94..b121e023a 100755
--- a/Projects/UOContent/Compression/ZstdArchive.cs
+++ b/Projects/UOContent/Compression/ZstdArchive.cs
@@ -42,17 +42,15 @@ namespace Server.Compression
}
}
- return TarArchive.ExtractToDirectory(fileNamePath, outputDirectory, "zstd -d", _pathToZstd);
+ return TarArchive.ExtractToDirectory(fileNamePath, outputDirectory, "zstd -q -d", _pathToZstd);
}
public static bool CreateFromPaths(
- List paths,
+ IEnumerable paths,
string destinationArchiveFileName,
- int compressionLevel = 10
+ string relativeTo
)
{
- Debug.Assert(compressionLevel is >= 1 and <= 22, $"{nameof(compressionLevel)} must be between 1 and 22");
-
// bsdtar has a bug and hangs, so we are doing it in two steps.
if (Core.IsWindows)
{
@@ -60,7 +58,7 @@ namespace Server.Compression
try
{
- if (!TarArchive.CreateFromPaths(paths, tempTarArchive))
+ if (!TarArchive.CreateFromPaths(paths, relativeTo, tempTarArchive))
{
return false;
}
@@ -70,7 +68,7 @@ namespace Server.Compression
StartInfo = new ProcessStartInfo
{
FileName = Path.Combine(_pathToZstd, "zstd.exe"),
- Arguments = $"-q -10 \"{tempTarArchive}\" -o \"{destinationArchiveFileName}\""
+ Arguments = $"-q \"{tempTarArchive}\" -o \"{destinationArchiveFileName}\""
}
};
@@ -89,7 +87,7 @@ namespace Server.Compression
}
}
- return TarArchive.CreateFromPaths(paths, destinationArchiveFileName, "zstd -10", _pathToZstd);
+ return TarArchive.CreateFromPaths(paths, destinationArchiveFileName, relativeTo, "zstd -q", _pathToZstd);
}
}
}
diff --git a/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs b/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs
index a542550bb..75f79c0e5 100644
--- a/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs
+++ b/Projects/UOContent/Engines/Spawners/Commands/ConvertPremiumSpawners.cs
@@ -77,7 +77,7 @@ namespace Server.Engines.Spawners
{
var stemDir = stem[..^file.Name.Length];
var fullOutputDir = Path.Combine(outputDir, stemDir);
- AssemblyHandler.EnsureDirectory(fullOutputDir);
+ PathUtility.EnsureDirectory(fullOutputDir);
var outputFileName = $"{file.Name[..^file.Extension.Length]}.json";
ParsePremiumSpawnerFile(file.FullName, Path.Combine(fullOutputDir, outputFileName), options);
diff --git a/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs b/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs
index 467ea4fe3..4e24ef087 100644
--- a/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs
+++ b/Projects/UOContent/Engines/Spawners/Commands/ExportSpawnersCommand.cs
@@ -53,7 +53,7 @@ namespace Server.Engines.Spawners
if (!Path.IsPathRooted(path))
{
path = Path.Combine(Core.BaseDirectory, path);
- AssemblyHandler.EnsureDirectory(directory);
+ PathUtility.EnsureDirectory(directory);
}
else if (!Directory.Exists(directory))
{
diff --git a/Projects/UOContent/Misc/CrashGuard.cs b/Projects/UOContent/Misc/CrashGuard.cs
index 7b029b35c..c41b213fa 100644
--- a/Projects/UOContent/Misc/CrashGuard.cs
+++ b/Projects/UOContent/Misc/CrashGuard.cs
@@ -4,6 +4,7 @@ using System.IO;
using Server.Accounting;
using Server.Logging;
using Server.Network;
+using Server.Saves;
namespace Server.Misc
{
@@ -76,24 +77,23 @@ namespace Server.Misc
}
}
- private static void CopyFile(string rootOrigin, string rootBackup, string path)
+ private static void DirectoryCopy(string sourceDirName, string destDirName)
{
- var originPath = Path.Combine(rootOrigin, path);
- if (!File.Exists(originPath))
+ // Get the subdirectories for the specified directory.
+ DirectoryInfo dir = new DirectoryInfo(sourceDirName);
+ DirectoryInfo[] dirs = dir.GetDirectories();
+
+ FileInfo[] files = dir.GetFiles();
+ foreach (FileInfo file in files)
{
- return;
+ string tempPath = Path.Combine(destDirName, file.Name);
+ file.CopyTo(tempPath, false);
}
- var backupPath = Path.Combine(rootBackup, path);
- Directory.CreateDirectory(Path.GetDirectoryName(backupPath));
-
- try
+ foreach (DirectoryInfo subdir in dirs)
{
- File.Copy(originPath, backupPath);
- }
- catch
- {
- // ignored
+ string tempPath = Path.Combine(destDirName, subdir.Name);
+ DirectoryCopy(subdir.FullName, tempPath);
}
}
@@ -105,26 +105,12 @@ namespace Server.Misc
{
var timeStamp = Utility.GetTimeStamp();
- var root = Core.BaseDirectory;
- var rootBackup = Path.Combine(root, $"Backups/Crashed/{timeStamp}/");
- var rootOrigin = Path.Combine(root, "Saves/");
-
- // Copy files
- CopyFile(rootOrigin, rootBackup, "Accounts/Accounts.xml");
-
- CopyFile(rootOrigin, rootBackup, "Items/Items.bin");
- CopyFile(rootOrigin, rootBackup, "Items/Items.idx");
- CopyFile(rootOrigin, rootBackup, "Items/Items.tdb");
-
- CopyFile(rootOrigin, rootBackup, "Mobiles/Mobiles.bin");
- CopyFile(rootOrigin, rootBackup, "Mobiles/Mobiles.idx");
- CopyFile(rootOrigin, rootBackup, "Mobiles/Mobiles.tdb");
-
- CopyFile(rootOrigin, rootBackup, "Guilds/Guilds.bin");
- CopyFile(rootOrigin, rootBackup, "Guilds/Guilds.idx");
-
- CopyFile(rootOrigin, rootBackup, "Regions/Regions.bin");
- CopyFile(rootOrigin, rootBackup, "Regions/Regions.idx");
+ var backupPath = PathUtility.EnsureDirectory(Path.Combine(AutoArchive.BackupPath, "Crashed", timeStamp));
+ var savePath = World.SavePath;
+ if (Directory.Exists(savePath))
+ {
+ DirectoryCopy(savePath, backupPath);
+ }
logger.Information("Backup done");
}
@@ -160,7 +146,7 @@ namespace Server.Misc
try
{
- op.WriteLine("Mobiles: {0}", World.Mobiles.Count);
+ op.WriteLine($"Accounts: {Accounts.Count}");
}
catch
{
@@ -169,15 +155,23 @@ namespace Server.Misc
try
{
- op.WriteLine("Items: {0}", World.Items.Count);
+ op.WriteLine($"Mobiles: {World.Mobiles.Count}");
}
catch
{
// ignored
}
- op.WriteLine("Exception:");
- op.WriteLine(e.Exception);
+ try
+ {
+ op.WriteLine($"Items: {World.Items.Count}");
+ }
+ catch
+ {
+ // ignored
+ }
+
+ op.WriteLine($"Exception: {e.Exception}");
op.WriteLine();
op.WriteLine("Clients:");
@@ -186,22 +180,22 @@ namespace Server.Misc
{
var states = TcpServer.Instances;
- op.WriteLine("- Count: {0}", states.Count);
+ op.WriteLine($"- Count: {states.Count}");
foreach (var ns in TcpServer.Instances)
{
- op.Write("+ {0}:", ns);
+ op.Write($"+ {ns}:");
if (ns.Account is Account a)
{
- op.Write(" (account = {0})", a.Username);
+ op.Write($" (account = {a.Username})");
}
var m = ns.Mobile;
if (m != null)
{
- op.Write(" (mobile = 0x{0:X} '{1}')", m.Serial.Value, m.Name);
+ op.Write($" (mobile = 0x{m.Serial.Value:X} '{m.Name}')");
}
op.WriteLine();
diff --git a/Projects/UOContent/World Saves/AutoArchive.cs b/Projects/UOContent/World Saves/AutoArchive.cs
index 6e1149639..fc839e830 100755
--- a/Projects/UOContent/World Saves/AutoArchive.cs
+++ b/Projects/UOContent/World Saves/AutoArchive.cs
@@ -2,9 +2,9 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
+using Microsoft.Toolkit.HighPerformance;
using Server.Compression;
using Server.Logging;
@@ -29,6 +29,7 @@ namespace Server.Saves
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(AutoArchive));
+ private static string _tempArchivePath;
private static int _isArchiving;
private static DateTime _nextHourlyArchive;
private static DateTime _nextDailyArchive;
@@ -40,16 +41,25 @@ namespace Server.Saves
public static Action Prune { get; set; }
public static string ArchivePath { get; private set; }
public static string BackupPath { get; private set; }
+ public static string AutomaticBackupPath { get; private set; }
public static void Configure()
{
- BackupPath = ServerConfiguration.GetOrUpdateSetting("autoArchive.backupPath", "Backups/Automatic");
- ArchivePath = ServerConfiguration.GetOrUpdateSetting("autoArchive.archivePath", "Archives");
- _compressionFormat = ServerConfiguration.GetOrUpdateSetting("autoArchive.compressionFormat", CompressionFormat.Zstd);
+ var tempArchivePath = ServerConfiguration.GetSetting("autoArchive.tempArchivePath", "temp");
+ _tempArchivePath = PathUtility.GetFullPath(tempArchivePath);
+
+ var backupPath = ServerConfiguration.GetOrUpdateSetting("autoArchive.backupPath", "Backups");
+ BackupPath = PathUtility.GetFullPath(backupPath);
+ AutomaticBackupPath = Path.Combine(BackupPath, "Automatic");
+
+ var archivePath = ServerConfiguration.GetOrUpdateSetting("autoArchive.archivePath", "Archives");
+ ArchivePath = PathUtility.GetFullPath(archivePath);
var useLocalArchives = ServerConfiguration.GetOrUpdateSetting("autoArchive.archiveLocally", true);
_enablePruning = ServerConfiguration.GetOrUpdateSetting("autoArchive.enableArchivePruning", true);
+ _compressionFormat = ServerConfiguration.GetOrUpdateSetting("autoArchive.compressionFormat", CompressionFormat.Zstd);
+
if (useLocalArchives)
{
Archive = AutoArchiveLocally;
@@ -85,8 +95,8 @@ namespace Server.Saves
return;
}
- var backupPath = Path.Combine(BackupPath, Utility.GetTimeStamp());
- AssemblyHandler.EnsureDirectory(BackupPath);
+ Directory.CreateDirectory(AutomaticBackupPath);
+ var backupPath = Path.Combine(AutomaticBackupPath, Utility.GetTimeStamp());
Directory.Move(args.OldSavePath, backupPath);
logger.Information($"Created backup at {backupPath}");
@@ -97,20 +107,16 @@ namespace Server.Saves
private static void RestoreFromArchive()
{
var savePath = Path.Combine(Core.BaseDirectory, ServerConfiguration.GetSetting("world.savePath", "Saves"));
-
- var files = Directory.Exists(savePath) ? Directory.GetFiles(savePath, "????-??-??-??-??-??.*") : null;
-
- if (files == null || files.Length == 0)
+ if (!Directory.Exists(savePath))
{
return;
}
- var latestFiles = GetInRange(files, DateTime.MinValue, DateTime.MaxValue, true);
-
- foreach (var file in latestFiles)
+ foreach (var file in PathsByTimestampName(savePath, true))
{
if (RestoreFromFile(file, savePath))
{
+ File.Delete(file);
break;
}
}
@@ -128,19 +134,10 @@ namespace Server.Saves
logger.Information($"Restoring latest world save from archive {fileName}");
- var tempFolder = Path.Combine(Core.BaseDirectory, "temp");
- AssemblyHandler.EnsureDirectory(tempFolder);
- bool successful;
-
- if (fileName.EndsWithOrdinal(".tar.zst"))
- {
- successful = ZstdArchive.ExtractToDirectory(fi.FullName, tempFolder);
- }
- else
- {
- TarArchive.ExtractToDirectory(fi.FullName, tempFolder);
- successful = true;
- }
+ var tempPath = PathUtility.EnsureRandomPath(_tempArchivePath);
+ var successful = fileName.EndsWithOrdinal(".tar.zst")
+ ? ZstdArchive.ExtractToDirectory(fi.FullName, tempPath)
+ : TarArchive.ExtractToDirectory(fi.FullName, tempPath);
if (!successful)
{
@@ -148,25 +145,17 @@ namespace Server.Saves
return false;
}
- var allFolders = Directory.EnumerateDirectories(tempFolder, "????-??-??-??-??-??");
- var worldSaveFolders = GetInRange(allFolders, DateTime.MinValue, DateTime.MaxValue);
- var first = true;
- foreach (var folder in worldSaveFolders)
+ foreach (var folder in PathsByTimestampName(tempPath))
{
- if (first)
- {
- first = false;
- Directory.Delete(savePath, true);
- var dirInfo = new DirectoryInfo(folder);
- logger.Information($"Restoring backup {dirInfo.Name}");
- Directory.Move(folder, savePath);
- }
- else
- {
- Directory.Delete(folder, true);
- }
+ Directory.Delete(savePath, true);
+ var dirInfo = new DirectoryInfo(folder);
+ logger.Information($"Restoring backup {dirInfo.Name}");
+ Directory.Move(folder, savePath);
+ break;
}
+ Directory.Delete(tempPath, true);
+
return true;
}
@@ -180,14 +169,26 @@ namespace Server.Saves
PruneLocalArchives(ArchivePeriod.Monthly, 12);
}
- if (Directory.Exists(BackupPath))
+ if (!Directory.Exists(AutomaticBackupPath))
{
- var allFolders = Directory.EnumerateDirectories(BackupPath, "????-??-??-??-??-??", SearchOption.AllDirectories);
- var latestBackupFolders = GetInRange(allFolders, DateTime.MinValue, Core.Now.AddMonths(-1));
+ return;
+ }
- foreach (var folder in latestBackupFolders)
+ var allFolders = Directory.EnumerateDirectories(AutomaticBackupPath);
+ var threshold = Core.Now.AddMonths(-1);
+
+ foreach (var folder in allFolders)
+ {
+ var dirName = new DirectoryInfo(folder).Name;
+
+ if (!TryGetDate(dirName, out var date))
{
- logger.Information($"Pruning backup {folder}");
+ continue;
+ }
+
+ if (date < threshold)
+ {
+ logger.Information($"Pruning old backup {folder}");
Directory.Delete(folder, true);
}
}
@@ -196,12 +197,14 @@ namespace Server.Saves
private static void PruneLocalArchives(ArchivePeriod period, int minRetained)
{
var periodStr = period.ToString();
- var path = Path.Combine(ArchivePath, periodStr);
- var allFiles = Directory.EnumerateFiles(path, "????-??-??-??-??-??.*");
- var archives = GetInRange(allFiles, DateTime.MinValue, DateTime.MaxValue, true);
+ var archivePath = Path.Combine(ArchivePath, periodStr);
+ if (!Directory.Exists(archivePath))
+ {
+ return;
+ }
var periodLowerStr = periodStr.ToLowerInvariant();
- foreach (var archive in archives)
+ foreach (var archive in PathsByTimestampName(archivePath, true))
{
if (minRetained > 0)
{
@@ -234,98 +237,150 @@ namespace Server.Saves
{
if (date >= _nextHourlyArchive)
{
- var lastHour = date.Date.AddHours(date.Hour);
- Rollup(ArchivePeriod.Hourly, lastHour.AddHours(-1), lastHour.ToTimeStamp());
- _nextHourlyArchive = _nextHourlyArchive.AddHours(2);
+ Rollup(ArchivePeriod.Hourly);
+ _nextHourlyArchive = _nextHourlyArchive.AddHours(1);
}
if (date >= _nextDailyArchive)
{
- var yesterday = date.Date.AddDays(-1);
- Rollup(ArchivePeriod.Daily, yesterday, yesterday.ToTimeStamp());
- _nextDailyArchive = _nextDailyArchive.AddDays(2);
+ Rollup(ArchivePeriod.Daily);
+ _nextDailyArchive = _nextDailyArchive.AddDays(1);
}
if (date >= _nextMonthlyArchive)
{
- var lastMonth = new DateTime(date.Year, date.Month, 1).AddMonths(-1);
- Rollup(ArchivePeriod.Monthly, lastMonth, lastMonth.ToTimeStamp());
- _nextMonthlyArchive = _nextMonthlyArchive.AddMonths(2);
+ Rollup(ArchivePeriod.Monthly);
+ _nextMonthlyArchive = _nextMonthlyArchive.AddMonths(1);
}
Prune?.Invoke();
-
_isArchiving = 0;
},
null
);
}
- private static void Rollup(ArchivePeriod archivePeriod, DateTime rangeStart, string archiveNameNoExtension)
+ private static void Rollup(ArchivePeriod archivePeriod)
{
- if (!Directory.Exists(BackupPath))
+ if (!Directory.Exists(AutomaticBackupPath))
{
return;
}
+ var currentRangeStart = Core.Now.ArchivePeriodStart(archivePeriod);
+ var allFolders = Directory.EnumerateDirectories(AutomaticBackupPath);
+
+ var items = new Dictionary>();
+ foreach (var path in allFolders)
+ {
+ var dirName = new DirectoryInfo(path).Name;
+
+ if (!TryGetDate(dirName, out var date))
+ {
+ continue;
+ }
+
+ var rangeStart = date.ArchivePeriodStart(archivePeriod);
+
+ // Only archive the past
+ if (rangeStart >= currentRangeStart)
+ {
+ continue;
+ }
+
+ if (items.TryGetValue(rangeStart, out var value))
+ {
+ value.Add(date, path);
+ }
+ else
+ {
+ value = new SortedDictionary(new DescendingComparer())
+ {
+ { date, path }
+ };
+
+ items.Add(rangeStart, value);
+ }
+ }
+
var archivePeriodStr = archivePeriod.ToString();
+ var archivePath = PathUtility.EnsureDirectory(Path.Combine(ArchivePath, archivePeriodStr));
var archivePeriodStrLower = archivePeriodStr.ToLowerInvariant();
-
- var stopWatch = new Stopwatch();
- stopWatch.Start();
- var allFolders = Directory.EnumerateDirectories(BackupPath, "????-??-??-??-??-??");
-
- var rangeEnd = archivePeriod switch
- {
- ArchivePeriod.Monthly => rangeStart.AddMonths(1),
- ArchivePeriod.Daily => rangeStart.AddDays(1),
- _ => rangeStart.AddHours(1),
- };
-
- var latestFolders = GetInRange(allFolders, rangeStart, rangeEnd).ToList();
-
- // We don't want to re-archive a single save. This usually means we rebooted and found the one we purposefully left behind
- if (latestFolders.Count <= 1)
- {
- return;
- }
-
- var archivePath = Path.Combine(ArchivePath, archivePeriodStr);
- AssemblyHandler.EnsureDirectory(archivePath);
-
var extension = _compressionFormat.GetFileExtension();
- var archiveFilePath = Path.Combine(archivePath, $"{archiveNameNoExtension}{extension}");
- logger.Information($"Creating {archivePeriodStrLower} archive");
- var archiveCreated = _compressionFormat == CompressionFormat.Zstd
- ? ZstdArchive.CreateFromPaths(latestFolders, archiveFilePath)
- : TarArchive.CreateFromPaths(latestFolders, archiveFilePath);
+ // Leave behind 1 hourly for daily, 1 daily for monthly.
+ var minimum = archivePeriod != ArchivePeriod.Monthly ? 1 : 0;
- if (archiveCreated)
+ foreach (var (rangeStart, sortedBackups) in items)
{
- stopWatch.Stop();
- var elapsed = stopWatch.Elapsed.TotalSeconds;
- logger.Information($"Created {archivePeriodStrLower} archive at {archiveFilePath} ({elapsed:F2} seconds)");
-
- // Keep the latest one, but delete the rest.
- for (var i = 1; i < latestFolders.Count; i++)
+ var backups = sortedBackups.Values;
+ if (backups.Count == 0)
{
- Directory.Delete(latestFolders[i], true);
+ continue;
}
- }
- else
- {
- logger.Warning($"Failed to create {archivePeriodStrLower} archive");
+
+ var stopWatch = new Stopwatch();
+ stopWatch.Start();
+
+ var fileName = $"{rangeStart.ToTimeStamp(archivePeriod)}{extension}";
+ var archiveFilePath = Path.Combine(archivePath, fileName);
+
+ var archiveCreated = CreateArchive(archiveFilePath, AutomaticBackupPath, backups);
+
+ if (archiveCreated)
+ {
+ var elapsed = stopWatch.Elapsed.TotalSeconds;
+ logger.Information($"Created {archivePeriodStrLower} archive at {archiveFilePath} ({elapsed:F2} seconds)");
+
+ var i = minimum;
+ foreach (var backup in backups)
+ {
+ // Keep the latest one, but delete the rest.
+ if (i-- > 0)
+ {
+ continue;
+ }
+
+ Directory.Delete(backup, true);
+ }
+ }
+ else
+ {
+ logger.Warning($"Failed to create {archivePeriodStrLower} archive");
+ }
+
+ stopWatch.Stop();
}
}
- private static IEnumerable GetInRange(
- IEnumerable allItems,
- DateTime rangeStart,
- DateTime rangeEnd,
- bool files = false
- )
+ private static bool CreateArchive(string archiveFilePath, string relativeTo, IEnumerable backups) =>
+ _compressionFormat == CompressionFormat.Zstd
+ ? ZstdArchive.CreateFromPaths(backups, archiveFilePath, relativeTo)
+ : TarArchive.CreateFromPaths(backups, archiveFilePath, relativeTo);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static string ToTimeStamp(this DateTime date, ArchivePeriod archivePeriod) =>
+ archivePeriod switch
+ {
+ ArchivePeriod.Monthly => date.ToString("yyyy-MM"),
+ ArchivePeriod.Daily => date.ToString("yyyy-MM-dd"),
+ ArchivePeriod.Hourly => date.ToString("yyyy-MM-dd-HH"),
+ _ => date.ToTimeStamp()
+ };
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static DateTime ArchivePeriodStart(this DateTime date, ArchivePeriod archivePeriod) =>
+ archivePeriod switch
+ {
+ ArchivePeriod.Monthly => date.Date.AddDays(-(date.Day - 1)),
+ ArchivePeriod.Daily => date.Date,
+ ArchivePeriod.Hourly => date.Date.AddHours(date.Hour),
+ _ => date
+ };
+
+ private static IEnumerable PathsByTimestampName(string path, bool files = false)
{
+ var allItems = files ? Directory.GetFiles(path) : Directory.GetDirectories(path);
var items = new SortedDictionary(new DescendingComparer());
foreach (var item in allItems)
{
@@ -333,14 +388,14 @@ namespace Server.Saves
if (files)
{
var fileName = new FileInfo(item).Name;
- name = fileName[..fileName.IndexOfOrdinal(".")];
+ name = fileName[..fileName.IndexOf('.')];
}
else
{
name = new DirectoryInfo(item).Name;
}
- if (IsInRange(name, rangeStart, rangeEnd, out var date))
+ if (TryGetDate(name, out var date))
{
items.Add(date, item);
}
@@ -349,38 +404,41 @@ namespace Server.Saves
return items.Values;
}
- private static bool IsInRange(string name, DateTime start, DateTime end, out DateTime date) =>
- TryGetDate(name, out date) && start <= date && end >= date;
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryGetDate(string value, out DateTime date)
{
- // Expecting YYYY-MM-DD-HH-mm-ss
- var split = value.Split("-");
- if (split.Length != 6)
- {
- date = DateTime.MinValue;
- return false;
- }
+ Span parts = stackalloc int[] { 0, 1, 1, 0, 0, 0 };
try
{
+ var i = 0;
+ foreach (var part in value.Tokenize('-'))
+ {
+ parts[i++] = int.Parse(part);
+ }
+
+ if (i == 0)
+ {
+ date = DateTime.MinValue;
+ return false;
+ }
+
date = new DateTime(
- int.Parse(split[0]),
- int.Parse(split[1]),
- int.Parse(split[2]),
- int.Parse(split[3]),
- int.Parse(split[4]),
- int.Parse(split[5])
+ parts[0],
+ parts[1],
+ parts[2],
+ parts[3],
+ parts[4],
+ parts[5],
+ DateTimeKind.Utc
);
+ return true;
}
catch
{
+ logger.Warning($"Path was not in the correct date format: {value}");
date = DateTime.MinValue;
return false;
}
-
- return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
From ef761f90357614552f121f458880249a2ec9cbe6 Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Tue, 5 Oct 2021 09:07:32 -0700
Subject: [PATCH 32/42] fix(build): Updates releases to use github token (#817)
---
.github/workflows/create-release.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
index a74350987..f08889d15 100644
--- a/.github/workflows/create-release.yml
+++ b/.github/workflows/create-release.yml
@@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
- token: ${{ secrets.ACCESS_TOKEN }}
+ token: ${{ secrets.GITHUB_TOKEN }}
- name: Install NGBV
uses: dotnet/nbgv@master
id: nbgv
From f3b722b9d6e2757c22b6d9857cbc89e12c033e81 Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Tue, 5 Oct 2021 20:06:00 -0700
Subject: [PATCH 33/42] fix: Fixes epoll calls on linux (#818)
---
Projects/Server/Network/PollGroup/EPollGroup.cs | 8 ++++----
Projects/UOContent/World Saves/AutoArchive.cs | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/Projects/Server/Network/PollGroup/EPollGroup.cs b/Projects/Server/Network/PollGroup/EPollGroup.cs
index bf615e4cc..ff00b44b2 100755
--- a/Projects/Server/Network/PollGroup/EPollGroup.cs
+++ b/Projects/Server/Network/PollGroup/EPollGroup.cs
@@ -93,16 +93,16 @@ namespace Server.Network
private static class Linux
{
- [DllImport("libc", SetLastError = true)]
+ [DllImport("libc.so.6", SetLastError = true)]
public static extern int epoll_create1(epoll_flags flags);
- [DllImport("libc", SetLastError = true)]
+ [DllImport("libc.so.6", SetLastError = true)]
public static extern int epoll_close(int epfd);
- [DllImport("libc", SetLastError = true)]
+ [DllImport("libc.so.6", SetLastError = true)]
public static extern int epoll_ctl(int epfd, epoll_op op, int fd, ref epoll_event ee);
- [DllImport("libc", SetLastError = true)]
+ [DllImport("libc.so.6", SetLastError = true)]
public static extern int epoll_wait(int epfd, [In, Out] epoll_event[] ee, int maxevents, int timeout);
}
diff --git a/Projects/UOContent/World Saves/AutoArchive.cs b/Projects/UOContent/World Saves/AutoArchive.cs
index fc839e830..ed4dd4afd 100755
--- a/Projects/UOContent/World Saves/AutoArchive.cs
+++ b/Projects/UOContent/World Saves/AutoArchive.cs
@@ -380,7 +380,7 @@ namespace Server.Saves
private static IEnumerable PathsByTimestampName(string path, bool files = false)
{
- var allItems = files ? Directory.GetFiles(path) : Directory.GetDirectories(path);
+ var allItems = files ? Directory.EnumerateFiles(path) : Directory.GetDirectories(path);
var items = new SortedDictionary(new DescendingComparer());
foreach (var item in allItems)
{
From 05ff3f2ad9d900714298df24b4cec73a307d6b16 Mon Sep 17 00:00:00 2001
From: Jabin <1094679+jabinb@users.noreply.github.com>
Date: Thu, 7 Oct 2021 01:45:05 +1100
Subject: [PATCH 34/42] Fix: epoll on linux (#820)
---
Projects/Server/Network/PollGroup/EPollGroup.cs | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/Projects/Server/Network/PollGroup/EPollGroup.cs b/Projects/Server/Network/PollGroup/EPollGroup.cs
index ff00b44b2..60b8d594f 100755
--- a/Projects/Server/Network/PollGroup/EPollGroup.cs
+++ b/Projects/Server/Network/PollGroup/EPollGroup.cs
@@ -69,10 +69,12 @@ namespace Server.Network
public ulong u64;
}
- [StructLayout(LayoutKind.Sequential)]
+ [StructLayout(LayoutKind.Explicit)]
private struct epoll_event
{
+ [FieldOffset(0)]
public epoll_events events;
+ [FieldOffset(4)]
public epoll_data data;
}
@@ -93,16 +95,16 @@ namespace Server.Network
private static class Linux
{
- [DllImport("libc.so.6", SetLastError = true)]
+ [DllImport("libc", SetLastError = true)]
public static extern int epoll_create1(epoll_flags flags);
- [DllImport("libc.so.6", SetLastError = true)]
+ [DllImport("libc", SetLastError = true)]
public static extern int epoll_close(int epfd);
- [DllImport("libc.so.6", SetLastError = true)]
+ [DllImport("libc", SetLastError = true)]
public static extern int epoll_ctl(int epfd, epoll_op op, int fd, ref epoll_event ee);
- [DllImport("libc.so.6", SetLastError = true)]
+ [DllImport("libc", SetLastError = true)]
public static extern int epoll_wait(int epfd, [In, Out] epoll_event[] ee, int maxevents, int timeout);
}
From 81486772f63e3c9db30a6ddb250a194da90566f7 Mon Sep 17 00:00:00 2001
From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com>
Date: Sat, 9 Oct 2021 11:55:31 -0700
Subject: [PATCH 35/42] fix: Fixes networking (#822)
* Fixes wepoll to be POSIX compliant.
---
.../Helpers/PacketTestUtilities.cs | 18 +---
.../Packets/Outgoing/LightPacketTests.cs | 2 +-
Projects/Server/Network/NetState/NetState.cs | 14 ++--
Projects/Server/Network/NetworkSocket.cs | 77 ------------------
.../Server/Network/PollGroup/EPollGroup.cs | 61 ++++++--------
.../Network/PollGroup/KQueuePollGroup.cs | 31 +++----
.../Server/Network/PollGroup/PollGroup.cs | 8 +-
Projects/Server/Network/TcpServer.cs | 2 +-
Projects/Server/wepoll.dll | Bin 21504 -> 21504 bytes
Projects/UOContent/Misc/ServerList.cs | 8 +-
10 files changed, 60 insertions(+), 161 deletions(-)
delete mode 100644 Projects/Server/Network/NetworkSocket.cs
diff --git a/Projects/Server.Tests/Helpers/PacketTestUtilities.cs b/Projects/Server.Tests/Helpers/PacketTestUtilities.cs
index b90c457e9..afcbc6423 100644
--- a/Projects/Server.Tests/Helpers/PacketTestUtilities.cs
+++ b/Projects/Server.Tests/Helpers/PacketTestUtilities.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections.Generic;
using System.Net.Sockets;
-using Moq;
using Server.Network;
namespace Server.Tests.Network
@@ -11,18 +9,8 @@ namespace Server.Tests.Network
public static Span Compile(this Packet p) =>
p.Compile(false, out var length).AsSpan(0, length);
- public static NetState CreateTestNetState()
- {
- var socket = new Mock