Cause And Effect

So, I finished up the changes to my effects code… although it remains to plug it in and integrate it with the main code base! I thought I would take this opportunity to show what was done and why…

Effects in the CRPG are, essentially, things that alter the status of a player, a monster, or the party in itself. This includes dealing wound damage, setting a counter for a spell effect, increasing the party’s rations, and so forth. There are around 40 effects in the game in total.

Originally, the code I wrote to process the effects was linear; each effect was a separate routine, and it used a passed register for the “address” to update, as well as values in a few extra registers. (For party effects, no passed register is needed, since there’s only one party ever.) Here’s a sample of how it looked:

* Effects subroutine
* R0 = Variable one
* R3 = Variable two
* R2 = STATE location pointer (player or monster)
* R1 = Effect
EFFCTS MOV  R11,*R10+
       SLA  R1,1
       MOV  @EFCASE(R1),R4
       B    *R4

* Reduces R0 fatigue
EF1    AI   R2,4
* Reduces R0 wounds
EF0    S    R0,*R2
       JGT  EF0A
       CLR  *R2
EF0A   B    @SUBRET

* Inflicts R0 fatigue
EF3    AI   R2,4
* Inflicts R0 wounds
EF2    A    R0,*R2
       B    @SUBRET

* Effects case array
EFCASE DATA EF0,EF1,EF2,EF3,EF4,EF5,EF6,EF7
       DATA EF8,EF9,EF10,EF11,EF12,EF13,EF14,EF15
       DATA EF16,EF17,EF18,EF19,EF20,EF21,EF22,EF23
       DATA EF24,EF25,EF26,EF27,EF28,EF29,EF30,EF31
       DATA EF32,EF33,EF34

So what’s the problem with that? Nothing… except that by the end, after crunching all the routines together along with the large reference array for each function (EFCASE), the routines take up around 426 bytes of space. Not a HUGE amount of space, even in a 32k system, but fairly significant. It also is hard to expand and maintain; I’ve had to revise effects multiple times and every time has been a hassle.

So, my new approach was to create an effects processing method. First, it’s in a separate workspace so that the registers can’t be corrupted by prior called routines. Second, all of the uniqueness of each effects is defined in a data structure, not as logic checks inside linear code. There’s more overhead writing a more “generic” processing system, but if your data is tight enough you eventually get more out of it.

The new code looks like this:

* Effects subroutine
*
* Inputs
* 
* @WORK - Effect #
* @WORK+2 - Array location for player/monster
* @WORK+4 - Value #1
* @WORK+5 = Value #2 (or #1 overflow)
EFFCTS DATA AWS,EFC0
EFC0   LI   R0,EFCDAT                  * Get effects data into R0
       LI   R9,60
EFC1   CB   @WORK,*R0                  * Check if current effect is in record
       JNE  EFC4                       * If not, jump to loop
       INC  R0                         * Increment R0 to type byte
       MOVB *R0+,R1                    * Copy type byte to R1
       MOV  R1,R2                      * Copy type byte to R2
       MOV  R1,R3                      * Copy type byte to R3
       ANDI R2,>1000                   * Get player/party bit
       JNE  EFC2
       MOV  @WORK+2,R4                 * Copy player/monster state to R4
       JMP  EFC3
EFC2   LI   R4,PARTY                   * Copy party state to R4
EFC3   AB   *R0+,@>8389                * Add Offset to R4 low-byte
       ANDI R3,>2000                   * Get byte/word bit
       ANDI R1,>0F00
       SRL  R1,8
       MOV  @EFCASE(R1),R2
       B    *R2
       JMP  EFC5
EFC4   AI   R0,4                       * Move to next record
EFC5   DEC  R9                         * Decrease effect count
       JNE  EFC1
EFC6   RTWP

* Effect "is zero"
EFZERO INC  R0                         * Set R0 to next record
       CB   @ZERO,*R4                  * Check if target value is zero
       JEQ  EFC5                       * If so, go to next record
       JMP  EFC4                       * Otherwise skip next record

* Effects add
EFADD  INC  R0                         * Set R0 to next record
       MOV  R3,R3                      * Word or byte operation?
       JEQ  EFADD1
       A    @WORK+4,*R4                * Add word value
       JMP  EFC5
EFADD1 AB   @WORK+4,*R4                * Add byte value
       JMP  EFC5

