+adamantyr Posted March 16, 2010 Share Posted March 16, 2010 I'm currently working on the combat engine portion of my CRPG. And I have come to the ultimate decision in any CRPG, or tabletop RPG... result determination. Or to put it frankly: "Did I hit him or not?" My first attempt to code up the main routine was a straight linear approach. There are three attack types (melee, ranged, and sorcery) so I made three separate functions for each. Here's that code, excluding some minor routines on the side: * Damage determination ATKDAM BLWP @RANDOM ANDI R4,>0003 A R4,@ATKDEF+6 BLWP @RANDOM ANDI R4,>0003 A R4,@ATKDEF+14 MOV @ATKDEF+14,R0 S @ATKDEF+6,R0 JGT ATKMD1 MOV @W1,R0 ATKMD1 MOV @ATKDEF+2,R1 A @ATKDEF+4,@PSTATE+4(R1) MOV @ATKDEF+10,R2 A R0,@PSTATE(R2) CLR @RESULT B @SUBRET * Attack routine * @ATKTYP - attack type * @ATKDEF,@ATKDEF+8 - attacker/defender unit (0-22 x2) * @RESULT - Combat result ATTACK MOV R11,*R10+ CLR @RESULT * Check if defender is invulnerable MOV @ATKDEF+10,R2 MOVB @PSTATE+24(R2),R0 JEQ ATTCK1 SETO @RESULT B @SUBRET * Get VDP arrays into memory ATTCK1 MOV @ATKDEF+2,R0 LI R1,HIBUFF LI R2,30 BLWP @VMBR MOV @ATKDEF+10,R0 LI R1,HIBUFF+30 LI R2,30 BLWP @VMBR * Determine fatigue bases MOV @W1,@ATKDEF+4 MOV @W1,@ATKDEF+12 MOV @ATKDEF+2,R1 MOV @PSTATE+12(R1),R0 JEQ ATTCK3 ANDI R0,>00FF JEQ ATTCK2 DEC @ATKDEF+4 JMP ATTCK3 ATTCK2 INC @ATKDEF+4 ATTCK3 MOV @PSTATE+12(R2),R0 JEQ ATTCK5 ANDI R0,>00FF JEQ ATTCK4 DEC @ATKDEF+12 JMP ATTCK5 ATTCK4 INC @ATKDEF+12 * Set attack and defense powers ATTCK5 CLR @ATKDEF+6 CLR @ATKDEF+14 * Check if attacker is exalted or cursed MOV @PSTATE+8(R1),R0 JEQ ATTCK7 ANDI R0,>00FF JEQ ATTCK6 DECT @ATKDEF+6 JMP ATTCK7 ATTCK6 INCT @ATKDEF+6 ATTCK7 MOV @PSTATE+8(R2),R0 JEQ ATTCK9 ANDI R0,>00FF DECT @ATKDEF+14 JMP ATTCK9 ATTCK8 INCT @ATKDEF+14 * Check if defender is guarding, if so, +2 to defense power ATTCK9 MOV @ATKDEF+8,R2 MOV @CSTATE(R2),R0 JEQ ATCK10 INCT @ATKDEF+14 * Set attacker's faded state to zero ATCK10 MOVB @ZERO,@PSTATE+21(R1) * Add luck to power MOV @ATKDEF,R1 MOV @ATKDEF+8,R2 A @PLUCK(R1),@ATKDEF+6 A @PLUCK(R2),@ATKDEF+14 * Branch to attack type MOV @ATKTYP,R1 B @AKCASE(R1) * Attack - melee * Get base attack/defense melee power ATKMEL CLR @PARRY CLR @BLOCK MOV @ATKDEF+8,R1 SLA R1,1 BL @FDODGE MOVB @HIBUFF,R1 SRL R1,8 A R1,@ATKDEF+6 MOVB @HIBUFF+30,R1 SRL R1,8 A R1,@ATKDEF+14 * Check player only defense elements C @ATKDEF,@W8 JEQ ATKM1 * Check for 2H weapon CB @HIBUFF+20,@B132 JL ATKM0 CB @HIBUFF+20,@B136 JH ATKM0 INC @ATKDEF+4 * Check if defender (player only) can parry ATKM0 C @ATKDEF+8,@W8 JEQ ATKM1 CB @HIBUFF+50,@ZERO JEQ ATKM0A CB @HIBUFF+50,@B128 JEQ ATKM0A INC @PARRY * Check if player can block ATKM0A CB @HIBUFF+52,@B147 JNE ATKM1 INC @BLOCK * Check if attacker is enraged (+4 to attacker power) ATKM1 MOV @ATKDEF+2,R1 MOVB @PSTATE+20(R1),R0 JEQ ATKM2 A @W4,@ATKDEF+6 * Check if defender is enraged (-4 to defender power) ATKM2 MOV @ATKDEF+10,R2 MOVB @PSTATE+20(R2),R0 JEQ ATKM3 S @W4,@ATKDEF+14 * Check if defender is armored (+4 to defender power) ATKM3 MOVB @PSTATE+16(R2),R0 JEQ ATKM4 A @W4,@ATKDEF+14 * Check for base miss * Check if defender is blurred (50% miss chance) ATKM4 MOVB @PSTATE+17(R2),R0 JEQ ATKM5 LI R3,2 JMP ATKM6 * Fetch base melee miss chance ATKM5 MOVB @HIBUFF+12,R3 SRL R3,8 ATKM6 BLWP @RNDNUM MOV R4,R4 JNE ATKM7 * Missed! MOV @W1,@RESULT B @SUBRET * Check for critical ATKM7 BLWP @RANDOM ANDI R4,>000F JNE ATKM8 * Critical! MOV @ATKDEF+6,R0 SRL R0,1 A R0,@ATKDEF+6 CLR @ATKDEF+4 B @ATKDAM * Defense checks * Check for parry ATKM8 C @ATKDEF+8,@W8 JEQ ATKM9 MOV @PARRY,@PARRY JEQ ATKM10 ATKM9 MOVB @HIBUFF+45,R3 SRL R3,8 JEQ ATKM10 BLWP @RNDNUM MOV R3,R3 JNE ATKM10 * Parried! MOV @W2,@RESULT MOV @ATKDEF+10,R1 A @ATKDEF+12,@PSTATE+4(R1) B @SUBRET * Check for block ATKM10 C @ATKDEF+8,@W8 JEQ ATKM11 MOV @BLOCK,@BLOCK JEQ ATKM12 ATKM11 MOVB @HIBUFF+46,R3 SRL R3,8 JEQ ATKM12 BLWP @RNDNUM MOV R3,R3 JNE ATKM12 * Blocked! MOV @W3,@RESULT MOV @ATKDEF+10,R1 A @ATKDEF+12,@PSTATE+4(R1) B @SUBRET * Check for dodge ATKM12 MOV @DODGE,@DODGE JEQ ATKMDM MOV @ATKDEF+10,R1 MOVB @PSTATE+23(R1),R0 JNE ATKMDM MOVB @HIBUFF+47,R3 SRL R3,8 JEQ ATKMDM BLWP @RNDNUM MOV R3,R3 JNE ATKMDM * Dodged! MOV @W4,@RESULT MOV @DODGE,R3 BLWP @RNDNUM SLA R4,1 MOV @ATKDEF+10,R1 A @ATKDEF+12,@PSTATE+4(R1) INC @PSTATE+4(R1) SRL R1,3 MOV @DMOVE(R4),@UNITS(R1) B @SUBRET ATKMDM B @ATKDAM * Attack - ranged * Get base attack/defense ranged power ATKRNG CLR @BLOCK MOVB @HIBUFF+3,R1 SRL R1,8 A R1,@ATKDEF+6 MOVB @HIBUFF+33,R1 SRL R1,8 A R1,@ATKDEF+14 * Check player only defense elements C @ATKDEF,@W8 JEQ ATKR1 * Check for Arbalest (-1 fatigue) CB @HIBUFF+24,@B139 JNE ATKR0 DEC @ATKDEF+4 * Check if defender (player only) can block * If attacker has firearm, nevermind ATKR0 CB @HIBUFF+24,@B140 JEQ ATKR1 CB @HIBUFF+52,@B147 JNE ATKR1 INC @BLOCK * Check if defender is shielded (+4 to defender power) ATKR1 MOV @ATKDEF+10,R2 MOVB @PSTATE+28(R2),R0 JEQ ATKR2 A @W4,@ATKDEF+14 * Check for base miss * Check if defender is deflecting (50% miss chance) ATKR2 MOVB @PSTATE+27(R2),R0 JEQ ATKR3 LI R3,2 JMP ATKR4 * Fetch base ranged miss chance ATKR3 MOVB @HIBUFF+13,R3 SRL R3,8 ATKR4 BLWP @RNDNUM MOV R4,R4 JNE ATKR5 * Missed! MOV @W1,@RESULT B @SUBRET * Check for critical ATKR5 BLWP @RANDOM ANDI R4,>000F JNE ATKR6 * Critical! MOV @ATKDEF+6,R0 SRL R0,1 A R0,@ATKDEF+6 CLR @ATKDEF+4 B @ATKDAM * Defense checks * Check for block ATKR6 C @ATKDEF+8,@W8 JEQ ATKR7 MOV @BLOCK,@BLOCK JEQ ATKRDM ATKR7 MOVB @HIBUFF+36,R3 SRL R3,8 JEQ ATKRDM BLWP @RNDNUM MOV R3,R3 JNE ATKRDM * Blocked! MOV @W3,@RESULT MOV @ATKDEF+10,R1 A @ATKDEF+12,@PSTATE+4(R1) B @SUBRET ATKRDM B @ATKDAM * Attack - sorcery (target and area) ATKSOR CLR @BLOCK MOVB @HIBUFF+6,R1 SRL R1,8 A R1,@ATKDEF+6 MOVB @HIBUFF+36,R1 SRL R1,8 A R1,@ATKDEF+14 * Get spell type LI R0,SPLDAT BLWP @VSBR SRL R1,4 MOVB R1,@WORK * Check player only defense elements C @ATKDEF,@W8 JEQ ATKS2 * Check for spell affinity CB @HIBUFF+19,@WORK JNE ATKS0 INCT @ATKDEF+6 DEC @ATKDEF+4 ATKS0 C @ATKDEF+8,@W8 JEQ ATKS1 CB @HIBUFF+49,@WORK JNE ATKS1 INCT @ATKDEF+14 DEC @ATKDEF+12 * Check if defender can block ATKS1 CB @HIBUFF+52,@B147 JL ATKS2 CB @HIBUFF+52,@B148 JH ATKS2 INC @BLOCK * Check if defender is resolved (+4 to defender power) ATKS2 MOV @ATKDEF+10,R2 MOVB @PSTATE+26(R2),R0 JEQ ATKS3 A @W4,@ATKDEF+14 * Check if defender is warded (50% miss chance) ATKS3 MOVB @PSTATE+29(R2),R0 JEQ ATKS4 LI R3,2 JMP ATKS5 * Check for base sorcery miss (targeted only) ATKS4 C @W6,@ATKTYP JEQ ATKS7 MOVB @HIBUFF+14,R3 SRL R3,8 ATKS5 BLWP @RNDNUM MOV R4,R4 JNE ATKS6 * Missed! MOV @W1,@RESULT B @SUBRET * Check for critical (targeted only) ATKS6 BLWP @RANDOM ANDI R4,>000F JNE ATKS7 * Critical! MOV @ATKDEF+6,R0 SRL R0,1 A R0,@ATKDEF+6 CLR @ATKDEF+4 JMP ATKSDM * Defense checks * Check for block (area only) ATKS7 C @W6,@ATKTYP JNE ATKS9 C @ATKDEF+8,@W8 JEQ ATKS8 MOV @BLOCK,@BLOCK JEQ ATKSDM ATKS8 MOVB @HIBUFF+46,R3 SRL R3,8 JEQ ATKSDM BLWP @RNDNUM MOV R3,R3 JNE ATKSDM * Blocked! MOV @W3,@RESULT MOV @ATKDEF+10,R1 A @ATKDEF+12,@PSTATE+4(R1) B @SUBRET * Check for resist (targeted only, non-damage only) ATKS9 MOVB @HIBUFF+48,R3 SRL R3,8 JEQ ATKSDM BLWP @RNDNUM MOV R3,R3 JNE ATKSDM * Resisted! MOV @W5,@RESULT MOV @ATKDEF+10,R1 A @ATKDEF+12,@PSTATE+4(R1) B @SUBRET * Damage determination ATKSDM B @ATKDAM For my CRPG design, I'm less concerned about speed than I am about space. So I'm always checking the byte count any time I finish a large block of new code. This block weighs in at 1168 bytes. Ouch. I only have about 9k of codespace left, and I still have a lot left to do, including the quest engine, NPC interaction, combat special effects... Need to trim it down! Adamantyr Quote Link to comment Share on other sites More sharing options...
+adamantyr Posted March 16, 2010 Author Share Posted March 16, 2010 (edited) To continue... One of the first things I did was expand my player data array so that some of my factors, such as fatigue costs, are just stored values instead of calculated on the spot. The cost in data space is cheap in comparison to the code space. The second thing was to categorize my attack/defense adjustments into a data array that is then ran through and applied for each attack type. Only a single function is used, with a switch variable to indicate the attack type. This recycles and reuses a lot of code and cuts down on duplication. So here's the new block, including the data array: * Attack routine * @ATKTYP - Attack type (0,2,4) * (Melee,Ranged,Sorcery) * @ATKDEF,@ATKDEF+2 - attacker/defender PSTATE location * @COMDEF,@COMDEF+32 - attacker/defender combat array location * @RESULT - Combat result ATTACK MOV R11,*R10+ CLR @RESULT * Populate COMDEF array LI R9,2 LI R8,ATKDEF CLR R7 ATKC1 MOV *R8+,R0 C R0,@W96 JH ATKC2 MPY @W3,R0 LI R0,PLAYER+32 A R1,R0 JMP ATKC3 ATKC2 LI R0,MONREC+6 ATKC3 LI R1,COMDEF A R7,R1 LI R2,32 BLWP @VMBR AI R7,32 DEC R9 JNE ATKC1 * Process adjustments MOV @ATKTYP,R6 SRL R6,1 MPY @W5,R6 SRL R7,8 LI R0,SPLDAT BLWP @VSBR SRL R1,4 MOVB R1,R8 LI R9,2 LI R2,ATKADJ+6 ATK0 MOV *R2+,R0 * Get affected units for check/change into R4/R5 ATK1 MOVB *R2+,R1 MOVB R1,R3 SRL R1,12 SRL R3,4 ANDI R1,>000F ANDI R3,>0020 MOV @ATKDEF(R1),R4 LI R5,COMDEF A R3,R5 MOVB *R2+,R3 * Check if spell affinity CB R3,@B128 JNE ATK1A CB R8,@COMDEF+26 JEQ ATK1B JMP ATK3 * Check targeted state ATK1A SRL R3,8 A R3,R4 CB *R4,@ZERO JEQ ATK3 * Make adjustment ATK1B MOVB *R2+,R3 SRL R3,8 A R3,R1 MOVB *R2+,R3 CB R3,@B7 JGT ATK2 AB R3,*R1 JMP ATK4 * Set the value ATK2 SB @B8,R3 MOVB R3,*R1 JMP ATK4 ATK3 INCT R2 ATK4 DEC R0 JNE ATK1 MOV @ATKTYP,R1 MOV @ATKADJ+2(R1),R2 DEC R9 JNE ATK0 * Defense adjustments for types LI R0,COMDEF+25 MOV @ATKTYP,R1 SLA R1,1 LI R2,DEFADJ A R1,R2 LI R3,4 ATK5 SZCB *R2+,*R0+ DEC R3 JNE ATK5 * Miss check MOV @ATKTYP,R1 SRL R1,1 MOVB @COMDEF+18(R1),R3 SRL R3,8 BLWP @RNDNUM MOV R4,R4 JNE ATK6 * Missed! MOV @W1,@RESULT B @SUBRET * Critical check ATK6 MOVB @COMDEF+4(R7),R0 SRL R0,8 BLWP @RANDOM ANDI R4,>000F C R4,R0 JGT ATK7 * Critical! (No defenses) SETO R9 JMP ATK10 * Defense check ATK7 CLR R9 LI R0,COMDEF+53 LI R5,4 LI R6,2 ATK8 MOVB *R0+,R3 JEQ ATK9 SRL R3,8 BLWP @RNDNUM MOV R4,R4 JNE ATK9 * Successful defense MOVB @COMDEF+49,R0 SRL R0,8 MOV @ATKDEF+2,R1 A R0,@PSTATE+4(R1) MOV R6,@RESULT B @SUBRET ATK9 INC R6 DEC R5 JNE ATK8 * Damage/debuff determination ATK10 CLR @RESULT MOVB @COMDEF(R7),R5 SRL R5,8 * Check if sorcery; add power value C @W4,@ATKTYP JLT ATK12 LI R0,SPLDAT+3 BLWP @VSBR SRL R1,8 A R1,R5 ATK11 MOVB @COMDEF+33(R7),R2 SRL R2,8 * Critical double value MOV R9,R9 JEQ ATK12 SLA R5,1 ATK12 S R2,R5 JGT ATK13 CLR R5 * Increase fatigue for attacker ATK13 MOVB @COMDEF+3(R7),R0 SRL R0,8 MOV @ATKDEF,R1 A R5,@PSTATE+4(R1) C @W4,@ATKTYP JLT ATK14 * Check if sorcery debuff LI R0,SPLDAT+4 BLWP @VSWR CI R1,2 JEQ ATK14 MOV R5,R0 MOV @ATKDEF+2,R2 ANDI R0,>0007 BL @EFFCTS B @SUBRET * Straight damage to defender ATK14 MOV @ATKDEF+2,R1 A R5,@PSTATE+2(R1) B @SUBRET * Defense adjustments DEFADJ DATA >0000,>00FF BYTE >FF00,>FFFF BYTE >FFFF,>FF00 * Attack adjustments ATKADJ DATA ATKADJ+16,ATKADJ+66,ATKADJ+104 * General adjustments (ATKADJ+6) DATA 2 BYTE 0,12,17,-1 * Attacker - Move Empowered check BYTE >22,13,17,1 * Defender - Move Weakened check * Melee adjustments (ATKADJ+16) DATA 12 BYTE 0,8,0,2 * Attacker - Melee Exalt check BYTE >22,8,1,2 * Defender - Melee Exalt check BYTE 0,9,0,-2 * Attacker - Melee Curse check BYTE >22,9,1,-2 * Defender - Melee Curse check BYTE 0,12,3,-1 * Attacker - Melee Empowered check BYTE 0,13,3,1 * Attacker - Melee Weakened check BYTE 0,20,3,2 * Attacker - Enraged checks BYTE 0,20,4,4 BYTE 1,17,18,10 * Defender - Blurred check BYTE >22,16,1,4 * Defender - Armored check BYTE >22,23,23,8 * Defender - Immobilized check BYTE >22,24,1,127 * Defender - Melee Invulnerable check * Ranged adjustments (ATKADJ+66) DATA 9 BYTE 0,8,5,2 * Attacker - Ranged Exalt check BYTE >22,8,6,2 * Defender - Ranged Exalt check BYTE 0,9,5,-2 * Attacker - Ranged Curse check BYTE >22,9,6,-2 * Defender - Ranged Curse check BYTE 0,12,8,-1 * Attacker - Ranged Empowered check BYTE 0,13,8,1 * Attacker - Ranged Weakened check BYTE 2,27,19,10 * Defender - Deflecting check BYTE >22,28,6,4 * Defender - Shielded check BYTE >22,24,6,127 * Defender - Ranged Invulnerable check * Sorcery Attack adjustments (ATKADJ+104) DATA 14 BYTE 0,8,10,2 * Attacker - Sorcery Exalt check BYTE >22,8,11,2 * Defender - Sorcery Exalt check BYTE 0,9,10,-2 * Attacker - Sorcery Curse check BYTE >22,9,11,-2 * Defender - Sorcery Curse check BYTE 0,12,13,-1 * Attacker - Sorcery Empowered check BYTE 0,13,13,1 * Attacker - Sorcery Weakened check BYTE 2,26,20,10 * Defender - Resolve check BYTE >22,29,11,4 * Defender - Warded check BYTE >22,24,11,127 * Defender - Sorcery Invulnerable check BYTE 0,13,13,1 * Attacker - Sorcery Weakened check BYTE 0,128,10,2 * Attacker - Sorcery Affinity BYTE 0,128,13,-1 BYTE >22,128,11,2 * Defender - Sorcery Affinity BYTE >22,128,17,-1 This new section and the data block take up 440 bytes and 162 bytes respectively, for a total of 602 bytes. Almost half the size, and a lot more elegant. Adamantyr Edited March 17, 2010 by adamantyr Quote Link to comment Share on other sites More sharing options...
The Codex Posted March 17, 2010 Share Posted March 17, 2010 Very slick! That's an appreciable savings there, and that you made it work better as well is pure gravy. Quote Link to comment Share on other sites More sharing options...
JamesD Posted March 17, 2010 Share Posted March 17, 2010 You know you can put comments on the end of some of those lines to make the code easier to read. Quote Link to comment Share on other sites More sharing options...
+adamantyr Posted March 17, 2010 Author Share Posted March 17, 2010 (edited) You know you can put comments on the end of some of those lines to make the code easier to read. Yes, indeed I can. The TI assemblers recognize anything past the opcode sections as comments. But my favored editor (Textpad) isn't quite clever enough to treat them as comments. So I usually just use whole lines instead of on-line comments. UPDATE: Hm, actually, now I can get it to work. I still have to use some character (an asterisk works well) but I was able to make column 40 be a comment line. Cool! Edited the above to be slightly more readable Adamantyr Edited March 17, 2010 by adamantyr Quote Link to comment Share on other sites More sharing options...
jchase1970 Posted March 17, 2010 Share Posted March 17, 2010 I've been fallowing your blogs on this and I am just amazed that you are pulling this off with such tight memory constraints. The first rpg I wrote but didn't finish was a ultima 3 clone written in pascal on a 128k pc and I was feeling pressed for memory on that. Although I was a less capable coder then and stuff was much more eh, sloppy. But, still this is really an impressive feat. John Quote Link to comment Share on other sites More sharing options...
JamesD Posted March 17, 2010 Share Posted March 17, 2010 Yes, indeed I can. The TI assemblers recognize anything past the opcode sections as comments. But my favored editor (Textpad) isn't quite clever enough to treat them as comments. So I usually just use whole lines instead of on-line comments. UPDATE: Hm, actually, now I can get it to work. I still have to use some character (an asterisk works well) but I was able to make column 40 be a comment line. Cool! Edited the above to be slightly more readable Adamantyr Actually, I wasn't referring to the data section but that looks much better! Quote Link to comment Share on other sites More sharing options...
+adamantyr Posted March 17, 2010 Author Share Posted March 17, 2010 Yes, indeed I can. The TI assemblers recognize anything past the opcode sections as comments. But my favored editor (Textpad) isn't quite clever enough to treat them as comments. So I usually just use whole lines instead of on-line comments. UPDATE: Hm, actually, now I can get it to work. I still have to use some character (an asterisk works well) but I was able to make column 40 be a comment line. Cool! Edited the above to be slightly more readable Adamantyr Actually, I wasn't referring to the data section but that looks much better! I assume you mean I should have every single line (or nearly so) commented. Bah! Actually, I originally started coding on the vintage system itself, so I had a real concern that my source files (which needed to fit on one disk) may get too big if I went overboard on comments. That isn't the case anymore; I directly assemble my source files from PC files using A99 (Written by Joe Delekto) and/or Classic99's E/A assembler. However, since I'm the only coder, I don't feel the need to thoroughly comment the lines for myself, since I have a pretty good knowledge of what the different labels are and how the whole system is set up. If I was to prepare the code base for porting to another platform, what I'd probably do is write it all up in pseudo-code, rather than just heavily comment an assembly file. Adamantyr Quote Link to comment Share on other sites More sharing options...
Opry99er Posted March 20, 2010 Share Posted March 20, 2010 Great stuff man-- I am learning alot from your experiences!!! Thank you for posting this stuff!!! Quote Link to comment Share on other sites More sharing options...
Willsy Posted March 20, 2010 Share Posted March 20, 2010 He he! You and I differ vastly when it comes to comments! Here's some source from my Forth project: ;[ HCHAR ( y x ascii count -- ) hcharh data gmodeh,5 text 'HCHAR ' hchar data $+2 bl @get4 ; get parameters from stack and calculate address bl @vsbwm ; write to screen b *next ; see ya ;] ;[ VCHAR ( y x ascii count -- ) vcharh data hcharh,5 text 'VCHAR ' vchar data $+2 bl @get4 ; get parameters from stack and calculate address li r6,24 ; row count mpy @xmax,r6 ; max visible address+1 (in r7) dec r7 ; correct max visible (we count from 0) mov @xmax,r6 ; get xmax in a register vchar1 bl @vsbw ; write a character a r6,r0 ; move down one line c r0,r7 ; gone off end of screen? jle vchar2 ; skip if not s r7,r0 ; reduce address vchar2 dec r2 ; decrement count jne vchar1 ; repeat if not finished b *next ;] ;[ GCHAR ( y x -- ascii ) gcharh data vcharh,5 text 'GCHAR ' gchar data $+2 bl @get2 ; get y & x from stack mpy @xmax,r6 ; compute y a r7,r0 ; compute screen address clr r1 ; use r1 for byte operations bl @vsbr ; read byte from vdp swpb r1 ; move byte to lsb inct stack ; make space on stack mov r1,*stack ; place on stack as 16 bit word b *next ;] ;[ DCHAR ( W1..Wx word_count ascii -- ) ; loads words from the stack into VDP memory at the ASCII ; code specified. Equivalent to CALL CHAR in BASIC. ; Note: To make code easier to read, the order of the graphic data does NOT ; have to be reversed! The routine looks *down* the stack and works up, ; correcting the stack at the end of the routine. ; word_count tells the routine how many words of graphic data exist on the ; stack. dcharh data gcharh,5 text 'DCHAR ' dchar data $+2 bl @sget2 ; get two parameters sla r9,3 ; multiply ascii by 8 sla r10,1 ; convert word count to byte count mov r10,r6 ; copy it inct r6 ; adjust for stack correction later s r10,stack ; adjust stack inct stack ; point at first data word mov r9,r0 ; ascii to r0 ai r0,>800 ; point to entry in pattern table dchar1 mov *stack,r1 ; get data from stack inct stack ; move back up the stack bl @vsbw ; write byte to vdp inc r0 ; next vdp address swpb r1 ; rotate graphic data bl @vsbw ; write byte to vdp inc r0 ; next vdp address dect r10 ; decrement count jne dchar1 ; repeat if not finished s r6,stack ; remove everything from stack b *next ;] ;[ SPRITE ( sprite y x ascii color -- ) ; sprite attribute list begins at 6*80h=300h sprith data dcharh,6 text 'SPRITE' sprite data $+2 bl @sget5 ; get 5 parameters sla r10,2 ; multiply sprite number by 4 (offset into SAL) li r11,sal ; address of SAL in CPU ram li r0,>300 ; address of SAL in VDP ram a r10,r11 ; add offset to cpu addr according to sprite number mov r11,r1 ; cpu source for vmbw a r10,r0 ; destination address for vmbw swpb r6 ; rotate colour swpb r7 ; rotate ascii swpb r8 ; rotate x swpb r9 ; rotate y movb r9,*r11+ ; move y to cpu buffer movb r8,*r11+ ; move x to cpu buffer movb r7,*r11+ ; moce ascii to cpu buffer movb r6,*r11+ ; move colour to cpu buffer li r2,4 bl @vmbw sprtx b *next ;] ;[ MAGNIFY ( x -- ) ; sets sprite magnification: ; only the least significant bits are used: ; bit 7: 1=magnified (0=not magnified) ; bit 6: 1=double size (4 character) ; Remember: TI number their bits backwards! Idiots! magfyh data sprith,7 text 'MAGNIFY ' magfy data $+2 bl @sget1 ; get one parameter from stack (r10) swpb r10 ; get value in msb andi r10,>0300 ; mask out any crap li r0,>0001 ; vdp register number in lsb clr r2 ; prepare for byte operations movb @VDPR1,r2 ; get copy of VDP R1 andi r2,>fc00 ; mask out magnification bits socb r2,r10 ; OR in new magnification value movb r10,r0 ; place in r0 msb movb r0,@>83d4 ; place copy in 83d4 movb r0,@VDPR1 ; reserve copy (VDP regs are read only) swpb r0 ; rotate bl @vwtr ; set the register b *next ;] ;[ SPRCOL ( sprite colour -- ) ; sets the colour of a sprite sprclh data magfyh,6 text 'SPRCOL' sprcol data $+2 bl @sget2 ; get 2 parameters: colour=r9, sprite=r10 li r0,>300+3 ; SAL in vdp (offset to colour byte added) li r8,SAL+3 ; SAL in CPU (offset to colour byte added) sla r10,2 ; multiply sprite number by 4 a r10,r0 ; point to correct address in vdp a r10,r8 ; point to correct address in CPU SAL swpb r9 ; rotate colour into MSB mov r9,r1 ; into r1 for VSBW movb r9,*r8 ; load into CPU SAL bl @vsbw ; write colour byte into VDP b *next ;] ;[ SPRLOC ( sprite y x -- ) ; sets the location of a sprite sprlch data sprclh,6 text 'SPRLOC' sprloc data $+2 bl @sget3 ; get 3 parameters from stack li r0,>300 ; address of SAL in VDP li r1,SAL ; address of SAL in CPU sla r10,2 ; get offset into tables a r10,r0 ; add to vdp addr a r10,r1 ; add to cpu addr swpb r8 ; rotate x swpb r9 ; rotate y movb r9,*r1+ ; write y to cpu SAL movb r8,*r1 ; write x to cpu SAL dec r1 ; point to beginning of entry in SAL li r2,2 ; two bytes to write bl @vmbw ; write to VDP b *next ;] ;[ SPRLOC? ( sprite -- y x ) ; gets the location of a sprite locsph data sprlch,7 text 'SPRLOC? ' locspr data $+2 bl @sget1 ; get sprite number (r10) li r0,SAL ; address of SAL in CPU ram sla r10,2 ; get offset a r10,r0 ; point to correct address in SAL clr r1 ; prepare for byte operations movb *r0+,r1 ; get y and point to x swpb r1 ; move to lsb inct stack ; make space on stack mov r1,*stack+ ; place on stack and make new stack entry clr r1 movb *r0,r1 ; get x swpb r1 ; move to lsb mov r1,*stack ; place on stack b *next ;] ;[ SPRPAT ( sprite ascii -- ) ; sets the pattern of a sprite sppath data locsph,6 text 'SPRPAT' sprpat data $+2 bl @sget2 ; get 2 parameters li r0,>300+2 ; address of SAL in vdp li r2,SAL+2 ; address of SAL in cpu sla r10,2 ; calculate offset a r10,r0 ; offset into vdp a r10,r2 ; offset into cpu swpb r9 ; rotate ascii into msb mov r9,r1 ; for vsbw movb r9,*r2 ; set in cpu ram bl @vsbw ; set in vdp ram b *next ;] Comments everywhere! This is because I do not trust myself to understand my own code in 6 months time! Quote Link to comment Share on other sites More sharing options...
JamesD Posted March 20, 2010 Share Posted March 20, 2010 I'm used to doing consulting work where I'll work on a project for 6 months or a year and then I'm gone. I have to write code someone else can understand. I've also had to verify compliance with govt regulations so I tend to follow the same rules myself. Quote Link to comment Share on other sites More sharing options...
JonArkanix Posted March 21, 2010 Share Posted March 21, 2010 Complaining about the lack of comments, especially in a retro project? Glad to see the CRPG is moving along nicely, the recent spat of updates on your blog is inspiration for my own CRPG project. Quote Link to comment Share on other sites More sharing options...
+adamantyr Posted March 22, 2010 Author Share Posted March 22, 2010 I use as many comments as I need for myself right now, as I'm the only coder on my project. If I was coding for a group, I'd have more judiciously spread around. However, with assembly, most of the operations are pretty obvious as a glance; the comments are really just there to detail what particular labels mean. I actually fully commented a 6502 listing of Eastern Front: 1941 before I started trying to work on a conversion... and it didn't help all that much. The problem being that you really don't grasp WHAT the code is trying to do on a whole, only what it literally does. I wasn't able to figure out the top-down design of the AI by studying the code line-by-line, it would require a lot more research to do so. As for forgetting stuff after six months... yeah, that could happen to me too, but that's why I have spreadsheets and other tools to track a lot of the "soft" data that makes up the game engine. Adamantyr Quote Link to comment Share on other sites More sharing options...
Tursi Posted March 22, 2010 Share Posted March 22, 2010 Complaining about the lack of comments, especially in a retro project? Not every contrary post is a complaint. I think people are just showing their own take on a concept. This project is, of course, one of the most impressive ones I've had the pleasure of seeing running. There are so many neat little features in it. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.