Battle Code: Doing It Right

For discussion of the code running behind the game

Moderator: Staff

User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Battle Code: Doing It Right

Postby Roots » Mon Feb 08, 2010 7:45 am

After spending a good couple of hours today just trying to figure out how the battle code works, scanning through all the various temporary functions, commented blocks of code, etc. trying to figure out how to fix the drawing problem of actor stamina icons, I have reached an inescapable conclusion. This sucks. The code for drawing stamina icons is split across three different classes and four or five different functions, and I'm left with the decision of "do a dirty hack to fix it" or "do it right". I chose to do it right. Therefore, I will be spending the next couple of weeks before the Feb. internal release trying to sort out this mess and in the process fix all of the little issues and annoyances in battles as well as possibly adding some small new features.

I'm not going to be re-writing/designing it from scratch so you can put down your pitchforks guys. :rolleyes: I wanted to create this thread as an avenue for discussion as I attempt to "do it right" and get feedback. My first order of business is going to be with battle state management. Battle actors have all of their own states, but the battle itself does not. The BattleMode::Update() method is a messy conglomeration of conditional statements that is pretty hard to follow. So I'm going to start by creating an enum for battle state and a ChangeState() method for the other battle code to use. If necessary, I'll also split up the Update() method into smaller methods that perform more distinct steps.

Here's my initial stab at the states. If you think this state list is incomplete let me know.

Code: Select all

//! \brief Used to indicate what state the overall battle is currently operating in
enum BATTLE_STATE {
   BATTLE_STATE_INVALID   = -1,
   BATTLE_STATE_INITIAL   =  0, //!< Character sprites are running in from off-screen to their battle positions
   BATTLE_STATE_NORMAL    =  1, //!< Normal state where player is watching actions play out and waiting for a turn
   BATTLE_STATE_COMMAND   =  2, //!< Player is choosing a command for a character
   BATTLE_STATE_EVENT     =  3, //!< A scripted event is taking place, suspending all standard action
   BATTLE_STATE_VICTORY   =  4, //!< Battle has ended with the characters victorious
   BATTLE_STATE_DEFEAT    =  5, //!< Battle has ended with the characters defeated
   BATTLE_STATE_TOTAL     =  6
};
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Mon Feb 08, 2010 11:52 pm

Here are some changes I'm planning to make.


1) Draw code order
The order of draw operations is wrong and disorderly. This is why we see things like the character selector graphic being drawn on top of the characters. The order of the major draw operations should go like this:

  1. Draw background image
  2. Draw background extras/animations
  3. Draw certain indicator graphics like the actor selector
  4. Draw the sprites (in proper x/y order)
  5. Draw the stamina bar
  6. Draw the stamina icons (in proper y order)
  7. Draw damage/healing text
  8. Draw the GUI (action window, dialogue window, etc)

A lot of the draw code I plan to swap location between the BattleMode and BattleActor classes. For example, BattleMode has a container for all damage text but it doesn't do a good job of making sure that two strings of battle text do not overwrite. This is why when a character gets attacked twice by two enemies in rapid succession, we see

Another draw operation I'm going to switch is the stamina icon from BattleActor to BattleMode. There is a proper order in which stamina icons should be drawn and its not being done right now. Plus its rather confusing the way the stamina icon draw position is determined, because its stamina icon location is set in both the BattleActor and BattleAction classes depending on what state the actor is in. Its just really... :eyespin: to try to figure out what's going on.


2) BattleAction processing

I'm making big changes to this class, its derived classes, and the way objects of this class are managed. Currently actors are completely unaware of what action they are preparing to take, they just know that they are about to or are currently taking an action. The BattleMode class meanwhile maintains a queue of actions to execute and processes them accordingly. The communication between actions, the actors using them, and battle mode is quite poor. Here's a list of the things I'm doing.

    - BattleActors maintain a BattleAction pointer member for the action that they are about to take (if in warm-up state) or are currently taking
    - Remove the warm-up timer from the BattleAction class. Instead re-use the wait timer from the GlobalActor class (I'll rename _wait_timer to _state_timer or something). It will also use this for the cool-down timer.
    - The "RunScript" method on BattleAction will return a bool. If the return value is true, the skill is finished executing and can be removed. This removes the need for the laughable "_should_be_removed" member
    - BattleMode will not keep a queue of battle actions anymore. Instead it will keep a queue of battle actors (each which contain a pointer to the action they wish to execute)
    - BattleMode will not directly interact with battle actions like it does now. Instead its the actors that execute their actions. BattleMode only has to worry about the state of each actor.

I also think that battle actions should be scripted in a similar manner to how map events are executed. But this isn't something I'm going to address now. Just wanted to throw it out there.


3) No modifications to global objects until battle finishes

