-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
NPC.cs.patch
2769 lines (2590 loc) · 103 KB
/
NPC.cs.patch
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
--- src/TerrariaNetCore/Terraria/NPC.cs
+++ src/tModLoader/Terraria/NPC.cs
@@ -1,5 +_,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ReLogic.Content;
@@ -21,12 +_,13 @@
using Terraria.Graphics.Effects;
using Terraria.ID;
using Terraria.Localization;
+using Terraria.ModLoader;
using Terraria.Utilities;
using Terraria.WorldBuilding;
namespace Terraria;
-public class NPC : Entity
+public partial class NPC : Entity
{
private const int NPC_TARGETS_START = 300;
public bool IsABestiaryIconDummy;
@@ -65,19 +_,52 @@
public int altTexture;
public int townNpcVariationIndex;
public Vector2 netOffset = Vector2.Zero;
- public short catchItem;
+
+ /// <summary>
+ /// The numerical ID of the item that this NPC becomes when caught.<br/>
+ /// Mainly used for critters that can be caught with bug nets, such as butterflies and worms.<br/>
+ /// Bug nets and other catching tools will only work on NPCs with this field set to something greater than 0.
+ /// </summary>
+ public int catchItem; //TML: Changed from short to int for convenience and consistency purposes.
+ /// <summary>
+ /// Identifies the player who released this NPC into the world. Used mainly for released critters. Helps limit how many critters a player can release. Default to 255, indicating that the npc was not released by a player.
+ /// </summary>
public short releaseOwner = 255;
+
+ /// <summary>
+ /// How rare the NPC is for the Lifeform Analyzer. For example: 4 is for Tim and Mimics, 1 is for bound town NPCs.
+ /// <br/> Defaults to 0.
+ /// </summary>
public int rarity;
public static bool taxCollector = false;
+ /// <summary>
+ /// Tracks which <see cref="Player"/>s have damaged this NPC. Damaging boss minions or other parts of a boss is usually counted as damaging the main body of the boss. Used to determine which players participated in a boss fight and should receive a boss bag.<br/><br/>
+ /// Indexed by each players <see cref="Entity.whoAmI"/> (or <see cref="Main.myPlayer"/> for code running locally).<br/><br/>
+ /// <see cref="lastInteraction"/> tracks which player was the last player to damage this NPC. <see cref="AnyInteractions"/> can be used to easily check if any player has damaged the NPC.
+ /// </summary>
public bool[] playerInteraction = new bool[256];
+ /// <summary>
+ /// The <see cref="Entity.whoAmI"/> of the <see cref="Player"/> that last damaged this NPC. Used to award Banner items to the player that killed the NPC, but can be used for many other effects.<br/><br/>
+ /// The default value is 255. If the value is 255, the NPC has not been damaged by any <see cref="Player"/>.<br/><br/>
+ /// The <see cref="playerInteraction"/> array tracks all players that have damaged this NPC.
+ /// </summary>
public int lastInteraction = 255;
public float takenDamageMultiplier = 1f;
public static bool freeCake = false;
private static int spawnSpaceX = 3;
private static int spawnSpaceY = 3;
+ /// <summary>
+ /// An offset from the actual position of the npc that will be added to the draw position. Used to fake the effect of NPC smoothly traveling up single block obstacles.
+ /// </summary>
public float gfxOffY;
+ /// <summary>
+ /// Has nothing to do with the speed that this NPC travels, that is dictated by AI code adjusting <see cref="Entity.velocity"/>.
+ /// </summary>
public float stepSpeed;
+ /*
private static float gravity = 0.3f;
+ */
+
public bool teleporting;
private static int maxAttack = 20;
private static int[] attackNPC = new int[maxAttack];
@@ -99,6 +_,13 @@
public static int deerclopsBoss = -1;
public int netSkip;
public bool netAlways;
+
+ /// <summary>
+ /// Stores the index (the <see cref="Entity.whoAmI"/>) of a single NPC. This NPC will then share a health pool with that NPC.<br/>
+ /// Used for the Destroyer's various segments and the Wall of Flesh's eyes and mouth.<br/>
+ /// This is useful for worm enemies. All the segments will point to the same npc, usually the "head" of the npc, and the head and all other non-multi-part NPCs will have the default value of -1 still.
+ /// <br/> Defaults to -1.
+ /// </summary>
public int realLife = -1;
private string _givenName = "";
public static int sWidth = 1920;
@@ -111,9 +_,15 @@
private static int activeRangeY = (int)((double)sHeight * 2.1);
private static int townRangeX = sWidth;
private static int townRangeY = sHeight;
+
+ /// <summary>
+ /// Represents how much this npc counts towards the npc spawn limit. Small critters like worms or flies have values around 0.1f and 0.25f. Enemies that are more difficult than normal have values higher than 1f, and bosses and mini bosses have values around 6.
+ /// <br/> Proper npcSlots values help balance the game and prevents the player from randomly being overwhelmed by powerful enemies. The <see href="https://terraria.wiki.gg/wiki/NPC_spawning#Spawn_rates">Spawn rates section on the NPC spawning wiki page</see> lists vanilla npcSlots values. Use this as a guide to find a suitable value.
+ /// <br/> Defaults to 1f.
+ /// </summary>
public float npcSlots = 1f;
private static bool noSpawnCycle = false;
- private static int activeTime = 750;
+ public static int activeTime = 750;
private static int defaultSpawnRate = 600;
private static int defaultMaxSpawns = 5;
public float shimmerTransparency;
@@ -121,7 +_,11 @@
public static readonly int maxBuffs = 20;
public int[] buffType = new int[maxBuffs];
public int[] buffTime = new int[maxBuffs];
+
+ /// <summary>
+ /// Contains the NPC buff immunities for this NPC. The default values in this array are populated from <see cref="NPCID.Sets.ImmuneToAllBuffs"/>, <see cref="NPCID.Sets.ImmuneToRegularBuffs"/>, <see cref="NPCID.Sets.SpecificDebuffImmunity"/>, and <see cref="BuffID.Sets.GrantImmunityWith"/>.
+ /// </summary>
- public bool[] buffImmune = new bool[BuffID.Count];
+ public bool[] buffImmune = new bool[BuffLoader.BuffCount];
public bool canDisplayBuffs = true;
public bool midas;
public bool ichor;
@@ -136,7 +_,19 @@
public bool shadowFlame;
public bool soulDrain;
public bool shimmering;
+ /// <summary>
+ /// The rate of this player's life regeneration or loss in health per tick, divided by <c>120</c>. To put it another way, it is health regeneration every 2 seconds. Used to implement the damage over time of debuffs.
+ /// <br/> For example, subtracting <c>12</c> from this value loses <c>12 / 120</c> = <c>1/10</c> health per tick, or 6 health per second.
+ /// <br/> Life regeneration is accumulated every tick in <see cref="lifeRegenCount"/>.
+ /// <br/> This value should be subtracted from in <see cref="ModNPC.UpdateLifeRegen(ref int)"/> or <see cref="GlobalNPC.UpdateLifeRegen(NPC, ref int)"/>. See <see href="https://github.com/tModLoader/tModLoader/blob/stable/ExampleMod/Common/GlobalNPCs/DamageOverTimeGlobalNPC.cs#L16">DamageOverTimeGlobalNPC.cs</see> for an example. The game does not actually use positive life regen for NPC, so positive values might not work as expected.
+ /// </summary>
public int lifeRegen;
+ /// <summary>
+ /// This NPC's accumulated life regeneration.
+ /// <br/> If this value reaches or exceeds <c>120</c>, the NPC gains <c><see cref="lifeRegenCount"/> / 120</c> health and this value decreases until it no longer exceeds <c>120</c>.
+ /// <br/> If this value reaches or exceeds <c>-120</c>, the NPC loses health in the same way.
+ /// <br/> In either case, text will appear above the NPC indicating the life regeneration or life loss.
+ /// </summary>
public int lifeRegenCount;
public int lifeRegenExpectedLossPerSecond = -1;
public bool confused;
@@ -182,35 +_,149 @@
public static bool unlockedArmsDealerSpawn = false;
public static bool unlockedNurseSpawn = false;
public static bool unlockedPrincessSpawn = false;
+
+ /// <summary>
+ /// Denotes whether or not Advanced Combat Techniques has been used in the current world.
+ /// </summary>
public static bool combatBookWasUsed = false;
public static bool combatBookVolumeTwoWasUsed = false;
public static bool peddlersSatchelWasUsed = false;
+
+ /// <summary>
+ /// Denotes whether or not the Eye of Cthulhu has been defeated at least once in the current world.
+ /// </summary>
public static bool downedBoss1 = false;
+
+ /// <summary>
+ /// Denotes whether or not the Eater of Worlds OR the Brain of Cthulhu have been defeated at least once in the current world.<br/>
+ /// This does NOT track the two of them separately; you will need to establish your own fields in a <see cref="ModSystem"/> for that.<br/>
+ /// </summary>
public static bool downedBoss2 = false;
+
+ /// <summary>
+ /// Denotes whether or not Skeletron has been defeated at least once in the current world.
+ /// </summary>
public static bool downedBoss3 = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Queen Bee has been defeated in the current world.
+ /// </summary>
public static bool downedQueenBee = false;
+
+ /// <summary>
+ /// Denotes whether or not King Slime has been defeated at least once in the current world.
+ /// </summary>
public static bool downedSlimeKing = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Goblin Army has been defeated in the current world.
+ /// </summary>
public static bool downedGoblins = false;
+
+ /// <summary>
+ /// Denotes whether or not the Frost Legion has been defeated at least once in the current world.
+ /// </summary>
public static bool downedFrost = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Pirate Invasion has been defeated in the current world.
+ /// </summary>
public static bool downedPirates = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Clown has been killed in the current world.<br/>
+ /// Only used to make the Clothier sell the Clown set once at least one has been killed.
+ /// </summary>
public static bool downedClown = false;
+
+ /// <summary>
+ /// Denotes whether or not Plantera has been defeated at least once in the current world.
+ /// </summary>
public static bool downedPlantBoss = false;
+
+ /// <summary>
+ /// Denotes whether or not Golem has been defeated at least once in the current world.
+ /// </summary>
public static bool downedGolemBoss = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Martian Madness event has been cleared in the current world.
+ /// </summary>
public static bool downedMartians = false;
+
+ /// <summary>
+ /// Denotes whether or not Duke Fishron has been defeated at least once in the current world.
+ /// </summary>
public static bool downedFishron = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Mourning Wood has been defeated in the current world.
+ /// </summary>
public static bool downedHalloweenTree = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Pumpking has been defeated in the current world.
+ /// </summary>
public static bool downedHalloweenKing = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Ice Queen has been defeated in the current world.
+ /// </summary>
public static bool downedChristmasIceQueen = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Everscream has been defeated in the current world.
+ /// </summary>
public static bool downedChristmasTree = false;
+
+ /// <summary>
+ /// Denotes whether or not at least one Santa-NK1 has been defeated in the current world.
+ /// </summary>
public static bool downedChristmasSantank = false;
+
+ /// <summary>
+ /// Denotes whether or not the Lunatic Cultist has been defeated at least once in the current world.
+ /// </summary>
public static bool downedAncientCultist = false;
+
+ /// <summary>
+ /// Denotes whether or not the Moon Lord has been defeated at least once in the current world.
+ /// </summary>
public static bool downedMoonlord = false;
+
+ /// <summary>
+ /// Denotes whether or not the Solar Pillar has been defeated at least once in the current world.
+ /// </summary>
public static bool downedTowerSolar = false;
+
+ /// <summary>
+ /// Denotes whether or not the Vortex Pillar has been defeated at least once in the current world.
+ /// </summary>
public static bool downedTowerVortex = false;
+
+ /// <summary>
+ /// Denotes whether or not the Nebula Pillar has been defeated at least once in the current world.
+ /// </summary>
public static bool downedTowerNebula = false;
+
+ /// <summary>
+ /// Denotes whether or not the Stardust Pillar has been defeated at least once in the current world.
+ /// </summary>
public static bool downedTowerStardust = false;
+
+ /// <summary>
+ /// Denotes whether or not the Empress of Light has been defeated at least once in the current world.
+ /// </summary>
public static bool downedEmpressOfLight = false;
+
+ /// <summary>
+ /// Denotes whether or not Queen Slime has been defeated at least once in the current world.
+ /// </summary>
public static bool downedQueenSlime = false;
+
+ /// <summary>
+ /// Denotes whether or not the Deerclops has been defeated at least once in the current world.
+ /// </summary>
public static bool downedDeerclops = false;
public static int ShieldStrengthTowerSolar = 0;
public static int ShieldStrengthTowerVortex = 0;
@@ -223,9 +_,25 @@
public static bool TowerActiveNebula = false;
public static bool TowerActiveStardust = false;
public static bool LunarApocalypseIsUp = false;
+
+ /// <summary>
+ /// Denotes whether or not ANY Mechanical Boss has been defeated at least once in the current world.
+ /// </summary>
public static bool downedMechBossAny = false;
+
+ /// <summary>
+ /// Denotes whether or not the Destroyer has been defeated at least once in the current world.
+ /// </summary>
public static bool downedMechBoss1 = false;
+
+ /// <summary>
+ /// Denotes whether or not the Twins have been defeated at least once in the current world.
+ /// </summary>
public static bool downedMechBoss2 = false;
+
+ /// <summary>
+ /// Denotes whether or not Skeletron Prime has been defeated at least once in the current world.
+ /// </summary>
public static bool downedMechBoss3 = false;
public static bool[] npcsFoundForCheckActive = new bool[NPCID.Count];
public static int[] lazyNPCOwnedProjectileSearchArray = new int[200];
@@ -233,51 +_,172 @@
private static int maxSpawns = defaultMaxSpawns;
public int soundDelay;
public static CoinLossRevengeSystem RevengeManager = new CoinLossRevengeSystem();
+
+ /// <summary>
+ /// This determines if an NPC can be hit by a item or projectile owned by a particular player (it is an array, each slot corresponds to different players (whoAmI)). It is decremented towards 0 every update. Melee items set immune[Player.whoAmI] to Player.itemAnimation, which starts at item.useAnimation and decrements towards 0. Penetrating projectiles usually set immune to 10, while non-penetrating projectiles do not set immune. <see href="https://github.com/tModLoader/tModLoader/blob/1.4.4/ExampleMod/Content/Projectiles/ExamplePiercingProjectile.cs">ExamplePiercingProjectile.cs</see> explains more about options for npc and projectile immunity.
+ /// </summary>
public int[] immune = new int[256];
public int directionY = 1;
+ /// <summary>
+ /// The NPC ID of this NPC. The NPC ID is a unique number assigned to each NPC loaded into the game. This will be equal to either an <see cref="NPCID"/> entry or <see cref="ModContent.NPCType{T}"/>, for example <see cref="NPCID.AngryBones"/> or ModContent.NPCType<MyModNPC>(). To check if an NPC instance is a specific NPC, check <c>NPC.type == NPCID.VanillaNPCHere</c> or <c>NPC.type == ModContent.NPCType<ModdedNPCHere>()</c> in an <see langword="if"/> statement.
+ /// <br/><br/><inheritdoc cref="type" path="/negativenote/node()"/>
+ /// </summary>
+ /// <negativenote>There are a few NPC that share the same <see cref="type"/> number, usually slight variations of enemies. <see cref="NPCID.GreenSlime"/> and <see cref="NPCID.RedSlime"/> have values of -3 and -8, respectively, but after they are spawned they will have the type equal to 1, which is the same value as <see cref="NPCID.BlueSlime"/>. If code logic requires differentiating between these variations, use <see cref="netID"/> instead.</negativenote>
public int type;
+ /// <summary>
+ /// An array with 4 slots used for any sort of data storage, which is occasionally synced from the server to clients. Each vanilla <see cref="NPCAIStyleID"/> uses these slots for different purposes. Set <see cref="netUpdate"/> to true to manually sync. The advantage of using these 4 floats is that they are synced automatically. Using fields in your <see cref="ModNPC"/> class will work just the same, but they might need to be synced via <see cref="ModNPC.SendExtraAI(System.IO.BinaryWriter)"/> and <see cref="ModNPC.ReceiveExtraAI(System.IO.BinaryReader)"/> if necessary.
+ /// <br/> Clever use of <see href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref#reference-return-values">Reference return values</see> as seen in <see href="https://github.com/tModLoader/tModLoader/blob/1.4.4/ExampleMod/Content/NPCs/ExampleCustomAISlimeNPC.cs#L36">ExampleCustomAISlimeNPC.cs</see> can be used to reuse the ai array entries with readable names.
+ /// <br/> Defaults to the values passed into <see cref="NPC.NewNPC(IEntitySource, int, int, int, int, float, float, float, float, int)"/>, usually [0, 0, 0, 0].
+ /// </summary>
public float[] ai = new float[maxAI];
+ /// <summary>
+ /// Acts like <see cref="ai"/>, but does not sync to the server. Many vanilla <see cref="NPCAIStyleID"/> use these slots for various purposes.
+ /// <br/> Defaults to [0, 0, 0, 0]
+ /// </summary>
public float[] localAI = new float[maxAI];
public int aiAction;
+
+ /// <summary>
+ /// Selects which vanilla code to use for the AI method. Vanilla NPC AI styles are enumerated in the <see cref="NPCAIStyleID"/> class. Modders can use vanilla aiStyle and optionally <see cref="ModNPC.AIType"/> to mimic AI code already in the game. This is usually only useful as a prototyping tool since it is difficult to customize existing aiStyle code. See <see cref="ModNPC.AIType"/> and <see href="https://github.com/tModLoader/tModLoader/blob/1.4.4/ExampleMod/Content/NPCs/PartyZombie.cs">PartyZombie</see> to see how to use vanilla ai. If you are using custom AI code, there is no need to set this field.
+ /// <para/> Customizing an existing aiStyle usually requires following the <see href="https://github.com/tModLoader/tModLoader/wiki/Advanced-Vanilla-Code-Adaption#example-projectile-or-npc-ai-code">Vanilla Code Adaption Guide</see>.
+ /// <para/> An aiStyle of 0 will face the player automatically, while an aiStyle of -1 is used for completely custom ai.
+ /// <para/> Defaults to 0 for vanilla NPC and -1 for modded NPC.
+ /// </summary>
public int aiStyle;
public bool justHit;
public int timeLeft;
public int target = -1;
+
+ /// <summary>
+ /// The amount of contact damage this NPC deals.<br/>
+ /// Changing this WILL NOT change the amount of damage done by projectiles.<br/>
+ /// Usually damage is scaled by some factor when NPC spawn projectiles with Projectile.NewProjectile in AI code. For example, passing in <c>(int)(NPC.damage * 0.5f)</c> as the Damage parameter.
+ /// <br/> The <see href="https://terraria.wiki.gg/wiki/List_of_NPCs">List of NPCs wiki page</see> can be useful for finding appropriate values.
+ /// </summary>
public int damage;
+
+ /// <summary>
+ /// How resistant to damage this NPC is.
+ /// <br/> The <see href="https://terraria.wiki.gg/wiki/List_of_NPCs">List of NPCs wiki page</see> can be useful for finding appropriate values.
+ /// </summary>
public int defense;
+
+ /// <summary>
+ /// Stores the value of <see cref="damage"/> at the end of SetDefaults. Useful for scaling damage in AI code conditionally.
+ /// </summary>
public int defDamage;
+
+ /// <summary>
+ /// Stores the value of <see cref="defense"/> at the end of SetDefaults. Useful for scaling defense in AI code, like how King Slime changes defense as it gets smaller.
+ /// </summary>
public int defDefense;
+
+ /// <summary>
+ /// Denotes whether or not this NPC counts as dealing cold damage for the purposes of the Warmth Potion.<br/>
+ /// Defaults to false.
+ /// </summary>
public bool coldDamage;
public bool trapImmune;
- public LegacySoundStyle HitSound;
- public LegacySoundStyle DeathSound;
+
+ /// <summary>
+ /// The sound that plays when this npc is hit. Set this to an existing <see cref="SoundID"/> entry or assign to a new <see cref="SoundStyle"/> for a custom sound.
+ /// <br/> The <see href="https://github.com/tModLoader/tModLoader/wiki/Basic-Sounds">Basic Sounds Guide</see> teaches how to find existing sounds to use, how to use custom sounds, and how to customize the playback properties of the sounds.
+ /// <br/> For example <c>NPC.HitSound = SoundID.NPCHit1;</c> can be used for the typical NPC hit sound.
+ /// <br/> Defaults to null.
+ /// </summary>
+ public SoundStyle? HitSound;
+
+ /// <summary>
+ /// The sound that plays when this npc dies. Set this to an existing <see cref="SoundID"/> entry or assign to a new <see cref="SoundStyle"/> for a custom sound.
+ /// <br/> The <see href="https://github.com/tModLoader/tModLoader/wiki/Basic-Sounds">Basic Sounds Guide</see> teaches how to find existing sounds to use, how to use custom sounds, and how to customize the playback properties of the sounds.
+ /// <br/> For example <c>NPC.DeathSound = SoundID.NPCDeath1;</c> can be used for the typical NPC kill sound.
+ /// <br/> Defaults to null.
+ /// </summary>
+ public SoundStyle? DeathSound;
+
+ /// <summary>
+ /// The current life of the NPC. Automatically set to the value of <see cref="lifeMax"/> at the end of SetDefaults.
+ /// </summary>
public int life;
+
+ /// <summary>
+ /// The maximum life of this NPC.
+ /// </summary>
public int lifeMax;
public Rectangle targetRect;
public double frameCounter;
public Rectangle frame;
public Color color;
+
+ /// <summary>
+ /// 0 is opaque, and 255 is transparent. Note that this is the opposite of how alpha is typically expressed in computer graphics. Can be used to fade an NPC in and out.
+ /// <br/> Use <see cref="Opacity"/> instead for a 0f to 1f scaling that uses the high value for fully opaque, as is more typical in computer graphics.
+ /// <br/> Defaults to 0.
+ /// </summary>
public int alpha;
+
+ /// <summary>
+ /// Indicates that this NPC is drawn at a specific layer in the render order. Must use in conjunction with <see cref="ModNPC.DrawBehind(int)"/>.
+ /// <br/> See <see href="https://github.com/tModLoader/tModLoader/blob/1.4.4/ExampleMod/Content/NPCs/ExampleDrawBehindNPC.cs">ExampleDrawBehindNPC</see> for examples of how to use this and all available layers to draw in.
+ /// </summary>
public bool hide;
+
+ /// <summary>
+ /// Makes the NPC bigger or smaller. Bigger than 1f is bigger.
+ /// <br/> Defaults to 1f.
+ /// </summary>
public float scale = 1f;
+
+ /// <summary>
+ /// How much of the knockback it receives will actually apply. 1f: full knockback; 0f: no knockback.
+ /// <br/> Defaults to 1f.
+ /// </summary>
public float knockBackResist = 1f;
+
public int oldDirectionY;
public int oldTarget;
public float rotation;
+
+ /// <summary>
+ /// If true, the npc will not be affected by gravity. Demon Eyes and other floating npc use this.
+ /// <br/> Defaults to <see langword="false"/>.
+ /// </summary>
public bool noGravity;
+
+ /// <summary>
+ /// If true, the npc does not collide with tiles, making the npc pass through tiles freely. Meteor Head and Worm npc use this.
+ /// <br/> Defaults to <see langword="false"/>.
+ /// </summary>
public bool noTileCollide;
public bool netUpdate;
public bool netUpdate2;
public bool collideX;
public bool collideY;
+
+ /// <summary>
+ /// Set to true if the NPC is a boss. Prevents off-screen despawn. Bosses also need <c>[AutoloadBossHead]</c> annotated on the ModNPC class itself to fully register as a boss.
+ /// </summary>
public bool boss;
public int spriteDirection = -1;
+
+ /// <summary>
+ /// Indicates that this NPC draws behind solid tiles.
+ /// <br/> See <see href="https://github.com/tModLoader/tModLoader/blob/1.4.4/ExampleMod/Content/NPCs/ExampleDrawBehindNPC.cs">ExampleDrawBehindNPC</see> for examples of how to use this and other options for layers to draw in.
+ /// </summary>
public bool behindTiles;
public bool lavaImmune;
+
+ /// <summary>
+ /// How many copper coins the NPC will drop when killed (100 copper coins = 1 silver coin etc.).
+ /// </summary>
public float value;
public int extraValue;
public bool dontTakeDamage;
private int catchableNPCTempImmunityCounter;
+ /// <summary>
+ /// Similar to <see cref="type"/>, but accounts for negative <see cref="NPCID"/> values. <br/>
+ /// <br/><br/><inheritdoc cref="type" path="/negativenote/node()"/>
+ /// </summary>
public int netID;
public int statsAreScaledForThisManyPlayers;
public float strengthMultiplier = 1f;
@@ -290,6 +_,11 @@
public bool oldHomeless;
public int oldHomeTileX = -1;
public int oldHomeTileY = -1;
+ /// <summary>
+ /// Indicates that an NPC is friendly to players. If true, a player won't damage the NPC and the NPC won't deal contact damage to players, unless otherwise forced. <br/>
+ /// Mostly set to true for town npc and rescuable town npc. <br/>
+ /// Naturally spawned critter NPC that are <see cref="Main.npcCatchable"/> will automatically be friendly for 1.5 seconds after spawning. <br/>
+ /// </summary>
public bool friendly;
public bool closeDoor;
public int doorX;
@@ -382,7 +_,9 @@
private static bool dayTimeHax;
private static bool rainingHax;
private static float cloudAlphaHax;
+ /*
private static int ignorePlayerInteractions = 0;
+ */
public static int ladyBugGoodLuckTime = 43200;
public static int ladyBugBadLuckTime = -10800;
private static int ladyBugRainTime = 1800;
@@ -462,8 +_,20 @@
}
}
- public string TypeName => Lang.GetNPCNameValue(netID);
+ /// <summary>
+ /// The TYPE name of this NPC.<br/>
+ /// Type names are the base titles given to any NPC, and are typically shared amongst all instances of an NPC. For example, the Stylist's type name will always be "Stylist".<br/>
+ /// To modify the type name of a specific NPC, make use of the ModifyTypeName hooks in <see cref="GlobalNPC"/> and <see cref="ModLoader.ModNPC"/>, according to your needs.<br/>
+ /// </summary>
+ public string TypeName => NPCLoader.ModifyTypeName(this, Lang.GetNPCNameValue(netID));
+ /// <summary>
+ /// The FULL name of this NPC.<br/>
+ /// If the NPC doesn't have a given name, this will just return the type name. A Stylist without a given name will always return "Stylist" here.<br/>
+ /// If the NPC does have a given name, this will return the NPC's full name; given name first, then type name.<br/>
+ /// Full name with a given name is given in the format of "X the Y", where X is their given name and Y is their type name.<br/>
+ /// For example, a Stylist might return "Scarlett the Stylist" here; with Scarlett being her given name, and Stylist being her type name.<br/>
+ /// </summary>
public string FullName {
get {
if (!HasGivenName)
@@ -473,8 +_,14 @@
}
}
+ /// <summary>
+ /// Whether or not this NPC has a given name.<br/>
+ /// </summary>
public bool HasGivenName => _givenName.Length != 0;
+ /// <summary>
+ /// If this NPC has a given name, returns their given name; otherwise, returns their type name.<br/>
+ /// </summary>
public string GivenOrTypeName {
get {
if (!HasGivenName)
@@ -484,6 +_,11 @@
}
}
+ /// <summary>
+ /// The GIVEN name of this NPC. Can be set directly.<br/>
+ /// Given names are unique to each NPC, though two NPCs can have the same given name.<br/>
+ /// Some vanilla examples of given names are Andrew (for the Guide), Yorai (for the Princess), Whitney (for the Steampunker), or Scarlett (for the Stylist).<br/>
+ /// </summary>
public string GivenName {
get {
return _givenName;
@@ -532,7 +_,7 @@
public bool isLikeATownNPC {
get {
- if (type == 453)
+ if (NPCID.Sets.ActsLikeTownNPC[type])
return true;
return townNPC;
@@ -726,6 +_,11 @@
return array;
}
+ /// <summary>
+ /// Returns the <see cref="FullName"/> of the first active NPC of the given type in the world. If not found, the type name is returned instead.
+ /// </summary>
+ /// <param name="npcID"></param>
+ /// <returns></returns>
public static string GetFullnameByID(int npcID)
{
for (int i = 0; i < 200; i++) {
@@ -863,7 +_,10 @@
for (int i = 0; i < 255; i++) {
Player player = Main.player[i];
+ /*
if (player.active && player.statLifeMax / 20 > 5)
+ */
+ if (player.active && player.ConsumedLifeCrystals > 0)
return true;
}
@@ -917,7 +_,7 @@
{
for (int i = 0; i < 200; i++) {
NPC nPC = Main.npc[i];
- if (nPC.active && nPC.type >= 0 && nPC.type < NPCID.Count)
+ if (nPC.active && nPC.type >= 0)
npcsFoundForCheckActive[nPC.type] = true;
}
@@ -1036,10 +_,37 @@
return false;
}
+ /*
public static string getNewNPCName(int npcType) => getNewNPCNameInner(npcType);
+ */
+ public string getNewNPCName()
+ {
+ string npcNameCategoryKey = getNewNPCNameInner(type);
+ var npcNameList = new List<string>();
+
+ if (!string.IsNullOrEmpty(npcNameCategoryKey))
+ npcNameList = LanguageManager.Instance.GetLocalizedEntriesInCategory(npcNameCategoryKey);
+
+ npcNameList = NPCLoader.ModifyNPCNameList(this, npcNameList);
+
+ if (npcNameList != null && npcNameList.Count > 0)
+ return npcNameList[WorldGen.genRand.Next(npcNameList.Count)];
+ else
+ return "";
+ }
+
+ //TML: A dummy for tricking 'getNewNPCNameInner' into returning category names.
+ private readonly ref struct NPCNameFakeLanguageCategoryPassthrough
+ {
+ public struct V { public string Value; }
+ public readonly V RandomFromCategory(string category, UnifiedRandom _) => new V { Value = category };
+ }
private static string getNewNPCNameInner(int npcType)
{
+ // Delightfully devilish.
+ var Language = new NPCNameFakeLanguageCategoryPassthrough();
+
switch (npcType) {
case 17:
return Language.RandomFromCategory("MerchantNames", WorldGen.genRand).Value;
@@ -1166,7 +_,7 @@
continue;
if (flag) {
- Main.npc[i].GivenName = getNewNPCName(npcType);
+ Main.npc[i].GivenName = Main.npc[i].getNewNPCName();
Main.npc[i].needsUniqueInfoUpdate = true;
continue;
}
@@ -1179,6 +_,7 @@
}
}
+ /*
public static string firstNPCName(int npcType)
{
for (int i = 0; i < 200; i++) {
@@ -1188,7 +_,13 @@
return getNewNPCName(npcType);
}
+ */
+ /// <summary>
+ /// Returns the <see cref="GivenOrTypeName"/> of the first active NPC of the given type in the world. If not found, null is returned.
+ /// </summary>
+ /// <param name="npcType"></param>
+ /// <returns></returns>
public static string GetFirstNPCNameOrNull(int npcType)
{
for (int i = 0; i < 200; i++) {
@@ -1199,6 +_,9 @@
return null;
}
+ /// <summary>
+ /// Use to check if a mechanism is allowed to spawn an NPC of the provided type at the provided world coordinates. Checks nearby area to see if the <see href="https://terraria.wiki.gg/wiki/Statues#Spawn_limits">Spawn Limits</see> have been reached.
+ /// </summary>
public static bool MechSpawn(float x, float y, int type)
{
int num = 0;
@@ -1345,7 +_,10 @@
case 684:
return 53;
default:
+ /*
return -1;
+ */
+ return NPCHeadLoader.GetNPCHeadSlot(type);
}
}
@@ -1380,9 +_,11 @@
case 440:
if (ai[0] != 5f || alpha == 255)
result = -1;
+ // Extra patch context.
break;
}
+ NPCLoader.BossHeadSlot(this, ref result);
return result;
}
@@ -1402,9 +_,11 @@
case 345:
if (ai[0] == 2f)
result = rotation;
+ // Extra patch context.
break;
}
+ NPCLoader.BossHeadRotation(this, ref result);
return result;
}
@@ -1415,6 +_,8 @@
if (num == 491 && spriteDirection == 1)
result = SpriteEffects.FlipHorizontally;
+ NPCLoader.BossHeadSpriteEffects(this, ref result);
+
return result;
}
@@ -2105,9 +_,27 @@
defDamage = damage;
defDefense = defense;
if (flag) {
- ScaleStats(spawnparams.playerCountForMultiplayerDifficultyOverride, spawnparams.gameModeData, spawnparams.strengthMultiplierOverride);
+ ScaleStats(spawnparams.playerCountForMultiplayerDifficultyOverride, spawnparams.gameModeData ?? Main.GameModeInfo, spawnparams.strengthMultiplierOverride);
life = lifeMax;
}
+
+ NPCLoader.SetDefaultsFromNetId(this);
+ }
+
+ // Added by TML.
+ public void CloneDefaults(int Type)
+ {
+ int originalType = type;
+ int originalNetID = netID;
+ var originalModNPC = ModNPC;
+ var originalGlobals = _globals;
+
+ SetDefaultsKeepPlayerInteraction(Type);
+
+ type = originalType;
+ netID = originalNetID;
+ ModNPC = originalModNPC;
+ _globals = originalGlobals;
}
public void SetDefaultsKeepPlayerInteraction(int Type)
@@ -2156,6 +_,15 @@
return;
}
+ ModNPC = null;
+ ShowNameOnHover = true;
+ HideStrikeDamage = false;
+ BossBar = null;
+ _globals = null;
+ GravityIgnoresLiquid = false;
+ GravityIgnoresSpace = false;
+ GravityIgnoresType = false;
+
waterMovementSpeed = (lavaMovementSpeed = 0.5f);
honeyMovementSpeed = 0.25f;
netOffset *= 0f;
@@ -2207,10 +_,17 @@
buffType[k] = 0;
}
+ if (buffImmune.Length != BuffLoader.BuffCount)
+ Array.Resize(ref buffImmune, BuffLoader.BuffCount);
+
- for (int l = 0; l < BuffID.Count; l++) {
+ for (int l = 0; l < buffImmune.Length; l++) {
buffImmune[l] = false;
}
+ // Added by TML:
+ // Use aiStyle -1 for modded NPCs by default so they don't always face the nearest player and/or trigger net updates.
+ aiStyle = Type < NPCID.Count ? 0 : -1;
+
setFrameSize = false;
netSkip = -2;
realLife = -1;
@@ -6970,7 +_,9 @@
DeathSound = SoundID.NPCDeath1;
npcSlots = 0.1f;
catchItem = 2002;
+ /* TML: Fix #3299
friendly = true;
+ */
}
else if (type == 358) {
width = 12;
@@ -7216,7 +_,9 @@
lifeMax = 5;
HitSound = SoundID.NPCHit1;
DeathSound = SoundID.NPCDeath1;
+ /* TML: Fix #3299
friendly = true;
+ */
catchItem = 2740;
npcSlots = 0.1f;
}
@@ -7680,7 +_,9 @@
lifeMax = 5;
HitSound = SoundID.NPCHit1;
DeathSound = SoundID.NPCDeath1;
+ /* TML: Fix #3299
friendly = true;
+ */
catchItem = 2893;
npcSlots = 0.1f;
rarity = 3;
@@ -7709,7 +_,9 @@
DeathSound = SoundID.NPCDeath1;
npcSlots = 0.1f;
catchItem = 2895;
+ /* TML: Fix #3299
friendly = true;
+ */
rarity = 3;
}
else if (type == 449) {
@@ -7936,7 +_,9 @@
DeathSound = SoundID.NPCDeath1;
npcSlots = 0.1f;
catchItem = (short)(3191 + type - 484);
+ /* TML: Fix #3299
friendly = true;
+ */
}
else if (type == 488) {
width = 18;
@@ -9994,7 +_,9 @@
DeathSound = SoundID.NPCDeath1;
npcSlots = 0.1f;
catchItem = 4363;
+ /* TML: Fix #3299
friendly = true;
+ */
}
else if (type == 607) {
noGravity = true;
@@ -10792,14 +_,84 @@
DeathSound = SoundID.NPCDeath6;
catchItem = 2121;
}
+ // TML: Moved before NPCLoader.SetDefaults to allow changing. DebuffImmunitySets broken into separate sets for easier manipulation.
+ if (NPCID.Sets.ImmuneToAllBuffs[type])
+ Array.Fill(buffImmune, true);
+
+ if (NPCID.Sets.ImmuneToRegularBuffs[type] || NPCID.Sets.ImmuneToAllBuffs[type]) {
+ // Equivalent to vanilla ImmuneToAllBuffsThatAreNotWhips
+ if (!NPCID.Sets.ImmuneToAllBuffs[type]) {
+ for (int i = 0; i < buffImmune.Length; i++) {
+ buffImmune[i] = !BuffID.Sets.IsATagBuff[i];
+ }
+ }
+
+ // If immune to most buffs...
+ // Apply all `SpecificDebuffImmunity` `false` overrides
+ for (int i = 0; i < NPCID.Sets.SpecificDebuffImmunity[type].Length; i++) {
+ bool? buffImmunity = NPCID.Sets.SpecificDebuffImmunity[type][i];
+ if(buffImmunity is false) {
+ buffImmune[i] = false;
+ }
+ }
+
+ // Apply all `GrantImmunityWith` in reverse (if vulnerable to OnFire, add vulnerability to OnFire3, keep in mind this is _all_ of the vulnerabilities in `[OnFire3]` list)
+ for (int i = 0; i < BuffID.Sets.GrantImmunityWith.Length; i++) {
+ var buffsToInherit = BuffID.Sets.GrantImmunityWith[i];
+ if (buffsToInherit.Count > 0 && buffsToInherit.All(x => !buffImmune[i]))
+ buffImmune[i] = false;
+ }
+
+ // Apply all `SpecificDebuffImmunity` `true` overrides
+ for (int i = 0; i < NPCID.Sets.SpecificDebuffImmunity[type].Length; i++) {
+ bool? buffImmunity = NPCID.Sets.SpecificDebuffImmunity[type][i];
+ if (buffImmunity is true) {
+ buffImmune[i] = true;
+ }
+ }
+ }
+ else {
+ // If not immune to most buffs...
+ // Apply all `SpecificDebuffImmunity` `true` overrides
+ for (int i = 0; i < NPCID.Sets.SpecificDebuffImmunity[type].Length; i++) {
+ bool? buffImmunity = NPCID.Sets.SpecificDebuffImmunity[type][i];
+ if (buffImmunity is true) {
+ buffImmune[i] = true;
+ }
+ }
+
+ // Apply all `GrantImmunityWith` (if immune to OnFire, add OnFire3, keep in mind this is _any_ of the immunities in `[OnFire3]` list)
+ for (int i = 0; i < BuffID.Sets.GrantImmunityWith.Length; i++) {
+ var buffsToInherit = BuffID.Sets.GrantImmunityWith[i];
+ foreach (var inheritableBuff in buffsToInherit) {
+ if (buffImmune[inheritableBuff]) {
+ buffImmune[i] = true;
+ break;
+ }
+ }
+ }
+
+ // Apply all `SpecificDebuffImmunity` `false` overrides
+ for (int i = 0; i < NPCID.Sets.SpecificDebuffImmunity[type].Length; i++) {
+ bool? buffImmunity = NPCID.Sets.SpecificDebuffImmunity[type][i];
+ if (buffImmunity is false) {
+ buffImmune[i] = false;
+ }
+ }
+ }
+
+ NPCLoader.SetDefaults(this);
if (Main.dedServ)
frame = default(Rectangle);
+ // Extra patch context.
else if (TextureAssets.Npc[type] != null && TextureAssets.Npc[type].IsLoaded)
frame = new Rectangle(0, 0, TextureAssets.Npc[type].Width(), TextureAssets.Npc[type].Height() / Main.npcFrameCount[type]);
else
setFrameSize = true;
+ // Extra patch context.
+
if (spawnparams.sizeScaleOverride.HasValue) {
int num3 = (int)((float)width * scale);
int num4 = (int)((float)height * scale);
@@ -10823,6 +_,7 @@
defDamage = damage;
defDefense = defense;
netID = type;
+ /*
if (NPCID.Sets.DebuffImmunitySets.TryGetValue(type, out var nPCDebuffImmunityData) && nPCDebuffImmunityData != null) {
nPCDebuffImmunityData.ApplyToNPC(this);
}
@@ -10833,6 +_,7 @@
}
buffImmune[353] = NPCID.Sets.ShimmerImmunity[type];
+ */
if (Main.zenithWorld)
getZenithSeedAdjustmentsBeforeEverything();
@@ -10841,12 +_,13 @@
else if (Main.tenthAnniversaryWorld)
getTenthAnniversaryAdjustments();
- if (type >= 0 && type < NPCID.Count && Main.npcCatchable[type]) {
+ if (type >= 0 && Main.npcCatchable[type] && catchableNPCTempImmunityCounter < 1) {
catchableNPCTempImmunityCounter = 90;
+ catchableNPCOriginallyFriendly = friendly;
friendly = true;
}
- ScaleStats(spawnparams.playerCountForMultiplayerDifficultyOverride, spawnparams.gameModeData, spawnparams.strengthMultiplierOverride);
+ ScaleStats(spawnparams.playerCountForMultiplayerDifficultyOverride, spawnparams.gameModeData.Value, spawnparams.strengthMultiplierOverride);
life = lifeMax;
}
@@ -11516,6 +_,8 @@
}
}
+ NPCLoader.ApplyDifficultyAndPlayerScaling(this, numPlayers, balance, bossAdjustment);
+
defDefense = defense;
defDamage = damage;
life = lifeMax;
@@ -11823,7 +_,7 @@
}
else if (!unlockedSlimeCopperSpawn && Main.npc.IndexInRange(npcIndex)) {
NPC nPC = Main.npc[npcIndex];
- if (nPC.type >= 0 && nPC.type < NPCID.Count && NPCID.Sets.CanConvertIntoCopperSlimeTownNPC[nPC.type]) {
+ if (nPC.type >= 0 && NPCID.Sets.CanConvertIntoCopperSlimeTownNPC[nPC.type]) {
unlockedSlimeCopperSpawn = true;
NetMessage.SendData(7);
Vector2 vector = nPC.velocity;
@@ -11877,7 +_,8 @@
if (flag)
vector3 = vector;
+ //TML: Added context
- Projectile.NewProjectile(new EntitySource_DebugCommand(), vector3 + vector2, Vector2.Zero, 995, 0, 0f, Main.myPlayer);
+ Projectile.NewProjectile(new EntitySource_DebugCommand("DryadStardewAnimation"), vector3 + vector2, Vector2.Zero, 995, 0, 0f, Main.myPlayer);
break;
}
}
@@ -12338,6 +_,17 @@
public void AI()