Health System

GSoC Week 5 update

Oreon Health System

Week 5 involved working on the Oreon Health System which encompasses logic (currently just) related to the reduction in health of the Oreon based on the situation. The initial implementation considers hunger as a parameter for this reduction i.e. an Oreon that has been hungry for a while and cannot find a Diner in the village will lose health over time, also notifying the player about its “plight” every once in a while. Every time the needs_food node is hit in the Oreon’s behavior tree and the Oreon cannot find a DIner in the village the OreonHealthSystem is triggered. If there are no tasks available in the village, then the huger check node is hit every time the BT is run, i.e this would lead to a deduction in the Oreon health parameter every tick, which would mean a hungry Oreon would have a rather short lifespan. I thought of two probable solutions for this:

  1. Reduce the health every tick but by a very small quantity, so it seems that Oreon can fight hunger for a long time.
  2. Use a variable to keep track of the last check and decide whether to run the HealthSystem logic based on the time elapsed.

I decided to go with the second approach, as the first approach would be rather resource intensive considering that the HealthSystem logic would be run every time the game updates, which in turn leads to fetching the OreonAttributeComponent, changing a field , replicating this change over the network every tick. So, I added a lastHungerCheck field to the component and use this decide whether to go ahead with the reduction or not.

What happens when Oreon health hits zero?

An obvious question which was just lurking around the corner while I was working on the health deduction logic. So, I had to destroy the Oreon entity, but this would lead to Oreons just outright vanish from the world, not an elegant solution. Luckily, there are already implemented animations for the die sequence in the Oreons module. Now it came down to how to run this animation once and destroy the Oreon entity after. In order to accomplish the above the effect I added a OreonDeathSystem.

Oreon Death System

A rather grim topic :D
This system handles the destruction of the Oreon entity after it has reached zero health. The challenge was to let the death animation play and trigger the destroy() method for the Oreon entity after it has ended. The logic behind this is first of all the Behavior component is removed so that the Oreon stops looking for any tasks, then all the animations from its skeletal mesh are removed and the ones defined in the DyingComponent in the Oreon prefabs are attached. Now, to calculate the duration of the animation I used the getFrameCount() method(code snippet below) and multiplied with the time required for each frame, for every animation defined in the pool in the DyingComponent.

    float lifespan = 0;
    for (MeshAnimation meshAnimation : skeletalMeshComponent.animationPool) {
        lifespan += meshAnimation.getTimePerFrame() * (meshAnimation.getFrameCount() - 1);
    }

This lifespan is then added to the current game time to get the time of death for the Oreon which is stored in a field defined in a DeathTimeComponent which is then added to this Oreons. The update() method of a UpdateSubscriberSystem is called every the game updates, so this method in the Death System checks if the game time is past the death time for the all the entities which have a DieAnimationRunningComponent and destroys the corresponding entity.

    @Override
    public void update(float delta) {
        float currentTime = time.getGameTime();

        for (EntityRef oreon : entityManager.getEntitiesWith(DeathTimeComponent.class)) {
            DeathTimeComponent deathTimeComponent = oreon.getComponent(DeathTimeComponent.class);

            //if the death time has already passed
            if (deathTimeComponent.deathTime < currentTime) {
                oreon.destroy();
            }
        }
    }
Oreon Dead
An Oreon has fallen!

Abandoning an assigned task

This week I also managed to pull a card from Backlog on my board, yay! If an Oreon was stuck “tricky” situtaion(like if it were made to fall in a ditch made using several TNT blasts :D) from which it cannot find a path to its target block in the assigned task area, it kept on jumping around and trying to get there but in vain. So to handle to handle this situtaion the Oreons must have the ability to ‘let go’ of the task and critter randomly for a while, which might lead them to an open area. In order to decide whether the Oreon is stuck a time based approach i.e based on the time elapsed since the Oreon was assigned the task, but this could lead to erroneous situtation if the Oreon was at a far off location from the assigned area or also would be a waste of time if the Oreon got stuck in the start of such an interval. A better was to check if the Oreon had a collision with the same block twice in a row. The move_to node in the Behaviors module uses the HorizontalCollisionEvent to decide when an NPC should jump so as to get around a block in front of them. I used this event in the TaskManagementSystem to store the last collision location and check against this value when the next collision occurs, if they are same then the Oreon is freed and this task is added back to the Holding for other Oreons to take up.

Some other minor bug fixes and enhancements

Multiple Holdings attached to a player

I also came across a minor bug while testing for another GSoC project under the org the Record and Replay project which enables you to record all the events in the game that the player experiences and then run them later. I ran a saved game with MOO enabled to test the recording though the project is still in an initial state and does not support all events required in the module, I did come across a bug. The HoldingComponent was attached to the player using the onPlayerSpawn() method, so every time the player is spawned a Holding component would be attached to their entity, though this was fine when starting new games, playing an already saved game would lead to a new Holding for the player every time a saved game is started, so the Oreons already spawned would be looking for tasks in a different holding than the one the player is adding tasks to. The fix for this was pretty easy in comparison to the impact it had on the gameplay logic, it involved just adding a check for the component if it is already attached to the player entity and only add if not found.

All Oreons now have a name

I used the NameGenerator to get a name for each of the Oreons and then added a NameTagComponent with their first names, to obtain a floating name over each Oreon. This also lead to another handy enhancement of adding these names to the notifications sent to the player so as to give them a more personalized feel :D.

Oreon Name
Say Hi to Dagina :D
What is a game tick?
A tick is a unit of measure for time, specifically it is refers to a single instance of a repeated action (usually a broad action) in a game, or the period of time that action consumes.

.