Right now we are modifying the data for global objects and characters as the battle progresses. I'm going to eventually change this so that global data is not updated until the battle ends. This will allow us to implement the battle restart feature with ease, not to mention it should make it easier for some status effects to work and reduce the likelihood that a bug occurs (imagine a status effect that increases a character's strength by 10, but then we forget to restore the original strength to the GlobalCharacter object). This is more of a long-term thing that I don't plan to do right away, but I do want to start making steps toward this direction.

----

Hit me up with any concerns/comments about any of these points.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Tue Feb 09, 2010 11:47 am

rujasu: This question is directed at you because I believe you were the one who got Kyle to function as an enemy at the end of the demo.

Should we create a battle actor class for representing playable characters who become enemies?

For actors we have the abstract BattleActor class and then the BattleCharacter and BattleEnemy classes that derive from it. I'm wondering if we should also have a BattleEnemyCharacter class. Right now BattleEnemy appears to be sort of hacked so that Kyle can work as an enemy, but I think we should have a separate class for enemies in battle who happen to be playable characters. There are some big differences between characters and enemies in battle, the most obvious being that characters don't have sprite damage frames but rather use fully animated sprites.

I don't plan to implement this class anytime soon (the hacked BattleEnemy class will do for this demo), but I would like to put a skeleton class in there with a to do note for the future.
Image
rujasu
Developer
Posts: 758
Joined: Sun Feb 25, 2007 5:40 am
Location: Maryland, USA

Re: Battle Code: Doing It Right

Postby rujasu » Tue Feb 09, 2010 5:22 pm

Roots wrote:rujasu: This question is directed at you because I believe you were the one who got Kyle to function as an enemy at the end of the demo.

Should we create a battle actor class for representing playable characters who become enemies?


:disapprove: Absolutely not, unless we come to a point where this is going to happen more than 2-3 times. Of course, we have no idea since 90% of the story isn't written yet, but I don't think this is a mechanic we're going to use ever again beyond this one sequence. If we need it later, fine, but I seriously doubt we do, and I don't want to further complicate the code with it.
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Tue Feb 09, 2010 6:38 pm

Ah, I just played through the Kyle fight scene again and realized that he acts like any normal enemy (no special animations for fighting). I was just confused because the EnemyActor class had code that suggested that it was playing custom animations. In that case this isn't an issue at all. I'll just fix the code so that the GlobalEnemy class isn't trying to pretend that it has custom animations.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Tue Feb 16, 2010 12:39 am

Quoting myself from a post in another thread I made a few days ago:

Roots wrote:Battle mode code changes are not going well. I hate to say it, but this is turning into a major re-write. I didn't want to do it and I really tried hard to avoid doing it, but things were just too messed up in there its very difficult and frustrating to try to fix one thing without fixing them all. I'll be continuing to work on this over the next few days but you won't see any commits from me until I have something stable in there again with almost all of our old functionality back.


So that's how it is. I know some of you may be rolling your eyes at me right now thinking "damnit, Roots is doing another unnecessary re-write" but trust me, we need this and the sooner the better. We need it because when I went in to try to make a quick fix to the display location of the stamina icons, I found myself having to make non-minor modifications in 3 different source files. When it takes that much work to fix a freaking draw position, something is very wrong.

Anyway, with all the changes I'm making I doubt this is going to be ready for the internal release coming in less than a week. So unless I fix everything and have ample time for testing, I won't be committing any of my changes for at least another week. I'm writing down a list of changes that I'm working on here for review. If you don't like something or you think something should be done a different/better way, let me know.


1) State-based processing
One problem we have in the battle code, especially in the BattleMode class, is that we have a clumsy handful of members for representing various states of operation. For example, there's a boolean for whether or not a dialogue is taking place. There's a boolean for if the battle is over or not. There's a boolean for if the characters were victorious or not, etc. This is hard to manage and error prone.

My solution is to have two state enums. One is for the BattleMode class and basically represents the general state of the battle. The other is for BattleActors and helps to determine things like if they are dead/paralyzed, where to draw their stamina icon, etc. All actor objects also have their own timer available for the states to use. This is helpful for each state to manage when it is to transition to the next state.

