Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add HealthComponent #1

Merged
merged 8 commits into from
May 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion module.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"author" : "",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to toss an author name in there :-)

"displayName" : "Health",
"description" : "This module provides basic implementation of Health",
"dependencies" : [],
"dependencies" : [{"id": "ModuleTestingEnvironment","minVersion": "0.1.0", "optional":true}],
"serverSideOnly" : false
}
155 changes: 155 additions & 0 deletions src/main/java/org/terasology/logic/health/HealthAuthoritySystem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* 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 gnu.trove.iterator.TFloatIterator;
import gnu.trove.iterator.TIntIterator;
import gnu.trove.list.TFloatList;
import gnu.trove.list.TIntList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.entitySystem.systems.UpdateSubscriberSystem;
import org.terasology.logic.health.event.BeforeHealEvent;
import org.terasology.logic.health.event.DoHealEvent;
import org.terasology.logic.health.event.FullHealthEvent;
import org.terasology.logic.health.event.OnHealedEvent;
import org.terasology.logic.players.event.OnPlayerRespawnedEvent;
import org.terasology.math.TeraMath;
import org.terasology.registry.In;

/**
* This system takes care of healing of entities with HealthComponent.
* To increase the health of an entity, send DoHealEvent
*
* Logic flow for healing:
* - DoHealEvent
* - BeforeHealEvent
* - (HealthComponent saved)
* - OnHealedEvent
* - FullHealthEvent (if at full health)
*/
@RegisterSystem(RegisterMode.AUTHORITY)
public class HealthAuthoritySystem extends BaseComponentSystem implements UpdateSubscriberSystem {

private static final Logger logger = LoggerFactory.getLogger(HealthAuthoritySystem.class);

@In
private EntityManager entityManager;

@In
private org.terasology.engine.Time time;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for having this fully qualified?


@Override
public void update(float delta) {
for (EntityRef entity : entityManager.getEntitiesWith(HealthComponent.class)) {
HealthComponent health = entity.getComponent(HealthComponent.class);
if (health.currentHealth <= 0) {
continue;
}

if (health.currentHealth == health.maxHealth || health.regenRate == 0) {
continue;
}

int healAmount = 0;
healAmount = regenerateHealth(health, healAmount);

checkHealed(entity, health, healAmount);
}
}

private int regenerateHealth(HealthComponent health, int healAmount) {
int newHeal = healAmount;
while (time.getGameTimeInMs() >= health.nextRegenTick) {
newHeal++;
health.nextRegenTick = health.nextRegenTick + (long) (1000 / health.regenRate);
}
return newHeal;
}

private void checkHealed(EntityRef entity, HealthComponent health, int healAmount) {
if (healAmount > 0) {
checkHeal(entity, healAmount, entity);
entity.saveComponent(health);
}
}

private void checkHeal(EntityRef entity, int healAmount, EntityRef instigator) {
BeforeHealEvent beforeHeal = entity.send(new BeforeHealEvent(healAmount, instigator));
if (!beforeHeal.isConsumed()) {
int modifiedAmount = calculateTotal(beforeHeal.getBaseHeal(), beforeHeal.getMultipliers(), beforeHeal.getModifiers());
if (modifiedAmount > 0) {
doHeal(entity, modifiedAmount, instigator);
} else if (modifiedAmount < 0) {
// TODO: Add damage events
// doDamage(entity, -modifiedAmount, EngineDamageTypes.HEALING.get(), instigator, EntityRef.NULL);
}
}
}

private void doHeal(EntityRef entity, int healAmount, EntityRef instigator) {
HealthComponent health = entity.getComponent(HealthComponent.class);
if (health != null) {
int healedAmount = Math.min(health.currentHealth + healAmount, health.maxHealth) - health.currentHealth;
health.currentHealth += healedAmount;
entity.saveComponent(health);
entity.send(new OnHealedEvent(healAmount, healedAmount, instigator));
if (health.currentHealth == health.maxHealth) {
entity.send(new FullHealthEvent(instigator));
}
}
}

private int calculateTotal(int base, TFloatList multipliers, TIntList modifiers) {
// For now, add all modifiers and multiply by all multipliers. Negative modifiers cap to zero, but negative
// multipliers remain (so damage can be flipped to healing)

float total = base;
TIntIterator modifierIter = modifiers.iterator();
while (modifierIter.hasNext()) {
total += modifierIter.next();
}
total = Math.max(0, total);
if (total == 0) {
return 0;
}
TFloatIterator multiplierIter = multipliers.iterator();
while (multiplierIter.hasNext()) {
total *= multiplierIter.next();
}
return TeraMath.floorToInt(total);

}

/** Handles DoHeal event to increase health of entity */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One-liner javadoc is good for simple fields, but methods should always use the expanded version including doc for parameters etc :-)

