Skip to content

Commit

Permalink
Merge PR #4 by e-aakash - Block Health & Damage
Browse files Browse the repository at this point in the history
  • Loading branch information
jellysnake committed Jun 5, 2019
2 parents f5ce613 + e2d2cb7 commit 184f07c
Show file tree
Hide file tree
Showing 11 changed files with 554 additions and 2 deletions.
11 changes: 11 additions & 0 deletions assets/atlas/BlockDamageEffects.atlas
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"
]
}
}
7 changes: 7 additions & 0 deletions assets/blocks/test.block
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 assets/prefabs/particleEffects/defaultBlockParticles.prefab
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": {}
}
Binary file added assets/textures/BlockDamageEffects.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/textures/fx_smoke.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions deltas/engine/prefabs/player/player.prefab
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
}
}
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);
}
}
}
}

0 comments on commit 184f07c

Please sign in to comment.