2) "Battle windows" and battle mode split

Right now we have a somewhat awkward file pair, battle_windows.* which contains two classes. One for the "action window" (where the player selects actions for their characters) and one for the "finish window" (which displays the victory/defeat menus). There's also a heap of members in the BattleMode class that more or less only need to be used by these two classes. I've given this some thought and here's a general list of things I'm going to do with this code, with the aim of: (1) reducing the clutter in the BattleMode class, (2) implementing cursor memory in the action window (remembering the selected targets/actions for each character individually), (3) separating the action/finish window code because they really nothing in common other than displaying menu windows.

  • Create new files: battle_command.h, battle_command.cpp, battle_finish.h, battle_finish.cpp. Remove files: battle_windows.h, battle_windows.cpp
  • battle_command.* will contain the code that is currently in the ActionWindow class. The code in these files handle anything and everything related to the player selecting commands for their characters.
  • battle_finish.* will contain the code that is currently in the FinishWindow class. The code here will handle anything and everything required to do after the battle has finished with either a victory or defeat
  • Move the victory/defeat music from BattleMode class to some class in battle_finish.* (and allow an option to set custom victory/defeat music)
  • Move the target selection graphics to the battle_command code from BattleMode.
  • Move all members of BattleMode related to current targets/attack points to battle_command code.


3) BattleTarget class

One small utility class that I think could come in handy is a class for representing battle targets. It would basically be a container class holding a target type (GLOBAL_TARGET_TYPE), pointer to a BattleActor, attack point index integer, and a pointer to a std:: container of BattleActor pointers. Only certain members of this class are valid depending upon the target type. This makes it rather simple to pass around target information where it is needed. I'll probably stick this class in battle_utils.*


4) Damage text

Right now the BattleMode has a single container of all damage text. I am changing this so that instead each actor has a container of damage text. This makes sense because actor's can automatically register damage (or healing) when their stats are modified. A new damage display manager class can also ensure that two damage text objects are not drawn on top of one another as they are currently. I think in the future this class can also be expanded to indicate additional damage or resistance due to elemental effects and changes in status effects.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Thu Feb 18, 2010 4:12 am

Things are continuing to go slowly, but they're going well. I feel pretty good about the new BattleTarget class and I think its going to make it easier for our action and item script code that implement the battle execution/use functions. All of the existing script functions will require some modifications to work with the new code but it shouldn't be bad. I've come across a couple problems that I want some feedback on.


1) Valid target functions

One problem with current actions is that they all assume that a valid target is one that is not dead. This will probably be the case for most skills/items, but there are definite exceptions to this rule. For example, a revival item would only be valid for dead targets, not living ones. Or perhaps there is a skill that removes all status effects on its target, so it should check that the target has at least one status effect active before it is executed. This is an issue for both battle and menu/field use cases to consider.

I can think of two solutions to this problem. The first is that the script functions can return a boolean value to indicate whether or not the target was valid. False indicates an invalid target and that the skill/item was not used, while true indicates that they were valid and the skill/item were used. The second solution is to have another function (which would be option) for skills/items to determine if the current target is valid or not. Actually the new function for the second solution would only be needed for battles, because menu use can just simply refuse the use and force the player to select a new target.

The advantage of the first solution is that we don't need to worry about another script function. The disadvantage of the first solution is that it would have to include code for finding the next available target, if one exists. This would likely lead to a lot of redundant code in the battle use/execute script functions where it would have to cycle through targets and find one that has valid criteria. If we do the second solution then we can do that target search on the C++ side, meaning less code to worry about.

I think I want to go with a combination of the two solutions. The menu use/execute function will return a boolean indicating whether the target was valid or not. The battle use/execute case will have a separate optional function for determining target validity, and it too returns a simple boolean. Finding the next valid target would be done on the C++ side.


2) Lingering skill/item usages

A problem related to #1 above is what if an actor has selected a skill to execute or an item to use, and no targets are valid for that particular skill/item? Right now I have it so that the actor immediately moves to the start of the idle state and cancels the skill/item action. The cool-down period is skipped since no action was done.


3) Actor containers

