4
\$\begingroup\$

I am finishing up an actor system (like those used in 90s games, like Doom, Unreal, etc.), which currently deals with damage and event handling.

actor.cpp

/*
 * Actor System
 * (with Event System included)
 *
 * Author: Gustavo Ramos "Gustavo6046" Rehermann.
 * (c)2018. The MIT License.
 *
 * This system basically allows the creation of
 * Actors (entities), in a game world. This does
 * not handle 3D coordinates nor collision. I
 * have neither ideas nor time to implement those,
 * so I hope someone will implement those on top
 * of this code, either extending it or modifying it.
 *
 * For the sake of extensibility, actors aren't removed
 * when they die. That task is here delegated to the 
 * upper levels.
 *
 * For an example of a possible extension:
 *
 * struct Vec3
 * {
 * int XYZ[3];
 * }
 *
 * struct G_PhysicalActor
 * {
 * G_Actor* baseActor;
 * Vec3 pos;
 * int colCylinderRadius;
 * int colCylinderHeight;
 * // ...
 * }
 */
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#include "actor.h" // > structs
E_ActorEvent* E_BaseEvent = NULL; // > Master actor event linked list.
G_Actor* G_Actors = NULL; // > Master actor linked list.
unsigned int G_NumActors = 0;
// > This function shall append actor events
// to the master event linked list (I call
// this event notification).
void G_NotifyActorEvent(E_ActorEvent* evt)
{ 
 evt->nextEvent = NULL;
 if ( E_BaseEvent == NULL )
 E_BaseEvent = evt;
 else
 {
 E_ActorEvent* e = NULL;
 for ( e = E_BaseEvent; e->nextEvent != NULL; e = e->nextEvent ); // > quick way to get
 // the last event on
 // the linked list :)
 e->nextEvent = evt;
 }
}
// > Returns the next actor event.
// 
E_ActorEvent* G_PollActorEvent()
{
 // > For the sake of while loop polling, we want
 // to return NULL (0) when there is no event.
 if ( !E_BaseEvent ) return NULL;
 // > Since our linked list is like a queue, we want
 // to take only the 1st event (FIFO), and the 2nd
 // will become the 1st. Fortunately, this is very
 // easy to do.
 E_ActorEvent* res = E_BaseEvent;
 E_BaseEvent = res->nextEvent;
 return res;
}
// > Handle actor death (health being
// smaller than or equal to 0).
void G_Die(G_Actor* actor)
{
 // > At the moment, all it does is notify an event.
 // This may change.
 E_ActorEvent* evt = new E_ActorEvent;
 evt->eventType = AEV_DEATH;
 evt->actor = actor;
 G_NotifyActorEvent(evt);
}
// > Create an actor and append it to
// the master Actor linked list.
G_Actor* G_CreateActor(int health)
{
 // > Initializes the actor.
 G_Actor* a = new G_Actor;
 _G_DamageTrack* dmg = new _G_DamageTrack;
 dmg->health = health;
 dmg->totalDamage = 0;
 dmg->totalHeal = 0;
 a->damage = *dmg;
 a->index = G_NumActors;
 char* estr = new char[1]; // allocate an empty string.
 *estr = 0; // strings terminate with a null byte.
 std::fill_n(a->damageFactor_K, 256, estr);
 std::fill_n(a->damageFactor_V, 256, 1000);
 // > Adds the actor to the list.
 if ( G_Actors == NULL )
 G_Actors = a;
 else
 {
 // > Find last element on actor list, then append
 // to it.
 G_Actor* prev;
 for ( prev = G_Actors; prev->nextActor != NULL; prev = prev->nextActor );
 prev->nextActor = a;
 }
 // > Notify the actor's creation.
 G_NumActors++;
 E_ActorEvent* evt = new E_ActorEvent;
 evt->eventType = AEV_CREATED;
 evt->actor = a;
 G_NotifyActorEvent(evt);
 a->nextActor = NULL;
 return a;
}
// > Removes this actor from the list, by address.
bool G_RemoveActor(G_Actor* actor)
{
 G_Actor* a = NULL;
 if ( G_Actors == actor )
 {
 G_Actors = NULL;
 return true;
 }
 if ( G_Actors == NULL )
 return false;
 for ( a = G_Actors; a->nextActor != NULL && a->nextActor != actor; a = a->nextActor )
 if ( a == NULL || a->nextActor == NULL )
 return false;
 if ( a->nextActor != NULL )
 {
 a->nextActor = a->nextActor->nextActor;
 if ( a->nextActor != NULL )
 for ( G_Actor* cur = a->nextActor; cur != NULL; cur = cur->nextActor )
 cur->index--;
 }
 else
 a->nextActor = NULL;
 E_ActorEvent* evt = new E_ActorEvent;
 evt->eventType = AEV_REMOVED;
 evt->actor = actor;
 G_NotifyActorEvent(evt); // > Notify the actor's removal.
 return true;
}
// > Perform damage checks on the actor pointed. If
// the actor's health is smaller than 1 (it's not
// a floating point number, so we can safely assume
// that i < 1 is the same as i <= 0), we call the die
// routine on it.
//
// > For the sake of modifiability, this function exists;
// however, it is only an if statement with a call to
// G_Die.
void G_DamageCheck(G_Actor* actor)
{
 if ( actor->damage.health < 1 )
 G_Die(actor);
}
// > Deal damage to an actor, and handle damage factors
// (unless specified otherwise). Automatically calls
// the damage check function (kills actor if health is
// null or negative).
void G_TakeDamage(G_Actor* actor, char* damageType, int damage, bool checkFactors)
{ 
 // > Apply damage factors.
 if ( checkFactors && actor->damageFactor_K != NULL && actor->damageFactor_V != NULL )
 for ( unsigned int i = 0; i < 256 && actor->damageFactor_K[i] != ""; i++ )
 if ( std::strcmp(actor->damageFactor_K[i], damageType) == 0 )
 damage = damage * actor->damageFactor_V[i] / 1000;
 actor->damage.health -= damage;
 // > Damage shall be tracked by other _G_DamageTrack
 // members as well!
 if ( damage < 0 )
 actor->damage.totalHeal -= damage;
 else
 actor->damage.totalDamage += damage; 
 if ( damage > 0 )
 {
 E_ActorEvent* evt1 = new E_ActorEvent;
 evt1->eventType = AEV_DAMAGE;
 evt1->eventData = damage;
 evt1->actor = actor;
 G_NotifyActorEvent(evt1); // > Notify the actor's damage.
 }
 else
 {
 E_ActorEvent* evt1 = new E_ActorEvent;
 evt1->eventType = AEV_HEAL;
 evt1->eventData = -damage;
 evt1->actor = actor;
 G_NotifyActorEvent(evt1); // > Notify the actor's heal.
 }
 E_ActorEvent* evt1 = new E_ActorEvent;
 evt1->eventType = AEV_MODHEALTH;
 evt1->eventData = -damage;
 evt1->actor = actor;
 G_NotifyActorEvent(evt1); // > Notify any modification to the
 // actor's physical integrity. 
 G_DamageCheck(actor);
}

