-
Notifications
You must be signed in to change notification settings - Fork 4
/
VaultUtils.cs
1373 lines (1245 loc) · 60.7 KB
/
VaultUtils.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ReLogic.Content;
using Steamworks;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using Terraria;
using Terraria.Audio;
using Terraria.Chat;
using Terraria.DataStructures;
using Terraria.GameContent;
using Terraria.GameContent.ItemDropRules;
using Terraria.GameInput;
using Terraria.ID;
using Terraria.Localization;
using Terraria.ModLoader;
using Terraria.ModLoader.Core;
using Terraria.ObjectData;
using Terraria.Social;
namespace InnoVault
{
/// <summary>
/// 一个通用的方法库
/// </summary>
public static class VaultUtils
{
#region Math
/// <summary>
/// 二次缓动的入场效果
/// </summary>
/// <remarks>该曲线呈加速状态</remarks>
public static float EaseQuadIn(float x) => x * x;
/// <summary>
/// 二次缓动的出场效果
/// </summary>
/// <remarks>该曲线呈减速状态</remarks>
public static float EaseQuadOut(float x) => 1f - EaseQuadIn(1f - x);
/// <summary>
/// 二次缓动的入场和出场效果
/// </summary>
/// <remarks>前半段为加速,后半段为减速</remarks>
public static float EaseQuadInOut(float x) => (x < 0.5f) ? 2f * x * x : -2f * x * x + 4f * x - 1f;
/// <summary>
/// 三次缓动的入场效果
/// </summary>
/// <remarks>该曲线呈加速状态</remarks>
public static float EaseCubicIn(float x) => x * x * x;
/// <summary>
/// 三次缓动的出场效果
/// </summary>
/// <remarks>该曲线呈减速状态</remarks>
public static float EaseCubicOut(float x) => 1f - EaseCubicIn(1f - x);
/// <summary>
/// 三次缓动的入场和出场效果
/// </summary>
/// <remarks>前半段为加速,后半段为减速</remarks>
public static float EaseCubicInOut(float x) => (x < 0.5f) ? 4f * x * x * x : 4f * x * x * x - 12f * x * x + 12f * x - 3f;
/// <summary>
/// 四次缓动的入场效果
/// </summary>
/// <remarks>该曲线呈加速状态</remarks>
public static float EaseQuarticIn(float x) => x * x * x * x;
/// <summary>
/// 四次缓动的出场效果
/// </summary>
/// <remarks>该曲线呈减速状态</remarks>
public static float EaseQuarticOut(float x) => 1f - EaseQuarticIn(1f - x);
/// <summary>
/// 四次缓动的入场和出场效果
/// </summary>
/// <remarks>前半段为加速,后半段为减速</remarks>
public static float EaseQuarticInOut(float x) => (x < 0.5f) ? 8f * x * x * x * x : -8f * x * x * x * x + 32f * x * x * x - 48f * x * x + 32f * x - 7f;
/// <summary>
/// 五次缓动的入场效果
/// </summary>
/// <remarks>该曲线呈加速状态</remarks>
public static float EaseQuinticIn(float x) => x * x * x * x * x;
/// <summary>
/// 五次缓动的出场效果
/// </summary>
/// <remarks>该曲线呈减速状态</remarks>
public static float EaseQuinticOut(float x) => 1f - EaseQuinticIn(1f - x);
/// <summary>
/// 五次缓动的入场和出场效果
/// </summary>
/// <remarks>前半段为加速,后半段为减速</remarks>
public static float EaseQuinticInOut(float x) => (x < 0.5f) ? 16f * x * x * x * x * x : 16f * x * x * x * x * x - 80f * x * x * x * x + 160f * x * x * x - 160f * x * x + 80f * x - 15f;
/// <summary>
/// 圆形缓动的入场效果
/// </summary>
/// <remarks>该曲线模拟圆形运动,呈加速状态</remarks>
public static float EaseCircularIn(float x) => 1f - (float)Math.Sqrt(1.0 - Math.Pow(x, 2));
/// <summary>
/// 圆形缓动的出场效果
/// </summary>
/// <remarks>该曲线模拟圆形运动,呈减速状态</remarks>
public static float EaseCircularOut(float x) => (float)Math.Sqrt(1.0 - Math.Pow(x - 1.0, 2));
/// <summary>
/// 圆形缓动的入场和出场效果
/// </summary>
/// <remarks>前半段为加速,后半段为减速,模拟圆形运动</remarks>
public static float EaseCircularInOut(float x) => (x < 0.5f) ? (1f - (float)Math.Sqrt(1.0 - Math.Pow(x * 2, 2))) * 0.5f : (float)((Math.Sqrt(1.0 - Math.Pow(-2 * x + 2, 2)) + 1) * 0.5);
/// <summary>
/// 两点间简略取值
/// </summary>
/// <param name="vr1"></param>
/// <param name="vr2"></param>
/// <returns></returns>
public static Vector2 To(this Vector2 vr1, Vector2 vr2) => vr2 - vr1;
/// <summary>
/// 简单安全的获取一个单位向量,如果出现非法情况则会返回 <see cref="Vector2.Zero"/>
/// </summary>
public static Vector2 UnitVector(this Vector2 vr) => vr.SafeNormalize(Vector2.Zero);
/// <summary>
/// 获取一个垂直于该向量的单位向量
/// </summary>
public static Vector2 GetNormalVector(this Vector2 vr) {
Vector2 nVr = new(vr.Y, -vr.X);
return Vector2.Normalize(nVr);
}
/// <summary>
/// 色彩混合
/// </summary>
/// <param name="percent"></param>
/// <param name="colors"></param>
/// <returns></returns>
public static Color MultiStepColorLerp(float percent, params Color[] colors) {
if (colors == null) {
Text("MultiLerpColor: 空的颜色数组!");
return Color.White;
}
float per = 1f / (colors.Length - 1f);
float total = per;
int currentID = 0;
while (percent / total > 1f && currentID < colors.Length - 2) {
total += per;
currentID++;
}
return Color.Lerp(colors[currentID], colors[currentID + 1], (percent - (per * currentID)) / per);
}
#endregion
#region System
/// <summary>
/// 生成乱码字符串
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
public static string GenerateRandomString(int length) {
const string characters = "!@#$%^&*()-_=+[]{}|;:'\",.<>/?`~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
char[] result = new char[length];
for (int i = 0; i < length; i++) {
result[i] = characters[Main.rand.Next(characters.Length)];
}
return new string(result);
}
/// <summary>
/// 根据当前环境实现网页重定向如果是在Steam环境中,则会通过Steam的内置浏览器打开网页,
/// 否则使用默认浏览器打开指定的URL
/// </summary>
/// <param name="str">要重定向的网页URL</param>
/// <param name="inSteam">是否在Steam环境下打开(默认为true)</param>
public static void WebRedirection(this string str, bool inSteam = true) {
if (SocialAPI.Mode == SocialMode.Steam && inSteam) {
// 如果当前运行环境为Steam,并且指定在Steam中打开,则使用Steam内置浏览器
SteamFriends.ActivateGameOverlayToWebPage(str);
}
else {
// 否则使用系统默认浏览器打开网页
Utils.OpenToURL(str);
}
}
/// <summary>
/// 在给定的 Mod 数组中查找包含指定类型的 Mod 实例
/// </summary>
/// <param name="type">要查找的类型</param>
/// <param name="mods">Mod 实例的数组,用于搜索包含该类型的 Mod</param>
/// <returns>如果找到包含该类型的 Mod,返回对应的 Mod 实例;否则返回 null</returns>
public static Mod FindModByType(Type type, Mod[] mods) {
foreach (var mod in mods) {
Type[] fromModCodeTypes = AssemblyManager.GetLoadableTypes(mod.Code);
if (fromModCodeTypes.Contains(type)) {
return mod;
}
}
return null;
}
/// <summary>
/// 尝试将指定类型与其所属的 Mod 映射关系添加到字典中
/// 如果无法找到与该类型对应的 Mod,会抛出异常
/// </summary>
/// <param name="typeByModHas">存储类型到 Mod 映射关系的字典</param>
/// <param name="type">需要映射的类型</param>
/// <param name="mods">当前已加载的所有 Mod 列表</param>
/// <exception cref="ArgumentNullException">当无法找到与类型对应的 Mod 时,抛出异常</exception>
public static void AddTypeModAssociation(Dictionary<Type, Mod> typeByModHas, Type type, Mod[] mods) {
Mod mod = FindModByType(type, mods);
if (mod != null) {
typeByModHas.Add(type, mod);
}
else {
string errorText = "试图添加其所属模组映射时,所属模组为Null";
string errorText2 = "Attempted to add its associated mod mapping, but the associated mod is null";
throw new ArgumentNullException(nameof(type), Translation(errorText, errorText2));
}
}
/// <summary>
/// 获取所有已加载Mod的代码中的所有类型返回一个包含所有类型的数组,
/// 可以用于反射操作或动态类型检测
/// </summary>
/// <returns>所有Mod代码中的可加载类型数组</returns>
public static Type[] GetAnyModCodeType() {
// 创建一个存储所有类型的列表
List<Type> types = new List<Type>();
Mod[] mods = ModLoader.Mods;
// 使用 LINQ 将每个Mod的代码程序集中的所有可加载类型平铺到一个集合中
types.AddRange(mods.SelectMany(mod => AssemblyManager.GetLoadableTypes(mod.Code)));
return types.ToArray();
}
/// <summary>
/// 获取指定基类的所有子类列表
/// </summary>
/// <param name="baseType">基类的类型</param>
/// <returns>子类列表</returns>
public static List<Type> GetSubclassTypeList(Type baseType) {
List<Type> subclasses = [];
Type[] allTypes = GetAnyModCodeType();
foreach (Type type in allTypes) {
if (type.IsClass && !type.IsAbstract && baseType.IsAssignableFrom(type)) {
subclasses.Add(type);
}
}
return subclasses;
}
/// <summary>
/// 根据给定的类型列表,创建符合条件的类型实例,并将实例添加到输出列表中,该方法默认要求类型拥有无参构造
/// </summary>
public static List<T> GetSubclassInstances<T>(bool parameterless = true) {
List<Type> inTypes = GetSubclassTypeList(typeof(T));
List<T> outInds = [];
foreach (Type type in inTypes) {
if (type != typeof(T)) {
object obj = parameterless ? Activator.CreateInstance(type) : RuntimeHelpers.GetUninitializedObject(type);
if (obj is T inds) {
outInds.Add(inds);
}
}
}
return outInds;
}
/// <summary>
/// 获取当前程序集(Assembly)中实现了指定接口(通过接口名称 `lname` 指定)的所有类的实例列表
/// </summary>
/// <typeparam name="T">接口类型,用于检查类是否实现该接口</typeparam>
/// <returns>一个包含所有实现了指定接口的类实例的列表</returns>
public static List<T> GetSubInterface<T>() {
string lname = typeof(T).Name;
List<T> subInterface = new List<T>();
Type[] allTypes = GetAnyModCodeType();
foreach (Type type in allTypes) {
if (type.IsClass && !type.IsAbstract && type.GetInterface(lname) != null) {
object obj = RuntimeHelpers.GetUninitializedObject(type);
if (obj is T instance) {
subInterface.Add(instance);
}
}
}
return subInterface;
}
#endregion
#region Game
/// <summary>
/// 让一个射弹安全的对应到弹药物品
/// </summary>
public static Dictionary<int, int> ProjectileToSafeAmmoMap { get; private set; } = new Dictionary<int, int>() {
{ ProjectileID.BoneArrow, ItemID.BoneArrow},
{ ProjectileID.MoonlordArrow, ItemID.MoonlordArrow},
{ ProjectileID.ChlorophyteArrow, ItemID.ChlorophyteArrow},
{ ProjectileID.CursedArrow, ItemID.CursedArrow},
{ ProjectileID.FlamingArrow, ItemID.FlamingArrow},
{ ProjectileID.FrostburnArrow, ItemID.FrostburnArrow},
{ ProjectileID.HellfireArrow, ItemID.HellfireArrow},
{ ProjectileID.HolyArrow, ItemID.HolyArrow},
{ ProjectileID.IchorArrow, ItemID.IchorArrow},
{ ProjectileID.JestersArrow, ItemID.JestersArrow},
{ ProjectileID.ShimmerArrow, ItemID.ShimmerArrow},
{ ProjectileID.UnholyArrow, ItemID.UnholyArrow},
{ ProjectileID.VenomArrow, ItemID.VenomArrow},
{ ProjectileID.WoodenArrowFriendly, ItemID.WoodenArrow},
{ ProjectileID.ChumBucket, ItemID.ChumBucket},
{ ProjectileID.ChlorophyteBullet, ItemID.ChlorophyteBullet},
{ ProjectileID.CrystalBullet, ItemID.CrystalBullet},
{ ProjectileID.CursedBullet, ItemID.CursedBullet},
{ ProjectileID.ExplosiveBullet, ItemID.ExplodingBullet},
{ ProjectileID.GoldenBullet, ItemID.GoldenBullet},
{ ProjectileID.BulletHighVelocity, ItemID.HighVelocityBullet},
{ ProjectileID.IchorBullet, ItemID.IchorBullet},
{ ProjectileID.MoonlordBullet, ItemID.MoonlordBullet},
{ ProjectileID.MeteorShot, ItemID.MeteorShot},
{ ProjectileID.Bullet, ItemID.MusketBall},
{ ProjectileID.NanoBullet, ItemID.NanoBullet},
{ ProjectileID.PartyBullet, ItemID.PartyBullet},
{ ProjectileID.SilverBullet, ItemID.SilverBullet},
{ ProjectileID.VenomBullet, ItemID.VenomBullet},
{ ProjectileID.SnowBallFriendly, ItemID.Snowball},
};
/// <summary>
/// 获取生成源
/// </summary>
public static IEntitySource FromObjectGetParent(this object obj) {
if (obj is Projectile projectile) {
return projectile.GetSource_FromAI();
}
if (obj is NPC nPC) {
return nPC.GetSource_FromAI();
}
if (obj is Player player) {
return player.GetSource_FromAI();
}
if (obj is Item item) {
return item.GetSource_FromAI();
}
return new EntitySource_Parent(Main.LocalPlayer, "NullSource");
}
/// <summary>
/// 检测玩家是否有效且正常存活
/// </summary>
/// <returns>返回 true 表示活跃,返回 false 表示为空或者已经死亡的非活跃状态</returns>
public static bool Alives(this Player player) => player != null && player.active && !player.dead;
/// <summary>
/// 检测弹幕是否有效且正常存活
/// </summary>
/// <returns>返回 true 表示活跃,返回 false 表示为空或者已经死亡的非活跃状态</returns>
public static bool Alives(this Projectile projectile) => projectile != null && projectile.active && projectile.timeLeft > 0;
/// <summary>
/// 检测NPC是否有效且正常存活
/// </summary>
/// <returns>返回 true 表示活跃,返回 false 表示为空或者已经死亡的非活跃状态</returns>
public static bool Alives(this NPC npc) => npc != null && npc.active && npc.timeLeft > 0;
/// <summary>
/// 检查指定玩家是否按下了鼠标键
/// </summary>
/// <param name="player">要检查的玩家</param>
/// <param name="leftCed">是否检查左鼠标键,否则检测右鼠标键</param>
/// <param name="netCed">是否进行网络同步检查</param>
/// <returns>如果按下了指定的鼠标键,则返回true,否则返回false</returns>
public static bool PressKey(this Player player, bool leftCed = true, bool netCed = true) {
return (!netCed || Main.myPlayer == player.whoAmI) && (leftCed ? PlayerInput.Triggers.Current.MouseLeft : PlayerInput.Triggers.Current.MouseRight);
}
/// <summary>
/// 让一个NPC可以正常的掉落物品而不触发其他死亡事件,只应该在非服务端上调用该方法
/// </summary>
/// <param name="npc"></param>
public static void DropItem(this NPC npc) {
DropAttemptInfo dropAttemptInfo = default;
dropAttemptInfo.player = Main.LocalPlayer;
dropAttemptInfo.npc = npc;
dropAttemptInfo.IsExpertMode = Main.expertMode;
dropAttemptInfo.IsMasterMode = Main.masterMode;
dropAttemptInfo.IsInSimulation = false;
dropAttemptInfo.rng = Main.rand;
DropAttemptInfo info = dropAttemptInfo;
Main.ItemDropSolver.TryDropping(info);
}
/// <summary>
/// 在游戏中发送文本消息
/// </summary>
/// <param name="message">要发送的消息文本</param>
/// <param name="colour">(可选)消息的颜色,默认为 null</param>
public static void Text(string message, Color? colour = null) {
Color newColor = (Color)(colour == null ? Color.White : colour);
if (Main.netMode == NetmodeID.Server) {
ChatHelper.BroadcastChatMessage(NetworkText.FromLiteral(message), (Color)(colour == null ? Color.White : colour));
return;
}
Main.NewText(message, newColor);
}
/// <summary>
/// 发送游戏文本并记录日志内容
/// </summary>
/// <param name="obj"></param>
/// <param name="mod"></param>
public static void LoggerDomp(this object obj, Mod mod = null) {
string text;
if (obj == null) {
text = "ERROR is Null";
}
else {
text = obj.ToString();
}
Text(text);
if (mod != null) {
mod.Logger.Info(text);
}
else {
VaultMod.Instance.Logger.Info(text);
}
}
/// <summary>
/// 一个根据语言选项返回字符的方法
/// </summary>
public static string Translation(string Chinese = null, string English = null, string Spanish = null, string Russian = null) {
string text = default(string);
if (English == null) {
English = "Invalid Character";
}
switch (Language.ActiveCulture.LegacyId) {
case (int)GameCulture.CultureName.Chinese:
text = Chinese;
break;
case (int)GameCulture.CultureName.Russian:
text = Russian;
break;
case (int)GameCulture.CultureName.Spanish:
text = Spanish;
break;
case (int)GameCulture.CultureName.English:
text = English;
break;
default:
text = English;
break;
}
return text;
}
/// <summary>
/// 销毁关于傀儡的物块结构
/// </summary>
/// <param name="point16">输入这个傀儡所占图格的任意一点</param>
public static void KillPuppet(Point16 point16) {
if (SafeGetTopLeft(point16.X, point16.Y, out Point16 tilePos)) {
TileObjectData data = TileObjectData.GetTileData(Main.tile[point16]);
for (int i = 0; i < data.Width; i++) {
for (int j = 0; j < data.Height; j++) {
Point16 newPoint = point16 + new Point16(i, j);
Tile newTile = Main.tile[newPoint];
if (newTile.TileType == 378) {
newTile.TileType = 0;
newTile.HasTile = false;
WorldGen.SquareTileFrame(newPoint.X, newPoint.Y);
}
}
}
}
}
/// <summary>
/// 获取玩家对象一个稳定的中心位置,考虑斜坡矫正与坐骑矫正,适合用于处理手持弹幕的位置获取
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
public static Vector2 GetPlayerStabilityCenter(this Player player) => player.MountedCenter.Floor() + new Vector2(0, player.gfxOffY);
/// <summary>
/// 计算并获取物品的前缀附加属性
/// 根据物品的前缀ID,确定前缀所提供的各种属性加成,包括伤害倍率、击退倍率、使用时间倍率、尺寸倍率、射速倍率、法力消耗倍率以及暴击加成,
/// 并根据这些加成计算出前缀的总体强度。对于模组的前缀,使用自定义的逻辑处理属性加成
/// </summary>
/// <param name="item">带有前缀的物品实例。</param>
/// <returns>
/// 返回包含前缀附加属性的结构体<see cref="PrefixState"/>,
/// 该结构体中包括前缀ID以及计算得到的属性加成与前缀强度
/// </returns>
public static PrefixState GetPrefixState(this Item item) {
int prefixID = item.prefix;
PrefixState additionStruct = new PrefixState();
float strength;
float damageMult = 1f;
float knockbackMult = 1f;
float useTimeMult = 1f;
float scaleMult = 1f;
float shootSpeedMult = 1f;
float manaMult = 1f;
int critBonus = 0;
if (prefixID >= PrefixID.Count && prefixID < PrefixLoader.PrefixCount) {
additionStruct.isModPreFix = true;
PrefixLoader.GetPrefix(prefixID).SetStats(ref damageMult, ref knockbackMult
, ref useTimeMult, ref scaleMult, ref shootSpeedMult, ref manaMult, ref critBonus);
}
else {
additionStruct.isModPreFix = false;
switch (prefixID) {
case 1:
scaleMult = 1.12f;
break;
case 2:
scaleMult = 1.18f;
break;
case 3:
damageMult = 1.05f;
critBonus = 2;
scaleMult = 1.05f;
break;
case 4:
damageMult = 1.1f;
scaleMult = 1.1f;
knockbackMult = 1.1f;
break;
case 5:
damageMult = 1.15f;
break;
case 6:
damageMult = 1.1f;
break;
case 81:
knockbackMult = 1.15f;
damageMult = 1.15f;
critBonus = 5;
useTimeMult = 0.9f;
scaleMult = 1.1f;
break;
case 7:
scaleMult = 0.82f;
break;
case 8:
knockbackMult = 0.85f;
damageMult = 0.85f;
scaleMult = 0.87f;
break;
case 9:
scaleMult = 0.9f;
break;
case 10:
damageMult = 0.85f;
break;
case 11:
useTimeMult = 1.1f;
knockbackMult = 0.9f;
scaleMult = 0.9f;
break;
case 12:
knockbackMult = 1.1f;
damageMult = 1.05f;
scaleMult = 1.1f;
useTimeMult = 1.15f;
break;
case 13:
knockbackMult = 0.8f;
damageMult = 0.9f;
scaleMult = 1.1f;
break;
case 14:
knockbackMult = 1.15f;
useTimeMult = 1.1f;
break;
case 15:
knockbackMult = 0.9f;
useTimeMult = 0.85f;
break;
case 16:
damageMult = 1.1f;
critBonus = 3;
break;
case 17:
useTimeMult = 0.85f;
shootSpeedMult = 1.1f;
break;
case 18:
useTimeMult = 0.9f;
shootSpeedMult = 1.15f;
break;
case 19:
knockbackMult = 1.15f;
shootSpeedMult = 1.05f;
break;
case 20:
knockbackMult = 1.05f;
shootSpeedMult = 1.05f;
damageMult = 1.1f;
useTimeMult = 0.95f;
critBonus = 2;
break;
case 21:
knockbackMult = 1.15f;
damageMult = 1.1f;
break;
case 82:
knockbackMult = 1.15f;
damageMult = 1.15f;
critBonus = 5;
useTimeMult = 0.9f;
shootSpeedMult = 1.1f;
break;
case 22:
knockbackMult = 0.9f;
shootSpeedMult = 0.9f;
damageMult = 0.85f;
break;
case 23:
useTimeMult = 1.15f;
shootSpeedMult = 0.9f;
break;
case 24:
useTimeMult = 1.1f;
knockbackMult = 0.8f;
break;
case 25:
useTimeMult = 1.1f;
damageMult = 1.15f;
critBonus = 1;
break;
case 58:
useTimeMult = 0.85f;
damageMult = 0.85f;
break;
case 26:
manaMult = 0.85f;
damageMult = 1.1f;
break;
case 27:
manaMult = 0.85f;
break;
case 28:
manaMult = 0.85f;
damageMult = 1.15f;
knockbackMult = 1.05f;
break;
case 83:
knockbackMult = 1.15f;
damageMult = 1.15f;
critBonus = 5;
useTimeMult = 0.9f;
manaMult = 0.9f;
break;
case 29:
manaMult = 1.1f;
break;
case 30:
manaMult = 1.2f;
damageMult = 0.9f;
break;
case 31:
knockbackMult = 0.9f;
damageMult = 0.9f;
break;
case 32:
manaMult = 1.15f;
damageMult = 1.1f;
break;
case 33:
manaMult = 1.1f;
knockbackMult = 1.1f;
useTimeMult = 0.9f;
break;
case 34:
manaMult = 0.9f;
knockbackMult = 1.1f;
useTimeMult = 1.1f;
damageMult = 1.1f;
break;
case 35:
manaMult = 1.2f;
damageMult = 1.15f;
knockbackMult = 1.15f;
break;
case 52:
manaMult = 0.9f;
damageMult = 0.9f;
useTimeMult = 0.9f;
break;
case 84:
knockbackMult = 1.17f;
damageMult = 1.17f;
critBonus = 8;
break;
case 36:
critBonus = 3;
break;
case 37:
damageMult = 1.1f;
critBonus = 3;
knockbackMult = 1.1f;
break;
case 38:
knockbackMult = 1.15f;
break;
case 53:
damageMult = 1.1f;
break;
case 54:
knockbackMult = 1.15f;
break;
case 55:
knockbackMult = 1.15f;
damageMult = 1.05f;
break;
case 59:
knockbackMult = 1.15f;
damageMult = 1.15f;
critBonus = 5;
break;
case 60:
damageMult = 1.15f;
critBonus = 5;
break;
case 61:
critBonus = 5;
break;
case 39:
damageMult = 0.7f;
knockbackMult = 0.8f;
break;
case 40:
damageMult = 0.85f;
break;
case 56:
knockbackMult = 0.8f;
break;
case 41:
knockbackMult = 0.85f;
damageMult = 0.9f;
break;
case 57:
knockbackMult = 0.9f;
damageMult = 1.18f;
break;
case 42:
useTimeMult = 0.9f;
break;
case 43:
damageMult = 1.1f;
useTimeMult = 0.9f;
break;
case 44:
useTimeMult = 0.9f;
critBonus = 3;
break;
case 45:
useTimeMult = 0.95f;
break;
case 46:
critBonus = 3;
useTimeMult = 0.94f;
damageMult = 1.07f;
break;
case 47:
useTimeMult = 1.15f;
break;
case 48:
useTimeMult = 1.2f;
break;
case 49:
useTimeMult = 1.08f;
break;
case 50:
damageMult = 0.8f;
useTimeMult = 1.15f;
break;
case 51:
knockbackMult = 0.9f;
useTimeMult = 0.9f;
damageMult = 1.05f;
critBonus = 2;
break;
}
}
strength = 1f * damageMult * (2f - useTimeMult) * (2f - manaMult) * scaleMult
* knockbackMult * shootSpeedMult * (1f + critBonus * 0.02f);
if (prefixID == 62 || prefixID == 69 || prefixID == 73 || prefixID == 77)
strength *= 1.05f;
if (prefixID == 63 || prefixID == 70 || prefixID == 74 || prefixID == 78 || prefixID == 67)
strength *= 1.1f;
if (prefixID == 64 || prefixID == 71 || prefixID == 75 || prefixID == 79 || prefixID == 66)
strength *= 1.15f;
if (prefixID == 65 || prefixID == 72 || prefixID == 76 || prefixID == 80 || prefixID == 68)
strength *= 1.2f;
additionStruct.prefixID = prefixID;
additionStruct.damageMult = damageMult;
additionStruct.knockbackMult = knockbackMult;
additionStruct.useTimeMult = useTimeMult;
additionStruct.scaleMult = scaleMult;
additionStruct.shootSpeedMult = shootSpeedMult;
additionStruct.manaMult = manaMult;
additionStruct.critBonus = critBonus;
additionStruct.strength = strength;
return additionStruct;
}
/// <summary>
/// 获取玩家当前射击状态,包括武器的伤害、击退、弹药类型等信息
/// 该方法根据玩家当前装备的武器以及所用的弹药类型来计算并返回完整的射击状态
/// </summary>
/// <param name="player">玩家实例,代表调用该方法的玩家对象</param>
/// <param name="shootKey">一个可选的标识符,用于区分不同的射击事件,默认为 "Null" 表示不使用特定键值</param>
/// <returns>返回一个包含射击相关信息的 <see cref="ShootState"/> 结构体</returns>
public static ShootState GetShootState(this Player player, string shootKey = "Null") {
ShootState shootState = new();
Item item = player.GetItem();
if (item.useAmmo == AmmoID.None) {
shootState.WeaponDamage = player.GetWeaponDamage(item);
shootState.WeaponKnockback = item.knockBack;
shootState.AmmoTypes = item.shoot;
shootState.ShootSpeed = item.shootSpeed;
shootState.UseAmmoItemType = ItemID.None;
shootState.HasAmmo = false;
if (shootState.AmmoTypes == 0 || shootState.AmmoTypes == 10) {
shootState.AmmoTypes = ProjectileID.Bullet;
}
return shootState;
}
shootState.HasAmmo = player.PickAmmo(item, out shootState.AmmoTypes, out shootState.ShootSpeed
, out shootState.WeaponDamage, out shootState.WeaponKnockback, out shootState.UseAmmoItemType, true);
if (shootKey == "Null") {
shootKey = null;
}
shootState.Source = new EntitySource_ItemUse_WithAmmo(player, item, shootState.UseAmmoItemType, shootKey);
return shootState;
}
/// <summary>
/// 获取玩家当前的弹药状态,包括所有有效的弹药类型、对应的物品、以及物品的数量等信息
/// </summary>
/// <param name="player">玩家实例,代表调用该方法的玩家对象</param>
/// <param name="assignAmmoType">指定的弹药类型,默认为 0,表示不限制弹药类型。如果指定某个类型,则只返回该类型的弹药</param>
/// <param name="numSort">是否对弹药物品进行排序,默认为 false。如果为 true,则按照物品堆叠数量降序排序</param>
/// <returns>返回一个包含当前弹药状态信息的 <see cref="AmmoState"/> 结构体</returns>
public static AmmoState GetAmmoState(this Player player, int assignAmmoType = 0, bool numSort = false) {
AmmoState ammoState = new();
int num = 0; // 当前弹药总数量
List<Item> itemInds = new(); // 存储有效的弹药物品
List<int> itemTypes = new(); // 存储弹药物品的类型
List<int> itemShootTypes = new(); // 存储弹药发射类型(即射击类型)
// 遍历玩家背包中的所有物品
foreach (Item item in player.inventory) {
// 跳过没有弹药的物品
if (item.ammo == AmmoID.None) {
continue;
}
// 如果指定了弹药类型并且物品的弹药类型不匹配,跳过该物品
if (assignAmmoType != 0 && item.ammo != assignAmmoType) {
continue;
}
// 添加物品类型、射击类型及堆叠数量
itemTypes.Add(item.type);
itemShootTypes.Add(item.shoot);
num += item.stack; // 累加物品的堆叠数量
}
// 遍历玩家快捷栏中的物品(位置从54到57)
for (int i = 54; i < 58; i++) {
Item item = player.inventory[i];
// 如果指定了弹药类型并且物品的弹药类型不匹配,或者物品没有弹药,跳过
if ((assignAmmoType != 0 && item.ammo != assignAmmoType) || item.ammo == AmmoID.None) {
continue;
}
itemInds.Add(player.inventory[i]);
}
// 遍历玩家背包的前54个物品
for (int i = 0; i < 54; i++) {
Item item = player.inventory[i];
// 如果指定了弹药类型并且物品的弹药类型不匹配,或者物品没有弹药,跳过
if ((assignAmmoType != 0 && item.ammo != assignAmmoType) || item.ammo == AmmoID.None) {
continue;
}
itemInds.Add(player.inventory[i]);
}
// 如果需要排序,则按堆叠数量降序排序
if (numSort) {
itemInds = itemInds.OrderByDescending(item => item.stack).ToList();
}
// 设置返回的弹药状态信息
ammoState.ValidProjectileIDs = itemShootTypes.ToArray(); // 有效的弹药发射类型
ammoState.CurrentItems = itemInds.ToArray(); // 当前有效的弹药物品
ammoState.ValidItemIDs = itemTypes.ToArray(); // 有效的弹药物品类型
ammoState.CurrentAmount = num; // 当前弹药总数量
// 设置最大和最小数量的物品
if (itemInds.Count > 0) {
ammoState.MaxAmountItem = itemInds[0]; // 最大数量的物品
ammoState.MinAmountItem = itemInds[itemInds.Count - 1]; // 最小数量的物品
}
else {
ammoState.MaxAmountItem = new Item(); // 若无有效物品,返回空物品
ammoState.MinAmountItem = new Item(); // 若无有效物品,返回空物品
}
return ammoState;
}
/// <summary>
/// 获取玩家选中的物品实例
/// </summary>
/// <param name="player"></param>
/// <returns></returns>
public static Item GetItem(this Player player) => Main.mouseItem.IsAir ? player.inventory[player.selectedItem] : Main.mouseItem;
#endregion
#region Net
/// <summary>
/// 判断是否处于客户端状态,如果是在单人或者服务端下将返回false
/// </summary>
public static bool isClient => Main.netMode == NetmodeID.MultiplayerClient;
/// <summary>
/// 判断是否处于服务端状态,如果是在单人或者客户端下将返回false
/// </summary>
public static bool isServer => Main.netMode == NetmodeID.Server;
/// <summary>
/// 仅判断是否处于单人状态,在单人模式下返回true
/// </summary>
public static bool isSinglePlayer => Main.netMode == NetmodeID.SinglePlayer;
/// <summary>
/// 检查一个 Projectile 对象是否属于当前客户端玩家拥有的,如果是,返回true
/// </summary>
public static bool IsOwnedByLocalPlayer(this Projectile projectile) => projectile.owner == Main.myPlayer;
/// <summary>
/// 生成Boss级实体,考虑网络状态
/// </summary>
/// <param name="player">触发生成的玩家实例</param>
/// <param name="bossType">要生成的 Boss 的类型</param>
/// <param name="obeyLocalPlayerCheck">是否要遵循本地玩家检查</param>
public static void SpawnBossNetcoded(Player player, int bossType, bool obeyLocalPlayerCheck = true) {
if (player.whoAmI == Main.myPlayer || !obeyLocalPlayerCheck) {
// 如果使用物品的玩家是客户端
// (在此明确排除了服务器端)
_ = SoundEngine.PlaySound(SoundID.Roar, player.position);
if (Main.netMode != NetmodeID.MultiplayerClient) {
// 如果玩家不在多人游戏中,直接生成 Boss
NPC.SpawnOnPlayer(player.whoAmI, bossType);
}
else {
// 如果玩家在多人游戏中,请求生成
// 仅当 NPCID.Sets.MPAllowedEnemies[type] 为真时才有效,需要在 NPC 代码中设置