I've been scratching my head figuring out the best container to store all of our actors in. Previously they were in std::deque and the battle classes did this odd job of referring to indeces in these structures, which wasn't very safe nor wise. I changed them to std::set because that way we only pass around actor pointers and don't have to worry about indeces. But there are some cases where actor order is important. For example, for determining the draw positions for characters in the status window at the bottom of the screen. And when selecting the next enemy to target, you'd kind of want the target jump order to make sense, and not just for the target to visually jump around.

I think what I might do is to have both a std::deque and a std::set container for both characters and enemies (so four containers total). The std::deque would contain CharacterActor and EnemyActor pointers and be ordered specifically, while the std::set would contain BattleActor pointers. The reason I'm thinking of keeping the std::set around is because for the BattleTarget class, a target is allowed to point to a party and I need the character and enemy parties to be of the same data type (std::set<BattleActor*>).

My major concern with this though is if we have skills that affect enemy or character parties differently somehow (perhaps visually) then it might not be such a great idea.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Fri Feb 19, 2010 3:56 am

Thought I'd share some skeleton code I've been writing today. These classes will help replace and extend the functionality of the old ActionWindow class.


First, a utility class to make it a little easier to keep track of the availability of battle usable items.

Code: Select all

/** ****************************************************************************
*** \brief A simple container class for items that may be used in battle
***
*** This class adds an additional member to be associated with GlobalItem objects
*** which keeps track of how many of that item are available to use. This is necessary
*** because when an actor selects an item to use, they do not immediately use that
*** item and may ultimately not use the item due to the user becoming incapacitated
*** or having no valid target for the item. At all times, the available count of an item
*** will be less than or equal to the actual count of the item.
***
*** The proper way to use this class is to call the following methods for the following
*** situations.
***
*** - DecrementAvailableCount(): call when an actor has selected to use an item
*** - IncrementAvaiableAcount(): call when an actor does not use an item that it selected
*** - DecrementCount(): call when the item is actually used
***
*** \note Do not call the IncrementCount(), DecrementCount(), or SetCount() methods on the GlobalItem
*** pointer. This will circumvent the ability of this class to keep an accurate and correct available
*** count. Instead, use the IncrementCount() and DecrementCount() methods of this BattleItem class
*** directly.
*** ***************************************************************************/
class BattleItem {
public:
   //! \param item A pointer to the item to represent. Should be a non-NULL value.
   BattleItem(hoa_global::GlobalItem* item);

   ~BattleItem();

   //! \brief Class member accessor methods
   //@{
   hoa_global::GlobalItem* GetItem() const
      { return _item; }

   uint32 GetAvailableCount() const
      { return _available_count; }
   //@}

   /** \brief Increases the available count of the item by one
   *** The available count will not be allowed to exceed the GlobalItem _count member
   **/
   void IncrementAvailableCount();

   /** \brief Decreases the available count of the item by one
   *** The available count will not be allowed to decrement below zero
   **/
   void DecrementAvailableCount();

   /** \brief A wrapper function that retrieves the actual count of the item
   *** \note Calling this function is equivalent to calling GetItem()->GetCount()
   **/
   uint32 GetCount() const
      { return _item->GetCount(); }

   /** \brief Increments the count of an item by one
   *** \note This method should not be called under normal battle circumstances
   **/
   void IncrementCount();

   /** \brief Decrements the count of the item by one
   *** Will also decrement the available count if the two counts are equal
   **/
   void DecrementCount();

private:
   //! \brief A pointer to the item that this class represents
   hoa_global::GlobalItem* _item;

   //! \brief The number of instances of this item that are available to be selected to be used
   uint32 _available_count;
}; // class BattleItem


Next, a class that basically manages all of the items that the character party has that are usable in battle. This will also include maintaining the GUI OptionBox list that the player uses to select an item to use.

Code: Select all

class ItemCommand {
public:
   ItemCommand();

   ~ItemCommand();

private:
}; // class InventoryCommand


Next, a class that manages all of the skills that a character may use. One of these is created for each character on the battle field and it will enable us to implement cursor memory on a per-character, per-skill type basis. Good stuff.

Code: Select all

class CharacterCommand {
public:
   CharacterCommand();

   ~CharacterCommand();

private:
}; // class CharacterCommand


And finally this class I suppose can be called the replacement of ActionWindow. Its responsible for processing user input, updating, and drawing of the action selection window and contents.

Code: Select all

class CommandSupervisor {
public:
   CommandSupervisor();

   ~CommandSupervisor();
private:
}; // class CommandSupervisor