* Effects subtract
EFSUB  INC  R0                         * Set R0 to next record
       MOV  R3,R3                      * Word or byte operation?
       JEQ  EFSUB1
       S    @WORK+4,*R4                * Subtract word value
       JLT  EFSUB3                     * If negative, clear to 0
       JMP  EFC5
EFSUB1 SB   @WORK+4,*R4                * Subtract byte value
       JLT  EFSUB2                     * If negative, clear to 0
       JMP  EFC5
EFSUB2 MOVB @ZERO,*R4                  * Set to 0
       JMP  EFC5
EFSUB3 CLR  *R4                        * Set to 0
       JMP  EFC5

* Effect set
EFSET  INC  R0                         * Set R0 to next record
       MOV  R3,R3                      * Word or byte operation?
       JEQ  EFADD1
       MOV  @WORK+4,*R4                * Set word value
       JMP  EFC5
EFSET1 MOVB @WORK+4,*R4                * Set byte value
       JMP  EFC5

* Effect clear
EFCLR  MOVB *R0+,R3
       SRL  R3,8                       * Set R3 to count of bytes to clear
EFCLR1 MOVB @ZERO,*R4+                 * Clear location
       DEC  R3
       JNE  EFCLR1
       JMP  EFC5

* Effects cases
EFCASE DATA EFADD,EFSUB,EFSET,EFCLR,EFZERO

* Effects data
EFCDAT DATA >0022,>0000
       DATA >0122,>0400
       DATA >0220,>0000
       DATA >0320,>0400
       DATA >0424,>0000
       DATA >0522,>0000
       DATA >0522,>0400
       DATA >0632,>1C00
       DATA >0730,>1C00
       DATA >0802,>0F00
       DATA >0902,>1300
       DATA >0902,>1400
       DATA >0902,>1600
       DATA >0A06,>1E01
       DATA >0B06,>1F01
       DATA >0C06,>0816
       DATA >0D14,>0700
       DATA >0D10,>1401
       DATA >0E10,>1500
       DATA >0F10,>1600
       DATA >1010,>1700
       DATA >1100,>0800
       DATA >1106,>0901
       DATA >1200,>0900
       DATA >1206,>0801
       DATA >1300,>0A00
       DATA >1306,>0B01
       DATA >1400,>0B00
       DATA >1406,>0A01
       DATA >1500,>0C00
       DATA >1506,>0D01
       DATA >1600,>0D00
       DATA >1606,>0C01
       DATA >1700,>0E00
       DATA >1706,>0F01
       DATA >1800,>0F00
       DATA >1806,>0E01
       DATA >1900,>1200
       DATA >1906,>1301
       DATA >1A00,>1300
       DATA >1A06,>1201
       DATA >1B00,>1400
       DATA >1C08,>1C00
       DATA >1C00,>1500
       DATA >1D08,>1C00
       DATA >1D00,>1600
       DATA >1E00,>1700
       DATA >1F00,>1800
       DATA >2008,>1C00
       DATA >2000,>1900
       DATA >2100,>1A00
       DATA >2200,>1B00
       DATA >2300,>1C00
       DATA >2408,>1000
       DATA >2400,>1100
       DATA >2508,>1100
       DATA >2500,>1000
       DATA >2600,>1D00
       DATA >2700,>1E00
       DATA >2800,>1F00

Each effect is defined with a one or more 4-byte records. The first byte indicates which effect it applies to. The second is the type (adding, subtracting, checking for zero, etc.), whether to do word or byte operations, and whether it’s a player/monster or party change. The last two bytes are used for values or ranges to change, depending on the type.

The entire effects array is parsed each time a new effect is done. That is so every case is checked and every record potentially ran. In BASIC this would be a terrible waste, but for assembly language this is fairly fast. Given that the most effects calculated at any time is under a dozen, even a human user wouldn’t notice it taking much longer than a split second.

I haven’t crunched the numbers yet, but my initial calculations were very favorable for the size of the new Effects system… The data table is 240 bytes, and the instructions add around another 128 or so… It may not be much smaller than the original, but it’s VERY easy for me to maintain and add new records.

I also need to integrate it better into the combat system itself. My attack routines for melee and ranged are adding wounds directly; they should use the effects system instead. That will allow me to substitute in different effects, such as spells, within the same framework.

This entry was posted in Coding, CRPG, TI-99/4a. Bookmark the permalink.

1 Response to Cause And Effect

  1. Alessandro says:

    News?

Leave a Reply

Your email address will not be published. Required fields are marked *