Repository: movingblocks/terasology
Issue: Telemetry: indirect breaking of blocks causes NullPointerException

Relatively minor issue: some types of destruction can result in indirect breakage of blocks, such as when you break a block *below* a billboard plant, torch, or loose rock in JoshariasSurvival. This can lead to `EntityDestructionAuthoritySystem.recordDestroyed()` encountering a null `instigator` causing an NPE and stopping the destruction (meaning the thing on top doesn't pop)

We should either null-proof the use of instigator or find an alternative way to associate the block destruction with whomever did it. Likely of interest to @GabrielXia :-)

```
13:29:57.153 [main] INFO  o.t.n.internal.NetworkSystemImpl - Component class org.terasology.network.NetworkComponent added to EntityRef{id = 55761, netId = 1999, prefab = 'Core:dustEffect'}
13:29:57.153 [main] INFO  o.t.n.internal.NetworkSystemImpl - Component class org.terasology.logic.location.LocationComponent added to EntityRef{id = 55761, netId = 1999, prefab = 'Core:dustEffect'}
13:29:57.153 [main] INFO  o.t.n.internal.NetworkSystemImpl - Component class org.terasology.entitySystem.entity.internal.EntityInfoComponent removed from EntityRef{id = 55758, netId = 1997}
13:29:57.153 [main] INFO  o.t.n.internal.NetworkSystemImpl - Component class org.terasology.entitySystem.entity.internal.EntityInfoComponent added to EntityRef{id = 55758, netId = 1997}
13:29:57.153 [main] INFO  o.t.n.internal.NetworkSystemImpl - Component class org.terasology.logic.health.HealthComponent removed from EntityRef{id = 55758, netId = 1997}
13:29:57.154 [main] ERROR o.t.e.event.internal.EventSystemImpl - Failed to invoke event
java.lang.NullPointerException: null
	at org.terasology.logic.health.EntityDestructionAuthoritySystem.recordDestroyed(EntityDestructionAuthoritySystem.java:47)
	at org.terasology.logic.health.EntityDestructionAuthoritySystem.onDestroy(EntityDestructionAuthoritySystem.java:33)
	at org.terasology.logic.health.EntityDestructionAuthoritySystemMethodAccess.invoke(Unknown Source)
	at org.terasology.entitySystem.event.internal.EventSystemImpl$ByteCodeEventHandlerInfo.invoke(EventSystemImpl.java:506)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.sendStandardEvent(EventSystemImpl.java:269)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.send(EventSystemImpl.java:260)
	at org.terasology.entitySystem.entity.internal.BaseEntityRef.send(BaseEntityRef.java:145)
	at org.terasology.world.block.structure.BlockStructuralSupportSystem.validateSupportForBlockOnSide(BlockStructuralSupportSystem.java:113)
	at org.terasology.world.block.structure.BlockStructuralSupportSystem.checkForSupportRemoved(BlockStructuralSupportSystem.java:85)
	at org.terasology.world.block.structure.BlockStructuralSupportSystemMethodAccess.invoke(Unknown Source)
	at org.terasology.entitySystem.event.internal.EventSystemImpl$ByteCodeEventHandlerInfo.invoke(EventSystemImpl.java:506)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.sendStandardEvent(EventSystemImpl.java:269)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.send(EventSystemImpl.java:260)
	at org.terasology.entitySystem.entity.internal.BaseEntityRef.send(BaseEntityRef.java:145)
	at org.terasology.world.internal.EntityAwareWorldProvider.updateBlockEntity(EntityAwareWorldProvider.java:155)
	at org.terasology.world.internal.EntityAwareWorldProvider.setBlock(EntityAwareWorldProvider.java:120)
	at org.terasology.world.internal.WorldProviderWrapper.setBlock(WorldProviderWrapper.java:52)
	at org.terasology.world.block.entity.BlockEntitySystem.doDestroy(BlockEntitySystem.java:88)
	at org.terasology.world.block.entity.BlockEntitySystemMethodAccess.invoke(Unknown Source)
	at org.terasology.entitySystem.event.internal.EventSystemImpl$ByteCodeEventHandlerInfo.invoke(EventSystemImpl.java:506)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.sendStandardEvent(EventSystemImpl.java:269)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.send(EventSystemImpl.java:260)
	at org.terasology.entitySystem.entity.internal.BaseEntityRef.send(BaseEntityRef.java:145)
	at org.terasology.logic.health.EntityDestructionAuthoritySystem.onDestroy(EntityDestructionAuthoritySystem.java:37)
	at org.terasology.logic.health.EntityDestructionAuthoritySystemMethodAccess.invoke(Unknown Source)
	at org.terasology.entitySystem.event.internal.EventSystemImpl$ByteCodeEventHandlerInfo.invoke(EventSystemImpl.java:506)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.sendStandardEvent(EventSystemImpl.java:269)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.send(EventSystemImpl.java:260)
	at org.terasology.entitySystem.entity.internal.BaseEntityRef.send(BaseEntityRef.java:145)
	at org.terasology.logic.health.HealthAuthoritySystem.doDamage(HealthAuthoritySystem.java:181)
	at org.terasology.logic.health.HealthAuthoritySystem.checkDamage(HealthAuthoritySystem.java:196)
	at org.terasology.logic.health.HealthAuthoritySystem.onDamage(HealthAuthoritySystem.java:188)
	at org.terasology.logic.health.HealthAuthoritySystemMethodAccess.invoke(Unknown Source)
	at org.terasology.entitySystem.event.internal.EventSystemImpl$ByteCodeEventHandlerInfo.invoke(EventSystemImpl.java:506)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.sendStandardEvent(EventSystemImpl.java:269)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.send(EventSystemImpl.java:260)
	at org.terasology.entitySystem.entity.internal.BaseEntityRef.send(BaseEntityRef.java:145)
	at org.terasology.logic.health.HealthAuthoritySystem.damageEntity(HealthAuthoritySystem.java:121)
	at org.terasology.logic.health.BlockDamageAuthoritySystem.onAttackHealthlessBlock(BlockDamageAuthoritySystem.java:221)
	at org.terasology.logic.health.BlockDamageAuthoritySystemMethodAccess.invoke(Unknown Source)
	at org.terasology.entitySystem.event.internal.EventSystemImpl$ByteCodeEventHandlerInfo.invoke(EventSystemImpl.java:506)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.sendConsumableEvent(EventSystemImpl.java:279)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.send(EventSystemImpl.java:258)
	at org.terasology.entitySystem.entity.internal.BaseEntityRef.send(BaseEntityRef.java:145)
	at org.terasology.logic.characters.CharacterSystem.onAttackRequest(CharacterSystem.java:217)
	at org.terasology.logic.characters.CharacterSystemMethodAccess.invoke(Unknown Source)
	at org.terasology.entitySystem.event.internal.EventSystemImpl$ByteCodeEventHandlerInfo.invoke(EventSystemImpl.java:506)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.sendStandardEvent(EventSystemImpl.java:269)
	at org.terasology.entitySystem.event.internal.EventSystemImpl.send(EventSystemImpl.java:260)
	at org.terasology.entitySystem.entity.internal.BaseEntityRef.send(BaseEntityRef.java:145)
	at org.terasology.network.internal.NetClient.processEvents(NetClient.java:527)
	at org.terasology.network.internal.NetClient.processReceivedMessages(NetClient.java:423)
	at org.terasology.network.internal.NetClient.update(NetClient.java:230)
	at org.terasology.network.internal.NetworkSystemImpl.update(NetworkSystemImpl.java:319)
	at org.terasology.engine.subsystem.common.NetworkSubsystem.preUpdate(NetworkSubsystem.java:46)
	at org.terasology.engine.TerasologyEngine.tick(TerasologyEngine.java:428)
	at org.terasology.engine.TerasologyEngine.mainLoop(TerasologyEngine.java:400)
	at org.terasology.engine.TerasologyEngine.run(TerasologyEngine.java:376)
	at org.terasology.engine.Terasology.main(Terasology.java:142)
13:29:57.483 [main] INFO  o.t.n.internal.NetworkSystemImpl - Component class org.terasology.particles.components.affectors.AccelerationAffectorComponent added to EntityRef{id = 55882, netId = 2000, prefab = 'Core:defaultBlockParticles'}
13:29:57.483 [main] INFO  o.t.n.internal.NetworkSystemImpl - Component class org.terasology.entitySystem.entity.internal.EntityInfoComponent added to EntityRef{id = 55882, netId = 2000, prefab = 'Core:defaultBlockParticles'}
13:29:57.483 [main] INFO  o.t.n.internal.NetworkSystemImpl - Component class org.terasology.network.NetworkComponent added to EntityRef{id = 55882, netId = 2000, prefab = 'Core:defaultBlockParticles'}
```