I'm still playing around and thinking about the best way to organize all of this. I might create a separate CommandSkill class and have each CommandCharacter class contain three of those (for attack, defense, support skills) just to make things a little bit more organized. I might also have CommandItem and CommandSkill inherit from an abstract class to make it easier to switch the active display in the action window for each of the four types of categories.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Fri Feb 26, 2010 4:21 pm

To date, battle mode has sort of been hacking its way by in terms of how skills are used and actions are executed. For example, it actually doesn't matter which attack point you target with a skill because calculations have always been done on the zeroth attack point. One of the original goals of demo 1.0 was to have complete, finalized battle damage formulas in place. I don't plan to do that, but I am planning to at least eliminate all the hacks we have in here. I'm starting work on this now and intend to do the following.


1) BattleActor class will inherit from GlobalActor

I want to have a copy of all stats (HP, strength, agility, etc.) that can be modified as needed for status effects and give us scripting flexibility (an actor could suddenly power up or weaken an in response to a specific event in battle). The GlobalActor contained by BattleActor will be a copy of the GlobalCharacter/GlobalEnemy class, not the original class itself. Instead, BattleActor will maintain a pointer to the original GlobalActor class that it does not modify until the end of battle. This way we have all of the original stats of the actor available for when we want to reset a stat to its normal value (e.g. when a status effect is neutralized) or if the player wants to retry the battle after losing, it will be simple to restore the battle back to the original state.


2) Actor-type targets

Right now skills or items that target an actor actually target the first attack point. This will be changed. I think the best option is to average the defense and evade ratings of all attack points on the target and use that in the damage calculation. This will also take into account the sum of the defense ratings of all armor equipped, not just the natural defense on each attack point.


3) Party-type targets

We've never been able to support skills or items that target the entire party. I'll be adding this feature as well. Support is already half-way there with the new BattleTarget class I introduced here some time ago. I think in Lua it may be a little more tricky to use though, because Lua doesn't know how to operate on a std::deque<BattleActor*> object (BattleTarget contains a pointer to this container to represent the party). I think BattleTarget may have to provide specific functions for Lua to use. Specifically, a function that returns the size of the party and a function that returns a BattleActor pointer at a specific index. Then, Lua can operate on each actor in the party individually.


4) Metaphysical attack/defense

Currently only physical attack/defense are factored into our damage formulas when in fact damage dealt should be the sum of both physical and metaphysical damage. I will change this.


5) Common damage calculation formulas

One annoyance right now is that there are no standard functions for calculating damage or evasion. As a result a lot of the skill functions in Lua do pretty much the same thing. I'm going to introduce some functions for Lua to use to make this process a little bit easier. Note that these are common formulas, not the only formulas. I expect in the future we'll have skills that will implement their own damage/evasion formula completely independently (for example, a skill that always reduces the target's HP by ~40%).

Formulas I plan to write include:
  • One which determines if the attack was evaded (missed)
  • One which determines standard damage based on attack/defense ratings
  • One which determines only physical damage
  • One which determines only metaphysical damage

Each of these functions will include the use of standard variation via gaussian distributions. I will also include arguments that make these calculations more flexible, such as increasing or decreasing the range of variation.


--- Edit: forgot one more point ---

6) Actor knock-back

When an actor gets hit when they are in the idle, warm-up, or cool-down state, they should have their timer knocked back a little bit (we discussed this in a design thread). I'm going to work on adding this in too, and the more damage dealt the greater the knock-back. In the future I bet we'll make use of skills that knock the target back completely.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Sat Feb 27, 2010 7:13 pm

Sanity check with the battle actor class inheritance.

  • GlobalActor is an abstract class containing actor stats such as HP, SP, strength, evade, etc.
  • GlobalCharacter derives from GlobalActor
  • GlobalEnemy derives from GlobalActor
  • BattleActor is an abstract class that derives from GlobalActor
  • BattleActor's constructor requires a pointer to a valid GlobalActor object
  • BattleActor uses the GlobalActor copy constructor to initialize its GlobalActor base
  • BattleActor retains as a member the GlobalActor pointer used in its construction and provides a method to access that pointer
  • BattleCharacter derives from BattleActor
  • BattleCharacter's constructor requires a pointer to a valid GlobalCharacter object
  • BattleCharacter retains as a member the GlobalCharacter pointer used in its construction and provides a method to access that pointer
  • BattleEnemy derives from BattleActor
  • BattleEnemy's constructor requires a pointer to a valid GlobalEnemy object
  • BattleEnemy retains as a member the GlobalEnemy pointer used in its construction and provides a method to access that pointer

