-
-
Notifications
You must be signed in to change notification settings - Fork 211
/
PhaseTracker.java
1216 lines (1103 loc) · 63.9 KB
/
PhaseTracker.java
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
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* 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.
*/
package org.spongepowered.common.event.tracking;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableList;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.crash.CrashReport;
import net.minecraft.crash.CrashReportCategory;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.IEntityOwnable;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.projectile.EntityThrowable;
import net.minecraft.util.ReportedException;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.WorldServer;
import net.minecraft.world.chunk.Chunk;
import org.apache.logging.log4j.Level;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.block.BlockState;
import org.spongepowered.api.block.tileentity.TileEntityType;
import org.spongepowered.api.data.Transaction;
import org.spongepowered.api.entity.Entity;
import org.spongepowered.api.entity.living.player.User;
import org.spongepowered.api.event.CauseStackManager;
import org.spongepowered.api.event.SpongeEventFactory;
import org.spongepowered.api.event.block.ChangeBlockEvent;
import org.spongepowered.api.event.cause.Cause;
import org.spongepowered.api.event.cause.EventContextKeys;
import org.spongepowered.api.event.entity.SpawnEntityEvent;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.api.util.Tuple;
import org.spongepowered.api.world.BlockChangeFlag;
import org.spongepowered.api.world.BlockChangeFlags;
import org.spongepowered.api.world.LocatableBlock;
import org.spongepowered.api.world.World;
import org.spongepowered.asm.util.PrettyPrinter;
import org.spongepowered.common.SpongeImpl;
import org.spongepowered.common.SpongeImplHooks;
import org.spongepowered.common.block.SpongeBlockSnapshot;
import org.spongepowered.common.bridge.OwnershipTrackedBridge;
import org.spongepowered.common.bridge.block.BlockBridge;
import org.spongepowered.common.bridge.entity.EntityBridge;
import org.spongepowered.common.bridge.world.chunk.ChunkBridge;
import org.spongepowered.common.bridge.world.ServerWorldBridge;
import org.spongepowered.common.config.SpongeConfig;
import org.spongepowered.common.config.category.PhaseTrackerCategory;
import org.spongepowered.common.config.type.GlobalConfig;
import org.spongepowered.common.entity.EntityUtil;
import org.spongepowered.common.entity.PlayerTracker;
import org.spongepowered.common.event.ShouldFire;
import org.spongepowered.common.event.tracking.context.SpongeProxyBlockAccess;
import org.spongepowered.common.event.tracking.phase.TrackingPhase;
import org.spongepowered.common.event.tracking.phase.general.GeneralPhase;
import org.spongepowered.common.event.tracking.phase.tick.NeighborNotificationContext;
import org.spongepowered.common.event.tracking.phase.tick.TickPhase;
import org.spongepowered.common.registry.type.event.SpawnTypeRegistryModule;
import org.spongepowered.common.world.SpongeBlockChangeFlag;
import org.spongepowered.common.world.SpongeLocatableBlockBuilder;
import org.spongepowered.common.world.WorldManager;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
import javax.annotation.Nullable;
/**
* The core state machine of Sponge. Acts a as proxy between various engine objects by processing actions through
* various {@link IPhaseState}s.
*/
@SuppressWarnings("unchecked")
public final class PhaseTracker {
public static final PhaseTracker CLIENT = new PhaseTracker();
public static final PhaseTracker SERVER = new PhaseTracker();
public void init() {
if (this != SERVER) {
return;
}
if (this.hasRun) {
return;
}
this.hasRun = true;
Task.builder()
.name("Sponge Async To Sync Entity Spawn Task")
.intervalTicks(1)
.execute(() -> {
if (ASYNC_CAPTURED_ENTITIES.isEmpty()) {
return;
}
final List<net.minecraft.entity.Entity> entities = new ArrayList<>(ASYNC_CAPTURED_ENTITIES);
ASYNC_CAPTURED_ENTITIES.removeAll(entities);
try (final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) {
// We are forcing the spawn, as we can't throw the proper event at the proper time, so
// we'll just mark it as "forced".
frame.addContext(EventContextKeys.SPAWN_TYPE, SpawnTypeRegistryModule.FORCED);
for (net.minecraft.entity.Entity entity : entities) {
// At this point, we don't care what the causes are...
PhaseTracker.getInstance().spawnEntityWithCause((World) entity.getEntityWorld(), (Entity) entity);
}
}
})
.submit(SpongeImpl.getPlugin());
}
public static final String MINECRAFT_CLIENT = "net.minecraft.client.Minecraft";
public static final String DEDICATED_SERVER = "net.minecraft.server.dedicated.DedicatedServer";
public static final String MINECRAFT_SERVER = "net.minecraft.server.MinecraftServer";
public static final String INTEGRATED_SERVER = "net.minecraft.server.integrated.IntegratedServer";
@Nullable private Thread sidedThread;
private boolean hasRun = false;
@SuppressWarnings("ThrowableNotThrown")
public void setThread(@Nullable final Thread thread) throws IllegalAccessException {
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
if ((stackTrace.length < 3)) {
throw new IllegalAccessException("Cannot call directly to change thread.");
}
final String callingClass = stackTrace[1].getClassName();
final String callingParent = stackTrace[2].getClassName();
if (
!(
(MINECRAFT_CLIENT.equals(callingClass) && MINECRAFT_CLIENT.equals(callingParent))
|| (MINECRAFT_SERVER.equals(callingClass) && MINECRAFT_SERVER.equals(callingParent))
|| (DEDICATED_SERVER.equals(callingClass) && MINECRAFT_CLIENT.equals(callingParent))
|| (INTEGRATED_SERVER.equals(callingClass) && MINECRAFT_CLIENT.equals(callingParent))
)
) {
throw new IllegalAccessException("Illegal Attempts to re-assign PhaseTracker threads on Sponge");
}
this.sidedThread = thread;
}
@Nullable
public Thread getSidedThread() {
return this.sidedThread;
}
public static final String ASYNC_BLOCK_CHANGE_MESSAGE = "Sponge adapts the vanilla handling of block changes to power events and plugins "
+ "such that it follows the known fact that block changes MUST occur on the server "
+ "thread (even on clients, this exists as the InternalServer thread). It is NOT "
+ "possible to change this fact and must be reported to the offending mod for async "
+ "issues.";
public static final String ASYNC_TRACKER_ACCESS = "Sponge adapts the vanilla handling of various processes, such as setting a block "
+ "or spawning an entity. Sponge is designed around the concept that Minecraft is "
+ "primarily performing these operations on the \"server thread\". Because of this "
+ "Sponge is safeguarding common access to the PhaseTracker as the entrypoint for "
+ "performing these sort of changes.";
public static PhaseTracker getInstance() {
return SERVER;
}
private static final CopyOnWriteArrayList<net.minecraft.entity.Entity> ASYNC_CAPTURED_ENTITIES = new CopyOnWriteArrayList<>();
public static final BiConsumer<PrettyPrinter, PhaseContext<?>> CONTEXT_PRINTER = (printer, context) ->
context.printCustom(printer, 4);
private static final BiConsumer<PrettyPrinter, PhaseContext<?>> PHASE_PRINTER = (printer, context) -> {
printer.add(" - Phase: %s", context.state);
printer.add(" Context:");
context.printCustom(printer, 4);
context.printTrace(printer);
};
private final PhaseStack stack = new PhaseStack();
private boolean hasPrintedEmptyOnce = false;
private boolean hasPrintedAboutRunnawayPhases = false;
private boolean hasPrintedAsyncEntities = false;
private int printRunawayCount = 0;
private final List<IPhaseState<?>> printedExceptionsForBlocks = new ArrayList<>();
private final List<IPhaseState<?>> printedExceptionsForEntities = new ArrayList<>();
private final List<Tuple<IPhaseState<?>, IPhaseState<?>>> completedIncorrectStates = new ArrayList<>();
private final List<IPhaseState<?>> printedExceptionsForState = new ArrayList<>();
private final Set<IPhaseState<?>> printedExceptionsForUnprocessedState = new HashSet<>();
private final Set<IPhaseState<?>> printedExceptionForMaximumProcessDepth = new HashSet<>();
// ----------------- STATE ACCESS ----------------------------------
@SuppressWarnings("rawtypes")
void switchToPhase(final IPhaseState<?> state, final PhaseContext<?> phaseContext) {
if (!SpongeImplHooks.isMainThread()) {
// lol no, report the block change properly
new PrettyPrinter(60).add("Illegal Async PhaseTracker Access").centre().hr()
.addWrapped(ASYNC_TRACKER_ACCESS)
.add()
.add(new Exception("Async Block Change Detected"))
.log(SpongeImpl.getLogger(), Level.ERROR);
// Maybe? I don't think this is wise.
return;
}
checkNotNull(state, "State cannot be null!");
checkNotNull(state.getPhase(), "Phase cannot be null!");
checkNotNull(phaseContext, "PhaseContext cannot be null!");
checkArgument(phaseContext.isComplete(), "PhaseContext must be complete!");
final IPhaseState<?> currentState = this.stack.peek().state;
if (SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose()) {
if (this.stack.size() > 6) {
if (this.stack.checkForRunaways(state, phaseContext)) {
this.printRunawayPhase(state, phaseContext);
}
}
}
if (Sponge.isServerAvailable() && ((IPhaseState) state).shouldProvideModifiers(phaseContext)) {
SpongeImpl.getCauseStackManager().registerPhaseContextProvider(phaseContext, ((IPhaseState) state).getFrameModifier());
}
this.stack.push(state, phaseContext);
}
@SuppressWarnings({"rawtypes", "unused", "try"})
void completePhase(final IPhaseState<?> prevState) {
if (!SpongeImplHooks.isMainThread()) {
// lol no, report the block change properly
new PrettyPrinter(60).add("Illegal Async PhaseTracker Access").centre().hr()
.addWrapped(ASYNC_TRACKER_ACCESS)
.add()
.add(new Exception("Async Block Change Detected"))
.log(SpongeImpl.getLogger(), Level.ERROR);
return;
}
final PhaseContext<?> currentContext = this.stack.peek();
final IPhaseState<?> state = currentContext.state;
final boolean isEmpty = this.stack.isEmpty();
if (isEmpty) {
// The random occurrence that we're told to complete a phase
// while a world is being changed unknowingly.
this.printEmptyStackOnCompletion();
return;
}
if (prevState != state) {
this.printIncorrectPhaseCompletion(prevState, state);
// The phase on the top of the stack was most likely never completed.
// Since we don't know when and where completePhase was intended to be called for it,
// we simply pop it to allow processing to continue (somewhat) as normal
this.stack.pop();
return;
}
if (SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() ) {
if (this.stack.checkForRunaways(GeneralPhase.Post.UNWINDING, null)) {
// This printing is to detect possibilities of a phase not being cleared properly
// and resulting in a "runaway" phase state accumulation.
this.printRunnawayPhaseCompletion(state);
}
}
final TrackingPhase phase = state.getPhase();
final boolean hasCaptures = currentContext.hasCaptures();
try (final UnwindingPhaseContext unwinding = UnwindingPhaseContext.unwind(state, currentContext, hasCaptures) ) {
// With UnwindingPhaseContext#unwind checking for post, if it is null, the try
// will not attempt to close the phase context. If it is required,
// it already automatically pushes onto the phase stack, along with
// a new list of capture lists
try { // Yes this is a nested try, but in the event the current phase cannot be unwound,
// at least unwind UNWINDING to process any captured objects so we're not totally without
// loss of objects
if (hasCaptures) {
((IPhaseState) state).unwind(currentContext);
}
} catch (Exception e) {
this.printMessageWithCaughtException("Exception Exiting Phase", "Something happened when trying to unwind", state, currentContext, e);
}
} catch (Exception e) {
this.printMessageWithCaughtException("Exception Post Dispatching Phase", "Something happened when trying to post dispatch state", state,
currentContext, e);
}
this.checkPhaseContextProcessed(state, currentContext);
// If pop is called, the Deque will already throw an exception if there is no element
// so it's an error properly handled.
this.stack.pop();
if (this.stack.isEmpty()) {
for (final WorldServer world : WorldManager.getWorlds()) {
final ServerWorldBridge mixinWorld = (ServerWorldBridge) world;
if (mixinWorld.bridge$getProxyAccess().hasProxy()) {
new PrettyPrinter().add("BlockPRoxy has extra proxies not pruned!").centre().hr()
.add("When completing the Phase: %s, some foreign BlockProxy was pushed, but never pruned.", state)
.add()
.add("Please analyze the following exception from the proxy:")
.add(new Exception())
.print(System.err);
}
}
}
}
private void printRunnawayPhaseCompletion(final IPhaseState<?> state) {
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() && !this.hasPrintedAboutRunnawayPhases) {
// Avoiding spam logs.
return;
}
final PrettyPrinter printer = new PrettyPrinter(60);
printer.add("Completing Phase").centre().hr();
printer.addWrapped(60, "Detecting a runaway phase! Potentially a problem "
+ "where something isn't completing a phase!!! Sponge will stop printing"
+ "after three more times to avoid generating extra logs");
printer.add();
printer.addWrapped(60, "%s : %s", "Completing phase", state);
printer.add(" Phases Remaining:");
printPhaseStackWithException(this, printer, new Exception("RunawayPhase"));
printer.trace(System.err, SpongeImpl.getLogger(), Level.ERROR);
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() && this.printRunawayCount++ > 3) {
this.hasPrintedAboutRunnawayPhases = true;
}
}
public void generateVersionInfo(final PrettyPrinter printer) {
for (final PluginContainer pluginContainer : SpongeImpl.getInternalPlugins()) {
pluginContainer.getVersion().ifPresent(version ->
printer.add("%s : %s", pluginContainer.getName(), version)
);
}
}
private void printIncorrectPhaseCompletion(final IPhaseState<?> prevState, final IPhaseState<?> state) {
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() && !this.completedIncorrectStates.isEmpty()) {
for (final Tuple<IPhaseState<?>, IPhaseState<?>> tuple : this.completedIncorrectStates) {
if ((tuple.getFirst().equals(prevState)
&& tuple.getSecond().equals(state))) {
// we've already printed once about the previous state and the current state
// being completed incorrectly. only print it once.
return;
}
}
}
final PrettyPrinter printer = new PrettyPrinter(60).add("Completing incorrect phase").centre().hr()
.addWrapped("Sponge's tracking system is very dependent on knowing when"
+ " a change to any world takes place, however, we are attempting"
+ " to complete a \"phase\" other than the one we most recently entered."
+ " This is an error usually on Sponge's part, so a report"
+ " is required on the issue tracker on GitHub.").hr()
.add("Expected to exit phase: %s", prevState)
.add("But instead found phase: %s", state)
.add("StackTrace:")
.add(new Exception());
printer.add(" Phases Remaining:");
printPhaseStackWithException(this, printer, new Exception("Incorrect Phase Completion"));
printer.trace(System.err, SpongeImpl.getLogger(), Level.ERROR);
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose()) {
this.completedIncorrectStates.add(new Tuple<>(prevState, state));
}
}
private void printEmptyStackOnCompletion() {
if (this.hasPrintedEmptyOnce) {
// We want to only mention it once that we are completing an
// empty state, of course something is bound to break, but
// we don't want to spam megabytes worth of log files just
// because of it.
return;
}
final PrettyPrinter printer = new PrettyPrinter(60).add("Unexpectedly Completing An Empty Stack").centre().hr()
.addWrapped(60, "Sponge's tracking system is very dependent on knowing when"
+ " a change to any world takes place, however, we have been told"
+ " to complete a \"phase\" without having entered any phases."
+ " This is an error usually on Sponge's part, so a report"
+ " is required on the issue tracker on GitHub.").hr()
.add("StackTrace:")
.add(new Exception())
.add();
this.generateVersionInfo(printer);
printer.trace(System.err, SpongeImpl.getLogger(), Level.ERROR);
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose()) {
this.hasPrintedEmptyOnce = true;
}
}
private void printRunawayPhase(IPhaseState<?> state, PhaseContext<?> context) {
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() && !this.hasPrintedAboutRunnawayPhases) {
// Avoiding spam logs.
return;
}
final PrettyPrinter printer = new PrettyPrinter(60);
printer.add("Switching Phase").centre().hr();
printer.addWrapped(60, "Detecting a runaway phase! Potentially a problem where something isn't completing a phase!!!");
printer.add(" %s : %s", "Entering Phase", state.getPhase());
printer.add(" %s : %s", "Entering State", state);
CONTEXT_PRINTER.accept(printer, context);
printer.addWrapped(60, "%s :", "Phases remaining");
printPhaseStackWithException(this, printer, new Exception("RunawayPhase"));
printer.trace(System.err, SpongeImpl.getLogger(), Level.ERROR);
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() && this.printRunawayCount++ > SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().getMaximumRunawayCount()) {
this.hasPrintedAboutRunnawayPhases = true;
}
}
public static void printNullSourceForBlock(final WorldServer worldServer, final BlockPos pos, final Block blockIn, final BlockPos otherPos,
final NullPointerException e) {
final PhaseTracker instance = PhaseTracker.getInstance();
final PrettyPrinter printer = new PrettyPrinter(60).add("Null Source Block from Unknown Source!").centre().hr()
.addWrapped("Hey, Sponge is saving the game from crashing or spamming because some source "
+ "put up a \"null\" Block as it's source for sending out a neighbor notification. "
+ "This is usually unsupported as the game will silently ignore some nulls by "
+ "performing \"==\" checks instead of calling methods, potentially making an "
+ "NPE. Because Sponge uses the source block to build information for tracking, "
+ "Sponge has to save the game from crashing by reporting this issue. Because the "
+ "source is unknown, it's recommended to report this issue to SpongeCommon's "
+ "issue tracker on GitHub. Please provide the following information: ")
.add()
.add(" %s : %s", "Source position", pos)
.add(" %s : %s", "World", ((World) worldServer).getName())
.add(" %s : %s", "Source Block Recovered", blockIn)
.add(" %s : %s", "Notified Position", otherPos).add();
printPhaseStackWithException(instance, printer, e);
printer
.log(SpongeImpl.getLogger(), Level.WARN);
}
public static void printNullSourceBlockWithTile(
final BlockPos pos, final Block blockIn, final BlockPos otherPos, final TileEntityType type, final boolean useTile,
final NullPointerException e) {
final PhaseTracker instance = PhaseTracker.getInstance();
final PrettyPrinter printer = new PrettyPrinter(60).add("Null Source Block on TileEntity!").centre().hr()
.addWrapped("Hey, Sponge is saving the game from crashing because a TileEntity "
+ "is sending out a \'null\' Block as it's source (more likely) and "
+ "attempting to perform a neighbor notification with it. Because "
+ "this is guaranteed to lead to a crash or a spam of reports, "
+ "Sponge is going ahead and fixing the issue. The offending Tile "
+ "is " + type.getId())
.add()
.add("%s : %s", "Source position", pos)
.add("%s : %s", "Source TileEntity", type)
.add("%s : %s", "Recovered using TileEntity as Source", useTile)
.add("%s : %s", "Source Block Recovered", blockIn)
.add("%s : %s", "Notified Position", otherPos);
printPhaseStackWithException(instance, printer, e);
printer
.log(SpongeImpl.getLogger(), Level.WARN);
}
public static void printNullSourceBlockNeighborNotificationWithNoTileSource(final BlockPos pos, final Block blockIn, final BlockPos otherPos,
final NullPointerException e) {
final PhaseTracker instance = PhaseTracker.getInstance();
final PrettyPrinter printer = new PrettyPrinter(60).add("Null Source Block on TileEntity!").centre().hr()
.addWrapped("Hey, Sponge is saving the game from crashing because a TileEntity "
+ "is sending out a \'null\' Block as it's source (more likely) and "
+ "attempting to perform a neighbor notification with it. Because "
+ "this is guaranteed to lead to a crash or a spam of reports, "
+ "Sponge is going ahead and fixing the issue. The offending Tile "
+ "is unknown, so we don't have any way to configure a reporting for you")
.add()
.add("%s : %s", "Source position", pos)
.add("%s : %s", "Source TileEntity", "UNKNOWN")
.add("%s : %s", "Recovered using TileEntity as Source", "false")
.add("%s : %s", "Source Block Recovered", blockIn)
.add("%s : %s", "Notified Position", otherPos);
printPhaseStackWithException(instance, printer, e);
printer
.log(SpongeImpl.getLogger(), Level.WARN);
}
public static void printPhaseStackWithException(final PhaseTracker instance, final PrettyPrinter printer, final Throwable e) {
instance.stack.forEach(data -> PHASE_PRINTER.accept(printer, data));
printer.add()
.add(" %s :", "StackTrace")
.add(e)
.add();
instance.generateVersionInfo(printer);
}
public void printMessageWithCaughtException(final String header, final String subHeader, @Nullable final Throwable e) {
this.printMessageWithCaughtException(header, subHeader, this.getCurrentState(), this.getCurrentContext(), e);
}
private void printMessageWithCaughtException(final String header, final String subHeader, final IPhaseState<?> state, final PhaseContext<?> context, @Nullable final Throwable t) {
final PrettyPrinter printer = new PrettyPrinter(60);
printer.add(header).centre().hr()
.add("%s %s", subHeader, state)
.addWrapped(60, "%s :", "PhaseContext");
CONTEXT_PRINTER.accept(printer, context);
printer.addWrapped(60, "%s :", "Phases remaining");
this.stack.forEach(data -> PHASE_PRINTER.accept(printer, data));
if (t != null) {
printer.add("Stacktrace:")
.add(t);
if (t.getCause() != null) {
printer.add(t.getCause());
}
}
printer.add();
this.generateVersionInfo(printer);
printer.trace(System.err, SpongeImpl.getLogger(), Level.ERROR);
}
public void printExceptionFromPhase(final Throwable e, final PhaseContext<?> context) {
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() && !this.printedExceptionsForState.isEmpty()) {
for (final IPhaseState<?> iPhaseState : this.printedExceptionsForState) {
if (context.state == iPhaseState) {
return;
}
}
}
final PrettyPrinter printer = new PrettyPrinter(60).add("Exception occurred during a PhaseState").centre().hr()
.addWrapped("Sponge's tracking system makes a best effort to not throw exceptions randomly but sometimes it is inevitable. In most "
+ "cases, something else triggered this exception and Sponge prevented a crash by catching it. The following stacktrace can be "
+ "used to help pinpoint the cause.").hr()
.add("The PhaseState having an exception: %s", context.state)
.add("The PhaseContext:")
;
printer
.add(context.printCustom(printer, 4));
printPhaseStackWithException(this, printer, e);
printer.trace(System.err, SpongeImpl.getLogger(), Level.ERROR);
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose()) {
this.printedExceptionsForState.add(context.state);
}
}
private void checkPhaseContextProcessed(final IPhaseState<?> state, final PhaseContext<?> context) {
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() && this.printedExceptionsForUnprocessedState.contains(state)) {
return;
}
if (context.notAllCapturesProcessed()) {
this.printUnprocessedPhaseContextObjects(state, context);
this.printedExceptionsForUnprocessedState.add(state);
}
}
private void printUnprocessedPhaseContextObjects(final IPhaseState<?> state, final PhaseContext<?> context) {
this.printMessageWithCaughtException("Failed to process all PhaseContext captured!",
"During the processing of a phase, certain objects were captured in a PhaseContext. All of them should have been removed from the PhaseContext by this point",
state, context, null);
}
private void printBlockTrackingException(final PhaseContext<?> phaseData, final IPhaseState<?> phaseState, final Throwable e) {
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() && !this.printedExceptionsForBlocks.isEmpty()) {
if (this.printedExceptionsForBlocks.contains(phaseState)) {
return;
}
}
final PrettyPrinter printer = new PrettyPrinter(60).add("Exception attempting to capture a block change!").centre().hr();
printPhasestack(phaseData, e, printer);
printer.trace(System.err, SpongeImpl.getLogger(), Level.ERROR);
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose()) {
this.printedExceptionsForBlocks.add(phaseState);
}
}
private void printPhasestack(final PhaseContext<?> phaseData, final Throwable e, final PrettyPrinter printer) {
printer.addWrapped(60, "%s :", "PhaseContext");
CONTEXT_PRINTER.accept(printer, phaseData);
printer.addWrapped(60, "%s :", "Phases remaining");
this.stack.forEach(data -> PHASE_PRINTER.accept(printer, data));
printer.add("Stacktrace:");
printer.add(e);
}
private void printUnexpectedBlockChange(final ServerWorldBridge mixinWorld, final BlockPos pos, final IBlockState currentState,
final IBlockState newState) {
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose()) {
return;
}
new PrettyPrinter(60).add("Unexpected World Change Detected!").centre().hr()
.add("Sponge's tracking system is very dependent on knowing when\n"
+ "a change to any world takes place, however there are chances\n"
+ "where Sponge does not know of changes that mods may perform.\n"
+ "In cases like this, it is best to report to Sponge to get this\n"
+ "change tracked correctly and accurately.").hr()
.add()
.add("%s : %s", "World", mixinWorld)
.add("%s : %s", "Position", pos)
.add("%s : %s", "Current State", currentState)
.add("%s : %s", "New State", newState)
.add()
.add("StackTrace:")
.add(new Exception())
.trace(System.err, SpongeImpl.getLogger(), Level.ERROR);
}
private void printExceptionSpawningEntity(final PhaseContext<?> context, final Throwable e) {
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose() && !this.printedExceptionsForEntities.isEmpty()) {
if (this.printedExceptionsForEntities.contains(context.state)) {
return;
}
}
final PrettyPrinter printer = new PrettyPrinter(60).add("Exception attempting to capture or spawn an Entity!").centre().hr();
printPhasestack(context, e, printer);
printer.log(SpongeImpl.getLogger(), Level.ERROR);
if (!SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose()) {
this.printedExceptionsForEntities.add(context.state);
}
}
String dumpStack() {
if (this.stack.isEmpty()) {
return "[Empty stack]";
}
final PrettyPrinter printer = new PrettyPrinter(40);
this.stack.forEach(data -> PHASE_PRINTER.accept(printer, data));
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
printer.print(new PrintStream(stream));
return stream.toString();
}
// ----------------- SIMPLE GETTERS --------------------------------------
public IPhaseState<?> getCurrentState() {
return this.stack.peekState();
}
public PhaseContext<?> getCurrentContext() {
return this.stack.peekContext();
}
// --------------------- DELEGATED WORLD METHODS -------------------------
/**
* Replacement of {@link net.minecraft.world.World#neighborChanged(BlockPos, Block, BlockPos)}
* that adds tracking into play.
*
* @param mixinWorld THe world
* @param notifyPos The original notification position
* @param sourceBlock The source block type
* @param sourcePos The source block position
*/
@SuppressWarnings("rawtypes")
public void notifyBlockOfStateChange(final ServerWorldBridge mixinWorld, final IBlockState notifyState, final BlockPos notifyPos, final Block sourceBlock, final BlockPos sourcePos) {
if (!SpongeImplHooks.isMainThread()) {
// lol no, report the block change properly
new PrettyPrinter(60).add("Illegal Async PhaseTracker Access").centre().hr()
.addWrapped(ASYNC_TRACKER_ACCESS)
.add()
.add(new Exception("Async Block Notifcation Detected"))
.log(SpongeImpl.getLogger(), Level.ERROR);
// Maybe? I don't think this is wise to try and sync back a notification on the main thread.
return;
}
try {
// Sponge start - prepare notification
final PhaseContext<?> peek = this.stack.peek();
final IPhaseState state = peek.state;
if (!((BlockBridge) notifyState.getBlock()).hasNeighborChangedLogic()) {
// A little short-circuit so we do not waste expense to call neighbor notifications on blocks that do
// not override the method neighborChanged
return;
}
// If the phase state does not want to allow neighbor notifications to leak while processing,
// it needs to be able to do so. It will replay the notifications in the order in which they were received,
// such that the notification will be sent out in the same order as the block changes that may have taken place.
if ((ShouldFire.CHANGE_BLOCK_EVENT || ShouldFire.NOTIFY_NEIGHBOR_BLOCK_EVENT) && state.doesCaptureNeighborNotifications(peek)) {
peek.getCapturedBlockSupplier().captureNeighborNotification(mixinWorld, notifyState, notifyPos, sourceBlock, sourcePos);
return;
}
state.associateNeighborStateNotifier(peek, sourcePos, notifyState.getBlock(), notifyPos, ((WorldServer) mixinWorld), PlayerTracker.Type.NOTIFIER);
final LocatableBlock block = new SpongeLocatableBlockBuilder()
.world(((World) mixinWorld))
.position(sourcePos.getX(), sourcePos.getY(), sourcePos.getZ())
.state((BlockState) sourceBlock.getDefaultState()).build();
try (final NeighborNotificationContext context = TickPhase.Tick.NEIGHBOR_NOTIFY.createPhaseContext()
.source(block)
.sourceBlock(sourceBlock)
.setNotifiedBlockPos(notifyPos)
.setNotifiedBlockState(notifyState)
.setSourceNotification(sourcePos)
.allowsCaptures(state) // We need to pass the previous state so we don't capture blocks when we're in world gen.
) {
// Since the notifier may have just been set from the previous state, we can
// ask it to contribute to our state
state.provideNotifierForNeighbors(peek, context);
context.buildAndSwitch(); // We need to enter the phase state, otherwise if the context is not switched into,
// the try with resources will perform a close without the phase context being entered, leading to issues of closing
// other phase contexts.
// Refer to https://github.com/SpongePowered/SpongeForge/issues/2706
if (PhaseTracker.checkMaxBlockProcessingDepth(state, peek, context.getDepth())) {
return;
}
// Sponge End
notifyState.neighborChanged(((WorldServer) mixinWorld), notifyPos, sourceBlock, sourcePos);
}
} catch (Throwable throwable) {
final CrashReport crashreport = CrashReport.makeCrashReport(throwable, "Exception while updating neighbours");
final CrashReportCategory crashreportcategory = crashreport.makeCategory("Block being updated");
crashreportcategory.addDetail("Source block type", () -> {
try {
return String.format("ID #%d (%s // %s)", Block.getIdFromBlock(sourceBlock),
sourceBlock.getTranslationKey(), sourceBlock.getClass().getCanonicalName());
} catch (Throwable var2) {
return "ID #" + Block.getIdFromBlock(sourceBlock);
}
});
CrashReportCategory.addBlockInfo(crashreportcategory, notifyPos, notifyState);
throw new ReportedException(crashreport);
}
}
/**
* Replacement of {@link WorldServer#setBlockState(BlockPos, IBlockState, int)}
* that adds cause tracking.
*
* @param pos The position of the block state to set
* @param newState The new state
* @param flag The notification flags
* @return True if the block was successfully set (or captured)
*/
@SuppressWarnings("rawtypes")
public boolean setBlockState(final ServerWorldBridge mixinWorld, final BlockPos pos, final IBlockState newState, final BlockChangeFlag flag) {
if (!SpongeImplHooks.isMainThread()) {
// lol no, report the block change properly
new PrettyPrinter(60).add("Illegal Async Block Change").centre().hr()
.addWrapped(ASYNC_BLOCK_CHANGE_MESSAGE)
.add()
.add(" %s : %s", "World", mixinWorld)
.add(" %s : %d, %d, %d", "Block Pos", pos.getX(), pos.getY(), pos.getZ())
.add(" %s : %s", "BlockState", newState)
.add()
.addWrapped("Sponge is not going to allow this block change to take place as doing so can "
+ "lead to further issues, not just with sponge or plugins, but other mods as well.")
.add()
.add(new Exception("Async Block Change Detected"))
.log(SpongeImpl.getLogger(), Level.ERROR);
return false;
}
final SpongeBlockChangeFlag spongeFlag = (SpongeBlockChangeFlag) flag;
final net.minecraft.world.World minecraftWorld = (WorldServer) mixinWorld;
// Vanilla start - get the chunk
final Chunk chunk = minecraftWorld.getChunk(pos);
// Sponge - double check the chunk is not empty.
// It is now possible for setBlockState to be called on an empty chunk due to our optimization
// for returning empty chunks when we don't want a chunk to load.
// If chunk is empty, we simply return to avoid any further logic.
if (chunk.isEmpty()) {
return false;
}
// Sponge End
final IBlockState currentState = chunk.getBlockState(pos);
// Forge patches - allows getting the light changes to check for relighting.
final int oldLight = SpongeImplHooks.getChunkPosLight(currentState, minecraftWorld, pos);
final int oldOpacity = SpongeImplHooks.getBlockLightOpacity(currentState, minecraftWorld, pos);
// Sponge Start - micro optimization to avoid calling extra stuff on the same block state instance
if (currentState == newState) {
// Some micro optimization in case someone is trying to set the new state to the same as current
final SpongeProxyBlockAccess proxyAccess = mixinWorld.bridge$getProxyAccess();
if (proxyAccess.hasProxy() && proxyAccess.getBlockState(pos) != currentState) {
proxyAccess.onChunkChanged(pos, newState);
}
return false;
}
final PhaseContext<?> context = this.stack.peek();
final IPhaseState<?> phaseState = context.state;
final boolean isComplete = phaseState == GeneralPhase.State.COMPLETE;
// Do a sanity check, if we're not in any phase state that accepts block changes, well, why the hell are
// we doing any changes?? The changes themselves will still go through, but we want to be as verbose
// about those changes as possible, if we're configured to do so.
if (isComplete && SpongeImpl.getGlobalConfigAdapter().getConfig().getPhaseTracker().isVerbose()) { // Fail fast.
// The random occurrence that we're told to complete a phase
// while a world is being changed unknowingly.
// this.printUnexpectedBlockChange(mixinWorld, pos, currentState, newState);
}
// We can allow the block to get changed, regardless how it's captured, not captured, etc.
// because MixinChunk will perform the necessary changes, and appropriately prevent any specific
// physics handling.
final ChunkBridge mixinChunk = (ChunkBridge) chunk;
// Sponge - Use our mixin method that allows using the BlockChangeFlag.
final IBlockState originalBlockState = mixinChunk.setBlockState(pos, newState, currentState, spongeFlag);
// Sponge End
if (originalBlockState == null) {
return false;
}
// else { // Sponge - unnecessary formatting
// Forge changes the BlockState.getLightOpacity to use Forge's hook.
if (SpongeImplHooks.getBlockLightOpacity(newState, minecraftWorld, pos) != oldOpacity || SpongeImplHooks.getChunkPosLight(newState, minecraftWorld, pos) != oldLight) {
// Sponge - End
minecraftWorld.profiler.startSection("checkLight");
minecraftWorld.checkLight(pos);
minecraftWorld.profiler.endSection();
}
// Sponge Start - At this point, we can stop and check for captures.
// by short circuiting here, we avoid additional block processing that would otherwise
// have potential side effects (and MixinChunk#setBlockState does a wonderful job at avoiding
// unnecessary logic in those cases).
if (((IPhaseState) phaseState).doesBulkBlockCapture(context) && ShouldFire.CHANGE_BLOCK_EVENT) {
// Basically at this point, there's nothing left for us to do since
// MixinChunk will capture the block change, and submit it to be
// "captured". It's only when there's immediate block event
// processing that we need to actually create the event and process
// that transaction.
return true;
}
if (((IPhaseState) phaseState).doesBlockEventTracking(context) && ShouldFire.CHANGE_BLOCK_EVENT) {
try {
// Fall back to performing a singular block capture and throwing an event with all the
// repercussions, such as neighbor notifications and whatnot. Entity spawns should also be
// properly handled since bulk captures technically should be disabled if reaching
// this point.
final SpongeBlockSnapshot originalBlockSnapshot = context.getSingleSnapshot();
final Transaction<BlockSnapshot> transaction = TrackingUtil.TRANSACTION_CREATION.apply(originalBlockSnapshot).get();
final ImmutableList<Transaction<BlockSnapshot>> transactions = ImmutableList.of(transaction);
// Create and throw normal event
final Cause currentCause = Sponge.getCauseStackManager().getCurrentCause();
final ChangeBlockEvent normalEvent =
originalBlockSnapshot.blockChange.createEvent(currentCause, transactions);
try (@SuppressWarnings("try") final CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) {
SpongeImpl.postEvent(normalEvent);
// We put the normal event at the end of the cause, still keeping in line with the
// API contract that the ChangeBlockEvnets are pushed to the cause for Post, but they
// will not replace the root causes. Likewise, this does not leak into the cause stack
// for plugin event listeners performing other operations that could potentially alter
// the cause stack (CauseStack:[Player, ScheduledTask] vs. CauseStack:[ChangeBlockEvent, Player, ScheduledTask])
final Cause normalizedEvent;
if (ShouldFire.CHANGE_BLOCK_EVENT_POST) {
normalizedEvent = currentCause.with(normalEvent);
} else {
normalizedEvent = currentCause;
}
if (normalEvent.isCancelled()) {
// If the normal event is cancelled, mark the transaction as invalid already
transaction.setValid(false);
}
final ChangeBlockEvent.Post post = ((IPhaseState) phaseState).createChangeBlockPostEvent(context, transactions, normalizedEvent);
if (ShouldFire.CHANGE_BLOCK_EVENT_POST) {
SpongeImpl.postEvent(post);
}
if (post.isCancelled()) {
// And finally, if the post event is cancelled, mark the transaction as invalid.
transaction.setValid(false);
}
if (!transaction.isValid()) {
transaction.getOriginal().restore(true, BlockChangeFlags.NONE);
if (((IPhaseState) phaseState).tracksBlockSpecificDrops(context)) {
((PhaseContext) context).getBlockDropSupplier().removeAllIfNotEmpty(pos);
}
return false; // Short circuit
}
// And now, proceed as normal.
// If we've gotten this far, the transaction wasn't cancelled, so pass 'noCancelledTransactions' as 'true'
TrackingUtil.performTransactionProcess(transaction, phaseState, context, 0);
return true;
}
} catch (Exception | NoClassDefFoundError e) {
this.printBlockTrackingException(context, phaseState, e);
return false;
}
}
// Sponge End - continue with vanilla mechanics
// Sponge - Use SpongeFlag. Inline world.isRemote since it's checked, and use the BlockChangeFlag#isNotifyClients()) And chunks are never null
// flags & 2 is replaced with BlockChangeFlag#isNotifyClients
// !this.isRemote is guaranteed since we are on the server
// chunks are never null
// if ((flags & 2) != 0 && (!this.isRemote || (flags & 4) == 0) && (chunk == null || chunk.isPopulated()))
if (spongeFlag.isNotifyClients() && chunk.isPopulated()) {
// Sponge End
minecraftWorld.notifyBlockUpdate(pos, originalBlockState, newState, spongeFlag.getRawFlag());
}
final Block block = newState.getBlock();
if (spongeFlag.updateNeighbors()) { // Sponge - Replace flags & 1 != 0 with BlockChangeFlag#updateNeighbors
minecraftWorld.notifyNeighborsRespectDebug(pos, originalBlockState.getBlock(), true);
if (newState.hasComparatorInputOverride()) {
minecraftWorld.updateComparatorOutputLevel(pos, block);
}
} else if ( spongeFlag.notifyObservers()) { // Sponge - Replace flags & 16 == 0 with BlockChangeFlag#notifyObservers.
minecraftWorld.updateObservingBlocksAt(pos, block);
}
return true;
// } // Sponge - unnecessary formatting
}
/**
* This is the replacement of {@link WorldServer#spawnEntity(net.minecraft.entity.Entity)}
* where it captures into phases. The causes and relations are processed by the phases.
*
* The difference between {@link #spawnEntityWithCause(World, Entity)} is that it bypasses
* any phases and directly throws a spawn entity event.
*
* @param world The world
* @param entity The entity
* @return True if the entity spawn was successful
*/
@SuppressWarnings("rawtypes")
public boolean spawnEntity(final WorldServer world, final net.minecraft.entity.Entity entity) {
checkNotNull(entity, "Entity cannot be null!");
// Forge requires checking if we're restoring in a world to avoid spawning item drops.
if (entity instanceof EntityItem && SpongeImplHooks.isRestoringBlocks(world)) {
return false;
}
// Sponge Start - handle construction phases
if (((EntityBridge) entity).bridge$isConstructing()) {
((EntityBridge) entity).bridge$fireConstructors();
}
final ServerWorldBridge mixinWorldServer = (ServerWorldBridge) world;
final PhaseContext<?> context = this.stack.peek();
final IPhaseState<?> phaseState = context.state;
final boolean isForced = entity.forceSpawn || entity instanceof EntityPlayer;
// Certain phases disallow entity spawns (such as block restoration)
if (!isForced && !phaseState.doesAllowEntitySpawns()) {
return false;
}
// Sponge End - continue with vanilla mechanics
final int chunkX = MathHelper.floor(entity.posX / 16.0D);
final int chunkZ = MathHelper.floor(entity.posZ / 16.0D);
if (!isForced && !mixinWorldServer.bridge$isMinecraftChunkLoaded(chunkX, chunkZ, true)) {
return false;
}
if (entity instanceof EntityPlayer) {
final EntityPlayer entityplayer = (EntityPlayer) entity;
world.playerEntities.add(entityplayer);
world.updateAllPlayersSleepingFlag();
SpongeImplHooks.firePlayerJoinSpawnEvent((EntityPlayerMP) entityplayer);
} else {
// Sponge start - check for vanilla owner
if (entity instanceof IEntityOwnable) {
final IEntityOwnable ownable = (IEntityOwnable) entity;
final net.minecraft.entity.Entity owner = ownable.getOwner();
if (owner instanceof EntityPlayer) {
context.owner = (User) owner;
if (entity instanceof OwnershipTrackedBridge) {
((OwnershipTrackedBridge) entity).tracked$setOwnerReference((User) owner);
} else {
((Entity) entity).setCreator(ownable.getOwnerId());
}
}
} else if (entity instanceof EntityThrowable) {
final EntityThrowable throwable = (EntityThrowable) entity;
final EntityLivingBase thrower = throwable.getThrower();
if (thrower != null) {