-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge PR #4 by e-aakash - Block Health & Damage
- Loading branch information
Showing
11 changed files
with
554 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"texture": "health:BlockDamageEffects", | ||
"textureSize": [160, 16], | ||
"grid": { | ||
"tileSize": [16, 16], | ||
"gridDimensions": [10, 1], | ||
"tileNames": [ | ||
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10" | ||
] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/** | ||
* Soils are just easily broken, dirt-like blocks. | ||
*/ | ||
{ | ||
"hardness": 20, | ||
"categories": ["soil"] | ||
} |
31 changes: 31 additions & 0 deletions
31
assets/prefabs/particleEffects/defaultBlockParticles.prefab
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"location": {}, | ||
"particleDataSprite": { | ||
"texture": "health:fx_smoke" | ||
}, | ||
"energyRangeGenerator": { | ||
"minEnergy": 0.5, | ||
"maxEnergy": 1 | ||
}, | ||
"velocityRangeGenerator": { | ||
"minVelocity": [-1.5, 3, -1.5], | ||
"maxVelocity": [1.5, 4, 1.5] | ||
}, | ||
"scaleRangeGenerator": { | ||
"minScale": [0.1, 0.1, 0.1], | ||
"maxScale": [0.2, 0.2, 0.2] | ||
}, | ||
"textureOffsetGenerator": {}, | ||
"accelerationAffector": { | ||
"acceleration": [0, -10, 0] | ||
}, | ||
"velocityAffector": {}, | ||
"particleEmitter": { | ||
"lifeTime": 1, | ||
"particleSpawnsLeft": 5, | ||
"maxParticles": 5, | ||
"particleCollision": false, | ||
"destroyEntityWhenDead": true | ||
}, | ||
"network": {} | ||
} |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"Health": { | ||
"currentHealth": 100, | ||
"maxHealth": 100, | ||
"regenRate": 3, | ||
"waitBeforeRegen": 10, | ||
"excessSpeedDamageMultiplier": 5, | ||
"fallingDamageSpeedThreshold": 18, | ||
"horizontalDamageSpeedThreshold": 50, | ||
"destroyEntityOnNoHealth": true | ||
} | ||
} |
265 changes: 265 additions & 0 deletions
265
src/main/java/org/terasology/logic/health/BlockDamageAuthoritySystem.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
/* | ||
* Copyright 2019 MovingBlocks | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.terasology.logic.health; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.terasology.audio.AudioManager; | ||
import org.terasology.audio.StaticSound; | ||
import org.terasology.audio.events.PlaySoundEvent; | ||
import org.terasology.entitySystem.entity.EntityBuilder; | ||
import org.terasology.entitySystem.entity.EntityManager; | ||
import org.terasology.entitySystem.entity.EntityRef; | ||
import org.terasology.entitySystem.event.ReceiveEvent; | ||
import org.terasology.entitySystem.systems.BaseComponentSystem; | ||
import org.terasology.entitySystem.systems.RegisterMode; | ||
import org.terasology.entitySystem.systems.RegisterSystem; | ||
import org.terasology.logic.characters.events.AttackEvent; | ||
import org.terasology.logic.health.event.BeforeDamagedEvent; | ||
import org.terasology.logic.health.event.OnDamagedEvent; | ||
import org.terasology.logic.health.event.OnFullyHealedEvent; | ||
import org.terasology.logic.location.LocationComponent; | ||
import org.terasology.math.TeraMath; | ||
import org.terasology.math.geom.Vector2f; | ||
import org.terasology.math.geom.Vector3f; | ||
import org.terasology.particles.components.ParticleDataSpriteComponent; | ||
import org.terasology.particles.components.generators.TextureOffsetGeneratorComponent; | ||
import org.terasology.registry.In; | ||
import org.terasology.rendering.assets.texture.Texture; | ||
import org.terasology.utilities.Assets; | ||
import org.terasology.utilities.random.FastRandom; | ||
import org.terasology.utilities.random.Random; | ||
import org.terasology.world.block.Block; | ||
import org.terasology.world.block.BlockAppearance; | ||
import org.terasology.world.block.BlockComponent; | ||
import org.terasology.world.block.BlockManager; | ||
import org.terasology.world.block.BlockPart; | ||
import org.terasology.world.block.entity.damage.BlockDamageModifierComponent; | ||
import org.terasology.world.block.family.BlockFamily; | ||
import org.terasology.world.block.regions.ActAsBlockComponent; | ||
import org.terasology.world.block.sounds.BlockSounds; | ||
import org.terasology.world.block.tiles.WorldAtlas; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
import java.util.stream.Stream; | ||
|
||
/** | ||
* This system is responsible for giving blocks health when they are attacked and | ||
* damaging them instead of destroying them. | ||
*/ | ||
@RegisterSystem | ||
public class BlockDamageAuthoritySystem extends BaseComponentSystem { | ||
private static final float BLOCK_REGEN_SECONDS = 4.0f; | ||
private static final Logger logger = LoggerFactory.getLogger(BlockDamageAuthoritySystem.class); | ||
|
||
@In | ||
private EntityManager entityManager; | ||
|
||
@In | ||
private AudioManager audioManager; | ||
|
||
@In | ||
private WorldAtlas worldAtlas; | ||
|
||
@In | ||
private BlockManager blockManager; | ||
|
||
private Random random = new FastRandom(); | ||
|
||
/** Consumes damage event if block is indestructible. */ | ||
@ReceiveEvent | ||
public void beforeDamaged(BeforeDamagedEvent event, EntityRef blockEntity, BlockComponent blockComp) { | ||
if (!blockComp.block.isDestructible()) { | ||
event.consume(); | ||
} | ||
} | ||
|
||
/** Consumes damage event if entity acting as block is indestructible. */ | ||
@ReceiveEvent | ||
public void beforeDamaged(BeforeDamagedEvent event, EntityRef blockEntity, ActAsBlockComponent blockComp) { | ||
if (blockComp.block != null && !blockComp.block.getArchetypeBlock().isDestructible()) { | ||
event.consume(); | ||
} | ||
} | ||
|
||
/** | ||
* Removes the marker component when block is fully healed. | ||
* @param event Event sent when block is fully healed | ||
* @param entity Block entity | ||
*/ | ||
@ReceiveEvent(components = {BlockDamagedComponent.class}) | ||
public void onRepaired(OnFullyHealedEvent event, EntityRef entity) { | ||
entity.removeComponent(BlockDamagedComponent.class); | ||
} | ||
|
||
/** Adds marker component to block which is damaged. */ | ||
@ReceiveEvent | ||
public void onDamaged(OnDamagedEvent event, EntityRef entity, BlockComponent blockComponent, LocationComponent locComp) { | ||
onDamagedCommon(event, blockComponent.block.getBlockFamily(), locComp.getWorldPosition(), entity); | ||
if (!entity.hasComponent(BlockDamagedComponent.class)) { | ||
entity.addComponent(new BlockDamagedComponent()); | ||
} | ||
} | ||
|
||
@ReceiveEvent | ||
public void onDamaged(OnDamagedEvent event, EntityRef entity, ActAsBlockComponent blockComponent, LocationComponent locComp) { | ||
if (blockComponent.block != null) { | ||
onDamagedCommon(event, blockComponent.block, locComp.getWorldPosition(), entity); | ||
} | ||
} | ||
|
||
private void onDamagedCommon(OnDamagedEvent event, BlockFamily blockFamily, Vector3f location, EntityRef entityRef) { | ||
BlockDamageModifierComponent blockDamageSettings = event.getType().getComponent(BlockDamageModifierComponent.class); | ||
boolean skipDamageEffects = false; | ||
if (blockDamageSettings != null) { | ||
skipDamageEffects = blockDamageSettings.skipPerBlockEffects; | ||
} | ||
if (!skipDamageEffects) { | ||
onPlayBlockDamageCommon(blockFamily, location, entityRef); | ||
} | ||
} | ||
|
||
/** Calls helper function to create block particle effect and plays damage sound. */ | ||
private void onPlayBlockDamageCommon(BlockFamily family, Vector3f location, EntityRef entityRef) { | ||
createBlockParticleEffect(family, location); | ||
|
||
BlockSounds sounds = family.getArchetypeBlock().getSounds(); | ||
if (!sounds.getDigSounds().isEmpty()) { | ||
StaticSound sound = random.nextItem(sounds.getDigSounds()); | ||
entityRef.send(new PlaySoundEvent(sound, 1f)); | ||
} | ||
} | ||
|
||
/** | ||
* Creates a new entity for the block damage particle effect. | ||
* | ||
* If the terrain texture of the damaged block is available, the particles will have the block texture. Otherwise, | ||
* the default sprite (smoke) is used. | ||
* | ||
* @param family the {@link BlockFamily} of the damaged block | ||
* @param location the location of the damaged block | ||
*/ | ||
private void createBlockParticleEffect(BlockFamily family, Vector3f location) { | ||
EntityBuilder builder = entityManager.newBuilder("health:defaultBlockParticles"); | ||
builder.getComponent(LocationComponent.class).setWorldPosition(location); | ||
|
||
Optional<Texture> terrainTexture = Assets.getTexture("engine:terrain"); | ||
if (terrainTexture.isPresent() && terrainTexture.get().isLoaded()) { | ||
final BlockAppearance blockAppearance = family.getArchetypeBlock().getPrimaryAppearance(); | ||
|
||
final float relativeTileSize = worldAtlas.getRelativeTileSize(); | ||
final float particleScale = 0.25f; | ||
|
||
final float spriteSize = relativeTileSize * particleScale; | ||
|
||
ParticleDataSpriteComponent spriteComponent = builder.getComponent(ParticleDataSpriteComponent.class); | ||
spriteComponent.texture = terrainTexture.get(); | ||
spriteComponent.textureSize.set(spriteSize, spriteSize); | ||
|
||
final List<Vector2f> offsets = computeOffsets(blockAppearance, particleScale); | ||
|
||
TextureOffsetGeneratorComponent textureOffsetGeneratorComponent = builder.getComponent(TextureOffsetGeneratorComponent.class); | ||
textureOffsetGeneratorComponent.validOffsets.addAll(offsets); | ||
} | ||
|
||
builder.build(); | ||
} | ||
|
||
/** | ||
* Computes n random offset values for each block part texture. | ||
* | ||
* @param blockAppearance the block appearance information to generate offsets from | ||
* @param scale the scale of the texture area (should be in 0 < scale <= 1.0) | ||
* | ||
* @return a list of random offsets sampled from all block parts | ||
*/ | ||
private List<Vector2f> computeOffsets(BlockAppearance blockAppearance, float scale) { | ||
final float relativeTileSize = worldAtlas.getRelativeTileSize(); | ||
final int absoluteTileSize = worldAtlas.getTileSize(); | ||
final float pixelSize = relativeTileSize / absoluteTileSize; | ||
final int spriteWidth = TeraMath.ceilToInt(scale * absoluteTileSize); | ||
|
||
final Stream<Vector2f> baseOffsets = Arrays.stream(BlockPart.sideValues()).map(blockAppearance::getTextureAtlasPos); | ||
|
||
return baseOffsets.flatMap(baseOffset -> | ||
IntStream.range(0, 8).boxed().map(i -> | ||
new Vector2f(baseOffset).add(random.nextInt(absoluteTileSize - spriteWidth) * pixelSize, random.nextInt(absoluteTileSize - spriteWidth) * pixelSize) | ||
) | ||
).collect(Collectors.toList()); | ||
} | ||
|
||
@ReceiveEvent(netFilter = RegisterMode.AUTHORITY) | ||
public void beforeDamage(BeforeDamagedEvent event, EntityRef entity, BlockComponent blockComp) { | ||
beforeDamageCommon(event, blockComp.block); | ||
} | ||
|
||
@ReceiveEvent(netFilter = RegisterMode.AUTHORITY) | ||
public void beforeDamage(BeforeDamagedEvent event, EntityRef entity, ActAsBlockComponent blockComp) { | ||
if (blockComp.block != null) { | ||
beforeDamageCommon(event, blockComp.block.getArchetypeBlock()); | ||
} | ||
} | ||
|
||
private void beforeDamageCommon(BeforeDamagedEvent event, Block block) { | ||
if (event.getDamageType() != null) { | ||
BlockDamageModifierComponent blockDamage = event.getDamageType().getComponent(BlockDamageModifierComponent.class); | ||
if (blockDamage != null) { | ||
BlockFamily blockFamily = block.getBlockFamily(); | ||
for (String category : blockFamily.getCategories()) { | ||
if (blockDamage.materialDamageMultiplier.containsKey(category)) { | ||
event.multiply(blockDamage.materialDamageMultiplier.get(category)); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** Causes damage to block without health component, leads to adding health component to the block. */ | ||
@ReceiveEvent(netFilter = RegisterMode.AUTHORITY) | ||
public void onAttackHealthlessBlock(AttackEvent event, EntityRef targetEntity, BlockComponent blockComponent) { | ||
if (!targetEntity.hasComponent(HealthComponent.class)) { | ||
HealthAuthoritySystem.damageEntity(event, targetEntity); | ||
} | ||
} | ||
|
||
@ReceiveEvent(netFilter = RegisterMode.AUTHORITY) | ||
public void onAttackHealthlessActAsBlock(AttackEvent event, EntityRef targetEntity, ActAsBlockComponent actAsBlockComponent) { | ||
if (!targetEntity.hasComponent(HealthComponent.class)) { | ||
HealthAuthoritySystem.damageEntity(event, targetEntity); | ||
} | ||
} | ||
|
||
/** Adds health component to blocks when damaged. */ | ||
@ReceiveEvent | ||
public void beforeDamagedEnsureHealthPresent(BeforeDamagedEvent event, EntityRef blockEntity, BlockComponent blockComponent) { | ||
if (!blockEntity.hasComponent(HealthComponent.class)) { | ||
Block type = blockComponent.block; | ||
if (type.isDestructible()) { | ||
HealthComponent healthComponent = new HealthComponent(); | ||
healthComponent.maxHealth = type.getHardness(); | ||
healthComponent.currentHealth = type.getHardness(); | ||
healthComponent.regenRate = type.getHardness() / BLOCK_REGEN_SECONDS; | ||
healthComponent.waitBeforeRegen = 1.0f; | ||
healthComponent.destroyEntityOnNoHealth = true; | ||
blockEntity.addComponent(healthComponent); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.