Make sense? I don't want to inherit from GlobalCharacter or GlobalEnemy because that would necessitate creating a copy of all images, skills, etc. I only need a copy of the actor's stats for the battle code to modify however it likes.
Image
rujasu
Developer
Posts: 758
Joined: Sun Feb 25, 2007 5:40 am
Location: Maryland, USA

Re: Battle Code: Doing It Right

Postby rujasu » Sat Feb 27, 2010 7:23 pm

Roots wrote:Sanity check with the battle actor class inheritance.

  • GlobalActor is an abstract class containing actor stats such as HP, SP, strength, evade, etc.
  • GlobalCharacter derives from GlobalActor
  • GlobalEnemy derives from GlobalActor
  • BattleActor is an abstract class that derives from GlobalActor
  • BattleActor's constructor requires a pointer to a valid GlobalActor object
  • BattleActor uses the GlobalActor copy constructor to initialize its GlobalActor base
  • BattleActor retains as a member the GlobalActor pointer used in its construction and provides a method to access that pointer
  • BattleCharacter derives from BattleActor
  • BattleCharacter's constructor requires a pointer to a valid GlobalCharacter object
  • BattleCharacter retains as a member the GlobalCharacter pointer used in its construction and provides a method to access that pointer
  • BattleEnemy derives from BattleActor
  • BattleEnemy's constructor requires a pointer to a valid GlobalEnemy object
  • BattleEnemy retains as a member the GlobalEnemy pointer used in its construction and provides a method to access that pointer

Make sense? I don't want to inherit from GlobalCharacter or GlobalEnemy because that would necessitate creating a copy of all images, skills, etc. I only need a copy of the actor's stats for the battle code to modify however it likes.


Sounds kind of bizarre, but it's a complex problem and I don't really see a better approach.
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Sat Feb 27, 2010 7:54 pm

I'm not exactly comfortable with it either, but its the best option I can think of. The alternative is to not have BattleActor derive from GlobalActor, but then BattleActor would have to implement all of its own members and methods for actor stats, which would be duplicating a lot of what GlobalActor already provides. But I'm open to suggestions. Maybe there's a third solution that I haven't thought of yet.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Sun Feb 28, 2010 8:29 am

With regards to battle events, I don't approve of the way they are done currently. Although I think its likely that rujasu just hacked something in there to get it working for the demo. The current system appears to have three script functions registered to the event: before, during, and after. They are executed before the battle begins, during the battle (on every update loop), and after the battle ends. Simple, but not very flexible.


I would like to design the battle event system similar to the map event system, and we can probably share some code between the two. Here's how I envision it working.

  1. When a BattleMode is created (usually from map mode), events are added using a numeric identifier, same as is done currently
  2. During battle execution, BattleMode::Update() will call a Lua function for each event. This Lua function is what I'm going to call the "trigger function"
  3. The purpose of the trigger function is to determine if specific battle conditions are met. If so, the trigger function will formulate an event dependency graph like the one below
    Image
  4. Each node in the graph represents a different "sub-event" of the overall event chain. Think of each node as its own Lua function which completes a small step, like moving a sprite or playing a sound
  5. The event chain is carried out until it ends, at which point the event is no longer active (but may become triggered again if the proper conditions arise)


We also need to note the clear distinction between two types of battle events: control events and passive events. A control event is one which changes the battle state to BATTLE_STATE_EVENT and effectively controls the entire battle, such as for battle dialogue as we see at the end of the final battle in the current demo. Only one control event can be active at any given time, and another control event may not take control away from an active control event. Passive events, on the other hand, don't interfere with the standard flow of battle. They are used to do things like draw graphics in the background (imagine sporadic explosions seen in the battle background as the battle wears on), play ambient sounds, etc. They can also change the stats of one or more actors, like perhaps randomly inflicting the characters with poison as they are fighting in a marsh swamp. The possibilities are endless. :cool:


So that's my "vision" if you will of how battle events should work. I think this system will afford us all the flexibility we will need, make it relatively easy to script battle events in Lua, and provides a powerful yet simple event management system. And it may be able to leverage existing code from map mode as well. Does anyone disagree with any part of this plan or have a better idea to share?
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Sun Feb 28, 2010 7:37 pm

