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.
News?