actor.h

#ifndef GUSTAVO_ACTORSYS_HEADER
#define GUSTAVO_ACTORSYS_HEADER
/*
 * Actor System (header)
 * (with Event System included)
 *
 * Author: Gustavo Ramos "Gustavo6046" Rehermann.
 * (c)2018. The MIT License.
 *
 * This system basically allows the creation of
 * Actors (entities), in a game world. This does
 * not handle 3D coordinates nor collision. I
 * have neither ideas nor time to implement those,
 * so I hope someone will implement those on top
 * of this code, either extending it or modifying it.
 *
 * For the sake of extensibility, actors aren't removed
 * when they die. That task is here delegated to the 
 * upper levels.
 *
 * For an example of a possible extension:
 *
 * struct Vec3
 * {
 * int XYZ[3];
 * }
 *
 * struct G_PhysicalActor
 * {
 * G_Actor* baseActor;
 * Vec3 pos;
 * int colCylinderRadius;
 * int colCylinderHeight;
 * // ...
 * }
 */
#define AEV_DEATH 0 // > Surprise! This actor died!
#define AEV_DAMAGE 1 // > This actor took positive damage.
#define AEV_HEAL 2 // > This actor took negative damage (heal).
#define AEV_MODHEALTH 3 // > This actor's health was modified (by
 // the G_TakeDamage function, most
 // likely).