FYI: adding a new header/source file pair for the new battle code: battle_indicators.h/.cpp. This code will contain a more powerful and complete implementation of what was formerly known as the "DamageText" class in battle.cpp. Instead of having a single container for damage text managed by the BattleMode class, instead each BattleActor will have its own sub-manager for damage text. I'm also making this flexible enough to draw the text in multiple colors and styles (so that larger damage will be drawn in larger text for example) and also writing skeleton code so that eventually we can also display images. I've got a pretty good idea of what I want to do here.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Thu Mar 04, 2010 9:15 am

I'm going to be creating a new battle utility class called BattleTimer. It inherits from SystemTimer and implements a few additional timing features that battles require. For instance, the ability to directly set the time expired so that we can modify an actor's state timer at will, which will be useful for implementing some status effects like paralysis.

Also I want to provide reassurance that I will document the design of the new battle code in the wiki at some point, perhaps beginning in a week or so. The design is mostly complete now but I've still got a lot of loose ends to tie up.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Sat Mar 06, 2010 4:06 am

As far as I can tell, I've only got three things remaining to add before battles are capable of doing everything that they did in the Feb. 2010 internal release.

1) Status Effects
2) Battle Events
3) Battle Dialogue

(1): My plan for status effects involves doing what I had mentioned in another thread: viewtopic.php?f=17&t=1974 . I'm going to dramatically scale down the GlobalStatusEffect class and take most of what currently exists in there to a new battle class in a new source file pair: battle_effects.*. The work on this already started long ago actually, when I reorganized BattleActor to inherit from GlobalActor. Because BattleActor keeps an original copy of the GlobalActor object used to create it, we can modify any stat of the BattleActor and reset it to its base value using the original copy. The new indicator system I put in (the stuff that shows damage text and misses) will also be used to show a change in an actor's status by floating up an icon representing the status and intensity. I'm also going to implement varying effects for the different intensities as well. I think I'm going to be able to get this system pretty much complete and its going to function and look a lot better.

(2): I already laid out my thoughts and plan on battle events a couple posts up. I'm not going to implement a full-blown system right now though since the only event that we use is a dialogue at the end of the last battle in the demo. I might write some skeleton stuff and a lot of to-do notes, and then just implement the event with a temporary function. A full event system can always be tested out later.

(3): I'm going to see what I can do about moving the core MapDialogue class to the common code, then use that base class to do anything needed for battle dialogues. I don't think this will be too much work.


So knocking out those three items is my current plan. I estimate it will take me a week at the most. After that's done I'll go through the battle code, fix all those minor bugs and quirks, clean up the code and comments as necessary, and write documentation. From there battle mode should be in a solid state where we have the core set of features available and any new features we make use of in the future should be pretty easy to add. I feel really good about where the battle code is going to be in another week or two. :approve:
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Sun Mar 07, 2010 8:32 pm

Status effects are coming along. There's more work to do to get them fully supported than I initially thought there would be. Changes in status will be indicated visually with indicator graphics, the same way that damage and misses are indicated with text. Below is the main method that registers a change in status on an actor, which is what skill/item scripts will be using. While I'm at it, I included the other set of functions we'll use in Lua to register changes in the characters. They're all named "Register[***]" to make it consistent. All register functions, in addition to causing the actual property change, will also automatically create any indicator text/graphic next to the actor so that the player is informed of the change. Its a pretty convenient and easy-to-use system. :approve:

From the BattleActor class...

