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.