#define AEV_CREATED 4 // > This actor was newly created.
#define AEV_REMOVED 5 // > This actor was removed from the master linked list..
// > This structure will track the physical integrity
// of an actor. It is supported by standard functions,
// like G_TakeDamage.
struct _G_DamageTrack
{
 int health; // > tracked actor's health
 unsigned int totalDamage; // > tracked actor's total damage (scaled
 // by damage factors)
 unsigned int totalHeal; // > tracked actor's total heal (negative damage)
};
struct G_Actor
{
 // char* name; // > actor's human name
 // char* id; // > actor's unique identifier
 unsigned int index; // > actor's G_Actors index + 1.
 // If this is equal to 0, this actor
 // should be assumed not to be in 
 // G_Actors.
 _G_DamageTrack damage; // > keeps track of 'health' (physical
 // integrity) and damage
 char* damageFactor_K[256]; // > this actor's damage factor map's keys
 int damageFactor_V[256]; // > this actor's damage factor map's values
 G_Actor* nextActor; // > actors follow a linked list.
};
struct E_ActorEvent
{
 unsigned char eventType; // > event's type (as defined in AEV_* constants)
 int eventData; // > extra information about the event
 G_Actor* actor; // > pointer to event's reference actor
 E_ActorEvent* nextEvent; // > events, like actors, follow a linked list.
};
extern E_ActorEvent* E_BaseEvent; // > Master actor event linked list.
extern G_Actor* G_Actors; // > Master actor linked list.
extern unsigned int G_NumActors;
// > This function shall append actor events
// to the master event linked list (I call
// this event notification).
extern void G_NotifyActorEvent(E_ActorEvent* evt);
// > Returns the next actor event.
// 
extern E_ActorEvent* G_PollActorEvent();
// > Handle actor death (health being
// smaller than or equal to 0).
extern void G_Die(G_Actor* actor);
// > Create an actor and append it to
// the master Actor linked list.
extern G_Actor* G_CreateActor(int health);
// > Perform damage checks on the actor pointed. If
// the actor's health is smaller than 1 (it's not
// a floating point number, so we can safely assume
// that i < 1 is the same as i <= 0), we call the die
// routine on it.
extern void G_DamageCheck(G_Actor* actor);
// > Deal damage to an actor, and handle damage factors
// (unless specified otherwise). Automatically calls
// the damage check function (kills actor if health is
// null or negative).
extern void G_TakeDamage(G_Actor* actor, char* damageType, int damage, bool checkFactors = true);
// > Removes this actor from the list, by address.
extern bool G_RemoveActor(G_Actor* actor);
#endif /* GUSTAVO_ACTORSYS_HEADER */

Below is a usage example/demo:

actormain.cpp

/*
 * Actor system usage example.
 *
 * Author: Gustavo Ramos "Gustavo6046" Rehermann.
 * (c)2018. The MIT License.
 */
