In this post I will describe the software defects ("bugs") I've
discovered in the code. These are not bugs that I've introduced into
the code, but bugs that the original authors wrote.
For example, if the original code misspelled a word in a message (such
as "INDENTIFY"), then the code that I re-engineered also had to
misspell the word in the source code so that it would match the
original code. As another example, if the original code used the
wrong subscript in an array, then the code that I wrote had to also
use the wrong subscript in the array.
Bug #1
------
During the transfer of characters to LOL, the message states that the
characters must be "IN CASTLE" and "LIVE". The code however, only
checks "STATUS=OK":
690 7 24:D 1 PROCEDURE REMOVCHR; (* P050118 *)
717 7 24:4 126 WRITELN( 'NOTE - CHARS MUST BE LIVE & IN
CASTLE');
706 7 24:4 72 IF PLAYREC2.STATUS = OK THEN
Bug #2
------
During the transfer of characters to Legacy of LLylgamyn, it is
possible to get caught in an infinite loop.
The following code tries to write a new value to BL5BUFF[ 12] on the
FROM disk. If the disk is write protected (as the instructions in the
manual suggest you do), then the value on the diskette is now
different from the value in BL5BUFF[ 12] and FRMRNDID (From Disk
Random ID).
674 7 23:D 1 PROCEDURE TRANSFER; (* P050117 *)
788 7 23:1 70 RDSCNTOC( 'INSERT SCENARIO WITH CHAR(S)
TO BE MOVED');
789 7 23:1 115 UNITREAD( DRIVE1, BL5BUFF,
SIZEOF( BL5BUFF), SERIALBL, 0);
790 7 23:1 127 MPD3 := BL5BUFF[ 10];
791 7 23:1 135 MOVELEFT( BL5BUFF, SERIALFR, 7);
792 7 23:1 146 BL5BUFF[ 12] := CHR( (ORD( BL5BUFF[ 12])
+ RANDNUM) MOD 256);
793 7 23:1 162 UNITWRITE( DRIVE1, BL5BUFF,
SIZEOF( BL5BUFF), SERIALBL, 0);
794 7 23:1 174 FRMRNDID := BL5BUFF[ 12];
795 7 23:1 182 REMOVCHR;
The following code tries to write the status of "LOST" into the
players record we are transferring FROM. The I/O of course will
fail, but no checking is performed. That is actually a good thing,
but...
690 7 24:D 1 PROCEDURE REMOVCHR; (* P050118 *)
738 7 24:1 406 PLAYREC1 := PLAYREC2;
739 7 24:1 414 PLAYREC1.STATUS := LOST;
740 7 24:1 418 WRCHARAC( PLAYREC1, PLAYINDX);
741 7 24:1 427 WRICACHE
...if the TO disk is full, or the player name already exists on the TO
disk, then...
674 7 23:D 1 PROCEDURE TRANSFER; (* P050117 *)
809 7 23:5 334 IF (
PLAYREC1.NAME =
PLAYREC2.NAME) AND
810 7 23:5 340 (PLAYREC1.STATUS <> LOST)
THEN
811 7 23:6 347 PLAYINDX := -1
818 7 23:1 385 IF (PLAYINDX = - 1) OR
819 7 23:1 391 (PLAYINDX = SCNTOC.RECPERDK[ ZCHAR])
OR
820 7 23:1 404 (COPY( SCNTOC.GAMENAME, 1, 10) <>
'THE LEGACY') THEN
821 7 23:2 437 TRANBAD
...the following code now tries to put the character back onto the
FROM disk, but since FRMRNDID was never written to the disk, we are
stuck in the following REPEAT-UNTIL loop:
745 7 25:D 1 PROCEDURE TRANBAD; (* P050119 *)
749 7 25:2 0 REPEAT
750 7 25:3 0 IF PLAYINDX = -1 THEN
751 7 25:4 9 RDSCNTOC( 'DUPLICATE NAME - PUT
IN SOURCE')
759 7 25:2 190 UNTIL (SCNTOC.GAMENAME = FRMGAMNM)
AND (FRMRNDID = BL5BUFF[ 12]);
Bug #3
------
A wrong message is displayed when "M"aking a Scenario Disk.
Here is the code at the beginning of MAKESCEN:
868 8 1:D 1 SEGMENT PROCEDURE MAKESCEN; (* P050201 *)
1119 8 1:1 0 REPEAT
1120 8 1:2 0 WRITE( CHR( HOMECLR));
1121 8 1:2 8 WRITE( 'DO YOU HAVE 1) OR 2) DRIVES ?');
1122 8 1:2 47 READ( INCHAR);
1123 8 1:1 55 UNTIL (INCHAR = '1') OR (INCHAR = '2');
1124 8 1:1 64 DRIVECNT := ORD( INCHAR) - ORD( '0');
The following code is testing the wrong variable, BASE04, instead of
DRIVECNT. As it turns out, BASE04 is always 1 here:
1129 8 1:1 117 IF BASE04 = 1 THEN
1130 8 1:2 122 BEGIN
1131 8 1:3 122 GOTOXY( 0, 3);
1132 8 1:3 127 WRITELN( 'INSERT AND REMOVE DISKS WHEN
PROMPTED.');
1133 8 1:3 181 WRITELN;
1134 8 1:3 187 WRITELN( 'REMEMBER : "BLANK" = YOUR
BLANK DISK.');
1135 8 1:3 241 WRITELN( ' "MASTER" = MASTER
SCENARIO.');
1136 8 1:2 295 END;
Bug #4
------
While "C"hanging a name of a character, a different error message is
displayed than the one you might expect.
The code in GETNAME() passes by reference a string defined as 40
characters into a routine that defines the parameter as 80
characters. The called routine updates this string and can therefore
overwrite (or clobber) a variable that follows it.
PASSWD, TONAME, and FROMNAME are all 40 characters in length:
441 7 17:D 1 PROCEDURE CHGNAMES; (* P050111 *)
444 7 17:D 1 PASSWD : STRING[ 40];
445 7 17:D 22 TONAME : STRING[ 40];
446 7 17:D 43 FROMNAME : STRING[ 40];
CHARNAME is the default value of 80 characters in length:
453 7 18:D 1 PROCEDURE GETNAME( VAR CHARNAME : STRING;
(* P050112 *)
GETNAME passes TONAME by reference (a 40 character name):
488 7 17:2 185 GETNAME( FROMNAME, 10, 'FROM NAME >');
489 7 17:2 204 GETNAME( TONAME, 12, ' TO NAME >');
To demonstrate the bug:
1. Start the game and select "U"tilities and "C"hange Name
2. Enter a "From>" name that is valid
3. Enter a "To>" name that is > 42 characters (will be off the
screen to the right)
4. Enter a valid "To>" name.
5. Result is: NAME NOT FOUND
That is a different error than if you type a "To>" name < 40
characters.
Note: This is at least one reason why the code uses the "$R-"
compiler option, otherwise it would not even compile.
Bug #5
------
SERIAL is defined as "STRING[7]". Sometimes it is referenced in the
code as 7 bytes, and sometimes as 8 bytes:
139 1 1:D 6 SERIAL : STRING[ 7];
1143 9 1:D 1 SEGMENT PROCEDURE OPTIONS; (* P050301 *)
1165 9 1:1 466 MOVELEFT( IOBUFF, SERIAL, 8);
1187 1 3:D 3 FUNCTION CHKCOPY : BOOLEAN; (* P050003 *)
1194 1 3:1 11 MOVELEFT( BUFFER, SERIAL, 7);
Bug #6
------
Variable RANDNUM is referenced on the right side of an assignment
before it has been initialized. (Probably not really a big deal?)
When the following code is first executed, RANDNUM has not been
assigned a value yet:
227 7 4:D 1 PROCEDURE GETKEY; (* P050104 *)
233 7 4:1 21 RANDNUM := (RANDNUM + ORD( INCHAR)) MOD
1027
Bug #7
------
IORESULT is not properly used.
From the documentation for IORESULT:
"Note that IORESULT only gives a valid return the first time it is
referenced after an I/O operation. If it is referenced again (without
another I/O operation), it will always return 0."
If there was an IO error to begin with, then FINDFILE is always set to
0 here:
237 7 5:D 3 FUNCTION FINDFILE( DRIVE :
INTEGER; (* P050105 *)
248 7 5:1 17 IF IORESULT <> 0 THEN
249 7 5:2 23 FINDFILE := -ABS( IORESULT)
Note, FINDFILE in MAKESCEN has the identical bug.
Bug #8
------
During "Create Duplicate Scenario", the last block is not copied.
There are 280 blocks on a Pascal disk, numbered from 0 to 279.
979 8 8:D 1 PROCEDURE COPYSCEN; (* P050208 *)
1099 8 8:1 32 WHILE DSKBLKXX < 279 DO
Bug #9
------
The word "OR" shows up consecutively in a message:
690 7 24:D 1 PROCEDURE REMOVCHR; (* P050118 *)
719 7 24:4 185 WRITELN( 'ENTER LETTER OF CHAR TO MOVE,
OR');
720 7 24:4 233 WRITE( 'OR PRESS [RET] TO EXIT');
Bug #10
-------
Two variables are uninitialized when first used (XGOTO and XGOTO2):
When SPECIALS is first called from the mainline the variables have not
been given values:
2787 9 1:D 1 SEGMENT PROCEDURE SPECIALS; (* P010301 *)
4002 9 1:0 0 BEGIN (* SPECIALS *)
4003 9 1:1 0 IF XGOTO = XINSAREA THEN
4004 9 1:2 7 INSPECT
4005 9 1:1 7 ELSE
4006 9 1:2 11 IF XGOTO = XCAMPSTF THEN
4007 9 1:3 18 DUMAPIC;
4008 9 1:1 20 XGOTO := XGOTO2;
Bug #11
-------
IDENTIF is not Set/Cleared when an item is gotten.
67 1 1:D 3 POSS : RECORD
68 1 1:D 3 POSSCNT : INTEGER;
69 1 1:D 3 POSSESS : ARRAY[ 1..8] OF
RECORD
70 1 1:D 3 EQUIPED : BOOLEAN;
71 1 1:D 3 CURSED : BOOLEAN;
72 1 1:D 3 IDENTIF : BOOLEAN;
73 1 1:D 3 EQINDEX : INTEGER;
74 1 1:D 3 END;
75 1 1:D 3 END;
3374 9 25:D 3 FUNCTION GOTITEM( CHARX: INTEGER; (*
P010319 *)
3392 9 25:3 112 POSSX := POSS.POSSCNT + 1;
3393 9 25:3 119 POSS.POSSCNT := POSSX;
3394 9 25:3 124 POSS.POSSESS[ POSSX].EQINDEX :=
ITEMX;
3395 9 25:3 136 POSS.POSSESS[ POSSX].EQUIPED :=
FALSE;
3396 9 25:3 146 POSS.POSSESS[ POSSX].CURSED :=
FALSE
This would seem to be a strange method for "randomly" setting/clearing
this field.
Bug #12
-------
XGOTO2 is uninitialized variable when first tested in CASTLE.
7710 16 1:0 0 BEGIN (* CASTLE P010A01 *)
7728 16 1:1 97 IF XGOTO2 <> XBOLTAC THEN
Bug #13
-------
In DISP20, we execute a loop 21 times instead of 20.
When entering "*" to display all the characters on a disk, we go
through the loop one too many times. Depending on circumstances, I
suppose this could sometimes display a garbage name on the screen.
6993 16 10:D 1 PROCEDURE DISP20; (* P010A0A *)
7004 16 10:1 31 FOR CHARX := 0 TO
SCNTOC.RECPERDK[ ZCHAR] DO
Bug #14
-------
The code tries to display the message "** WRITE-PROTECT CHEAT!**", but
it will never be displayed.
Because of the nature of GETREC() and GETRECW(), IORESULT will never
be > 0 in the following code:
6970 16 8:D 1 PROCEDURE ADDPARTY; (* P010A08 *)
7029 16 8:0 0 BEGIN (* ADDPARTY *)
7051 16 8:1 173 MOVELEFT( IOCACHE[ GETREC( ZCHAR,
CHARI, SIZEOF( TCHAR))],
7052 16 8:1 186 CHARACTR[ PARTYCNT],
7053 16 8:1 192 SIZEOF( TCHAR));
7084 16 8:1 532 MOVELEFT( CHARACTR[ PARTYCNT],
7085 16 8:1 538 IOCACHE[ GETRECW( ZCHAR,
CHARI, SIZEOF( TCHAR))],
7086 16 8:1 551 SIZEOF( TCHAR));
7087 16 8:1 556 IF IORESULT > 0 THEN
7088 16 8:2 562 EXITADDP( '** WRITE-PROTECT CHEAT!
**');
The "character" record is read into the IOCACHE area. That record is
then updated. When the GETRECW() is executed at line 7085, this will
not try to do any I/O because that record is already in the IOCACHE
and the CACHEWRI flag has not yet been set.
Bug #15
-------
You are not allowed to "change class" if there is only 1 class to
"change to".
For example, let's say you are a FIGHTER with STRENGTH=11 and
PIETY=10. You can then lose 1 STRENGTH and gain 1 PIETY. You should
now be able to change to PRIEST, but instead NOCHANGE is called.
8246 17 21:D 1 PROCEDURE CHGCLASS; (* P010B15 *)
8272 17 21:1 47 IF ORD( CHG2LST[ FIGHTER]) +
8273 17 21:1 55 ORD( CHG2LST[ MAGE]) +
8274 17 21:1 64 ORD( CHG2LST[ PRIEST]) +
8275 17 21:1 73 ORD( CHG2LST[ THIEF]) +
8276 17 21:1 82 ORD( CHG2LST[ BISHOP]) +
8277 17 21:1 91 ORD( CHG2LST[ SAMURAI]) +
8278 17 21:1 100 ORD( CHG2LST[ LORD]) +
8279 17 21:1 109 ORD( CHG2LST[ NINJA]) < 2
THEN
8280 17 21:2 122 NOCHANGE;
Bug #16
-------
NECROLOGY ROD is defined to be the KANDI spell, but its spell hash
value is wrong (1885 vs. 1185).
8698 18 9:D 1 PROCEDURE CASTSPEL( SPELHASH: INTEGER);
(* P010C09 *)
8755 18 9:D 2 KANDI = 1185; (* 1885 *)
8978 18 9:0 0 BEGIN (* CASTSPEL *)
9013 18 9:4 244 ELSE IF SPELHASH = KANDI THEN
9014 18 9:6 255 DOKANDI
9065 18 20:D 1 PROCEDURE USEITEM; (* P010C14 *)
9113 18 20:1 244
CASTSPEL( SCNTOC.SPELLHSH[ THEITEM.SPELLPWR])
SPELLPWR = 42 for NECROLOGY ROD.
SPELLHSH[ 42] = 1885.
Spell 42 is KANDI, and is defined as 1185;
If you use the hash function on "KANDI" it should be 1185. The
SPELLHSH[42] value is a typo.
This makes the NECROLOGY ROD not a spell casting item.
8989 18 9:3 114 SPELHASH := LENGTH( SPELNAME);
8990 18 9:3 120 FOR SPELLI := 1 TO
LENGTH( SPELNAME) DO
8991 18 9:4 136 IF SPELHASH < 20000 THEN
8992 18 9:5 143 BEGIN
8993 18 9:6 143 HASHCALC :=
ORD( SPELNAME[ SPELLI]) - 64;
8994 18 9:6 152 SPELHASH := SPELHASH +
HASHCALC * HASHCALC * SPELLI
8995 18 9:5 158 END;
Bug #17
-------
The MILWA spell cancels the LOMILWA spell.
8698 18 9:D 1 PROCEDURE CASTSPEL( SPELHASH: INTEGER);
(* P010C09 *)
9005 18 9:2 205 ELSE IF SPELHASH = MILWA THEN
9006 18 9:4 216 BEGIN
9007 18 9:5 216 CHKSPCNT( 1, 25);
9008 18 9:5 220 DECPRIEST( 1);
9009 18 9:5 223 LIGHT := 30 + (RANDOM MOD 15)
9010 18 9:4 231 END
Bug #18
-------
When spells are FIZZLING, some spells decrement the "spell count", but
others do not. For exmaple:
8808 18 13:D 1 PROCEDURE DECPRIEST( PRIESTGR:
INTEGER); (* P010C0D *)
8809 18 13:D 2
8810 18 13:0 0 BEGIN
8811 18 13:1 0 IF NOT USEITEM THEN
8812 18 13:2 6
CHARACTR[ CAMPCHAR].PRIESTSP[ PRIESTGR] :=
8813 18 13:2 20
CHARACTR[ CAMPCHAR].PRIESTSP[ PRIESTGR] - 1;
8814 18 13:1 38 IF FIZZLES > 0 THEN
8815 18 13:2 45 EXITCAST( 'NO EFFECT')
8958 18 19:D 1 PROCEDURE DOMALOR; (* P010C13 *)
8959 18 19:D 1
8960 18 19:0 0 BEGIN
8961 18 19:1 0 IF NOT USEITEM THEN
8962 18 19:2 6 IF
(CHARACTR[ CAMPCHAR].MAGESP[ 7] = 0) OR
8963 18 19:2 23 (NOT
CHARACTR[ CAMPCHAR].SPELLSKN[ 19]) THEN
8964 18 19:3 41 EXITCAST( 'YOU CANT CAST IT');
8965 18 19:1 62 IF FIZZLES > 0 THEN
8966 18 19:2 69 EXITCAST( 'SPELL FAILS');
8967 18 19:1 85 IF NOT USEITEM THEN
8968 18 19:2 91
CHARACTR[ CAMPCHAR].MAGESP[ 7] :=
8969 18 19:2 105 CHARACTR[ CAMPCHAR].MAGESP[ 7]
- 1;
Bug #19
-------
In "FIGHTS" variable "DONE" is not initialized when first tested.
651 7 9:D 1 PROCEDURE NEWMAZE; (* P010109 *)
652 7 9:D 1
653 7 9:D 1 VAR
654 7 9:D 1 UNUSEDXX : INTEGER;
655 7 9:D 2 UNUSEDYY : INTEGER;
656 7 9:D 3 UNUDEDZZ : INTEGER;
657 7 9:D 4 DONE : BOOLEAN;
873 7 9:0 0 BEGIN (* NEWMAZE *)
891 7 9:1 54 FIGHTS;
761 7 10:2 63 IF NOT DONE THEN
762 7 10:3 69 FINDSPOT;
Bug #20
-------
Inconsistency in displaying a character's "level". Is it MAXLEVAC? Or
CHARLEV?
1614 7 41:D 1 PROCEDURE DSPPARTY; (* P010129 *)
1627 7 41:3 62 PRINTCHR( PARTYWIN, 'L');
1628 7 41:3 67 PRINTNUM( PARTYWIN,
CHARACTR[ PARTYXXX].MAXLEVAC, 3);
8477 18 1:D 1 SEGMENT PROCEDURE CAMP; (* P010C01 *)
8627 18 7:D 1 PROCEDURE DSPSTATS; (* P010C07 *)
8637 18 7:3 74 PRINTSTR( CAMPWIN, ' LEV');
8638 18 7:3 87 PRINTNUM( CAMPWIN, CHARLEV, 4);
Bug #21
-------
"YOU BOUGHT THE LAST ONE" can never be displayed.
OBJECT.BOLTACXX is tested before it has been decremented. It will
always be > 0 (or -1).
2068 8 17:1 130 IF OBJECT.BOLTACXX = 0 THEN
2069 8 17:2 137 AASTRAA( 'YOU BOUGHT THE LAST ONE')
2099 8 17:1 468 IF OBJECT.BOLTACXX > 0 THEN
2100 8 17:2 475 OBJECT.BOLTACXX := OBJECT.BOLTACXX - 1;
Bug #22
-------
"INDENTIFY" in message is mispelled.
2147 8 20:D 1 PROCEDURE SELLIDUN( ACTION: INTEGER); (*
P010214 *)
2311 8 20:4 74 PRINTSTR( BOLTCWIN, 'INDENTIFY');
Bug #23
-------
"CEMETERY" in message is mispelled.
2385 8 24:D 1 PROCEDURE CEMETARY; (* P010218 *)
2489 8 24:1 84 PRINTSTR( TEMPWIN, ' PRESS (RETURN) TO
LEAVE THE CEMETERY');
Bug #24
-------
FINDFILE reads 104 bytes (same as TCHAR Record), and must therefore
"find" the file in one of first 4 slots in directory.
I suspect their code has "SIZEOF( TCHAR)" instead of 104. But this is
reading DIRECTORY entries, not CHARACTER records.
2968 9 9:D 3 FUNCTION FINDFILE( DRIVE: INTEGER; (*
P010309 *)
2995 9 9:1 11 UNITREAD( DRIVE, DIR, 104, 2, 0);
The following is also "interesting", because we do not have "FILECNT"
entries in DIR at this point.
3001 9 9:3 37 FOR FILEX := 1 TO DIR[ 0].FILECNT
DO
Bug #25
-------
"HALITO" is wrong constant used instead of "SOPIC".
When determining the enemy spell to throw, line 4156 should have
"SOPIC" and not HALITO.
4134 11 6:D 1 PROCEDURE GETMAGSP( SPELLLEV: INTEGER); (*
P010506 *)
4148 11 6:1 50 1: IF TWOTHIRD THEN
4149 11 6:3 53 SPELLCAS := KATINO
4150 11 6:2 53 ELSE
4151 11 6:3 60 SPELLCAS := HALITO;
4152 11 6:3 67
4153 11 6:1 67 2: IF TWOTHIRD THEN
4154 11 6:3 70 SPELLCAS := DILTO
4155 11 6:2 70 ELSE
4156 11 6:3 77 SPELLCAS := HALITO;
Bug #26
-------
MYCHARX is set to -1, and is then used as an invalid subscipt in an
array.
Case entry "6" is for "TAKE BACK". Note MYCHARX is set to -1.
See line 5106, where MYCHARX is used as a subscript!?
4614 12 2:D 1 PROCEDURE CACTION; (* P010602 *)
5100 12 2:7 851 6: BEGIN
5101 12 2:9 851
BATTLERC[ 0].A.TEMP04[ MYCHARX].SPELLHSH := -1;
5102 12 2:9 868 MYCHARX := -1
5103 12 2:8 868 END;
5104 12 2:7 874 END;
5105 12 2:7 896
5106 12 2:6 896 UNTIL
BATTLERC[ 0].A.TEMP04[ MYCHARX].SPELLHSH <> -999
Bug #27
-------
Multiple "spell names" can be hashed to the same value.
This isn't really a "bug" per se, but some names of spells can be
entered by using different characters based on the "hash" algorithm.
I have a note from long ago that says "POGS" is equivalent to "DIOS".
4789 12 12:D 1 PROCEDURE GETSPELL; (* P01060C *)
4895 12 12:1 69 SPELLCST := SPELNAML;
4896 12 12:1 72 FOR SPELNAMI := 1 TO SPELNAML DO
4897 12 12:2 83 BEGIN
4898 12 12:3 83 SPELCHRA := ORD( SPELLNAM[ SPELNAMI])
- 64;
4899 12 12:3 91 SPELLCST := SPELLCST + (SPELCHRA *
SPELCHRA * SPELNAMI)
4900 12 12:2 97 END;
Bug #28
-------
INAUDCNT is not correctly updated.
The wrong variable is used in the subscripts (ALIVECNT) instead of X.
During the MELEE rounds, if you (or an enemy) is hit with MONTINO, it
is supposed to last a "few rounds". Since the wrong variable is used,
for starters the INAUDCNT is NOT decremented during the HEALHEAR
routine. In addition, there is likely a "random store" that occurs.
5265 12 26:D 1 PROCEDURE HEALHEAR; (* P01061A *)
5267 12 27:D 1 PROCEDURE DECINAUD( GROUPI: INTEGER; (*
P01061B *)
5268 12 27:D 2 ALIVECNT: INTEGER);
5269 12 27:D 3
5270 12 27:D 3 VAR
5271 12 27:D 3 X : INTEGER;
5272 12 27:D 4
5273 12 27:0 0 BEGIN
5274 12 27:1 0 FOR X := 0 TO ALIVECNT - 1 DO
5275 12 27:2 13 IF
BATTLERC[ GROUPI].A.TEMP04[ ALIVECNT].INAUDCNT > 0 THEN
5276 12 27:3 30
BATTLERC[ GROUPI].A.TEMP04[ ALIVECNT].INAUDCNT :=
5277 12 27:3 44
BATTLERC[ GROUPI].A.TEMP04[ ALIVECNT].INAUDCNT - 1
5278 12 27:0 57 END; (* DECINAUD *)
Bug #29
-------
There is a random store in HAMMAGIC code.
We go through the loop one too many times.
TEMP2 should take on values from 0 to (…ALIVECNT - 1).
5789 14 18:D 1 PROCEDURE HAMMAGIC; (* P010812 *)
5790 14 18:D 1
5791 14 18:0 0 BEGIN
5792 14 18:1 0 FOR TEMP1 := 1 TO 3 DO
5793 14 18:2 14 BEGIN
5794 14 18:3 14 FOR TEMP2 := 0 TO
BATTLERC[ TEMP1].A.ALIVECNT DO
5795 14 18:4 37
BATTLERC[ TEMP1].A.TEMP04[ TEMP2].DRAIN2LV := 1;
5796 14 18:3 67 BATTLERC[ TEMP1].B.UNAFFCT :=
0
5797 14 18:2 79 END
5798 14 18:0 81 END;
Note also that TEMP1 only goes from 1 to 3, even though there might be
4 enemy groups.
Bug #30
-------
SMAKANIT has random stores similar to above. ENEMYX should go from 0
to (…ALIVECNT - 1).
5965 14 25:D 1 PROCEDURE SMAKANIT; (* P010819 *)
6010 14 25:8 225 FOR ENEMYX := 0 TO
BATTLERC[ GROUPI].A.ALIVECNT DO
6011 14 25:9 243 BEGIN
6012 14 25:0 243 WITH
BATTLERC[ GROUPI].A.TEMP04[ ENEMYX] DO
6013 14 25:1 257 BEGIN
6014 14 25:2 257 HPLEFT := 0;
6015 14 25:2 262 STATUS := DEAD
6016 14 25:1 265 END
6017 14 25:9 267 END
Bug #31
-------
DOPRIEST has random stores.
The subscript should be "GROUPI" and not "BASE04":
6048 14 28:D 1 PROCEDURE DOPRIEST; (* P01081C *)
6084 14 28:1 264 IF SPELL = LATUMAPI THEN
6085 14 28:2 273 BEGIN
6086 14 28:3 273 FOR GROUPI := 1 TO 4 DO
6087 14 28:4 284
BATTLERC[ BASE04].A.IDENTIFI := TRUE;
6088 14 28:3 300 IDMONSTR := 32767
6089 14 28:2 300 END;
Bug #32
-------
A DOMAGE call to MODAC causes a random store.
DOMAGE passes the following to MODAC:
1. the group index
2. modify amount
3. first index
4. last alive index
The "first index" should be 0, and the "last alive index" should be (…
ALIVECNT - 1), but this code passes "1" and "ALIVECNT".
TEMP04 is normally indexed from 0 to 8. This accomodates up to 9
enemies in 1 group. Note also that the first enemy in this group
(because of the bug) is not affected, since his index is 0.
6141 14 29:D 1 PROCEDURE DOMAGE; (* P01081D *)
6167 14 29:1 194 IF SPELL = MAMORLIS THEN
6168 14 29:2 203 FOR GROUPI := 1 TO 4 DO
6169 14 29:3 214 MODAC( GROUPI, -3, 1,
BATTLERC[ GROUPI].A.ALIVECNT);
5616 14 7:D 1 PROCEDURE MODAC( GROUPI: INTEGER; (* P010807
*)
5617 14 7:D 2 ACMOD: INTEGER;
5618 14 7:D 3 CHARF: INTEGER;
5619 14 7:D 4 CHARL: INTEGER);
5620 14 7:D 5
5621 14 7:D 5 VAR
5622 14 7:D 5 X : INTEGER;
5623 14 7:D 6
5624 14 7:0 0 BEGIN
5625 14 7:1 0 FOR X := CHARF TO CHARL DO
5626 14 7:2 11
BATTLERC[ GROUPI].A.TEMP04[ X].ARMORCL :=
5627 14 7:2 25
BATTLERC[ GROUPI].A.TEMP04[ X].ARMORCL + ACMOD;
5628 14 7:0 48 END;
Bug #33
-------
TILTOWAIT does 10-150 damage.
The documentation for TILTOWAIT says it does 10-100 damage, but the
code gives 10-150 damage.
6190 14 29:1 440 IF SPELL = TILTOWAIT THEN
6191 14 29:2 449 IF BATG = 0 THEN
6192 14 29:3 456 FOR GROUPI := 1 TO 4 DO
6193 14 29:4 467 HITGROUP( GROUPI, 10, 15, 0)
6194 14 29:2 471 ELSE
6195 14 29:3 482 HITGROUP( 0, 10, 15, 0)
Bug #34
-------
A character's STATUS can be set outside the valid range of OK..LOST.
Casting the spell DI or KADORTO on a character with VITALITY = 3
changes its STATUS to "LOST + 1".
8884 18 17:D 1 PROCEDURE DIKADORT; (* P010C11 *)
8896 18 17:3 76 IF
CHARACTR[ HEALME].ATTRIB[ VITALITY] = 3 THEN
8897 18 17:4 94 CHARACTR[ HEALME].STATUS := LOST
8902 18 17:1 137 IF CHARACTR[ HEALME].STATUS = OK THEN
8903 18 17:2 150 EXITCAST( 'OK')
8904 18 17:1 155 ELSE
8905 18 17:2 159 BEGIN
8906 18 17:3 159 CHARACTR[ HEALME].STATUS :=
SUCC( CHARACTR[ HEALME].STATUS);
Bug #35
-------
In DOTRAPDM, it is possible for damage from a trap to actually help a
character.
Case 1 (POISON) shows that POISNAMT[ 1] is a field that can be
incremented beyond 1.
Case 2 (GAS) set POISNAMT[ 1] to exactly 1. Therefore, if it was
greater than 1 to start with, this actually helps the character.
9900 19 14:1 104 CASE TRAPTYPE OF
9901 19 14:1 109
9902 19 14:1 109 1: (* POISON *)
9903 19 14:1 109
9904 19 14:2 109
CHARACTR[ CHRXCHST].LOSTXYL.POISNAMT[ 1] :=
9905 19 14:2 123
CHARACTR[ CHRXCHST].LOSTXYL.POISNAMT[ 1] + 1;
9906 19 14:2 143
9907 19 14:1 143 2: (* GAS *)
9908 19 14:1 143
9909 19 14:2 143 FOR CHARX := 0 TO PARTYCNT - 1 DO
9910 19 14:3 156 IF (RANDOM MOD 20) <
CHARACTR[ CHARX].LUCKSKIL[ 3] THEN
9911 19 14:4 178
CHARACTR[ CHARX].LOSTXYL.POISNAMT[ 1] := 1;
Bug #36
-------
HPLEFT can be set to 0 without status being "Dead". I think this is a
bug.
11815 20 57:D 1 PROCEDURE PITOUCH; (* P010E39 *)
11832 20 57:5 126 IF CHARACTR[ CHARX].HPLEFT < 0 THEN
11833 20 57:6 137 BEGIN
11834 20 57:7 137 CHARACTR[ CHARX].HPLEFT := 0;
11835 20 57:7 146 CHARACTR[ CHARX].STATUS := DEAD
11836 20 57:6 153 END
--Tommy