Changed files:
engine/src/main/java/org/terasology/logic/health/EntityDestructionAuthoritySystem.java

diff --git a/engine/src/main/java/org/terasology/logic/health/EntityDestructionAuthoritySystem.java b/engine/src/main/java/org/terasology/logic/health/EntityDestructionAuthoritySystem.java
index e79321d66..84c633a32 100644
--- a/engine/src/main/java/org/terasology/logic/health/EntityDestructionAuthoritySystem.java
+++ b/engine/src/main/java/org/terasology/logic/health/EntityDestructionAuthoritySystem.java
@@ -30,7 +30,7 @@
 public class EntityDestructionAuthoritySystem extends BaseComponentSystem {
     @ReceiveEvent
     public void onDestroy(DestroyEvent event, EntityRef entity) {
-        recordDestroyed(event,entity);
+        recordDestroyed(event, entity);
         BeforeDestroyEvent destroyCheck = new BeforeDestroyEvent(event.getInstigator(), event.getDirectCause(), event.getDamageType());
         entity.send(destroyCheck);
         if (!destroyCheck.isConsumed()) {
@@ -41,40 +41,42 @@ public void onDestroy(DestroyEvent event, EntityRef entity) {
 
     private void recordDestroyed(DestroyEvent event, EntityRef entityRef) {
         EntityRef instigator = event.getInstigator();
-        if (entityRef.hasComponent(BlockComponent.class)) {
-            BlockComponent blockComponent = entityRef.getComponent(BlockComponent.class);
-            String blockName = blockComponent.getBlock().getDisplayName();
-            if (instigator.hasComponent(GamePlayStatsComponent.class)) {
-                GamePlayStatsComponent gamePlayStatsComponent = instigator.getComponent(GamePlayStatsComponent.class);
-                Map<String, Integer> blockDestroyedMap = gamePlayStatsComponent.blockDestroyedMap;
-                if (blockDestroyedMap.containsKey(blockName)) {
-                    blockDestroyedMap.put(blockName, blockDestroyedMap.get(blockName) + 1);
+        if (instigator != null) {
+            if (entityRef.hasComponent(BlockComponent.class)) {
+                BlockComponent blockComponent = entityRef.getComponent(BlockComponent.class);
+                String blockName = blockComponent.getBlock().getDisplayName();
+                if (instigator.hasComponent(GamePlayStatsComponent.class)) {
+                    GamePlayStatsComponent gamePlayStatsComponent = instigator.getComponent(GamePlayStatsComponent.class);
+                    Map<String, Integer> blockDestroyedMap = gamePlayStatsComponent.blockDestroyedMap;
+                    if (blockDestroyedMap.containsKey(blockName)) {
+                        blockDestroyedMap.put(blockName, blockDestroyedMap.get(blockName) + 1);
+                    } else {
+                        blockDestroyedMap.put(blockName, 1);
+                    }
+                    instigator.saveComponent(gamePlayStatsComponent);
                 } else {
+                    GamePlayStatsComponent gamePlayStatsComponent = new GamePlayStatsComponent();
+                    Map<String, Integer> blockDestroyedMap = gamePlayStatsComponent.blockDestroyedMap;
                     blockDestroyedMap.put(blockName, 1);
+                    instigator.addOrSaveComponent(gamePlayStatsComponent);
                 }
-                instigator.saveComponent(gamePlayStatsComponent);
-            } else {
-                GamePlayStatsComponent gamePlayStatsComponent = new GamePlayStatsComponent();
-                Map<String, Integer> blockDestroyedMap = gamePlayStatsComponent.blockDestroyedMap;
-                blockDestroyedMap.put(blockName, 1);
-                instigator.addOrSaveComponent(gamePlayStatsComponent);
-            }
-        } else if (entityRef.hasComponent(CharacterComponent.class)) {
-            String monsterName = entityRef.getParentPrefab().getName();
-            if (instigator.hasComponent(GamePlayStatsComponent.class)) {
-                GamePlayStatsComponent gamePlayStatsComponent = instigator.getComponent(GamePlayStatsComponent.class);
-                Map<String, Integer> creatureKilled = gamePlayStatsComponent.creatureKilled;
-                if (creatureKilled.containsKey(monsterName)) {
-                    creatureKilled.put(monsterName, creatureKilled.get(monsterName) + 1);
+            } else if (entityRef.hasComponent(CharacterComponent.class)) {
+                String creatureName = entityRef.getParentPrefab().getName();
+                if (instigator.hasComponent(GamePlayStatsComponent.class)) {
+                    GamePlayStatsComponent gamePlayStatsComponent = instigator.getComponent(GamePlayStatsComponent.class);
+                    Map<String, Integer> creatureKilled = gamePlayStatsComponent.creatureKilled;
+                    if (creatureKilled.containsKey(creatureName)) {
+                        creatureKilled.put(creatureName, creatureKilled.get(creatureName) + 1);
+                    } else {
+                        creatureKilled.put(creatureName, 1);
+                    }
+                    instigator.saveComponent(gamePlayStatsComponent);
                 } else {
-                    creatureKilled.put(monsterName, 1);
+                    GamePlayStatsComponent gamePlayStatsComponent = new GamePlayStatsComponent();
+                    Map<String, Integer> creatureKilled = gamePlayStatsComponent.creatureKilled;
+                    creatureKilled.put(creatureName, 1);
+                    instigator.addOrSaveComponent(gamePlayStatsComponent);
                 }
-                instigator.saveComponent(gamePlayStatsComponent);
-            } else {
-                GamePlayStatsComponent gamePlayStatsComponent = new GamePlayStatsComponent();
-                Map<String, Integer> creatureKilled = gamePlayStatsComponent.creatureKilled;
-                creatureKilled.put(monsterName, 1);
-                instigator.addOrSaveComponent(gamePlayStatsComponent);
             }
         }
     }