#include <iostream>
#include "actor.h"
int main()
{
 G_Actor* myActor = G_CreateActor(80);
 G_Actor* myActor2 = G_CreateActor(200);
 myActor2->damageFactor_K[0] = "fire"; // Let's just say it's a dragon!
 myActor2->damageFactor_V[0] = 100;
 myActor2->damageFactor_K[1] = "default"; // And it has hard scales.
 myActor2->damageFactor_V[1] = 575;
 myActor2->damageFactor_K[2] = "water"; // But it hates water!
 myActor2->damageFactor_V[2] = 2420;
 std::cout << "Actor 0 address: " << myActor << "\n";
 std::cout << "Actor 1 address: " << myActor2 << "\n";
 std::cout << "Actor 0's initial health: " << myActor->damage.health << "\n";
 std::cout << "Actor 1's initial health: " << myActor2->damage.health << "\n";
 G_TakeDamage(myActor, "default", 30);
 G_TakeDamage(myActor, "default", 40);
 G_TakeDamage(myActor, "default", -30);
 G_TakeDamage(myActor, "default", 100);
 G_TakeDamage(myActor2, "fire", 314);
 G_TakeDamage(myActor2, "default", 120);
 G_TakeDamage(myActor2, "water", 90);
 std::cout << "Actor 0's final health: " << myActor->damage.health << "\n";
 std::cout << "Actor 1's final health: " << myActor2->damage.health << "\n";
 G_RemoveActor(myActor);
 std::cout << "Actor removed.\n";
 E_ActorEvent* evt;
 std::cout << "\n== EVENT LOG ==\n\n";
 // Print every damage/death event notified in the meanwhile.
 while ( evt = G_PollActorEvent() )
 {
 if ( evt->eventType == AEV_CREATED )
 std::cout << "Actor " << evt->actor->index << " was created!\n";
 if ( evt->eventType == AEV_DAMAGE )
 std::cout << "Actor " << evt->actor->index << " took " << evt->eventData << "hp damage!\n";
 else if ( evt->eventType == AEV_DEATH )
 std::cout << "Actor " << evt->actor->index << " has died!\n";
 else if ( evt->eventType == AEV_HEAL )
 std::cout << "Actor " << evt->actor->index << " healed " << evt->eventData << "hp!\n";
 else if ( evt->eventType == AEV_REMOVED )
 std::cout << "Actor " << evt->actor->index << " was removed from the actor list.\n";
 }
 return 0;
}

NOTE: This works when compiled with Open Watcom C++. Tell me if it doesn't work with your compiler!

Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Aug 26, 2018 at 20:46
\$\endgroup\$
5
  • \$\begingroup\$ Any reason for not using std::list? \$\endgroup\$ Commented Aug 26, 2018 at 22:52
  • \$\begingroup\$ I don't even know why I made this in C++ instead of C, but I wanted to make a program/library that could be run under DOS. \$\endgroup\$ Commented Aug 27, 2018 at 3:57
  • 1
    \$\begingroup\$ I see quite a few uses of new. Still looking for the first delete... \$\endgroup\$ Commented Aug 27, 2018 at 9:48
  • \$\begingroup\$ @MikeBorkland my issue is that, if I don't use new, the Open Watcom compiler will simply allocate only one variable per function, not per function call, so G_Actor newActor; inside a function would not allocate a new actor, but instead allocate only one, and reuse it everytime the function is called. I don' t have much of a clue about deinitialization right now, but I'll try it. \$\endgroup\$ Commented Aug 28, 2018 at 1:11
  • \$\begingroup\$ I think this works: gist.github.com/Gustavo6046/2bead16a3c1e4da8f281affc6ded2d07 \$\endgroup\$ Commented Aug 28, 2018 at 1:31

1 Answer 1

1
\$\begingroup\$

Defining constants with #define is only ok in c. In C++ there are better alternatives. Consider using instead const int or an enum for the states instead of #define in C++98.

If C++11 would be available you could even change the #define to constexpr.

Is there a reason to use unsigned int? it is usually not worth using it. The extra precision you gain is not worth it since it can lead to obscure bugs. https://stackoverflow.com/questions/22587451/c-c-use-of-int-or-unsigned-int

return 0 is not necessary to be added in the main function in c++. By definition it is auto generated by the compiler. This only applys for main.

Consider making a Actor class were you create the Actor take damage etc.

answered Aug 28, 2018 at 4:49
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.