@ReceiveEvent(components = {HealthComponent.class})
public void onHeal(DoHealEvent event, EntityRef entity) {
checkHeal(entity, event.getAmount(), event.getInstigator());
}

/** Resets the health of player to maxHealth on re-spawn. */
@ReceiveEvent
public void onRespawn(OnPlayerRespawnedEvent event, EntityRef entity, HealthComponent healthComponent) {
healthComponent.currentHealth = healthComponent.maxHealth;
entity.saveComponent(healthComponent);
}
}
64 changes: 64 additions & 0 deletions src/main/java/org/terasology/logic/health/HealthComponent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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.terasology.entitySystem.Component;
import org.terasology.network.Replicate;
import org.terasology.rendering.nui.properties.TextField;

/**
* Provides Health to entity attached with HealthComponent. Contains the parameters
* required for all health related events.
*/
public class HealthComponent implements Component {

/** Maximum allowed health, capped to this if exceeding this value. */
@Replicate
public int maxHealth = 20;

/** Amount of health restored in each regeneration tick. */
@Replicate
public float regenRate;

/** Time delay before regeneration starts. */
@Replicate
public float waitBeforeRegen;

/** Falling speed threshold above which damage is inflicted to entity. */
@Replicate
public float fallingDamageSpeedThreshold = 20;

/** Horizontal speed threshold above which damage is inflicted to entity. */
@Replicate
public float horizontalDamageSpeedThreshold = 20;

/** The multiplier used to calculate damage when horizontal or vertical threshold is crossed. */
@Replicate
public float excessSpeedDamageMultiplier = 10f;


/** The current value of health. */
@Replicate
@TextField
public int currentHealth = 20;

/** Next tick time that will trigger regeneration. */
public long nextRegenTick;

/** Used to send Destroy event when health breaches zero. */
public boolean destroyEntityOnNoHealth;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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.event;

import gnu.trove.list.TFloatList;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TFloatArrayList;
import gnu.trove.list.array.TIntArrayList;
import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.entitySystem.event.ConsumableEvent;

/**
* This event is sent to an entity to allow modification and cancellation of healing.
*
*/
public class BeforeHealEvent implements ConsumableEvent {
private int baseHeal;
private EntityRef instigator;

private boolean consumed;
private TFloatList multipliers = new TFloatArrayList();
private TIntList modifiers = new TIntArrayList();

public BeforeHealEvent(int amount, EntityRef instigator) {
this.baseHeal = amount;
this.instigator = instigator;
}

public int getBaseHeal() {
return baseHeal;
}

public EntityRef getInstigator() {
return instigator;
}

public TFloatList getMultipliers() {
return multipliers;
}

public TIntList getModifiers() {
return modifiers;
}

public void multiply(float amount) {
multipliers.add(amount);
}

public void add(int amount) {
modifiers.add(amount);
}

public void subtract(int amount) {
modifiers.add(-amount);
}

@Override
public boolean isConsumed() {
return consumed;
}

@Override
public void consume() {
consumed = true;
}
}
45 changes: 45 additions & 0 deletions src/main/java/org/terasology/logic/health/event/DoHealEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.event;

import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.entitySystem.event.Event;

/**
* Send this event to an entity to heal it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"to heal it ..." - fully? Or could a negative number be hurtful instead? Maybe that's covered in the System

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A negative value is supposed to cause damage, but not working at present. Will update the Javadoc when the issue is solved.

*
*/
public class DoHealEvent implements Event {
private int amount;
private EntityRef instigator;

public DoHealEvent(int amount) {
this(amount, EntityRef.NULL);
}

public DoHealEvent(int amount, EntityRef instigator) {
this.amount = amount;
this.instigator = instigator;
}

public int getAmount() {
return amount;
}

public EntityRef getInstigator() {
return instigator;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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.event;

import org.terasology.entitySystem.entity.EntityRef;
import org.terasology.entitySystem.event.Event;

/**
* Event sent upon an entity reaching full health if previously on less than full health.
*
*/
public class FullHealthEvent implements Event {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might this be more descriptive as OnFullHealthEvent ?

Would also put it more in line with OnHealedEvent - although then I'd wonder if this would work better as OnFullyHealedEvent plus wonder what the implications of both triggering would be

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming it as OnFullyHealedEvent, seems more apt for full health event.

private EntityRef instigator;

public FullHealthEvent(EntityRef instigator) {
this.instigator = instigator;
}

public EntityRef getInstigator() {
return instigator;
}

}