Jump to content

CRPG Attack Routine: First and Revised


13 replies to this topic

#1  

    Moonsweeper

  • 420 posts
  • Joined: 01-February 08

Posted Tue Mar 16, 2010 4:31 PM

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

#2  

    Moonsweeper

  • 420 posts
  • Joined: 01-February 08

Posted Tue Mar 16, 2010 4:34 PM

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 by adamantyr, Tue Mar 16, 2010 7:28 PM.


#3  

    Dragonstomper

  • 573 posts
  • Joined: 02-February 10
  • Location:Richmond, VA

Posted Tue Mar 16, 2010 6:46 PM

Very slick! That's an appreciable savings there, and that you made it work better as well is pure gravy.

#4 ONLINE  

    River Patroller

  • 2,813 posts
  • Joined: 20-August 06

Posted Tue Mar 16, 2010 6:54 PM

You know you can put comments on the end of some of those lines to make the code easier to read. :roll:

#5  

    Moonsweeper

  • 420 posts
  • Joined: 01-February 08

Posted Tue Mar 16, 2010 6:58 PM

View PostJamesD, on Tue Mar 16, 2010 6:54 PM, said:

You know you can put comments on the end of some of those lines to make the code easier to read. :roll:

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 by adamantyr, Tue Mar 16, 2010 7:28 PM.


#6  

    Moonsweeper

  • 339 posts
  • Joined: 06-March 10
  • Location:Newburgh IN

Posted Tue Mar 16, 2010 7:46 PM

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

#7 ONLINE  

    River Patroller

  • 2,813 posts
  • Joined: 20-August 06

Posted Tue Mar 16, 2010 7:57 PM

View Postadamantyr, on Tue Mar 16, 2010 6:58 PM, said:

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! :D

#8  

    Moonsweeper

  • 420 posts
  • Joined: 01-February 08

Posted Tue Mar 16, 2010 11:51 PM

View PostJamesD, on Tue Mar 16, 2010 7:57 PM, said:

View Postadamantyr, on Tue Mar 16, 2010 6:58 PM, said:

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! :D

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

#9  

    River Patroller

  • 3,019 posts
  • Joined: 18-November 09
  • Location:Elizabethtown, KY

Posted Sat Mar 20, 2010 2:45 AM

Great stuff man-- I am learning alot from your experiences!!! Thank you for posting this stuff!!!

#10  

    Dragonstomper

  • 599 posts
  • Joined: 16-November 09
  • Location:Uzbekistan (no, really!)

Posted Sat Mar 20, 2010 9:53 AM

He he! You and I differ vastly when it comes to comments! Here's some source from my Forth project: :P

;[ 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! :ponder:

#11 ONLINE  

    River Patroller

  • 2,813 posts
  • Joined: 20-August 06

Posted Sat Mar 20, 2010 5:50 PM

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.

#12  

    Space Invader

  • 15 posts
  • Joined: 21-March 10
  • Location:Seattle WA

Posted Sun Mar 21, 2010 12:37 PM

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.

#13  

    Moonsweeper

  • 420 posts
  • Joined: 01-February 08

Posted Sun Mar 21, 2010 7:11 PM

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

#14  

    Stargunner

  • 1,369 posts
  • Joined: 01-May 07
  • Location:SJC

Posted Mon Mar 22, 2010 12:45 AM

View PostJonArkanix, on Sun Mar 21, 2010 12:37 PM, said:

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.





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users