Code: Select all

   /** \brief Deals damage to the actor by reducing its hit points by a certain amount
   *** \param amount The number of hit points to decrease on the actor
   ***
   *** If the state of the actor is ACTOR_STATE_DEAD, this function will print a warning and change nothing.
   *** If the amount of damage dealt is greater than the actor's current hit points, the actor will be placed
   *** in the ACTOR_STATE_DEAD state.
   **/
   void RegisterDamage(uint32 amount);

   /** \brief Heals the actor by restoring a certain amount of hit points
   *** \param amount The number of hit points to add to the actor
   ***
   *** If the state of the actor is ACTOR_STATE_DEAD, this function will print a warning and change nothing.
   *** The number of hit points on the actor are not allowed to increase beyond the actor's maximum hit
   *** points.
   **/
   void RegisterHealing(uint32 amount);

   //! \brief Indicates that an action failed to connect on this target
   void RegisterMiss();

   /** \brief Causes a change in a character's status
   *** \param status The type of status to change
   *** \param intensity The intensity of the change
   ***
   *** This is the single method for registering a change in status for an actor. It can be used to add
   *** a new status, remove an existing status, or change the intensity level of an existing status. A
   *** positive intensity argument will increase the intensity while a negative intensity will decrease
   *** the intensity. Many different changes can occur depending upon the current state of the actor and
   *** any active status effects when this function is called, as the list below describes.
   ***
   *** - If this status is not already active on the character and the intensity argument is positive,
   ***   the actor will have the new status added at that intensity.
   *** - If this status is not already active on the character and the intensity argument is negative,
   ***   no change will occur.
   *** - If this status is active and intensity is positive, intensity will be increased but will not
   ***   exceed the maximum intensity level.
   *** - If this status is active, the intensity is positive, and the current intensity of the status
   ***   is already at the maximum level, the status timer will be reset and the intensity will not change.
   *** - If this status is active and intensity is negative, intensity will be decreased but not allowed
   ***   to go lower than the neutral level. If the neutral level is reached, the status will be removed.
   *** - If this status is active and the intensity is GLOBAL_INTENSITY_NEG_EXTREME, the status will be
   ***   removed regardless of its current intensity level.
   *** - If this status has an opposite status type that is active on the actor and the intensity argument
   ***   is positive, this will decrease the intensity of the opposite status by the degree of intensity.
   ***   This may cause that opposite status to be removed and this new status to be added if the intensity
   ***   change is signficantly high.
   **/
   void RegisterStatusChange(hoa_global::GLOBAL_STATUS status, hoa_global::GLOBAL_INTENSITY intensity);


I anticipate that more Register methods will be added at a later time. For now though, I think these four will meet all of our needs.
Image
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Wed Mar 10, 2010 8:48 pm

I know that many of you have remarked about the indicator text and icons not drawing in the correct location. Let me elaborate on the situation and the problem here, because a solution is not going to be trivial. First, when I wrote the indicator code I decided that they should be drawn immediately behind the respective targets. This means to the left of the character sprites, who face to the right, and to the right of the enemy sprites, who face to the left. I also set the draw alignment to left or right appropriately for both sides. So far so good.


now the problem stems not from the code, but from the images themselves. Look at the idle animation sequence for Claudius:

http://allacrost.svn.sourceforge.net/vi ... iew=markup

There's four frames there, obviously. But look at how much blank space there is to the left and right of each sprite. That's the killer. I can't account for that in the code currently. So what am I to do? At first I thought the simple solution was to make all character sprite frames left aligned instead of center aligned within their respective frame images. But that will cause other problems since map mode, which we want to re-use these animations in, requires that all sprites are center aligned. Otherwise we'll get some funky problems there with image "jumping" when switching from centered to non-centered animations and improper collision detection.


So short term, I think the best thing to do is cheat and always assume that the edge of the actual sprite is roughly 16 pixels to the left of the center and all the way to the right for enemies. A more elegant solution would be able to either examine or somehow know the sprite boundaries within the image so it can ignore the transparent area when computing the drawing coordinates. So that's what my plan is for now, and hopefully it will fix this problem.
Image
rujasu
Developer
Posts: 758
Joined: Sun Feb 25, 2007 5:40 am
Location: Maryland, USA

Re: Battle Code: Doing It Right

Postby rujasu » Wed Mar 10, 2010 9:24 pm

Wait, you're drawing damage indicators to the side of the sprites? I'd suggest drawing the text at the center of the sprite -- anything else will look wrong, in my opinion.
User avatar
Roots
Dictator
Posts: 8665
Joined: Wed Jun 16, 2004 6:07 pm
Location: Austin TX
Contact:

Re: Battle Code: Doing It Right

Postby Roots » Wed Mar 10, 2010 9:35 pm

rujasu wrote:Wait, you're drawing damage indicators to the side of the sprites? I'd suggest drawing the text at the center of the sprite -- anything else will look wrong, in my opinion.


Well considering that its been that way for some time and I know you've seen it, I guess it doesn't look so wrong does it? :angel: I disagree that drawing it in the dead center of the sprite is the best option. I have been considering drawing it so that it covers the sprite partially or completely on its backside. Its really easy to tweak and figure out what you think looks best. (Go to battle_indicators.cpp line 111, function void IndicatorText::Draw() -- change the values passed in to the "MoveRelative" calls to adjust the indicator positions).
Image

Return to “Programming”

Who is online

Users browsing this forum: No registered users and 5 guests