QB64.org Forum

Active Forums => Programs => Topic started by: SMcNeill on September 07, 2019, 09:26:57 am

Title: Rogue-Like (work in progress)
Post by: SMcNeill on September 07, 2019, 09:26:57 am
Having a little free time once more, I decided to play around with one of those old game concepts from ages lost, and toss my hat into the ring with trying to create a "Rogue-like" game.  (You can watch this poor fellow try out the game for the first time in a video here:
OR, you can have fun and test out this timeless classic yourself with a modern emulator here: https://www.myabandonware.com/game/rogue-4n/play-4n )

So, here's my little start to the game, which generates us a rogue-like map and lets us roam our hero around it and explore:
Code: QB64: [Select]
  1. TYPE Light_Type
  2.     Name AS STRING * 20
  3.     Reach AS INTEGER
  4.     Left AS INTEGER
  5.  
  6. TYPE Hero_Type
  7.     X AS INTEGER
  8.     Y AS INTEGER
  9.     HP AS INTEGER
  10.     Level AS INTEGER
  11.     EXP_Earned AS LONG
  12.     EXP_Needed AS LONG
  13.     Light AS Light_Type
  14.  
  15. DIM SHARED Hero AS Hero_Type
  16. DIM SHARED Level AS INTEGER: Level = 1
  17.  
  18.  
  19. SCREEN _NEWIMAGE(800, 600, 32)
  20. REDIM SHARED MapArray(0, 0) AS _BYTE
  21.  
  22. Init
  23. CreateMap 99, 74, 10, 1
  24.     CLS
  25.     DrawMap
  26.     _DISPLAY
  27.     GetInput
  28.  
  29. SUB Init
  30.     Hero.HP = 10
  31.     Hero.Level = 1
  32.     Hero.EXP_Earned = 0
  33.     Hero.EXP_Needed = 1000
  34.     Hero.Light.Name = "Magic Candle"
  35.     Hero.Light.Reach = 2
  36.     Hero.Light.Left = -1 'infinite
  37.  
  38. SUB GetInput
  39.     DO
  40.         k = _KEYHIT
  41.         SELECT CASE k
  42.             CASE 18432 'up
  43.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  44.                     IF MapArray(Hero.X, Hero.Y - 1) AND (2 OR 4) THEN 'and it's a room or passageway
  45.                         Hero.Y = Hero.Y - 1
  46.                     END IF
  47.                 END IF
  48.             CASE 19200 'left
  49.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  50.                     IF MapArray(Hero.X - 1, Hero.Y) AND (2 OR 4) THEN 'and it's a room or passageway
  51.                         Hero.X = Hero.X - 1
  52.                     END IF
  53.                 END IF
  54.             CASE 20480 'down
  55.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  56.                     IF MapArray(Hero.X, Hero.Y + 1) AND (2 OR 4) THEN 'and it's a room or passageway
  57.                         Hero.Y = Hero.Y + 1
  58.                     END IF
  59.                 END IF
  60.             CASE 19712 'right
  61.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  62.                     IF MapArray(Hero.X + 1, Hero.Y) AND (2 OR 4) THEN 'and it's a room or passageway
  63.                         Hero.X = Hero.X + 1
  64.                     END IF
  65.                 END IF
  66.             CASE 32 'space to just wait and skip a turn
  67.             CASE 60 ' "<" key
  68.                 Level = Level + 1
  69.                 IF MapArray(Hero.X, Hero.Y) AND 8 THEN CreateMap 99, 74, 10, Level
  70.             CASE ASC("+"), ASC("=")
  71.                 IF Hero.Light.Reach < 5 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  72.             CASE ASC("-"), ASC("_")
  73.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  74.         END SELECT
  75.         _LIMIT 60
  76.     LOOP UNTIL k
  77.     _KEYCLEAR 'one keystroke at a time
  78.  
  79. SUB Illuminate (TX, TY, Range)
  80.     X = TX: Y = TY
  81.     XL = LBOUND(MapArray, 1): XH = UBOUND(MapArray, 1)
  82.     YL = LBOUND(MapArray, 2): YH = UBOUND(MapArray, 2)
  83.     IF X >= XL AND X <= XH AND Y >= YL AND Y <= YH THEN
  84.         IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 16 'illuminate the room
  85.         IF Range > 0 THEN
  86.             IF X > XL THEN IF MapArray(X - 1, Y) <> 0 THEN Illuminate X - 1, Y, Range - 1
  87.             IF X < XH THEN IF MapArray(X + 1, Y) <> 0 THEN Illuminate X + 1, Y, Range - 1
  88.             IF Y > YL THEN IF MapArray(X, Y - 1) <> 0 THEN Illuminate X, Y - 1, Range - 1
  89.             IF Y < YH THEN IF MapArray(X, Y + 1) <> 0 THEN Illuminate X, Y + 1, Range - 1
  90.         END IF
  91.     END IF
  92.  
  93.  
  94. SUB DrawMap
  95.     XL = LBOUND(MapArray, 1): XH = UBOUND(MapArray, 1)
  96.     YL = LBOUND(MapArray, 2): YH = UBOUND(MapArray, 2)
  97.  
  98.     Illuminate Hero.X, Hero.Y, Hero.Light.Reach
  99.  
  100.     FOR y = 0 TO YH
  101.         FOR x = 0 TO XH
  102.             LOCATE y + 1, x + 1
  103.             IF MapArray(x, y) AND 1 THEN 'It's a visible part of the map, draw it
  104.                 IF MapArray(x, y) AND 2 THEN 'it's a visible room
  105.                     COLOR 0, &HFF000000
  106.                     _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), CHR$(219)
  107.                 END IF
  108.                 IF MapArray(x, y) AND 4 THEN 'it's a visible path
  109.                     COLOR &HFF000000, &HFF777777
  110.                     _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), "."
  111.                 END IF
  112.                 IF MapArray(x, y) AND 8 THEN 'it's the stairs to the next level
  113.                     COLOR &HFF00FF00, &HFFFFFF00
  114.                     _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), CHR$(240)
  115.                 END IF
  116.                 IF MapArray(x, y) AND 16 THEN 'it's currently illuminated by the lightsource
  117.                     COLOR &H40FFFF00, 0
  118.                     _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), CHR$(219)
  119.                     MapArray(x, y) = MapArray(x, y) AND NOT 16
  120.                 END IF
  121.  
  122.             ELSE
  123.                 COLOR &HFF0000FF, &HFF000000
  124.                 _PRINTSTRING (x * _FONTWIDTH, y * _FONTHEIGHT), CHR$(219)
  125.             END IF
  126.         NEXT
  127.     NEXT
  128.     COLOR &HFFFFFF00, 0 'Yellow Hero
  129.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  130.  
  131.  
  132.  
  133.  
  134.  
  135. SUB CreateMap (XLimit, YLimit, Rooms, Level)
  136.     ERASE MapArray 'clear the old map and reset everything to 0
  137.     REDIM MapArray(XLimit, YLimit) AS _BYTE
  138.  
  139.     DIM RoomCenterX(Rooms) AS _BYTE, RoomCenterY(Rooms) AS _BYTE
  140.  
  141.     FOR i = 1 TO Rooms
  142.         DO
  143.             RoomSize = INT(RND * 9) + 2
  144.             RoomX = INT(RND * (XLimit - RoomSize))
  145.             RoomY = INT(RND * (YLimit - RoomSize))
  146.             'test for positioning
  147.             good = -1 'it's good starting out
  148.             FOR y = 0 TO RoomSize: FOR x = 0 TO RoomSize
  149.                     IF MapArray(RoomX + x, RoomY + y) = 2 THEN good = 0: EXIT FOR 'don't draw a room on a room
  150.             NEXT x, y
  151.         LOOP UNTIL good
  152.         FOR y = 0 TO RoomSize: FOR x = 0 TO RoomSize
  153.                 MapArray(RoomX + x, RoomY + y) = 2 'go ahead and draw a room
  154.         NEXT x, y
  155.         RoomCenterX(i) = RoomX + .5 * RoomSize
  156.         RoomCenterY(i) = RoomY + .5 * RoomSize
  157.     NEXT
  158.     FOR i = 1 TO Rooms - 1
  159.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  160.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  161.         DO UNTIL StartX = EndX AND StartY = EndY
  162.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  163.             Meander = 10
  164.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  165.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  166.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  167.                     Ychange = 0
  168.                 ELSE
  169.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  170.                     Ychange = 0
  171.                 END IF
  172.             ELSE
  173.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  174.                     Ychange = SGN(EndY - StartY)
  175.                     XChange = 0
  176.                 ELSE
  177.                     Ychange = INT(RND * 3) - 1
  178.                     XChange = 0
  179.                 END IF
  180.             END IF
  181.             StartX = StartX + XChange
  182.             StartY = StartY + Ychange
  183.             IF StartX < 0 THEN StartX = 0
  184.             IF StartY < 0 THEN StartY = 0
  185.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  186.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  187.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 4
  188.         LOOP
  189.     NEXT
  190.     DO
  191.         Hero.X = INT(RND * XLimit + 1)
  192.         Hero.Y = INT(RND * YLimit + 1)
  193.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 2 'place the hero randomly, until they're in a room somewhere
  194.     DO
  195.         x = INT(RND * XLimit + 1)
  196.         y = INT(RND * YLimit + 1)
  197.     LOOP UNTIL MapArray(x, y) AND 2 'get a random spot in a room, for the stairs to the next level
  198.     MapArray(x, y) = MapArray(x, y) OR 8
  199.  

I'm actually rather happy with what I've got here so far, and I really like the illumination code, which can be used to simulate various light sources for us.  A candle has a minor glow, only illuminating the ground close to our hero, whereas a torch might illuminate further, and a lantern even more than that...

Navigation/Key commands
For testing purposes, at this point, you can alter your light's range manually with the "+" and "-" keys. 
Arrow keys move your hero across the screen.
The "<" key allows you to go down stairs to the next level. 



Test it out, see what you think of things as they currently exist, and let me know how it performs on your machine/OS.  The illumination routine isn't half as efficient as it could be, as it recursively calls itself over and over and repeats lighting checks on the same areas multiple times, but I don't think it should really be that big an issue since a lightsource should never be larger than 5 or 6 tiles.  If I was going to have the light extend dozens of tiles, I'd work on fixing the repetitive recursive calls, but in this case it seems minor enough to keep it simple as it is.  On my machine, it runs perfectly fine with no issue, but if it lags or uses too much CPU power on somebody else's, I'll redo it with a little better logic checking.

Try it out, tell me what you think, and if there's any issues with navigation, illumination, or such, let me know and I'll work on correcting it before I move on and start adding monsters, gear, and everything else into the game one step at a time.
Title: Re: Rogue-Like (work in progress)
Post by: Ashish on September 07, 2019, 09:59:32 am
Hi Steve! Nice.. The only issue is that it lags. Seems like it wait for a bit to respond to keys.
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 07, 2019, 10:12:46 am
Hi Steve! Nice.. The only issue is that it lags. Seems like it wait for a bit to respond to keys.

I was a little worried that it might on lower-end systems. 

Quick question: Is the lag in general, or when you've got a strong light illuminating your surroundings?  The game should be one which operates on a "turn-based" style, where nothing changes or happens, until the player makes a keypress.  It's not a "real-time" game, where monsters and such will move independently and the game progresses consistantly; instead it only updates with each keystroke from the player.  (And even then, only from registered keystrokes.  Arrow keys to move, Space to wait and skip a turn, "<" to go down stairs... Other keys for future functionality...)

Are you seeing a general sluggishness to respond, with high CPU usage (such as the light calling itself over and over in an unregulated loop), or is it just the style which might not be what you were expecting?  I know a lot of you younger folks have probably never been exposed to Rogue (after all, it was a 1980 text game which ran on consoles/terminals), and I think the movement seems fairly faithful on my system, but I'm still worried there might be an issue in there somewhere for folks with older machines.  ;)
Title: Re: Rogue-Like (work in progress)
Post by: Ashish on September 07, 2019, 10:35:44 am
  The game should be one which operates on a "turn-based" style, where nothing changes or happens, until the player makes a keypress.
Ok. Got it. No, no high cpu usage.
Title: Re: Rogue-Like (work in progress)
Post by: Cobalt on September 07, 2019, 12:07:33 pm
Plays pretty smooth for me, looks a lot like ye old ROGUE games. Which I have to admit I always found them particularly hard with the lack of a visible interface and usually a lack of instructions\information on game play.

I find the lighting trick rather cool, lots of possibilities with that one; blindness, candle, torch, ect...

The mapping always interested me. I was once told, by a guy who made probably my fave roguish type game ever 'Reaping the Dungeon', that he generated some 'rooms' then had a 'worm' make tunnels connecting them. Alas I have tried many a time to get that to work and just never really succeed, the maps are either big messes of tunnels everywhere or half the rooms can never be reached. couldn't get a good balance.

I've done a lot of work on my own Reaping clone, thats when I asked Galleon about being able to change the BIOS font and he set that up for me way back when. But never was happy with the maps I was able to generate, and that always held me back from finishing it.

Anyway it looks really awesome and nostalgic! 
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 07, 2019, 12:35:54 pm
Plays pretty smooth for me, looks a lot like ye old ROGUE games. Which I have to admit I always found them particularly hard with the lack of a visible interface and usually a lack of instructions\information on game play.

I find the lighting trick rather cool, lots of possibilities with that one; blindness, candle, torch, ect...

The mapping always interested me. I was once told, by a guy who made probably my fave roguish type game ever 'Reaping the Dungeon', that he generated some 'rooms' then had a 'worm' make tunnels connecting them. Alas I have tried many a time to get that to work and just never really succeed, the maps are either big messes of tunnels everywhere or half the rooms can never be reached. couldn't get a good balance.

I've done a lot of work on my own Reaping clone, thats when I asked Galleon about being able to change the BIOS font and he set that up for me way back when. But never was happy with the maps I was able to generate, and that always held me back from finishing it.

Anyway it looks really awesome and nostalgic!

The lighting trick is also going to become an “awareness trick”.  Using the same behavior as the light, with the monsters, I’ll be able to give the monsters a “sight” and “hearing” range.  The way I envision it, something like a bat will have almost 0 sight but a decent hearing range, so sneaking might allow you to bypass it without being found, whereas an invisibility potion would be useless.  A different creature might have a high visibility, but be deaf... 

Just as we produce a light around us, monsters should have an awareness range around them.  At least, that’s my intention for how things should work...  ;)
Title: Re: Rogue-Like (work in progress)
Post by: Cobalt on September 07, 2019, 02:19:52 pm
wow that lighting system is really slow! I played around with it a bit and when you get up near 10-11 distance it darn near locks. no super site abilities I guess! which makes me wonder just how well the performance will hold up if your using something similar for awareness, 2 or 3 creatures and that would be it.

Though looking at the Illuminate code I don't see anything directly that makes it look slow unless its all the recursion? even at 10 should only be checking what, 220-221 spaces?

anyways, I did add a little info area, very very basic.
Code: QB64: [Select]
  1. SUB Display_Info_Area
  2.  LOCATE 1, 1: PRINT "ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿"
  3.  FOR i%% = 0 TO 2
  4.   LOCATE 2 + i%%, 1: PRINT "³"; TAB(94); "³"
  5.  NEXT i%%
  6.  LOCATE 2 + i%%, 1: PRINT "ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ"
  7.  LOCATE 2, 2: PRINT "HP:"; Hero.HP; TAB(12); "Map X:"; Hero.X; TAB(24); "Map Y:"; Hero.Y; TAB(36); "Level:"; Hero.Level;
  8.  PRINT TAB(46); "Candle Power:"; Hero.Light.Reach; TAB(62); "Aflliction:";
  9.  LOCATE 3, 2: PRINT "Load:"; TAB(15); "Right Hand:"; TAB(35); "Left Hand:"
  10.  
kept it all text based but the border might be better as a line statement or something. Granted the map can go into the area, but it was just an idea.

It might be on your list but did you see this code?
Code: QB64: [Select]
  1.    CASE 60 ' "<" key
  2.     Level = Level + 1
  3.     IF MapArray(Hero.X, Hero.Y) AND 8 THEN CreateMap 99, 74, 10, Level
  4.  
so every time the player hits the "<" key level increases! even if there is no 'ladder' there.
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 07, 2019, 03:46:49 pm
wow that lighting system is really slow! I played around with it a bit and when you get up near 10-11 distance it darn near locks. no super site abilities I guess! which makes me wonder just how well the performance will hold up if your using something similar for awareness, 2 or 3 creatures and that would be it.

Though looking at the Illuminate code I don't see anything directly that makes it look slow unless its all the recursion? even at 10 should only be checking what, 220-221 spaces?

I agree with all you said here 100%.  The lag generated with higher values is completely beyond my expectations, and I honestly don't see why it's as extreme as it is.  And, I think you're right with it either limiting the creatures to a minute awareness range, or else becoming an anchor on the program which will lag it to the point of insanity...

To correct the issue, I've replaced it with this little set of code:

Code: QB64: [Select]
  1. TYPE Damage_Type
  2.     Low AS INTEGER
  3.     High AS INTEGER
  4.  
  5. TYPE Light_Type
  6.     Name AS STRING * 20
  7.     Reach AS INTEGER
  8.     Left AS INTEGER
  9.  
  10. TYPE Weapon_Type
  11.     Name AS STRING * 20
  12.     Reach AS INTEGER
  13.     Damage AS Damage_Type
  14.     HitBonus AS INTEGER
  15.     DamageBonus AS INTEGER
  16.     Left AS INTEGER 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  17.  
  18. TYPE Armor_Type
  19.     Name AS STRING * 20
  20.     PD AS INTEGER 'Passive Defense (dodge)
  21.     DR AS INTEGER 'Damage Resistance (absorption)
  22.     Left AS INTEGER 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  23.  
  24.  
  25.  
  26. TYPE Hero_Type
  27.     Name AS STRING * 20
  28.     X AS INTEGER
  29.     Y AS INTEGER
  30.     Life AS Damage_Type
  31.     Level AS INTEGER
  32.     EXP_Earned AS LONG
  33.     EXP_Needed AS LONG
  34.     Light AS Light_Type
  35.     Weapon1 AS Weapon_Type
  36.     Weapon2 AS Weapon_Type
  37.     Armor AS Armor_Type
  38.  
  39. DIM SHARED Hero AS Hero_Type
  40. DIM SHARED Level AS INTEGER: Level = 1
  41. DIM SHARED XL, XH, YL, YH
  42.  
  43.  
  44. SCREEN _NEWIMAGE(800, 700, 32)
  45. REDIM SHARED MapArray(0, 0) AS _BYTE
  46. REDIM SHARED Distance(0, 0) AS _BYTE
  47.  
  48. Init
  49. CreateMap 99, 74, 10, 1
  50.     CLS , &HFF0000FF
  51.     DrawMap
  52.     DisplayCharacter
  53.     _DISPLAY
  54.     GetInput
  55.  
  56. SUB DisplayCharacter
  57.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  58.     COLOR &HFFFFFFFF, 0
  59.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  60.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  61.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  62.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  63.  
  64.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  65.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  66.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  67.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  68.  
  69. SUB Init
  70.     Hero.Name = "Steve The Tester!"
  71.     Hero.Life.Low = 10
  72.     Hero.Life.High = 10
  73.     Hero.Level = 1
  74.     Hero.EXP_Earned = 0
  75.     Hero.EXP_Needed = 2
  76.     Hero.Light.Name = "Magic Candle"
  77.     Hero.Light.Reach = 2
  78.     Hero.Light.Left = -1 'infinite
  79.     Hero.Weapon1.Name = "Bare Fist"
  80.     Hero.Weapon1.Reach = 1
  81.     Hero.Weapon1.Damage.Low = 1
  82.     Hero.Weapon1.Damage.High = 2
  83.     Hero.Weapon1.HitBonus = 0
  84.     Hero.Weapon1.DamageBonus = 0
  85.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  86.     Hero.Weapon2.Name = "Magic Candle"
  87.     Hero.Weapon2.Reach = 0
  88.     Hero.Weapon2.Damage.Low = 0
  89.     Hero.Weapon2.Damage.High = 0
  90.     Hero.Weapon2.HitBonus = 0
  91.     Hero.Weapon2.DamageBonus = 0
  92.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  93.     Hero.Armor.Name = "Naked"
  94.     Hero.Armor.PD = 0
  95.     Hero.Armor.DR = 0
  96.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  97.  
  98. SUB GetInput
  99.     DO
  100.         k = _KEYHIT
  101.         SELECT CASE k
  102.             CASE 18432 'up
  103.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  104.                     IF MapArray(Hero.X, Hero.Y - 1) AND (4 OR 8) THEN 'and it's a room or passageway
  105.                         Hero.Y = Hero.Y - 1
  106.                     END IF
  107.                 END IF
  108.             CASE 19200 'left
  109.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  110.                     IF MapArray(Hero.X - 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  111.                         Hero.X = Hero.X - 1
  112.                     END IF
  113.                 END IF
  114.             CASE 20480 'down
  115.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  116.                     IF MapArray(Hero.X, Hero.Y + 1) AND (4 OR 8) THEN 'and it's a room or passageway
  117.                         Hero.Y = Hero.Y + 1
  118.                     END IF
  119.                 END IF
  120.             CASE 19712 'right
  121.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  122.                     IF MapArray(Hero.X + 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  123.                         Hero.X = Hero.X + 1
  124.                     END IF
  125.                 END IF
  126.             CASE 32 'space to just wait and skip a turn
  127.             CASE 60 ' "<" key
  128.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  129.                     Level = Level + 1
  130.                     CreateMap 99, 74, 10, Level
  131.                 END IF
  132.             CASE ASC("+"), ASC("=")
  133.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  134.             CASE ASC("-"), ASC("_")
  135.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  136.         END SELECT
  137.         _LIMIT 60
  138.     LOOP UNTIL k
  139.     _KEYCLEAR 'one keystroke at a time
  140.  
  141. SUB Illuminate (TX, TY, TRange)
  142.     X = TX: Y = TY: Range = TRange
  143.     PathFind TX, TY, TRange
  144.     FOR X = 0 TO XH
  145.         FOR Y = 0 TO YH
  146.             IF Distance(X, Y) <> 0 THEN 'It's close enough to check for illumination
  147.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  148.             END IF
  149.         NEXT
  150.     NEXT
  151.  
  152.  
  153. SUB DrawMap
  154.     Illuminate Hero.X, Hero.Y, Hero.Light.Reach
  155.     FOR Y = 0 TO YH
  156.         FOR X = 0 TO XH
  157.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  158.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  159.                     COLOR &HFF000000, 0
  160.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  161.                 END IF
  162.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  163.                     COLOR &HFF000000, &HFF777777
  164.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  165.                 END IF
  166.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  167.                     COLOR &HFF00FF00, &HFFFFFF00
  168.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  169.                 END IF
  170.             END IF
  171.             'note: highlighting for the light should come AFTER the map is drawn
  172.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  173.                 COLOR &H40FFFF00, 0
  174.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  175.                 MapArray(X, Y) = MapArray(X, Y) - 1
  176.             END IF
  177.         NEXT
  178.     NEXT
  179.     COLOR &HFFFFFF00, 0 'Yellow Hero
  180.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  181.  
  182.  
  183.  
  184.  
  185.  
  186. SUB CreateMap (XLimit, YLimit, Rooms, Level)
  187.     ERASE MapArray 'clear the old map and reset everything to 0
  188.     REDIM MapArray(XLimit, YLimit) AS _BYTE
  189.     XL = LBOUND(MapArray, 1): XH = UBOUND(MapArray, 1)
  190.     YL = LBOUND(MapArray, 2): YH = UBOUND(MapArray, 2)
  191.  
  192.     DIM RoomCenterX(Rooms) AS _BYTE, RoomCenterY(Rooms) AS _BYTE
  193.  
  194.     FOR i = 1 TO Rooms
  195.         DO
  196.             RoomSize = INT(RND * 9) + 2
  197.             RoomX = INT(RND * (XLimit - RoomSize))
  198.             RoomY = INT(RND * (YLimit - RoomSize))
  199.             'test for positioning
  200.             good = -1 'it's good starting out
  201.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  202.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  203.             NEXT X, Y
  204.         LOOP UNTIL good
  205.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  206.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  207.         NEXT X, Y
  208.         RoomCenterX(i) = RoomX + .5 * RoomSize
  209.         RoomCenterY(i) = RoomY + .5 * RoomSize
  210.     NEXT
  211.     FOR i = 1 TO Rooms - 1
  212.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  213.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  214.         DO UNTIL StartX = EndX AND StartY = EndY
  215.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  216.             Meander = 10
  217.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  218.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  219.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  220.                     Ychange = 0
  221.                 ELSE
  222.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  223.                     Ychange = 0
  224.                 END IF
  225.             ELSE
  226.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  227.                     Ychange = SGN(EndY - StartY)
  228.                     XChange = 0
  229.                 ELSE
  230.                     Ychange = INT(RND * 3) - 1
  231.                     XChange = 0
  232.                 END IF
  233.             END IF
  234.             StartX = StartX + XChange
  235.             StartY = StartY + Ychange
  236.             IF StartX < 0 THEN StartX = 0
  237.             IF StartY < 0 THEN StartY = 0
  238.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  239.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  240.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  241.         LOOP
  242.     NEXT
  243.     DO
  244.         Hero.X = INT(RND * XLimit + 1)
  245.         Hero.Y = INT(RND * YLimit + 1)
  246.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  247.     DO
  248.         X = INT(RND * XLimit + 1)
  249.         Y = INT(RND * YLimit + 1)
  250.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  251.     MapArray(X, Y) = MapArray(X, Y) OR 16
  252.  
  253. SUB PathFind (FromX, FromY, Range)
  254.     REDIM Distance(XH, YH) AS _BYTE
  255.     DIM Temp(XH, YH) AS _BYTE
  256.     DIM m AS _MEM, m1 AS _MEM
  257.     m = _MEM(Distance()): m1 = _MEM(Temp())
  258.     Temp(FromX, FromY) = -1
  259.     FOR i = 1 TO Range
  260.         FOR x = 0 TO XH
  261.             FOR y = 0 TO YH
  262.                 IF x < XH THEN
  263.                     IF Temp(x + 1, y) <> 0 THEN Distance(x, y) = Distance(x, y) + 1
  264.                 END IF
  265.                 IF x > 0 THEN
  266.                     IF Temp(x - 1, y) <> 0 THEN Distance(x, y) = Distance(x, y) + 1
  267.                 END IF
  268.                 IF y < YH THEN
  269.                     IF Temp(x, y + 1) <> 0 THEN Distance(x, y) = Distance(x, y) + 1
  270.                 END IF
  271.                 IF y > 0 THEN
  272.                     IF Temp(x, y - 1) <> 0 THEN Distance(x, y) = Distance(x, y) + 1
  273.                 END IF
  274.             NEXT
  275.         NEXT
  276.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  277.     NEXT
  278.     _MEMFREE m
  279.     _MEMFREE m1

Now I don't just have an illumination routine; I have a full fledged pathfinding routine in place to make use of later.  Since I can map all points distance from the character, all I need to do is check to see if the tile a mob is on  has a distance value <= their awareness levels....  If so, all I have to do is check the tiles around them and move them to the tile with the next lowest value so they can approach the hero, one menacing step at a time.

Testing this, we can move our lightsource up to a whopping 25 tiles (NUKE!!) and still move around without any lag or issues with gamplay.  (Higher light values probably work just as well, but I didn't bother to test beyond 25.) 

I think the lag from illumination issue has probably been solved for good with this change, and I hope all it'll need is a slight tweak or so, so that it'll work for mob awareness levels and such as well.
Title: Re: Rogue-Like (work in progress)
Post by: Cobalt on September 07, 2019, 04:40:13 pm
Yep, much more 'stable' in performance. I was going to ask if you were going to use a path finding routine to allow the creatures to follow or intercept the player, guess there is my answer. Looking really good.
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 07, 2019, 04:49:50 pm
A slight tweak to illumination/pathfinding... Now we no longer count steps through a solid wall. :P

Code: QB64: [Select]
  1. TYPE Damage_Type
  2.     Low AS _UNSIGNED _BYTE
  3.     High AS _UNSIGNED _BYTE
  4.  
  5. TYPE Light_Type
  6.     Name AS STRING * 20
  7.     Reach AS _UNSIGNED _BYTE
  8.     Left AS _UNSIGNED _BYTE
  9.  
  10. TYPE Weapon_Type
  11.     Name AS STRING * 20
  12.     Reach AS _UNSIGNED _BYTE
  13.     Damage AS Damage_Type
  14.     HitBonus AS _UNSIGNED _BYTE
  15.     DamageBonus AS _UNSIGNED _BYTE
  16.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  17.  
  18. TYPE Armor_Type
  19.     Name AS STRING * 20
  20.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  21.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  22.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  23.  
  24.  
  25.  
  26. TYPE Hero_Type
  27.     Name AS STRING * 20
  28.     Life AS Damage_Type
  29.     Level AS _UNSIGNED _BYTE
  30.     EXP_Earned AS LONG
  31.     EXP_Needed AS LONG
  32.     Light AS Light_Type
  33.     Weapon1 AS Weapon_Type
  34.     Weapon2 AS Weapon_Type
  35.     Armor AS Armor_Type
  36.  
  37. DIM SHARED Hero AS Hero_Type
  38. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  39. DIM SHARED XL, XH, YL, YH
  40.  
  41.  
  42. SCREEN _NEWIMAGE(800, 700, 32)
  43. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  44. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  45.  
  46.  
  47. Init
  48. CreateMap 99, 74, 10, 1
  49.     CLS , &HFF0000FF
  50.     DrawMap
  51.     DisplayCharacter
  52.     _DISPLAY
  53.     GetInput
  54.  
  55. SUB DisplayCharacter
  56.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  57.     COLOR &HFFFFFFFF, 0
  58.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  59.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  60.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  61.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  62.  
  63.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  64.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  65.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  66.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  67.  
  68. SUB Init
  69.     Hero.Name = "Steve The Tester!"
  70.     Hero.Life.Low = 10
  71.     Hero.Life.High = 10
  72.     Hero.Level = 1
  73.     Hero.EXP_Earned = 0
  74.     Hero.EXP_Needed = 2
  75.     Hero.Light.Name = "Magic Candle"
  76.     Hero.Light.Reach = 2
  77.     Hero.Light.Left = -1 'infinite
  78.     Hero.Weapon1.Name = "Bare Fist"
  79.     Hero.Weapon1.Reach = 1
  80.     Hero.Weapon1.Damage.Low = 1
  81.     Hero.Weapon1.Damage.High = 2
  82.     Hero.Weapon1.HitBonus = 0
  83.     Hero.Weapon1.DamageBonus = 0
  84.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  85.     Hero.Weapon2.Name = "Magic Candle"
  86.     Hero.Weapon2.Reach = 0
  87.     Hero.Weapon2.Damage.Low = 0
  88.     Hero.Weapon2.Damage.High = 0
  89.     Hero.Weapon2.HitBonus = 0
  90.     Hero.Weapon2.DamageBonus = 0
  91.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  92.     Hero.Armor.Name = "Naked"
  93.     Hero.Armor.PD = 0
  94.     Hero.Armor.DR = 0
  95.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  96.  
  97. SUB GetInput
  98.     DO
  99.         k = _KEYHIT
  100.         SELECT CASE k
  101.             CASE 18432 'up
  102.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  103.                     IF MapArray(Hero.X, Hero.Y - 1) AND (4 OR 8) THEN 'and it's a room or passageway
  104.                         Hero.Y = Hero.Y - 1
  105.                         PathFind
  106.                     END IF
  107.                 END IF
  108.             CASE 19200 'left
  109.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  110.                     IF MapArray(Hero.X - 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  111.                         Hero.X = Hero.X - 1
  112.                         PathFind
  113.                     END IF
  114.                 END IF
  115.             CASE 20480 'down
  116.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  117.                     IF MapArray(Hero.X, Hero.Y + 1) AND (4 OR 8) THEN 'and it's a room or passageway
  118.                         Hero.Y = Hero.Y + 1
  119.                         PathFind
  120.                     END IF
  121.                 END IF
  122.             CASE 19712 'right
  123.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  124.                     IF MapArray(Hero.X + 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  125.                         Hero.X = Hero.X + 1
  126.                         PathFind
  127.                     END IF
  128.                 END IF
  129.             CASE 32 'space to just wait and skip a turn
  130.             CASE 60 ' "<" key
  131.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  132.                     Level = Level + 1
  133.                     CreateMap 99, 74, 10, Level
  134.                     PathFind
  135.                 END IF
  136.             CASE ASC("+"), ASC("=")
  137.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  138.             CASE ASC("-"), ASC("_")
  139.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  140.         END SELECT
  141.         _LIMIT 60
  142.     LOOP UNTIL k
  143.     _KEYCLEAR 'one keystroke at a time
  144.  
  145. SUB Illuminate (Range)
  146.     FOR X = 0 TO XH
  147.         FOR Y = 0 TO YH
  148.             IF Distance(X, Y) <= Range THEN 'It's close enough to check for illumination
  149.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  150.             END IF
  151.         NEXT
  152.     NEXT
  153.  
  154.  
  155. SUB DrawMap
  156.     Illuminate Hero.Light.Reach
  157.     FOR Y = 0 TO YH
  158.         FOR X = 0 TO XH
  159.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  160.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  161.                     COLOR &HFF000000, 0
  162.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  163.                 END IF
  164.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  165.                     COLOR &HFF000000, &HFF777777
  166.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  167.                 END IF
  168.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  169.                     COLOR &HFF00FF00, &HFFFFFF00
  170.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  171.                 END IF
  172.             END IF
  173.             'note: highlighting for the light should come AFTER the map is drawn
  174.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  175.                 COLOR &H40FFFF00, 0
  176.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  177.                 MapArray(X, Y) = MapArray(X, Y) - 1
  178.             END IF
  179.         NEXT
  180.     NEXT
  181.     COLOR &HFFFFFF00, 0 'Yellow Hero
  182.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  183.  
  184.  
  185.  
  186.  
  187.  
  188. SUB CreateMap (XLimit, YLimit, Rooms, Level)
  189.     ERASE MapArray 'clear the old map and reset everything to 0
  190.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  191.     REDIM Distance(XH, YH) AS _UNSIGNED _BYTE
  192.     REDIM Temp(XH, YH) AS _UNSIGNED _BYTE
  193.  
  194.     XL = LBOUND(MapArray, 1): XH = UBOUND(MapArray, 1)
  195.     YL = LBOUND(MapArray, 2): YH = UBOUND(MapArray, 2)
  196.  
  197.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  198.  
  199.     FOR i = 1 TO Rooms
  200.         DO
  201.             RoomSize = INT(RND * 9) + 2
  202.             RoomX = INT(RND * (XLimit - RoomSize))
  203.             RoomY = INT(RND * (YLimit - RoomSize))
  204.             'test for positioning
  205.             good = -1 'it's good starting out
  206.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  207.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  208.             NEXT X, Y
  209.         LOOP UNTIL good
  210.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  211.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  212.         NEXT X, Y
  213.         RoomCenterX(i) = RoomX + .5 * RoomSize
  214.         RoomCenterY(i) = RoomY + .5 * RoomSize
  215.     NEXT
  216.     FOR i = 1 TO Rooms - 1
  217.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  218.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  219.         DO UNTIL StartX = EndX AND StartY = EndY
  220.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  221.             Meander = 10
  222.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  223.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  224.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  225.                     Ychange = 0
  226.                 ELSE
  227.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  228.                     Ychange = 0
  229.                 END IF
  230.             ELSE
  231.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  232.                     Ychange = SGN(EndY - StartY)
  233.                     XChange = 0
  234.                 ELSE
  235.                     Ychange = INT(RND * 3) - 1
  236.                     XChange = 0
  237.                 END IF
  238.             END IF
  239.             StartX = StartX + XChange
  240.             StartY = StartY + Ychange
  241.             IF StartX < 0 THEN StartX = 0
  242.             IF StartY < 0 THEN StartY = 0
  243.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  244.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  245.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  246.         LOOP
  247.     NEXT
  248.     DO
  249.         Hero.X = INT(RND * XLimit + 1)
  250.         Hero.Y = INT(RND * YLimit + 1)
  251.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  252.     DO
  253.         X = INT(RND * XLimit + 1)
  254.         Y = INT(RND * YLimit + 1)
  255.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  256.     MapArray(X, Y) = MapArray(X, Y) OR 16
  257.     PathFind
  258.  
  259. SUB PathFind
  260.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  261.     DIM pass AS _UNSIGNED _BYTE
  262.     m = _MEM(Distance()): m1 = _MEM(Temp())
  263.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  264.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  265.     Temp(Hero.X, Hero.Y) = 0
  266.     pass = 0
  267.     DO
  268.         changed = 0
  269.         y = 0
  270.         DO
  271.             x = 0
  272.             DO
  273.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  274.                     IF x < XH THEN
  275.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  276.                     END IF
  277.                     IF x > 0 THEN
  278.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  279.                     END IF
  280.                     IF y < YH THEN
  281.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  282.                     END IF
  283.                     IF y > 0 THEN
  284.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  285.                     END IF
  286.                 END IF
  287.                 x = x + 1
  288.             LOOP UNTIL x > XH
  289.             y = y + 1
  290.         LOOP UNTIL y > YH
  291.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  292.         pass = pass + 1
  293.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  294.     Distance(Hero.X, Hero.Y) = 0

I think this is a much better way for everything to behave, and by using the + key to set your lightsource to an insane level now, you can see how the monster awareness is going to work.  If a bat has a hearing rating of 10, and you boost your lightsource up to a 10, you can see how close you'd have to get to the creature for it to notice and react to your hero.  ;)



Unless somebody sees an issue with something at this point in time, it seems as if it's about time to start adding a few random monsters into the game and then work on the basics of combat, experience, and leveling up.  :)
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 09, 2019, 01:25:39 pm
The game now generates monsters for us and places them randomly on the map so we can wander around and ....  do nothing at this point... with them.

Code: QB64: [Select]
  1. TYPE Damage_Type
  2.     Low AS _UNSIGNED _BYTE
  3.     High AS _UNSIGNED _BYTE
  4.  
  5. TYPE Light_Type
  6.     Name AS STRING * 20
  7.     Reach AS _UNSIGNED _BYTE
  8.     Left AS _UNSIGNED _BYTE
  9.  
  10. TYPE Weapon_Type
  11.     Name AS STRING * 20
  12.     Reach AS _UNSIGNED _BYTE
  13.     Damage AS Damage_Type
  14.     HitBonus AS _UNSIGNED _BYTE
  15.     DamageBonus AS _UNSIGNED _BYTE
  16.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  17.  
  18. TYPE Armor_Type
  19.     Name AS STRING * 20
  20.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  21.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  22.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  23.  
  24.  
  25.  
  26. TYPE Hero_Type
  27.     Name AS STRING * 20
  28.     Life AS Damage_Type
  29.     Level AS _UNSIGNED _BYTE
  30.     EXP_Earned AS LONG
  31.     EXP_Needed AS LONG
  32.     Light AS Light_Type
  33.     Weapon1 AS Weapon_Type
  34.     Weapon2 AS Weapon_Type
  35.     Armor AS Armor_Type
  36.  
  37. TYPE Map_Identifer_TYPE
  38.     Symbol AS _UNSIGNED _BYTE
  39.  
  40. TYPE Monster_TYPE
  41.     Name AS STRING * 20
  42.     Life AS Damage_Type
  43.     Level AS INTEGER
  44.     ExpBonus AS INTEGER
  45.     Sight AS INTEGER
  46.     Hearing AS INTEGER
  47.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  48.     Weapon1 AS Weapon_Type
  49.     Weapon2 AS Weapon_Type
  50.     Armor AS Armor_Type
  51.     ID AS Map_Identifer_TYPE
  52.  
  53. TYPE Encounter_TYPE
  54.     Active AS INTEGER
  55.     X AS INTEGER
  56.     Y AS INTEGER
  57.     M AS INTEGER
  58.  
  59. REDIM SHARED Monster(100) AS Monster_TYPE
  60. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  61.  
  62. DIM SHARED Hero AS Hero_Type
  63. REDIM SHARED Monster(100) AS Monster_TYPE
  64. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  65. DIM SHARED XL, XH, YL, YH
  66.  
  67.  
  68. SCREEN _NEWIMAGE(800, 700, 32)
  69. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  70. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  71.  
  72.  
  73. Init
  74. CreateMap 99, 74, 10, 1
  75.     CLS , &HFF0000FF
  76.     DrawMap
  77.     DisplayCharacter
  78.     _DISPLAY
  79.     GetInput
  80.  
  81. SUB DisplayCharacter
  82.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  83.     COLOR &HFFFFFFFF, 0
  84.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  85.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  86.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  87.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  88.  
  89.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  90.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  91.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  92.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  93.  
  94. SUB Init
  95.     Hero.Name = "Steve The Tester!"
  96.     Hero.Life.Low = 10
  97.     Hero.Life.High = 10
  98.     Hero.Level = 1
  99.     Hero.EXP_Earned = 0
  100.     Hero.EXP_Needed = 2
  101.     Hero.Light.Name = "Magic Candle"
  102.     Hero.Light.Reach = 2
  103.     Hero.Light.Left = -1 'infinite
  104.     Hero.Weapon1.Name = "Bare Fist"
  105.     Hero.Weapon1.Reach = 1
  106.     Hero.Weapon1.Damage.Low = 1
  107.     Hero.Weapon1.Damage.High = 2
  108.     Hero.Weapon1.HitBonus = 0
  109.     Hero.Weapon1.DamageBonus = 0
  110.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  111.     Hero.Weapon2.Name = "Magic Candle"
  112.     Hero.Weapon2.Reach = 0
  113.     Hero.Weapon2.Damage.Low = 0
  114.     Hero.Weapon2.Damage.High = 0
  115.     Hero.Weapon2.HitBonus = 0
  116.     Hero.Weapon2.DamageBonus = 0
  117.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  118.     Hero.Armor.Name = "Naked"
  119.     Hero.Armor.PD = 0
  120.     Hero.Armor.DR = 0
  121.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  122.  
  123. SUB GetInput
  124.     DO
  125.         k = _KEYHIT
  126.         SELECT CASE k
  127.             CASE 18432 'up
  128.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  129.                     IF MapArray(Hero.X, Hero.Y - 1) AND (4 OR 8) THEN 'and it's a room or passageway
  130.                         Hero.Y = Hero.Y - 1
  131.                         PathFind
  132.                     END IF
  133.                 END IF
  134.             CASE 19200 'left
  135.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  136.                     IF MapArray(Hero.X - 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  137.                         Hero.X = Hero.X - 1
  138.                         PathFind
  139.                     END IF
  140.                 END IF
  141.             CASE 20480 'down
  142.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  143.                     IF MapArray(Hero.X, Hero.Y + 1) AND (4 OR 8) THEN 'and it's a room or passageway
  144.                         Hero.Y = Hero.Y + 1
  145.                         PathFind
  146.                     END IF
  147.                 END IF
  148.             CASE 19712 'right
  149.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  150.                     IF MapArray(Hero.X + 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  151.                         Hero.X = Hero.X + 1
  152.                         PathFind
  153.                     END IF
  154.                 END IF
  155.             CASE 32 'space to just wait and skip a turn
  156.             CASE 60 ' "<" key
  157.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  158.                     Level = Level + 1
  159.                     CreateMap 99, 74, 10, Level
  160.                     PathFind
  161.                 END IF
  162.             CASE ASC("+"), ASC("=")
  163.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  164.             CASE ASC("-"), ASC("_")
  165.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  166.         END SELECT
  167.         _LIMIT 60
  168.     LOOP UNTIL k
  169.     _KEYCLEAR 'one keystroke at a time
  170.  
  171. SUB Illuminate (Range)
  172.     FOR X = 0 TO XH
  173.         FOR Y = 0 TO YH
  174.             IF Distance(X, Y) <= Range THEN 'It's close enough to check for illumination
  175.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  176.             END IF
  177.         NEXT
  178.     NEXT
  179.  
  180.  
  181. SUB DrawMap
  182.     Illuminate Hero.Light.Reach
  183.     FOR Y = 0 TO YH
  184.         FOR X = 0 TO XH
  185.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  186.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  187.                     COLOR &HFF000000, 0
  188.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  189.                 END IF
  190.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  191.                     COLOR &HFF000000, &HFF777777
  192.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  193.                 END IF
  194.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  195.                     COLOR &HFF00FF00, &HFFFFFF00
  196.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  197.                 END IF
  198.             END IF
  199.             'note: highlighting for the light should come AFTER the map is drawn
  200.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  201.                 COLOR &H40FFFF00, 0
  202.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  203.                 MapArray(X, Y) = MapArray(X, Y) - 1
  204.                 FOR i = 1 TO EncounterLimit
  205.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  206.                         COLOR Monster(Encounter(i).M).ID.Color
  207.                         t$ = CHR$(Monster(Encounter(i).M).ID.Symbol)
  208.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), t$
  209.                     END IF
  210.                     Encounter(i).M = RandomMonster
  211.                 NEXT
  212.  
  213.             END IF
  214.         NEXT
  215.     NEXT
  216.     COLOR &HFFFFFF00, 0 'Yellow Hero
  217.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  218.  
  219.  
  220.  
  221.  
  222.  
  223. SUB CreateMap (XLimit, YLimit, Rooms, Level)
  224.     ERASE MapArray 'clear the old map and reset everything to 0
  225.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  226.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  227.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  228.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  229.  
  230.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  231.  
  232.     FOR i = 1 TO Rooms
  233.         DO
  234.             RoomSize = INT(RND * 9) + 2
  235.             RoomX = INT(RND * (XLimit - RoomSize))
  236.             RoomY = INT(RND * (YLimit - RoomSize))
  237.             'test for positioning
  238.             good = -1 'it's good starting out
  239.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  240.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  241.             NEXT X, Y
  242.         LOOP UNTIL good
  243.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  244.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  245.         NEXT X, Y
  246.         RoomCenterX(i) = RoomX + .5 * RoomSize
  247.         RoomCenterY(i) = RoomY + .5 * RoomSize
  248.     NEXT
  249.     FOR i = 1 TO Rooms - 1
  250.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  251.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  252.         DO UNTIL StartX = EndX AND StartY = EndY
  253.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  254.             Meander = 10
  255.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  256.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  257.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  258.                     Ychange = 0
  259.                 ELSE
  260.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  261.                     Ychange = 0
  262.                 END IF
  263.             ELSE
  264.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  265.                     Ychange = SGN(EndY - StartY)
  266.                     XChange = 0
  267.                 ELSE
  268.                     Ychange = INT(RND * 3) - 1
  269.                     XChange = 0
  270.                 END IF
  271.             END IF
  272.             StartX = StartX + XChange
  273.             StartY = StartY + Ychange
  274.             IF StartX < 0 THEN StartX = 0
  275.             IF StartY < 0 THEN StartY = 0
  276.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  277.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  278.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  279.         LOOP
  280.     NEXT
  281.     DO
  282.         Hero.X = INT(RND * XLimit + 1)
  283.         Hero.Y = INT(RND * YLimit + 1)
  284.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  285.     DO
  286.         X = INT(RND * XLimit + 1)
  287.         Y = INT(RND * YLimit + 1)
  288.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  289.     MapArray(X, Y) = MapArray(X, Y) OR 16
  290.     PathFind
  291.     EncounterLimit = INT(RND * 6) + 5
  292.     FOR i = 1 TO EncounterLimit
  293.         Encounter(i).M = RandomMonster
  294.         Encounter(i).Active = -1
  295.         DO
  296.             Encounter(i).X = INT(RND * XLimit + 1)
  297.             Encounter(i).Y = INT(RND * YLimit + 1)
  298.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 'get a random spot in a room, for the stairs to the next level
  299.     NEXT
  300.  
  301. SUB PathFind
  302.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  303.     DIM pass AS _UNSIGNED _BYTE
  304.     m = _MEM(Distance()): m1 = _MEM(Temp())
  305.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  306.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  307.     Temp(Hero.X, Hero.Y) = 0
  308.     pass = 0
  309.     DO
  310.         changed = 0
  311.         y = 0
  312.         DO
  313.             x = 0
  314.             DO
  315.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  316.                     IF x < XH THEN
  317.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  318.                     END IF
  319.                     IF x > 0 THEN
  320.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  321.                     END IF
  322.                     IF y < YH THEN
  323.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  324.                     END IF
  325.                     IF y > 0 THEN
  326.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  327.                     END IF
  328.                 END IF
  329.                 x = x + 1
  330.             LOOP UNTIL x > XH
  331.             y = y + 1
  332.         LOOP UNTIL y > YH
  333.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  334.         pass = pass + 1
  335.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  336.     Distance(Hero.X, Hero.Y) = 0
  337.  
  338. FUNCTION RandomMonster
  339.     'Shared variable level tells us what level of the dungeon we're on.
  340.     STATIC MC, DS 'monster count and data set
  341.     IF NOT DS THEN
  342.         DS = -1
  343.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  344.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  345.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  346.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  347.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  348.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  349.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  350.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  351.         'Monster(1).Armor.Name = ""
  352.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  353.  
  354.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  355.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  356.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  357.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  358.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  359.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  360.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  361.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  362.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  363.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  364.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  365.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  366.         NEXT
  367.     END IF
  368.     SELECT CASE Level 'the starting level
  369.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  370.     END SELECT
  371.     RandomMonster = INT(RND * MC) + 1

Play around with the lightsource a little, and you can really see how much it'll affect game play for us in the future, once things start coming all together.  The monsters are there all the time (unless you defeat them, which is impossible at the moment with no combat implemented yet), but you can now move around and see how they pop on and off our screen, as they come into and out of our light's range.  I have a feeling, strong light sources might end up becoming a treasured resource in this game -- after all, the sooner you see a monster, the sooner you can react to it.  (Shoot it with a bow from a range; zap it with magic from a distance...  If you can't see it, you're just shooting blindly into the dark!)

Currently, all monsters appear as a red question mark on the map, but that's intentional.  You're going to need to encounter and fight a monster at least once, before YOU get to assign the color and symbol you want to use to represent it on your map.  The idea I have here is that the more you progress, and the more you explore in the dungeon, the less you encounter those mysterious and dreaded question marks -- and it allows you to create a custom symbol set for each game, so you can create one which seems familiar, comfortable, and easy to remember/interact with for YOU.

Progress seems to be coming along rather nicely to this point.  Now I just need to add the very basics of combat to the game, and then I'll have the "core" of my rogue-like set, where all I'll do from that point on is flesh it out with extra weapons/armor/gear/minor mechanics...
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 11, 2019, 02:00:44 am
Code: QB64: [Select]
  1. TYPE Damage_Type
  2.     Low AS _UNSIGNED _BYTE
  3.     High AS _UNSIGNED _BYTE
  4.  
  5. TYPE Light_Type
  6.     Name AS STRING * 20
  7.     Reach AS _UNSIGNED _BYTE
  8.     Left AS _UNSIGNED _BYTE
  9.  
  10. TYPE Weapon_Type
  11.     Name AS STRING * 20
  12.     Reach AS _UNSIGNED _BYTE
  13.     Damage AS Damage_Type
  14.     HitBonus AS _UNSIGNED _BYTE
  15.     DamageBonus AS _UNSIGNED _BYTE
  16.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  17.  
  18. TYPE Armor_Type
  19.     Name AS STRING * 20
  20.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  21.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  22.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  23.  
  24.  
  25.  
  26. TYPE Hero_Type
  27.     Name AS STRING * 20
  28.     Life AS Damage_Type
  29.     Level AS _UNSIGNED _BYTE
  30.     EXP_Earned AS LONG
  31.     EXP_Needed AS LONG
  32.     Light AS Light_Type
  33.     Weapon1 AS Weapon_Type
  34.     Weapon2 AS Weapon_Type
  35.     Armor AS Armor_Type
  36.  
  37. TYPE Map_Identifer_TYPE
  38.     Symbol AS _UNSIGNED _BYTE
  39.  
  40. TYPE Monster_TYPE
  41.     Name AS STRING * 20
  42.     Life AS Damage_Type
  43.     Level AS INTEGER
  44.     ExpBonus AS INTEGER
  45.     Sight AS INTEGER
  46.     Hearing AS INTEGER
  47.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  48.     Weapon1 AS Weapon_Type
  49.     Weapon2 AS Weapon_Type
  50.     Armor AS Armor_Type
  51.     ID AS Map_Identifer_TYPE
  52.  
  53. TYPE Encounter_TYPE
  54.     Active AS INTEGER
  55.     X AS INTEGER
  56.     Y AS INTEGER
  57.     M AS INTEGER
  58.     Life AS INTEGER
  59.  
  60. REDIM SHARED Monster(100) AS Monster_TYPE
  61. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  62.  
  63. DIM SHARED Hero AS Hero_Type
  64. REDIM SHARED Monster(100) AS Monster_TYPE
  65. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  66. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  67.  
  68.  
  69. SCREEN _NEWIMAGE(800, 700, 32)
  70. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  71. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  72.  
  73.  
  74. Init
  75. CreateMap 99, 74, 10
  76.     CLS , &HFF0000FF
  77.     DrawMap
  78.     DisplayCharacter
  79.     _DISPLAY
  80.     GetInput
  81.     MonstersTurn
  82.  
  83. SUB DisplayCharacter
  84.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  85.     COLOR &HFFFFFFFF, 0
  86.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  87.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  88.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  89.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  90.  
  91.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  92.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  93.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  94.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  95.  
  96. SUB Init
  97.     Hero.Name = "Steve The Tester!"
  98.     Hero.Life.Low = 10
  99.     Hero.Life.High = 10
  100.     Hero.Level = 1
  101.     Hero.EXP_Earned = 0
  102.     Hero.EXP_Needed = 2
  103.     Hero.Light.Name = "Magic Candle"
  104.     Hero.Light.Reach = 2
  105.     Hero.Light.Left = -1 'infinite
  106.     Hero.Weapon1.Name = "Bare Fist"
  107.     Hero.Weapon1.Reach = 1
  108.     Hero.Weapon1.Damage.Low = 1
  109.     Hero.Weapon1.Damage.High = 2
  110.     Hero.Weapon1.HitBonus = 0
  111.     Hero.Weapon1.DamageBonus = 0
  112.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  113.     Hero.Weapon2.Name = "Magic Candle"
  114.     Hero.Weapon2.Reach = 0
  115.     Hero.Weapon2.Damage.Low = 0
  116.     Hero.Weapon2.Damage.High = 0
  117.     Hero.Weapon2.HitBonus = 0
  118.     Hero.Weapon2.DamageBonus = 0
  119.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  120.     Hero.Armor.Name = "Naked"
  121.     Hero.Armor.PD = 0
  122.     Hero.Armor.DR = 0
  123.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  124.  
  125. SUB GetInput
  126.     DO
  127.         k = _KEYHIT
  128.         SELECT CASE k
  129.             CASE 18432 'up
  130.                 IF Hero.Y > LBOUND(maparray, 2) THEN 'if we can move up
  131.                     IF MapArray(Hero.X, Hero.Y - 1) AND (4 OR 8) THEN 'and it's a room or passageway
  132.                         Hero.Y = Hero.Y - 1
  133.                         PathFind
  134.                     END IF
  135.                 END IF
  136.             CASE 19200 'left
  137.                 IF Hero.X > LBOUND(maparray, 1) THEN 'if we can move right
  138.                     IF MapArray(Hero.X - 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  139.                         Hero.X = Hero.X - 1
  140.                         PathFind
  141.                     END IF
  142.                 END IF
  143.             CASE 20480 'down
  144.                 IF Hero.Y < UBOUND(maparray, 2) THEN 'if we can move down
  145.                     IF MapArray(Hero.X, Hero.Y + 1) AND (4 OR 8) THEN 'and it's a room or passageway
  146.                         Hero.Y = Hero.Y + 1
  147.                         PathFind
  148.                     END IF
  149.                 END IF
  150.             CASE 19712 'right
  151.                 IF Hero.X < UBOUND(maparray, 1) THEN 'if we can move right
  152.                     IF MapArray(Hero.X + 1, Hero.Y) AND (4 OR 8) THEN 'and it's a room or passageway
  153.                         Hero.X = Hero.X + 1
  154.                         PathFind
  155.                     END IF
  156.                 END IF
  157.             CASE 32 'space to just wait and skip a turn
  158.             CASE 60 ' "<" key
  159.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  160.                     Level = Level + 1
  161.                     CreateMap 99, 74, 10
  162.                     PathFind
  163.                 END IF
  164.             CASE ASC("+"), ASC("=")
  165.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  166.             CASE ASC("-"), ASC("_")
  167.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  168.         END SELECT
  169.         _LIMIT 60
  170.     LOOP UNTIL k > 0
  171.     _KEYCLEAR 'one keystroke at a time
  172.  
  173. SUB MonstersTurn
  174.     '    Sight AS INTEGER
  175.     '    Hearing AS INTEGER
  176.     '    Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  177.  
  178.  
  179.  
  180.     FOR i = 1 TO EncounterLimit
  181.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  182.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  183.             D = Distance(MX, MY) 'distance from monster to the hero
  184.             E = Encounter(i).M 'the actual monster in question
  185.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  186.  
  187.                 attack = 0
  188.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  189.                     'insert attack code here
  190.  
  191.                     _TITLE "ATTACK!"
  192.                     _CONTINUE
  193.                 END IF
  194.                 IF D <= Monster(E).Weapon2.Reach THEN 'we're in reach for the monster to attack with their off hand.
  195.                     'insert attack code here
  196.                     _CONTINUE
  197.                 END IF
  198.  
  199.                 IF attack = 0 THEN 'if the monster didn't attack, it can now move towards the hero.
  200.                     IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  201.                         IF D > Distance(MX - 1, MY) THEN
  202.                             Encounter(i).X = Encounter(i).X - 1 'move left
  203.                             _CONTINUE
  204.                         END IF
  205.                     END IF
  206.                     IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  207.                         IF D > Distance(MX, MY - 1) THEN
  208.                             Encounter(i).Y = Encounter(i).Y - 1 'move up
  209.                             _CONTINUE
  210.                         END IF
  211.                     END IF
  212.                     IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  213.                         IF D > Distance(MX + 1, MY) THEN
  214.                             Encounter(i).X = Encounter(i).X + 1 'move right
  215.                             _CONTINUE
  216.                         END IF
  217.                     END IF
  218.                     IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  219.                         IF D > Distance(MX, MY + 1) THEN
  220.                             Encounter(i).Y = Encounter(i).Y + 1 'move down
  221.                             _CONTINUE
  222.                         END IF
  223.                     END IF
  224.                 END IF
  225.  
  226.  
  227.             END IF
  228.         END IF
  229.  
  230.     NEXT
  231.  
  232.  
  233.  
  234.  
  235. SUB Illuminate (Range)
  236.     FOR X = 0 TO XH
  237.         FOR Y = 0 TO YH
  238.             IF Distance(X, Y) <= Range THEN 'It's close enough to check for illumination
  239.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  240.             END IF
  241.         NEXT
  242.     NEXT
  243.  
  244.  
  245. SUB DrawMap
  246.     Illuminate Hero.Light.Reach
  247.     FOR Y = 0 TO YH
  248.         FOR X = 0 TO XH
  249.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  250.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  251.                     COLOR &HFF000000, 0
  252.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  253.                 END IF
  254.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  255.                     COLOR &HFF000000, &HFF777777
  256.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  257.                 END IF
  258.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  259.                     COLOR &HFF00FF00, &HFFFFFF00
  260.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  261.                 END IF
  262.             END IF
  263.             'note: highlighting for the light should come AFTER the map is drawn
  264.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  265.                 COLOR &H40FFFF00, 0
  266.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  267.                 MapArray(X, Y) = MapArray(X, Y) - 1
  268.                 FOR i = 1 TO EncounterLimit
  269.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  270.                         COLOR Monster(Encounter(i).M).ID.Color
  271.                         t$ = CHR$(Monster(Encounter(i).M).ID.Symbol)
  272.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), t$
  273.                     END IF
  274.                     Encounter(i).M = RandomMonster
  275.                 NEXT
  276.  
  277.             END IF
  278.         NEXT
  279.     NEXT
  280.     COLOR &HFFFFFF00, 0 'Yellow Hero
  281.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  282.  
  283.  
  284.  
  285.  
  286.  
  287. SUB CreateMap (XLimit, YLimit, Rooms)
  288.     ERASE MapArray 'clear the old map and reset everything to 0
  289.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  290.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  291.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  292.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  293.  
  294.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  295.  
  296.     FOR i = 1 TO Rooms
  297.         DO
  298.             RoomSize = INT(RND * 9) + 2
  299.             RoomX = INT(RND * (XLimit - RoomSize))
  300.             RoomY = INT(RND * (YLimit - RoomSize))
  301.             'test for positioning
  302.             good = -1 'it's good starting out
  303.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  304.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  305.             NEXT X, Y
  306.         LOOP UNTIL good
  307.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  308.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  309.         NEXT X, Y
  310.         RoomCenterX(i) = RoomX + .5 * RoomSize
  311.         RoomCenterY(i) = RoomY + .5 * RoomSize
  312.     NEXT
  313.     FOR i = 1 TO Rooms - 1
  314.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  315.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  316.         DO UNTIL StartX = EndX AND StartY = EndY
  317.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  318.             Meander = 10
  319.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  320.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  321.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  322.                     Ychange = 0
  323.                 ELSE
  324.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  325.                     Ychange = 0
  326.                 END IF
  327.             ELSE
  328.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  329.                     Ychange = SGN(EndY - StartY)
  330.                     XChange = 0
  331.                 ELSE
  332.                     Ychange = INT(RND * 3) - 1
  333.                     XChange = 0
  334.                 END IF
  335.             END IF
  336.             StartX = StartX + XChange
  337.             StartY = StartY + Ychange
  338.             IF StartX < 0 THEN StartX = 0
  339.             IF StartY < 0 THEN StartY = 0
  340.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  341.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  342.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  343.         LOOP
  344.     NEXT
  345.     DO
  346.         Hero.X = INT(RND * XLimit + 1)
  347.         Hero.Y = INT(RND * YLimit + 1)
  348.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  349.     DO
  350.         X = INT(RND * XLimit + 1)
  351.         Y = INT(RND * YLimit + 1)
  352.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  353.     MapArray(X, Y) = MapArray(X, Y) OR 16
  354.     PathFind
  355.     EncounterLimit = INT(RND * 6) + 5
  356.     FOR i = 1 TO EncounterLimit
  357.         Encounter(i).M = RandomMonster
  358.         Encounter(i).Active = -1
  359.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  360.         DO
  361.             Encounter(i).X = INT(RND * XLimit + 1)
  362.             Encounter(i).Y = INT(RND * YLimit + 1)
  363.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 'get a random spot in a room, for the stairs to the next level
  364.     NEXT
  365.  
  366. SUB PathFind
  367.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  368.     DIM pass AS _UNSIGNED _BYTE
  369.     m = _MEM(Distance()): m1 = _MEM(Temp())
  370.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  371.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  372.     Temp(Hero.X, Hero.Y) = 0
  373.     pass = 0
  374.     DO
  375.         changed = 0
  376.         y = 0
  377.         DO
  378.             x = 0
  379.             DO
  380.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  381.                     IF x < XH THEN
  382.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  383.                     END IF
  384.                     IF x > 0 THEN
  385.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  386.                     END IF
  387.                     IF y < YH THEN
  388.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  389.                     END IF
  390.                     IF y > 0 THEN
  391.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  392.                     END IF
  393.                 END IF
  394.                 x = x + 1
  395.             LOOP UNTIL x > XH
  396.             y = y + 1
  397.         LOOP UNTIL y > YH
  398.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  399.         pass = pass + 1
  400.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  401.     Distance(Hero.X, Hero.Y) = 0
  402.  
  403. FUNCTION RandomMonster
  404.     'Shared variable level tells us what level of the dungeon we're on.
  405.     STATIC MC, DS 'monster count and data set
  406.     IF NOT DS THEN
  407.         DS = -1
  408.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  409.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  410.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  411.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  412.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  413.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  414.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  415.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  416.         'Monster(1).Armor.Name = ""
  417.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  418.  
  419.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  420.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  421.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  422.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  423.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  424.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  425.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  426.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  427.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  428.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  429.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  430.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  431.         NEXT
  432.     END IF
  433.     SELECT CASE Level 'the starting level
  434.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  435.     END SELECT
  436.     RandomMonster = INT(RND * MC) + 1
  437.  

Our monsters are now detecting the hero and responding to him.  At the moment, they do nothing but move up and stand close enough to the hero to attack him, and they do absolutely nothing else...  After all, I still haven't added any actual combat routines in the game yet!  :P

Currently monsters will stack on top of each other, as they're completely unaware of each other's presence, but I plan on correcting that with the next update.  Movement should be limited to open tiles only; not ones where another creature is currently standing up; but I'm happy with what I've got here so far.  From what I can tell (sans combat), monster movement seems very similar to what we used to see in the actual game Rogue, waaay back in the day.  I still remember being beat up and almost dying, and then having to run backwards to take time to heal, all while hoping I didn't run into a second monster while the first one was still right on my heels....

:D
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 11, 2019, 11:05:58 am
And the monsters now block each other and the hero's movement.  (As well as the hero blocking the monster's movement.)  At this point, there's still no combat, but you can have fun challenging yourself to a simple little game of, "How many levels can I run through before the monsters catch me and trap me in a corner...)  :P

Code: QB64: [Select]
  1. _CONSOLE ON 'for debugging purposes while making/testing things
  2.  
  3. TYPE Damage_Type
  4.     Low AS _UNSIGNED _BYTE
  5.     High AS _UNSIGNED _BYTE
  6.  
  7. TYPE Light_Type
  8.     Name AS STRING * 20
  9.     Reach AS _UNSIGNED _BYTE
  10.     Left AS _UNSIGNED _BYTE
  11.  
  12. TYPE Weapon_Type
  13.     Name AS STRING * 20
  14.     Reach AS _UNSIGNED _BYTE
  15.     Damage AS Damage_Type
  16.     HitBonus AS _UNSIGNED _BYTE
  17.     DamageBonus AS _UNSIGNED _BYTE
  18.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  19.  
  20. TYPE Armor_Type
  21.     Name AS STRING * 20
  22.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  23.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  24.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  25.  
  26.  
  27.  
  28. TYPE Hero_Type
  29.     Name AS STRING * 20
  30.     Life AS Damage_Type
  31.     Level AS _UNSIGNED _BYTE
  32.     EXP_Earned AS LONG
  33.     EXP_Needed AS LONG
  34.     Light AS Light_Type
  35.     Weapon1 AS Weapon_Type
  36.     Weapon2 AS Weapon_Type
  37.     Armor AS Armor_Type
  38.  
  39. TYPE Map_Identifer_TYPE
  40.     Symbol AS _UNSIGNED _BYTE
  41.  
  42. TYPE Monster_TYPE
  43.     Name AS STRING * 20
  44.     Life AS Damage_Type
  45.     Level AS INTEGER
  46.     ExpBonus AS INTEGER
  47.     Sight AS INTEGER
  48.     Hearing AS INTEGER
  49.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  50.     Weapon1 AS Weapon_Type
  51.     Weapon2 AS Weapon_Type
  52.     Armor AS Armor_Type
  53.     ID AS Map_Identifer_TYPE
  54.  
  55. TYPE Encounter_TYPE
  56.     Active AS INTEGER
  57.     X AS INTEGER
  58.     Y AS INTEGER
  59.     M AS INTEGER
  60.     Life AS INTEGER
  61.  
  62. REDIM SHARED Monster(100) AS Monster_TYPE
  63. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  64.  
  65. DIM SHARED Hero AS Hero_Type
  66. REDIM SHARED Monster(100) AS Monster_TYPE
  67. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  68. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  69.  
  70.  
  71. SCREEN _NEWIMAGE(800, 700, 32)
  72. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  73. '1 map is illuminated
  74. '2 map is uncovered
  75. '4 map is a wall
  76. '8 map is a pathway
  77. '16 map is a stairway
  78. '32 map is simply blocked (perhaps with a monster?)
  79. '64 map is secret (can not be uncovered)
  80.  
  81. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  82.  
  83.  
  84. Init
  85. CreateMap 99, 74, 10
  86.     CLS , &HFF0000FF
  87.     DrawMap
  88.     DisplayCharacter
  89.     _DISPLAY
  90.     GetInput
  91.     MonstersTurn
  92.  
  93. SUB DisplayCharacter
  94.     LINE (0, 601)-(_WIDTH - 1, _HEIGHT - 1), &HFF000000, BF
  95.     COLOR &HFFFFFFFF, 0
  96.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  97.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  98.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  99.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  100.  
  101.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  102.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  103.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  104.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  105.  
  106. SUB Init
  107.     Hero.Name = "Steve The Tester!"
  108.     Hero.Life.Low = 10
  109.     Hero.Life.High = 10
  110.     Hero.Level = 1
  111.     Hero.EXP_Earned = 0
  112.     Hero.EXP_Needed = 2
  113.     Hero.Light.Name = "Magic Candle"
  114.     Hero.Light.Reach = 2
  115.     Hero.Light.Left = -1 'infinite
  116.     Hero.Weapon1.Name = "Bare Fist"
  117.     Hero.Weapon1.Reach = 1
  118.     Hero.Weapon1.Damage.Low = 1
  119.     Hero.Weapon1.Damage.High = 2
  120.     Hero.Weapon1.HitBonus = 0
  121.     Hero.Weapon1.DamageBonus = 0
  122.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  123.     Hero.Weapon2.Name = "Magic Candle"
  124.     Hero.Weapon2.Reach = 0
  125.     Hero.Weapon2.Damage.Low = 0
  126.     Hero.Weapon2.Damage.High = 0
  127.     Hero.Weapon2.HitBonus = 0
  128.     Hero.Weapon2.DamageBonus = 0
  129.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  130.     Hero.Armor.Name = "Naked"
  131.     Hero.Armor.PD = 0
  132.     Hero.Armor.DR = 0
  133.     Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  134.  
  135. SUB GetInput
  136.     DO
  137.         k = _KEYHIT: valid = -1
  138.         SELECT CASE k
  139.             CASE 18432: IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  140.             CASE 19200: IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  141.             CASE 20480: IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  142.             CASE 19712: IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  143.             CASE 32 'space to just wait and skip a turn
  144.             CASE 60 ' "<" key
  145.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  146.                     Level = Level + 1
  147.                     CreateMap 99, 74, 10
  148.                     PathFind
  149.                 END IF
  150.             CASE ASC("+"), ASC("=")
  151.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  152.             CASE ASC("-"), ASC("_")
  153.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  154.             CASE ELSE
  155.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  156.         END SELECT
  157.         _LIMIT 60
  158.     LOOP UNTIL k AND valid
  159.     _KEYCLEAR 'one keystroke at a time
  160.  
  161. SUB MoveHero (MoveX, MoveY)
  162.     IF MapArray(Hero.X + MoveX, Hero.Y + MoveY) AND (4 OR 8) THEN 'and it's a room or passageway
  163.         IF (MapArray(Hero.X + MoveX, Hero.Y + MoveY) AND 32) = 0 THEN 'and it's not blocked for some reason
  164.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  165.             IF MoveX THEN Hero.X = Hero.X + MoveX
  166.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  167.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  168.             PathFind
  169.         END IF
  170.     END IF
  171.  
  172.  
  173. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  174.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  175.     D = Distance(MX, MY) 'distance from monster to the hero
  176.     E = Encounter(i).M 'the actual monster in question
  177.  
  178.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  179.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  180.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  181.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  182.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  183.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  184.             MoveMonster = -1
  185.         END IF
  186.     END IF
  187.  
  188.  
  189.  
  190. SUB MonstersTurn
  191.     FOR i = 1 TO EncounterLimit
  192.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  193.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  194.             D = Distance(MX, MY) 'distance from monster to the hero
  195.             E = Encounter(i).M 'the actual monster in question
  196.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  197.  
  198.                 attack = 0
  199.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  200.                     'insert attack code here
  201.  
  202.                     _TITLE "ATTACK!"
  203.                     _CONTINUE
  204.                 END IF
  205.                 IF D <= Monster(E).Weapon2.Reach THEN 'we're in reach for the monster to attack with their off hand.
  206.                     'insert attack code here
  207.                     _CONTINUE
  208.                 END IF
  209.  
  210.                 IF attack = 0 THEN 'if the monster didn't attack, it can now move towards the hero.
  211.                     IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  212.                         IF D > Distance(MX - 1, MY) THEN
  213.                             IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  214.                         END IF
  215.                     END IF
  216.                     IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  217.                         IF D > Distance(MX, MY - 1) THEN
  218.                             IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  219.                         END IF
  220.                     END IF
  221.                     IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  222.                         IF D > Distance(MX + 1, MY) THEN
  223.                             IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  224.                         END IF
  225.                     END IF
  226.                     IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  227.                         IF D > Distance(MX, MY + 1) THEN
  228.                             IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  229.                         END IF
  230.                     END IF
  231.                 END IF
  232.             END IF
  233.         END IF
  234.  
  235.     NEXT
  236.  
  237.  
  238.  
  239.  
  240. SUB DrawMap
  241.     FOR Y = 0 TO YH
  242.         FOR X = 0 TO XH
  243.             IF Distance(X, Y) <= Hero.Light.Reach THEN 'It's close enough to check for illumination
  244.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  245.             END IF
  246.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  247.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  248.                     COLOR &HFF000000, 0
  249.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  250.                 END IF
  251.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  252.                     COLOR &HFF000000, &HFF777777
  253.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  254.                 END IF
  255.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  256.                     COLOR &HFF00FF00, &HFFFFFF00
  257.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  258.                 END IF
  259.             END IF
  260.             'note: highlighting for the light should come AFTER the map is drawn
  261.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  262.                 COLOR &H40FFFF00, 0
  263.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  264.                 MapArray(X, Y) = MapArray(X, Y) - 1
  265.                 FOR i = 1 TO EncounterLimit
  266.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  267.                         E = Encounter(i).M
  268.                         COLOR Monster(E).ID.Color
  269.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(Monster(E).ID.Symbol)
  270.                     END IF
  271.                 NEXT
  272.  
  273.             END IF
  274.         NEXT
  275.     NEXT
  276.     COLOR &HFFFFFF00, 0 'Yellow Hero
  277.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  278.  
  279.  
  280.  
  281.  
  282.  
  283. SUB CreateMap (XLimit, YLimit, Rooms)
  284.     ERASE MapArray 'clear the old map and reset everything to 0
  285.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  286.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  287.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  288.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  289.  
  290.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  291.  
  292.     FOR i = 1 TO Rooms
  293.         DO
  294.             RoomSize = INT(RND * 9) + 2
  295.             RoomX = INT(RND * (XLimit - RoomSize))
  296.             RoomY = INT(RND * (YLimit - RoomSize))
  297.             'test for positioning
  298.             good = -1 'it's good starting out
  299.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  300.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  301.             NEXT X, Y
  302.         LOOP UNTIL good
  303.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  304.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  305.         NEXT X, Y
  306.         RoomCenterX(i) = RoomX + .5 * RoomSize
  307.         RoomCenterY(i) = RoomY + .5 * RoomSize
  308.     NEXT
  309.     FOR i = 1 TO Rooms - 1
  310.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  311.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  312.         DO UNTIL StartX = EndX AND StartY = EndY
  313.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  314.             Meander = 10
  315.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  316.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  317.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  318.                     Ychange = 0
  319.                 ELSE
  320.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  321.                     Ychange = 0
  322.                 END IF
  323.             ELSE
  324.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  325.                     Ychange = SGN(EndY - StartY)
  326.                     XChange = 0
  327.                 ELSE
  328.                     Ychange = INT(RND * 3) - 1
  329.                     XChange = 0
  330.                 END IF
  331.             END IF
  332.             StartX = StartX + XChange
  333.             StartY = StartY + Ychange
  334.             IF StartX < 0 THEN StartX = 0
  335.             IF StartY < 0 THEN StartY = 0
  336.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  337.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  338.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  339.         LOOP
  340.     NEXT
  341.     DO
  342.         Hero.X = INT(RND * XLimit + 1)
  343.         Hero.Y = INT(RND * YLimit + 1)
  344.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  345.     MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  346.     DO
  347.         X = INT(RND * XLimit + 1)
  348.         Y = INT(RND * YLimit + 1)
  349.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  350.     MapArray(X, Y) = MapArray(X, Y) OR 16
  351.     PathFind
  352.     EncounterLimit = INT(RND * 6) + 5
  353.     FOR i = 1 TO EncounterLimit
  354.         Encounter(i).M = RandomMonster
  355.         Encounter(i).Active = -1
  356.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  357.         valid = -1
  358.         DO
  359.             Encounter(i).X = INT(RND * XLimit + 1)
  360.             Encounter(i).Y = INT(RND * YLimit + 1)
  361.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  362.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  363.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  364.     NEXT
  365.  
  366. SUB PathFind
  367.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  368.     DIM pass AS _UNSIGNED _BYTE
  369.     m = _MEM(Distance()): m1 = _MEM(Temp())
  370.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  371.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  372.     Temp(Hero.X, Hero.Y) = 0
  373.     pass = 0
  374.     DO
  375.         changed = 0
  376.         y = 0
  377.         DO
  378.             x = 0
  379.             DO
  380.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  381.                     IF x < XH THEN
  382.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  383.                     END IF
  384.                     IF x > 0 THEN
  385.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  386.                     END IF
  387.                     IF y < YH THEN
  388.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  389.                     END IF
  390.                     IF y > 0 THEN
  391.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  392.                     END IF
  393.                 END IF
  394.                 x = x + 1
  395.             LOOP UNTIL x > XH
  396.             y = y + 1
  397.         LOOP UNTIL y > YH
  398.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  399.         pass = pass + 1
  400.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  401.     Distance(Hero.X, Hero.Y) = 0
  402.  
  403. FUNCTION RandomMonster
  404.     'Shared variable level tells us what level of the dungeon we're on.
  405.     STATIC MC, DS 'monster count and data set
  406.     IF NOT DS THEN
  407.         DS = -1
  408.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  409.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  410.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  411.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  412.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  413.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  414.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  415.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  416.         'Monster(1).Armor.Name = ""
  417.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  418.  
  419.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  420.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  421.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  422.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  423.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  424.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  425.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  426.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  427.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  428.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  429.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  430.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  431.         NEXT
  432.     END IF
  433.     SELECT CASE Level 'the starting level
  434.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  435.     END SELECT
  436.     RandomMonster = INT(RND * MC) + 1

To still be coming in at less than 500 lines total, I'm quite impressed with what all this little code is actually doing so far.  It's got lighting, navigation, trivial monster AI, random level generation, and basic collision detection.  Basic combat should be the next element I add into the game, so expect to see us being able to actually kill those mysterious monsters soon(tm)!  (Or be killed by them...)
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 11, 2019, 02:39:38 pm
Our hero can now attack, but the monsters can't attack back. Soon(tm) they shall though!

Code: QB64: [Select]
  1. TYPE TextArea
  2.     InUse AS INTEGER
  3.     x1 AS LONG 'left
  4.     y1 AS LONG 'top
  5.     w AS LONG 'width
  6.     h AS LONG 'height
  7.     FrameColor AS _UNSIGNED LONG
  8.     BackColor AS _UNSIGNED LONG
  9.     Xpos AS INTEGER
  10.     Ypos AS INTEGER
  11.     VerticalAlignment AS INTEGER
  12.     Justification AS INTEGER
  13.     UpdateMethod AS INTEGER
  14.     TextColor AS _UNSIGNED LONG
  15.     TextBackgroundColor AS _UNSIGNED LONG
  16.     SavedBackground AS INTEGER
  17.     HideFrame AS INTEGER
  18.     ScreenX AS INTEGER
  19.     ScreenY AS INTEGER
  20.  
  21. REDIM SHARED TextHandles(0) AS TextArea
  22.  
  23. CONST True = -1, False = 0
  24. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  25. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  26. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  27. '********************************************************
  28. '* Text Frames before this line
  29. '********************************************************
  30.  
  31.  
  32.  
  33.  
  34.  
  35.  
  36. _CONSOLE ON 'for debugging purposes while making/testing things
  37.  
  38. TYPE Damage_Type
  39.     Low AS INTEGER
  40.     High AS INTEGER
  41.  
  42. TYPE Light_Type
  43.     Name AS STRING * 20
  44.     Reach AS _UNSIGNED _BYTE
  45.     Left AS _UNSIGNED _BYTE
  46.  
  47. TYPE Weapon_Type
  48.     Name AS STRING * 20
  49.     Reach AS _UNSIGNED _BYTE
  50.     Damage AS Damage_Type
  51.     HitBonus AS _UNSIGNED _BYTE
  52.     DamageBonus AS _UNSIGNED _BYTE
  53.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  54.  
  55. TYPE Armor_Type
  56.     Name AS STRING * 20
  57.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  58.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  59.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  60.  
  61. TYPE Hero_Type
  62.     Name AS STRING * 20
  63.     Life AS Damage_Type
  64.     Level AS _UNSIGNED _BYTE
  65.     EXP_Earned AS LONG
  66.     EXP_Needed AS LONG
  67.     Light AS Light_Type
  68.     Weapon1 AS Weapon_Type
  69.     Weapon2 AS Weapon_Type
  70.     Armor AS Armor_Type
  71.  
  72. TYPE Map_Identifer_TYPE
  73.     Symbol AS _UNSIGNED _BYTE
  74.  
  75. TYPE Monster_TYPE
  76.     Name AS STRING * 20
  77.     Life AS Damage_Type
  78.     Level AS INTEGER
  79.     ExpBonus AS INTEGER
  80.     Sight AS INTEGER
  81.     Hearing AS INTEGER
  82.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  83.     Weapon1 AS Weapon_Type
  84.     Weapon2 AS Weapon_Type
  85.     Armor AS Armor_Type
  86.     ID AS Map_Identifer_TYPE
  87.  
  88. TYPE Encounter_TYPE
  89.     Active AS INTEGER
  90.     X AS INTEGER
  91.     Y AS INTEGER
  92.     M AS INTEGER
  93.     Life AS INTEGER
  94.  
  95. REDIM SHARED Monster(100) AS Monster_TYPE
  96. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  97.  
  98. DIM SHARED Hero AS Hero_Type
  99. REDIM SHARED Monster(100) AS Monster_TYPE
  100. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  101. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  102. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  103.  
  104. SCREEN _NEWIMAGE(800, 700, 32)
  105. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  106. '1 map is illuminated
  107. '2 map is uncovered
  108. '4 map is a wall
  109. '8 map is a pathway
  110. '16 map is a stairway
  111. '32 map is simply blocked (perhaps with a monster?)
  112. '64 map is secret (can not be uncovered)
  113.  
  114. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  115.  
  116.  
  117. Init
  118. CreateMap 99, 74, 10
  119.  
  120.     DrawMap
  121.     DisplayCharacter
  122.  
  123.     GetInput
  124.     MonstersTurn
  125.     CheckForHeroGrowth
  126.  
  127. SUB Init
  128.     Hero.Name = "Steve The Tester!"
  129.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  130.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  131.     Hero.Light.Name = "Magic Candle"
  132.     Hero.Light.Reach = 2: Hero.Light.Left = -1 'infinite
  133.     Hero.Weapon1.Name = "Bare Fist"
  134.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  135.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  136.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  137.     Hero.Weapon2.Name = "Magic Candle"
  138.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  139.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  140.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  141.     Hero.Armor.Name = "Naked"
  142.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  143.  
  144.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  145.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  146.     DrawTextArea PrintArea
  147.     SetPrintPositionX PrintArea, CenterJustify
  148.     SetPrintUpdate PrintArea, NewLine
  149.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  150.     PrintOut PrintArea, "created by STEVE!"
  151.     PrintOut PrintArea, ""
  152.     SetPrintPositionX PrintArea, LeftJustify
  153.  
  154. SUB CheckForHeroGrowth
  155.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  156.         BEEP
  157.         CLS
  158.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  159.         _DELAY 5
  160.         SYSTEM
  161.     END IF
  162.  
  163.  
  164. SUB DisplayCharacter
  165.     LINE (0, 601)-(229, 799), &HFF000000, BF
  166.     COLOR &HFFFFFFFF, 0
  167.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  168.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  169.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  170.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  171.  
  172.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  173.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  174.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  175.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  176.  
  177. SUB GetInput
  178.     DO
  179.         k = _KEYHIT: valid = -1
  180.         SELECT CASE k
  181.             CASE 18432: IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  182.             CASE 19200: IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  183.             CASE 20480: IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  184.             CASE 19712: IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  185.             CASE 32 'space to just wait and skip a turn
  186.             CASE 60 ' "<" key
  187.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  188.                     Level = Level + 1
  189.                     CreateMap 99, 74, 10
  190.                     PathFind
  191.                 END IF
  192.             CASE ASC("+"), ASC("=")
  193.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  194.             CASE ASC("-"), ASC("_")
  195.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  196.             CASE ELSE
  197.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  198.         END SELECT
  199.         _LIMIT 60
  200.     LOOP UNTIL k AND valid
  201.     _KEYCLEAR 'one keystroke at a time
  202.  
  203. SUB MoveHero (MoveX, MoveY)
  204.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  205.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  206.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  207.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  208.             IF MoveX THEN Hero.X = Hero.X + MoveX
  209.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  210.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  211.             PathFind
  212.         ELSE
  213.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  214.             FOR i = 1 TO EncounterLimit
  215.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  216.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  217.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  218.                         Swing 0, i, 1 'swing with the right hand
  219.                     END IF
  220.                 END IF
  221.             NEXT
  222.         END IF
  223.     END IF
  224.  
  225. SUB Swing (Who, AtWhom, HandUsed)
  226.     M = Encounter(AtWhom).M
  227.     BaseChancetohit = 10 'base 10 chance to hit
  228.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  229.         IF Hero.Weapon1.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  230.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  231.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  232.             totalroll = 0
  233.             DO
  234.                 roll = INT(RND * 20) + 1
  235.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  236.                 IF roll = 20 THEN totalroll = totalroll + 20
  237.                 totalroll = totalroll + roll
  238.             LOOP UNTIL roll <> 1 AND roll <> 20
  239.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  240.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  241.             out$ = _TRIM$(Hero.Name)
  242.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  243.                 SetTextColor PrintArea, &HFFF000F0, 0
  244.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  245.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  246.                 Hero.Life.Low = Hero.Life.Low - damage
  247.             ELSEIF totalroll < Chancetohit THEN
  248.                 SetTextColor PrintArea, &HFFF0F000, 0
  249.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  250.             ELSEIF totalroll > Chancetohit + 20 THEN
  251.                 SetTextColor PrintArea, &HFF00FF00, 0
  252.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  253.                 damage = damage * (totalroll \ 20 + 1)
  254.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  255.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  256.             ELSEIF totalroll >= Chancetohit THEN
  257.                 SetTextColor PrintArea, &HFF00FF00, 0
  258.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  259.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  260.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  261.             END IF
  262.             PrintOut PrintArea, out$
  263.         END IF
  264.         IF Hero.Weapon2.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  265.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  266.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  267.             totalroll = 0
  268.             DO
  269.                 roll = INT(RND * 20) + 1
  270.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  271.                 IF roll = 20 THEN totalroll = totalroll + 20
  272.                 totalroll = totalroll + roll
  273.             LOOP UNTIL roll <> 1 AND roll <> 20
  274.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  275.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  276.             out$ = _TRIM$(Hero.Name)
  277.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  278.                 SetTextColor PrintArea, &HFFF000F0, 0
  279.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  280.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  281.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  282.                 Hero.Life.Low = Hero.Life.Low - damage
  283.             ELSEIF totalroll < Chancetohit THEN
  284.                 SetTextColor PrintArea, &HFFF0F000, 0
  285.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  286.             ELSEIF totalroll > Chancetohit + 20 THEN
  287.                 SetTextColor PrintArea, &HFF00FF00, 0
  288.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  289.                 damage = damage * (totalroll \ 20 + 1)
  290.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  291.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  292.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  293.             ELSEIF totalroll >= Chancetohit THEN
  294.                 SetTextColor PrintArea, &HFF00FF00, 0
  295.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  296.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  297.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  298.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  299.             END IF
  300.             PrintOut PrintArea, out$
  301.         END IF
  302.  
  303.         IF Encounter(AtWhom).Life <= 0 THEN 'the monster died!
  304.             SetTextColor PrintArea, &HFFFF0000, 0
  305.             out$ = _TRIM$(Monster(M).Name) + " died!"
  306.             PrintOut PrintArea, out$
  307.             Encounter(AtWhom).Active = 0
  308.             Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  309.             MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) = MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  310.         END IF
  311.     ELSE 'it's a monster attacking
  312.  
  313.     END IF
  314.  
  315.  
  316. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  317.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  318.     D = Distance(MX, MY) 'distance from monster to the hero
  319.     E = Encounter(i).M 'the actual monster in question
  320.  
  321.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  322.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  323.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  324.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  325.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  326.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  327.             MoveMonster = -1
  328.         END IF
  329.     END IF
  330.  
  331.  
  332.  
  333. SUB MonstersTurn
  334.     FOR i = 1 TO EncounterLimit
  335.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  336.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  337.             D = Distance(MX, MY) 'distance from monster to the hero
  338.             E = Encounter(i).M 'the actual monster in question
  339.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  340.  
  341.                 attack = 0
  342.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  343.                     'insert attack code here
  344.  
  345.                     _TITLE "ATTACK!"
  346.                     _CONTINUE
  347.                 END IF
  348.                 IF D <= Monster(E).Weapon2.Reach THEN 'we're in reach for the monster to attack with their off hand.
  349.                     'insert attack code here
  350.                     _CONTINUE
  351.                 END IF
  352.  
  353.                 IF attack = 0 THEN 'if the monster didn't attack, it can now move towards the hero.
  354.                     IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  355.                         IF D > Distance(MX - 1, MY) THEN
  356.                             IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  357.                         END IF
  358.                     END IF
  359.                     IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  360.                         IF D > Distance(MX, MY - 1) THEN
  361.                             IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  362.                         END IF
  363.                     END IF
  364.                     IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  365.                         IF D > Distance(MX + 1, MY) THEN
  366.                             IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  367.                         END IF
  368.                     END IF
  369.                     IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  370.                         IF D > Distance(MX, MY + 1) THEN
  371.                             IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  372.                         END IF
  373.                     END IF
  374.                 END IF
  375.             END IF
  376.         END IF
  377.  
  378.     NEXT
  379.  
  380.  
  381.  
  382.  
  383. SUB DrawMap
  384.     LINE (0, 0)-(800, 600), &HFF0000FF, BF 'clear the map
  385.     FOR Y = 0 TO YH
  386.         FOR X = 0 TO XH
  387.             IF Distance(X, Y) <= Hero.Light.Reach THEN 'It's close enough to check for illumination
  388.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  389.             END IF
  390.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  391.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  392.                     COLOR &HFF000000, 0
  393.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  394.                 END IF
  395.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  396.                     COLOR &HFF000000, &HFF777777
  397.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  398.                 END IF
  399.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  400.                     COLOR &HFF00FF00, &HFFFFFF00
  401.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  402.                 END IF
  403.             END IF
  404.             'note: highlighting for the light should come AFTER the map is drawn
  405.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  406.                 COLOR &H40FFFF00, 0
  407.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  408.                 MapArray(X, Y) = MapArray(X, Y) - 1
  409.                 FOR i = 1 TO EncounterLimit
  410.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  411.                         E = Encounter(i).M
  412.                         COLOR Monster(E).ID.Color
  413.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(Monster(E).ID.Symbol)
  414.                     END IF
  415.                 NEXT
  416.  
  417.             END IF
  418.         NEXT
  419.     NEXT
  420.     COLOR &HFFFFFF00, 0 'Yellow Hero
  421.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  422.  
  423.  
  424.  
  425.  
  426.  
  427. SUB CreateMap (XLimit, YLimit, Rooms)
  428.     ERASE MapArray 'clear the old map and reset everything to 0
  429.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  430.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  431.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  432.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  433.  
  434.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  435.  
  436.     FOR i = 1 TO Rooms
  437.         DO
  438.             RoomSize = INT(RND * 9) + 2
  439.             RoomX = INT(RND * (XLimit - RoomSize))
  440.             RoomY = INT(RND * (YLimit - RoomSize))
  441.             'test for positioning
  442.             good = -1 'it's good starting out
  443.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  444.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  445.             NEXT X, Y
  446.         LOOP UNTIL good
  447.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  448.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  449.         NEXT X, Y
  450.         RoomCenterX(i) = RoomX + .5 * RoomSize
  451.         RoomCenterY(i) = RoomY + .5 * RoomSize
  452.     NEXT
  453.     FOR i = 1 TO Rooms - 1
  454.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  455.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  456.         DO UNTIL StartX = EndX AND StartY = EndY
  457.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  458.             Meander = 10
  459.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  460.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  461.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  462.                     Ychange = 0
  463.                 ELSE
  464.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  465.                     Ychange = 0
  466.                 END IF
  467.             ELSE
  468.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  469.                     Ychange = SGN(EndY - StartY)
  470.                     XChange = 0
  471.                 ELSE
  472.                     Ychange = INT(RND * 3) - 1
  473.                     XChange = 0
  474.                 END IF
  475.             END IF
  476.             StartX = StartX + XChange
  477.             StartY = StartY + Ychange
  478.             IF StartX < 0 THEN StartX = 0
  479.             IF StartY < 0 THEN StartY = 0
  480.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  481.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  482.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  483.         LOOP
  484.     NEXT
  485.     DO
  486.         Hero.X = INT(RND * XLimit + 1)
  487.         Hero.Y = INT(RND * YLimit + 1)
  488.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  489.     MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  490.     DO
  491.         X = INT(RND * XLimit + 1)
  492.         Y = INT(RND * YLimit + 1)
  493.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  494.     MapArray(X, Y) = MapArray(X, Y) OR 16
  495.     PathFind
  496.     EncounterLimit = INT(RND * 6) + 5
  497.     FOR i = 1 TO EncounterLimit
  498.         Encounter(i).M = RandomMonster
  499.         Encounter(i).Active = -1
  500.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  501.         valid = -1
  502.         DO
  503.             Encounter(i).X = INT(RND * XLimit + 1)
  504.             Encounter(i).Y = INT(RND * YLimit + 1)
  505.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  506.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  507.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  508.     NEXT
  509.  
  510. SUB PathFind
  511.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  512.     DIM pass AS _UNSIGNED _BYTE
  513.     m = _MEM(Distance()): m1 = _MEM(Temp())
  514.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  515.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  516.     Temp(Hero.X, Hero.Y) = 0
  517.     pass = 0
  518.     DO
  519.         changed = 0
  520.         y = 0
  521.         DO
  522.             x = 0
  523.             DO
  524.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  525.                     IF x < XH THEN
  526.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  527.                     END IF
  528.                     IF x > 0 THEN
  529.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  530.                     END IF
  531.                     IF y < YH THEN
  532.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  533.                     END IF
  534.                     IF y > 0 THEN
  535.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  536.                     END IF
  537.                 END IF
  538.                 x = x + 1
  539.             LOOP UNTIL x > XH
  540.             y = y + 1
  541.         LOOP UNTIL y > YH
  542.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  543.         pass = pass + 1
  544.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  545.     Distance(Hero.X, Hero.Y) = 0
  546.  
  547. FUNCTION RandomMonster
  548.     'Shared variable level tells us what level of the dungeon we're on.
  549.     STATIC MC, DS 'monster count and data set
  550.     IF NOT DS THEN
  551.         DS = -1
  552.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  553.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  554.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  555.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  556.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  557.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  558.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  559.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  560.         'Monster(1).Armor.Name = ""
  561.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  562.  
  563.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  564.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  565.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  566.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  567.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  568.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  569.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  570.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  571.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  572.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  573.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  574.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  575.         NEXT
  576.     END IF
  577.     SELECT CASE Level 'the starting level
  578.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  579.     END SELECT
  580.     RandomMonster = INT(RND * MC) + 1
  581.  
  582.  
  583. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  584. '# SUBroutines and FUNCTIONs below #'
  585. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  586. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  587.     u = UBOUND(TextHandles)
  588.     Handle = WhichHandle
  589.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  590.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  591.     Where = TextHandles(Handle).VerticalAlignment
  592.     How = TextHandles(Handle).Justification
  593.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  594.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  595.  
  596.  
  597. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  598.     'WhichHandle is the handle which designates which text area we want to use
  599.     'Where is where we want it to go in that text area
  600.     '  -- Online prints the text to the current print position line in that text area.
  601.     '  -- CenterLine centers the text to the center of that text area.
  602.     '  -- any other value will print to that line positon in that particular box.
  603.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  604.     'What is the text that we want to print in our text area
  605.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  606.  
  607.     D = _DEST: S = _SOURCE
  608.  
  609.  
  610.     u = UBOUND(TextHandles)
  611.     fh = _FONTHEIGHT
  612.     pw = _PRINTWIDTH(What)
  613.     Handle = WhichHandle
  614.  
  615.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  616.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  617.     IF TextHandles(Handle).HideFrame THEN
  618.         _DEST TextHandles(Handle).SavedBackground
  619.         _SOURCE TextHandles(Handle).SavedBackground
  620.     END IF
  621.     h = TextHandles(Handle).h - 4
  622.     w = TextHandles(Handle).w - 4
  623.  
  624.     SELECT CASE Where
  625.         CASE BottomLine
  626.             y = h \ fh
  627.         CASE OnLine
  628.             y = TextHandles(Handle).Ypos
  629.             IF y = 0 THEN y = 1
  630.         CASE CenterLine
  631.             linesused = 0
  632.             tpw = pw: tw = w: tWhat$ = What
  633.             DO UNTIL tpw <= tw
  634.                 textallowed = WordBreak(LEFT$(tWhat$, w \ _FONTWIDTH))
  635.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  636.                 linesused = linesused + 1
  637.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  638.                 tpw = _PRINTWIDTH(tWhat$)
  639.             LOOP
  640.             linesused = linesused + 1
  641.             py = (h - linesused * fh) \ 2
  642.             y = py \ fh + 1
  643.             IF y < 1 THEN y = 1
  644.         CASE ELSE
  645.             y = Where
  646.     END SELECT
  647.  
  648.     IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  649.     blend = _BLEND
  650.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  651.         'first let's get a temp image handle for the existing area of the screen.
  652.         x1 = TextHandles(Handle).x1 + 2
  653.         y1 = TextHandles(Handle).y1 + 2
  654.         x2 = TextHandles(Handle).x1 + w
  655.         y2 = TextHandles(Handle).y1 + h
  656.         nh = y2 - y1 + 1 - fh
  657.         nw = x2 - x1 + 1
  658.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  659.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  660.         DrawTextArea Handle
  661.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  662.         y = y - 1
  663.     LOOP
  664.     IF blend THEN _BLEND
  665.  
  666.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  667.  
  668.     SELECT CASE How
  669.         CASE LeftJustify
  670.             x = 0
  671.             IF pw > w THEN
  672.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  673.                 text$ = RTRIM$(LEFT$(What, textallowed))
  674.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  675.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  676.             ELSE
  677.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  678.                 finished = -1
  679.             END IF
  680.         CASE CenterJustify
  681.             IF pw > w THEN
  682.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  683.                 text$ = RTRIM$(LEFT$(What, textallowed))
  684.                 x = (w - _PRINTWIDTH(text$)) \ 2
  685.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  686.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  687.             ELSE
  688.                 x = (w - pw) \ 2
  689.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  690.                 finished = -1
  691.             END IF
  692.         CASE RightJustify
  693.             IF pw > w THEN
  694.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  695.                 text$ = RTRIM$(LEFT$(What, textallowed))
  696.                 x = w - _PRINTWIDTH(text$)
  697.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  698.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  699.             ELSE
  700.                 x = w - pw
  701.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  702.                 finished = -1
  703.             END IF
  704.         CASE NoJustify
  705.             x = TextHandles(Handle).Xpos
  706.             firstlinelimit = (w - x) \ _FONTWIDTH 'the limit of characters on the first line
  707.             IF LEN(What) > firstlinelimit THEN
  708.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  709.                 text$ = RTRIM$(LEFT$(What, textallowed))
  710.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  711.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  712.             ELSE
  713.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  714.                 finished = -1
  715.             END IF
  716.     END SELECT
  717.  
  718.     IF finished THEN
  719.         SELECT CASE TextHandles(Handle).UpdateMethod
  720.             CASE NoUpdate 'We don't update the position at all.
  721.             CASE DoUpdate
  722.                 TextHandles(Handle).Xpos = x + pw
  723.                 TextHandles(Handle).Ypos = y
  724.             CASE NewLine
  725.                 TextHandles(Handle).Ypos = y + 1
  726.                 TextHandles(Handle).Xpos = 1
  727.         END SELECT
  728.         _DEST D: _SOURCE S
  729.         COLOR FG, BG
  730.     END IF
  731.  
  732. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  733.     u = UBOUND(TextHandles)
  734.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  735.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  736.     TextHandles(Handle).TextColor = Foreground
  737.  
  738.  
  739. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  740.     u = UBOUND(TextHandles)
  741.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  742.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  743.     TextHandles(Handle).TextBackgroundColor = Background
  744.  
  745.  
  746.  
  747. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  748.     u = UBOUND(TextHandles)
  749.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  750.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  751.     TextHandles(Handle).TextColor = Foreground
  752.     TextHandles(Handle).TextBackgroundColor = Background
  753.  
  754.  
  755. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  756.     u = UBOUND(TextHandles)
  757.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  758.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  759.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  760.     TextHandles(Handle).UpdateMethod = Method
  761.  
  762.  
  763. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  764.     u = UBOUND(TextHandles)
  765.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  766.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  767.     SELECT CASE Y
  768.         CASE BottomLine
  769.             TextHandles(Handle).VerticalAlignment = -2
  770.         CASE CenterLine
  771.             TextHandles(Handle).VerticalAlignment = -1
  772.         CASE ELSE
  773.             TextHandles(Handle).VerticalAlignment = 0
  774.     END SELECT
  775.     IF X < 1 AND X > -4 THEN
  776.         TextHandles(Handle).Justification = X
  777.     ELSE
  778.         TextHandles(Handle).Xpos = X
  779.     END IF
  780.     IF Y < 1 THEN EXIT SUB
  781.     TextHandles(Handle).Ypos = Y
  782.  
  783. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  784.     u = UBOUND(TextHandles)
  785.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  786.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  787.     IF X < 1 AND X > -4 THEN
  788.         TextHandles(Handle).Justification = X
  789.     ELSE
  790.         TextHandles(Handle).Xpos = X
  791.     END IF
  792.  
  793. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  794.     u = UBOUND(TextHandles)
  795.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  796.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  797.     SELECT CASE Y
  798.         CASE BottomLine
  799.             TextHandles(Handle).VerticalAlignment = -2
  800.         CASE CenterLine
  801.             TextHandles(Handle).VerticalAlignment = -1
  802.         CASE ELSE
  803.             TextHandles(Handle).VerticalAlignment = 0
  804.     END SELECT
  805.     IF Y < 1 THEN EXIT SUB
  806.     TextHandles(Handle).Ypos = Y
  807.  
  808.  
  809. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  810.     u = UBOUND(TextHandles)
  811.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  812.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  813.     GetPrintPositionY = TextHandles(Handle).Ypos
  814.  
  815. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  816.     u = UBOUND(TextHandles)
  817.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  818.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  819.     GetPrintPositionX = TextHandles(Handle).Xpos
  820.  
  821.  
  822.  
  823. FUNCTION WordBreak (text$)
  824.     CONST Breaks = " ;,.?!-"
  825.     FOR i = LEN(text$) TO 0 STEP -1
  826.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  827.         loopcount = loopcount + 1
  828.     NEXT
  829.     IF i = 0 THEN i = LEN(text$)
  830.     WordBreak = i
  831.  
  832.  
  833.  
  834. SUB ClearTextArea (Handle AS INTEGER)
  835.     u = UBOUND(TextHandles)
  836.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  837.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  838.     IF TextHandles(Handle).SavedBackground THEN
  839.         w = TextHandles(Handle).w
  840.         h = TextHandles(Handle).h
  841.         x1 = TextHandles(Handle).ScreenX
  842.         y1 = TextHandles(Handle).ScreenY
  843.         x2 = x1 + w - 1
  844.         y2 = y1 + h - 1
  845.         blend = _BLEND
  846.         _DONTBLEND
  847.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  848.         IF blend THEN _BLEND
  849.     END IF
  850.     DrawTextArea Handle
  851.  
  852.  
  853.  
  854. SUB DrawTextArea (Handle AS INTEGER)
  855.     u = UBOUND(TextHandles)
  856.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  857.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  858.     w = TextHandles(Handle).w
  859.     h = TextHandles(Handle).h
  860.     x1 = TextHandles(Handle).ScreenX
  861.     y1 = TextHandles(Handle).ScreenY
  862.     x2 = x1 + w - 1
  863.     y2 = y1 + h - 1
  864.  
  865.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  866.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  867.  
  868.  
  869.  
  870. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  871.     u = UBOUND(TextHandles)
  872.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  873.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  874.     TextHandles(Handle).FrameColor = FrameColor
  875.     TextHandles(Handle).BackColor = BackColor
  876.  
  877.  
  878.  
  879. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  880.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  881.     x2 = tx2: y2 = ty2
  882.     IF x1 > x2 THEN SWAP x1, x2
  883.     IF y1 > y2 THEN SWAP y1, y2
  884.     w = x2 - x1 + 1
  885.     h = y2 - y1 + 1
  886.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  887.     'Error checking for if the user sends coordinates which are off the screen
  888.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  889.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  890.  
  891.     u = UBOUND(TextHandles)
  892.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  893.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  894.     NEXT
  895.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  896.         Handle = u + 1
  897.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  898.     END IF
  899.     TextHandles(Handle).x1 = x1
  900.     TextHandles(Handle).y1 = y1
  901.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  902.     TextHandles(Handle).InUse = True
  903.     TextHandles(Handle).Xpos = 0
  904.     TextHandles(Handle).Ypos = 1
  905.     TextHandles(Handle).UpdateMethod = NewLine
  906.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  907.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  908.  
  909.     IF SaveBackground THEN
  910.         imagehandle = _NEWIMAGE(w, h, 32)
  911.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  912.         TextHandles(Handle).SavedBackground = imagehandle
  913.     END IF
  914.     TextHandles(Handle).ScreenX = x1
  915.     TextHandles(Handle).ScreenY = y1
  916.  
  917.     NewTextArea% = Handle
  918.  
  919. SUB FreeTextArea (Handle AS INTEGER)
  920.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  921.         IF TextHandles(Handle).InUse THEN
  922.             TextHandles(Handle).InUse = False
  923.             IF TextHandles(Handle).SavedBackground THEN
  924.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  925.                     w = TextHandles(Handle).w
  926.                     h = TextHandles(Handle).h
  927.                     x1 = TextHandles(Handle).ScreenX
  928.                     y1 = TextHandles(Handle).ScreenY
  929.                     x2 = x1 + w - 1
  930.                     y2 = y1 + h - 1
  931.                     blend = _BLEND
  932.                     _DONTBLEND
  933.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  934.                     IF blend THEN _BLEND
  935.                 END IF
  936.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  937.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  938.             END IF
  939.         ELSE
  940.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  941.         END IF
  942.     ELSE
  943.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  944.     END IF
  945.  
  946. SUB HideFrame (Handle AS INTEGER)
  947.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  948.         TextHandles(Handle).HideFrame = -1
  949.         w = TextHandles(Handle).w
  950.         h = TextHandles(Handle).h
  951.         x1 = TextHandles(Handle).ScreenX
  952.         y1 = TextHandles(Handle).ScreenY
  953.         x2 = x1 + w - 1
  954.         y2 = y1 + h - 1
  955.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  956.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  957.         IF TextHandles(Handle).SavedBackground THEN
  958.             blend = _BLEND
  959.             _DONTBLEND
  960.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  961.             _FREEIMAGE TextHandles(Handle).SavedBackground
  962.             IF blend THEN _BLEND
  963.         END IF
  964.         TextHandles(Handle).SavedBackground = imagehandle
  965.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  966.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  967.     END IF
  968.  
  969. SUB RestoreFrame (Handle AS INTEGER)
  970.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  971.         TextHandles(Handle).HideFrame = 0
  972.         w = TextHandles(Handle).w
  973.         h = TextHandles(Handle).h
  974.         x1 = TextHandles(Handle).ScreenX
  975.         y1 = TextHandles(Handle).ScreenY
  976.         x2 = x1 + w - 1
  977.         y2 = y1 + h - 1
  978.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  979.         blend = _BLEND
  980.         _DONTBLEND
  981.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  982.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  983.         _FREEIMAGE TextHandles(Handle).SavedBackground
  984.         IF blend THEN _BLEND
  985.         TextHandles(Handle).SavedBackground = imagehandle
  986.         TextHandles(Handle).x1 = x1 'When the frames are frames are restored, we need to recalculate our print position
  987.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  988.     END IF
  989.  
  990. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  991.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  992.     u = UBOUND(TextHandles)
  993.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  994.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  995.     HideFrame Handle
  996.     TextHandles(Handle).ScreenX = x1
  997.     TextHandles(Handle).ScreenY = y1
  998.     RestoreFrame Handle
  999.  

The code has doubled in size, but that's because I plugged in my text frame library and am using it to wordwrap/justify/scroll text on the screen for me, without interfering with the rest of the program.  A lot of the library I can strip out later, as I don't think I'll need to be able to move/hide/restore frames, or anything of that sort, in this program, so honestly things aren't half as complex as they might seem at first glance in the program now.  ;)
Title: Re: Rogue-Like (work in progress)
Post by: bplus on September 11, 2019, 03:18:33 pm
Hi Steve,

Getting interesting and the text frame library too, but text is kinda small for these old eyes.

I have allot of room on left or right of graphics area for large text areas, aren't most screen wider than high? Might be better use of screen area, just an idea. Maybe you prefer preserving retro look?
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 12, 2019, 01:49:35 am
Hi Steve,

Getting interesting and the text frame library too, but text is kinda small for these old eyes.

I have allot of room on left or right of graphics area for large text areas, aren't most screen wider than high? Might be better use of screen area, just an idea. Maybe you prefer preserving retro look?

See what you think of my "Scale System" implementation:

Code: QB64: [Select]
  1. DEFLNG A-Z 'default to long instead of single
  2. TYPE TextArea
  3.     InUse AS INTEGER
  4.     x1 AS LONG 'left
  5.     y1 AS LONG 'top
  6.     w AS LONG 'width
  7.     h AS LONG 'height
  8.     FrameColor AS _UNSIGNED LONG
  9.     BackColor AS _UNSIGNED LONG
  10.     Xpos AS INTEGER
  11.     Ypos AS INTEGER
  12.     VerticalAlignment AS INTEGER
  13.     Justification AS INTEGER
  14.     UpdateMethod AS INTEGER
  15.     TextColor AS _UNSIGNED LONG
  16.     TextBackgroundColor AS _UNSIGNED LONG
  17.     SavedBackground AS INTEGER
  18.     HideFrame AS INTEGER
  19.     ScreenX AS INTEGER
  20.     ScreenY AS INTEGER
  21.  
  22. REDIM SHARED TextHandles(0) AS TextArea
  23.  
  24. CONST True = -1, False = 0
  25. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  26. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  27. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  28. '********************************************************
  29. '* Text Frames before this line
  30. '********************************************************
  31.  
  32.  
  33.  
  34.  
  35.  
  36.  
  37. _CONSOLE ON 'for debugging purposes while making/testing things
  38.  
  39. TYPE Damage_Type
  40.     Low AS INTEGER
  41.     High AS INTEGER
  42.  
  43. TYPE Light_Type
  44.     Name AS STRING * 20
  45.     Reach AS _UNSIGNED _BYTE
  46.     Left AS _UNSIGNED _BYTE
  47.  
  48. TYPE Weapon_Type
  49.     Name AS STRING * 20
  50.     Reach AS _UNSIGNED _BYTE
  51.     Damage AS Damage_Type
  52.     HitBonus AS _UNSIGNED _BYTE
  53.     DamageBonus AS _UNSIGNED _BYTE
  54.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  55.  
  56. TYPE Armor_Type
  57.     Name AS STRING * 20
  58.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  59.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  60.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  61.  
  62. TYPE Hero_Type
  63.     Name AS STRING * 20
  64.     Life AS Damage_Type
  65.     Level AS _UNSIGNED _BYTE
  66.     EXP_Earned AS LONG
  67.     EXP_Needed AS LONG
  68.     Light AS Light_Type
  69.     Weapon1 AS Weapon_Type
  70.     Weapon2 AS Weapon_Type
  71.     Armor AS Armor_Type
  72.  
  73. TYPE Map_Identifer_TYPE
  74.     Symbol AS _UNSIGNED _BYTE
  75.  
  76. TYPE Monster_TYPE
  77.     Name AS STRING * 20
  78.     Life AS Damage_Type
  79.     Level AS INTEGER
  80.     ExpBonus AS INTEGER
  81.     Sight AS INTEGER
  82.     Hearing AS INTEGER
  83.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  84.     Weapon1 AS Weapon_Type
  85.     Weapon2 AS Weapon_Type
  86.     Armor AS Armor_Type
  87.     ID AS Map_Identifer_TYPE
  88.  
  89. TYPE Encounter_TYPE
  90.     Active AS INTEGER
  91.     X AS INTEGER
  92.     Y AS INTEGER
  93.     M AS INTEGER
  94.     Life AS INTEGER
  95.  
  96. REDIM SHARED Monster(100) AS Monster_TYPE
  97. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  98.  
  99. DIM SHARED Hero AS Hero_Type
  100. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  101. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  102. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  103. DIM SHARED Scale AS _FLOAT, WorkScreen AS LONG, DisplayScreen AS LONG
  104. DIM SHARED TextFont
  105.  
  106. WorkScreen = _NEWIMAGE(800, 600, 32)
  107. DisplayScreen = _NEWIMAGE(800, 700, 32)
  108. SCREEN DisplayScreen
  109. Scale = 1
  110.  
  111. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  112. '1 map is illuminated
  113. '2 map is uncovered
  114. '4 map is a wall
  115. '8 map is a pathway
  116. '16 map is a stairway
  117. '32 map is simply blocked (perhaps with a monster?)
  118. '64 map is secret (can not be uncovered)
  119.  
  120. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  121.  
  122.  
  123.  
  124.  
  125.  
  126. Init
  127. CreateMap 99, 74, 10
  128.  
  129.     DrawMap
  130.     DisplayCharacter
  131.     _DISPLAY
  132.     GetInput
  133.     MonstersTurn
  134.     CheckForHeroGrowth
  135.  
  136. SUB Init
  137.     Hero.Name = "Steve The Tester!"
  138.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  139.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  140.     Hero.Light.Name = "Magic Candle"
  141.     Hero.Light.Reach = 2: Hero.Light.Left = -1 'infinite
  142.     Hero.Weapon1.Name = "Bare Fist"
  143.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  144.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  145.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  146.     Hero.Weapon2.Name = "Magic Candle"
  147.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  148.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  149.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  150.     Hero.Armor.Name = "Naked"
  151.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  152.  
  153.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  154.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  155.     DrawTextArea PrintArea
  156.     SetPrintPositionX PrintArea, CenterJustify
  157.     SetPrintUpdate PrintArea, NewLine
  158.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  159.     PrintOut PrintArea, "created by STEVE!"
  160.     PrintOut PrintArea, ""
  161.     SetPrintPositionX PrintArea, LeftJustify
  162.  
  163. SUB CheckForHeroGrowth
  164.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  165.         BEEP
  166.         CLS
  167.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  168.         _DELAY 5
  169.         SYSTEM
  170.     END IF
  171.  
  172.  
  173. SUB DisplayCharacter
  174.     LINE (0, 601)-(229, 799), &HFF000000, BF
  175.     COLOR &HFFFFFFFF, 0
  176.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  177.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  178.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  179.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  180.  
  181.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  182.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  183.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  184.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  185.  
  186. SUB GetInput
  187.     DO
  188.         k = _KEYHIT: valid = -1
  189.         SELECT CASE k
  190.             CASE 18432 'up
  191.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  192.                     Scale = Scale + .1
  193.                     IF Scale > 4 THEN Scale = 4
  194.                 ELSE
  195.                     IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  196.                 END IF
  197.             CASE 19200: 'left
  198.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  199.                     Scale = Scale - .1
  200.                     IF Scale < 1 THEN Scale = 1
  201.                 ELSE
  202.                     IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  203.                 END IF
  204.             CASE 20480: 'down
  205.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  206.                     Scale = Scale - .1
  207.                     IF Scale < 1 THEN Scale = 1
  208.                 ELSE
  209.                     IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  210.                 END IF
  211.             CASE 19712: 'right
  212.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  213.                     Scale = Scale + .1
  214.                     IF Scale > 4 THEN Scale = 4
  215.                 ELSE
  216.                     IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  217.                 END IF
  218.             CASE 32 'space to just wait and skip a turn
  219.             CASE 60 ' "<" key
  220.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  221.                     Level = Level + 1
  222.                     CreateMap 99, 74, 10
  223.                     PathFind
  224.                 END IF
  225.             CASE ASC("+"), ASC("=")
  226.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  227.             CASE ASC("-"), ASC("_")
  228.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  229.             CASE ELSE
  230.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  231.         END SELECT
  232.         _LIMIT 60
  233.     LOOP UNTIL k AND valid
  234.     _KEYCLEAR 'one keystroke at a time
  235.  
  236. SUB Box (X, Y, Wide, High, Caption AS STRING, Kolor AS _UNSIGNED LONG)
  237.     LINE (X, Y)-STEP(Wide, High), Kolor, BF
  238.     LINE (X, Y)-STEP(Wide, High), &HFFFFFFFF, B
  239.     pw = _PRINTWIDTH(Caption): ph = _FONTHEIGHT
  240.     _PRINTSTRING (X + (Wide - pw) \ 2, Y + (High - ph) \ 2), Caption
  241.  
  242.  
  243. SUB MoveHero (MoveX, MoveY)
  244.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  245.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  246.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  247.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  248.             IF MoveX THEN Hero.X = Hero.X + MoveX
  249.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  250.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  251.             PathFind
  252.         ELSE
  253.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  254.             FOR i = 1 TO EncounterLimit
  255.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  256.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  257.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  258.                         Swing 0, i, 1 'swing with the right hand
  259.                     END IF
  260.                 END IF
  261.             NEXT
  262.         END IF
  263.     END IF
  264.  
  265. SUB Swing (Who, AtWhom, HandUsed)
  266.     M = Encounter(AtWhom).M
  267.     BaseChancetohit = 10 'base 10 chance to hit
  268.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  269.         IF Hero.Weapon1.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  270.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  271.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  272.             totalroll = 0
  273.             DO
  274.                 roll = INT(RND * 20) + 1
  275.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  276.                 IF roll = 20 THEN totalroll = totalroll + 20
  277.                 totalroll = totalroll + roll
  278.             LOOP UNTIL roll <> 1 AND roll <> 20
  279.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  280.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  281.             out$ = _TRIM$(Hero.Name)
  282.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  283.                 SetTextColor PrintArea, &HFFF000F0, 0
  284.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  285.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  286.                 Hero.Life.Low = Hero.Life.Low - damage
  287.             ELSEIF totalroll < Chancetohit THEN
  288.                 SetTextColor PrintArea, &HFFF0F000, 0
  289.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  290.             ELSEIF totalroll > Chancetohit + 20 THEN
  291.                 SetTextColor PrintArea, &HFF00FF00, 0
  292.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  293.                 damage = damage * (totalroll \ 20 + 1)
  294.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  295.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  296.             ELSEIF totalroll >= Chancetohit THEN
  297.                 SetTextColor PrintArea, &HFF00FF00, 0
  298.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  299.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  300.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  301.             END IF
  302.             PrintOut PrintArea, out$
  303.         END IF
  304.         IF Hero.Weapon2.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  305.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  306.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  307.             totalroll = 0
  308.             DO
  309.                 roll = INT(RND * 20) + 1
  310.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  311.                 IF roll = 20 THEN totalroll = totalroll + 20
  312.                 totalroll = totalroll + roll
  313.             LOOP UNTIL roll <> 1 AND roll <> 20
  314.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  315.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  316.             out$ = _TRIM$(Hero.Name)
  317.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  318.                 SetTextColor PrintArea, &HFFF000F0, 0
  319.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  320.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  321.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  322.                 Hero.Life.Low = Hero.Life.Low - damage
  323.             ELSEIF totalroll < Chancetohit THEN
  324.                 SetTextColor PrintArea, &HFFF0F000, 0
  325.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  326.             ELSEIF totalroll > Chancetohit + 20 THEN
  327.                 SetTextColor PrintArea, &HFF00FF00, 0
  328.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  329.                 damage = damage * (totalroll \ 20 + 1)
  330.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  331.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  332.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  333.             ELSEIF totalroll >= Chancetohit THEN
  334.                 SetTextColor PrintArea, &HFF00FF00, 0
  335.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  336.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  337.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  338.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  339.             END IF
  340.             PrintOut PrintArea, out$
  341.         END IF
  342.  
  343.         IF Encounter(AtWhom).Life <= 0 THEN 'the monster died!
  344.             SetTextColor PrintArea, &HFFFF0000, 0
  345.             out$ = _TRIM$(Monster(M).Name) + " died!"
  346.             PrintOut PrintArea, out$
  347.             Encounter(AtWhom).Active = 0
  348.             Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  349.             MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) = MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  350.  
  351.             RedSet = 128: 'GreenSet = 128: BlueSet = 128
  352.             IF Monster(M).ID.Symbol = 63 THEN 'it's a first time kill!
  353.                 TempArea = NewTextArea(200, 100, 600, 500, True)
  354.                 ColorTextArea TempArea, _RGB32(255, 255, 255), _RGB32(128, 128, 128)
  355.                 DrawTextArea TempArea
  356.                 SetPrintPositionX TempArea, CenterJustify
  357.                 SetPrintUpdate TempArea, NewLine
  358.                 SetTextColor TempArea, &HFFFFFFFF, 0
  359.                 out$ = "CONGRATULATIONS!  You just killed " + _TRIM$(Monster(M).Name) + " for the first time!"
  360.                 PrintOut TempArea, out$
  361.                 DO
  362.                     SetTextColor TempArea, _RGB32(RedSet, GreenSet, BlueSet), 0
  363.                     COLOR _RGB32(RedSet, GreenSet, BlueSet), 0
  364.                     FOR y = 0 TO 15
  365.                         FOR x = 0 TO 15
  366.                             Box x * 16 + 275, y * 16 + 150, 16, 16, CHR$(y * 16 + x), _RGB32(200, 200, 200)
  367.                         NEXT
  368.                     NEXT
  369.                     Box 275, 425, 256, 20, "", _RGB32(200, 200, 200)
  370.                     Box 275, 425, RedSet, 20, "", _RGB32(255, 0, 0)
  371.                     Box 275, 450, 256, 20, "", _RGB32(200, 200, 200)
  372.                     Box 275, 450, GreenSet, 20, "", _RGB32(0, 256, 0)
  373.                     Box 275, 475, 256, 20, "", _RGB32(200, 200, 200)
  374.                     Box 275, 475, BlueSet, 20, "", _RGB32(0, 0, 256)
  375.                     _DISPLAY
  376.                     WHILE _MOUSEINPUT: WEND
  377.                     IF _MOUSEBUTTON(1) THEN
  378.                         IF _MOUSEX > 250 AND _MOUSEX < 556 THEN 'we're clicked at the right mouse coordinate
  379.                             SELECT CASE _MOUSEY
  380.                                 CASE 425 TO 445
  381.                                     RedSet = _MOUSEX - 275
  382.                                     IF RedSet < 0 THEN RedSet = 0
  383.                                     IF RedSet > 255 THEN RedSet = 255
  384.                                 CASE 450 TO 470
  385.                                     GreenSet = _MOUSEX - 275
  386.                                     IF GreenSet < 0 THEN GreenSet = 0
  387.                                     IF GreenSet > 255 THEN GreenSet = 255
  388.                                 CASE 475 TO 495
  389.                                     BlueSet = _MOUSEX - 275
  390.                                     IF BlueSet < 0 THEN BlueSet = 0
  391.                                     IF BlueSet > 255 THEN BlueSet = 255
  392.                             END SELECT
  393.                         END IF
  394.                     END IF
  395.                     _LIMIT 60
  396.                 LOOP
  397.  
  398.             END IF
  399.         END IF
  400.     ELSE 'it's a monster attacking
  401.  
  402.     END IF
  403.  
  404.  
  405. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  406.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  407.     D = Distance(MX, MY) 'distance from monster to the hero
  408.     E = Encounter(i).M 'the actual monster in question
  409.  
  410.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  411.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  412.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  413.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  414.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  415.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  416.             MoveMonster = -1
  417.         END IF
  418.     END IF
  419.  
  420.  
  421.  
  422. SUB MonstersTurn
  423.     FOR i = 1 TO EncounterLimit
  424.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  425.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  426.             D = Distance(MX, MY) 'distance from monster to the hero
  427.             E = Encounter(i).M 'the actual monster in question
  428.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  429.  
  430.                 attack = 0
  431.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  432.                     'insert attack code here
  433.  
  434.                     _TITLE "ATTACK!"
  435.                     _CONTINUE
  436.                 END IF
  437.                 IF D <= Monster(E).Weapon2.Reach THEN 'we're in reach for the monster to attack with their off hand.
  438.                     'insert attack code here
  439.                     _CONTINUE
  440.                 END IF
  441.  
  442.                 IF attack = 0 THEN 'if the monster didn't attack, it can now move towards the hero.
  443.                     IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  444.                         IF D > Distance(MX - 1, MY) THEN
  445.                             IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  446.                         END IF
  447.                     END IF
  448.                     IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  449.                         IF D > Distance(MX, MY - 1) THEN
  450.                             IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  451.                         END IF
  452.                     END IF
  453.                     IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  454.                         IF D > Distance(MX + 1, MY) THEN
  455.                             IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  456.                         END IF
  457.                     END IF
  458.                     IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  459.                         IF D > Distance(MX, MY + 1) THEN
  460.                             IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  461.                         END IF
  462.                     END IF
  463.                 END IF
  464.             END IF
  465.         END IF
  466.  
  467.     NEXT
  468.  
  469.  
  470.  
  471.  
  472. SUB DrawMap
  473.     _DEST WorkScreen
  474.     _FONT 8
  475.     LINE (0, 0)-(800, 600), &HFF0000FF, BF 'clear the map
  476.     FOR Y = 0 TO YH
  477.         FOR X = 0 TO XH
  478.             IF Distance(X, Y) <= Hero.Light.Reach THEN 'It's close enough to check for illumination
  479.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  480.             END IF
  481.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  482.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  483.                     COLOR &HFF000000, 0
  484.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  485.                 END IF
  486.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  487.                     COLOR &HFF000000, &HFF777777
  488.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  489.                 END IF
  490.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  491.                     COLOR &HFF00FF00, &HFFFFFF00
  492.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  493.                 END IF
  494.             END IF
  495.             'note: highlighting for the light should come AFTER the map is drawn
  496.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  497.                 COLOR &H40FFFF00, 0
  498.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  499.                 MapArray(X, Y) = MapArray(X, Y) - 1
  500.                 FOR i = 1 TO EncounterLimit
  501.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  502.                         E = Encounter(i).M
  503.                         COLOR Monster(E).ID.Color
  504.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(Monster(E).ID.Symbol)
  505.                     END IF
  506.                 NEXT
  507.  
  508.             END IF
  509.         NEXT
  510.     NEXT
  511.     COLOR &HFFFFFF00, 0 'Yellow Hero
  512.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  513.     XOffset## = 400 / Scale
  514.     YOffset## = 300 / Scale
  515.     CenterX = Hero.X * 8 'convert hero coordinate to grid coordinate
  516.     CenterY = Hero.Y * 8
  517.     _DEST DisplayScreen
  518.     LINE (0, 0)-(800, 600), &HFF0000FF, BF 'clear the map
  519.     _PUTIMAGE (0, 0)-(800, 600), WorkScreen, DisplayScreen, (CenterX - XOffset##, CenterY - YOffset##)-(CenterX + XOffset##, CenterY + YOffset##)
  520.  
  521.  
  522.  
  523.  
  524.  
  525. SUB CreateMap (XLimit, YLimit, Rooms)
  526.     ERASE MapArray 'clear the old map and reset everything to 0
  527.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  528.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  529.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  530.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  531.  
  532.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  533.  
  534.     FOR i = 1 TO Rooms
  535.         DO
  536.             RoomSize = INT(RND * 9) + 2
  537.             RoomX = INT(RND * (XLimit - RoomSize))
  538.             RoomY = INT(RND * (YLimit - RoomSize))
  539.             'test for positioning
  540.             good = -1 'it's good starting out
  541.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  542.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  543.             NEXT X, Y
  544.         LOOP UNTIL good
  545.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  546.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  547.         NEXT X, Y
  548.         RoomCenterX(i) = RoomX + .5 * RoomSize
  549.         RoomCenterY(i) = RoomY + .5 * RoomSize
  550.     NEXT
  551.     FOR i = 1 TO Rooms - 1
  552.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  553.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  554.         DO UNTIL StartX = EndX AND StartY = EndY
  555.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  556.             Meander = 10
  557.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  558.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  559.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  560.                     Ychange = 0
  561.                 ELSE
  562.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  563.                     Ychange = 0
  564.                 END IF
  565.             ELSE
  566.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  567.                     Ychange = SGN(EndY - StartY)
  568.                     XChange = 0
  569.                 ELSE
  570.                     Ychange = INT(RND * 3) - 1
  571.                     XChange = 0
  572.                 END IF
  573.             END IF
  574.             StartX = StartX + XChange
  575.             StartY = StartY + Ychange
  576.             IF StartX < 0 THEN StartX = 0
  577.             IF StartY < 0 THEN StartY = 0
  578.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  579.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  580.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  581.         LOOP
  582.     NEXT
  583.     DO
  584.         Hero.X = INT(RND * XLimit + 1)
  585.         Hero.Y = INT(RND * YLimit + 1)
  586.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  587.     MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  588.     DO
  589.         X = INT(RND * XLimit + 1)
  590.         Y = INT(RND * YLimit + 1)
  591.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  592.     MapArray(X, Y) = MapArray(X, Y) OR 16
  593.     PathFind
  594.     EncounterLimit = INT(RND * 6) + 5
  595.     FOR i = 1 TO EncounterLimit
  596.         Encounter(i).M = RandomMonster
  597.         Encounter(i).Active = -1
  598.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  599.         valid = -1
  600.         DO
  601.             Encounter(i).X = INT(RND * XLimit + 1)
  602.             Encounter(i).Y = INT(RND * YLimit + 1)
  603.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  604.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  605.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  606.     NEXT
  607.  
  608. SUB PathFind
  609.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  610.     DIM pass AS _UNSIGNED _BYTE
  611.     m = _MEM(Distance()): m1 = _MEM(Temp())
  612.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  613.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  614.     Temp(Hero.X, Hero.Y) = 0
  615.     pass = 0
  616.     DO
  617.         changed = 0
  618.         y = 0
  619.         DO
  620.             x = 0
  621.             DO
  622.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  623.                     IF x < XH THEN
  624.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  625.                     END IF
  626.                     IF x > 0 THEN
  627.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  628.                     END IF
  629.                     IF y < YH THEN
  630.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  631.                     END IF
  632.                     IF y > 0 THEN
  633.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  634.                     END IF
  635.                 END IF
  636.                 x = x + 1
  637.             LOOP UNTIL x > XH
  638.             y = y + 1
  639.         LOOP UNTIL y > YH
  640.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  641.         pass = pass + 1
  642.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  643.     Distance(Hero.X, Hero.Y) = 0
  644.  
  645. FUNCTION RandomMonster
  646.     'Shared variable level tells us what level of the dungeon we're on.
  647.     STATIC MC, DS 'monster count and data set
  648.     IF NOT DS THEN
  649.         DS = -1
  650.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  651.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  652.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  653.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  654.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  655.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  656.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  657.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  658.         'Monster(1).Armor.Name = ""
  659.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  660.  
  661.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  662.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  663.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  664.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  665.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  666.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  667.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  668.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  669.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  670.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  671.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  672.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  673.         NEXT
  674.     END IF
  675.     SELECT CASE Level 'the starting level
  676.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  677.     END SELECT
  678.     RandomMonster = INT(RND * MC) + 1
  679.  
  680.  
  681. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  682. '# SUBroutines and FUNCTIONs below #'
  683. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  684. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  685.     u = UBOUND(TextHandles)
  686.     Handle = WhichHandle
  687.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  688.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  689.     Where = TextHandles(Handle).VerticalAlignment
  690.     How = TextHandles(Handle).Justification
  691.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  692.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  693.  
  694.  
  695. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  696.     'WhichHandle is the handle which designates which text area we want to use
  697.     'Where is where we want it to go in that text area
  698.     '  -- Online prints the text to the current print position line in that text area.
  699.     '  -- CenterLine centers the text to the center of that text area.
  700.     '  -- any other value will print to that line positon in that particular box.
  701.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  702.     'What is the text that we want to print in our text area
  703.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  704.  
  705.     D = _DEST: S = _SOURCE
  706.  
  707.  
  708.     u = UBOUND(TextHandles)
  709.     fh = _FONTHEIGHT
  710.     pw = _PRINTWIDTH(What)
  711.     Handle = WhichHandle
  712.  
  713.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  714.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  715.     IF TextHandles(Handle).HideFrame THEN
  716.         _DEST TextHandles(Handle).SavedBackground
  717.         _SOURCE TextHandles(Handle).SavedBackground
  718.     END IF
  719.     h = TextHandles(Handle).h - 4
  720.     w = TextHandles(Handle).w - 4
  721.  
  722.     SELECT CASE Where
  723.         CASE BottomLine
  724.             y = h \ fh
  725.         CASE OnLine
  726.             y = TextHandles(Handle).Ypos
  727.             IF y = 0 THEN y = 1
  728.         CASE CenterLine
  729.             linesused = 0
  730.             tpw = pw: tw = w: tWhat$ = What
  731.             DO UNTIL tpw <= tw
  732.                 textallowed = WordBreak(LEFT$(tWhat$, w \ _FONTWIDTH))
  733.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  734.                 linesused = linesused + 1
  735.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  736.                 tpw = _PRINTWIDTH(tWhat$)
  737.             LOOP
  738.             linesused = linesused + 1
  739.             py = (h - linesused * fh) \ 2
  740.             y = py \ fh + 1
  741.             IF y < 1 THEN y = 1
  742.         CASE ELSE
  743.             y = Where
  744.     END SELECT
  745.  
  746.     IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  747.     blend = _BLEND
  748.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  749.         'first let's get a temp image handle for the existing area of the screen.
  750.         x1 = TextHandles(Handle).x1 + 2
  751.         y1 = TextHandles(Handle).y1 + 2
  752.         x2 = TextHandles(Handle).x1 + w
  753.         y2 = TextHandles(Handle).y1 + h
  754.         nh = y2 - y1 + 1 - fh
  755.         nw = x2 - x1 + 1
  756.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  757.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  758.         DrawTextArea Handle
  759.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  760.         y = y - 1
  761.     LOOP
  762.     IF blend THEN _BLEND
  763.  
  764.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  765.  
  766.     SELECT CASE How
  767.         CASE LeftJustify
  768.             x = 0
  769.             IF pw > w THEN
  770.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  771.                 text$ = RTRIM$(LEFT$(What, textallowed))
  772.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  773.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  774.             ELSE
  775.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  776.                 finished = -1
  777.             END IF
  778.         CASE CenterJustify
  779.             IF pw > w THEN
  780.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  781.                 text$ = RTRIM$(LEFT$(What, textallowed))
  782.                 x = (w - _PRINTWIDTH(text$)) \ 2
  783.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  784.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  785.             ELSE
  786.                 x = (w - pw) \ 2
  787.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  788.                 finished = -1
  789.             END IF
  790.         CASE RightJustify
  791.             IF pw > w THEN
  792.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  793.                 text$ = RTRIM$(LEFT$(What, textallowed))
  794.                 x = w - _PRINTWIDTH(text$)
  795.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  796.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  797.             ELSE
  798.                 x = w - pw
  799.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  800.                 finished = -1
  801.             END IF
  802.         CASE NoJustify
  803.             x = TextHandles(Handle).Xpos
  804.             firstlinelimit = (w - x) \ _FONTWIDTH 'the limit of characters on the first line
  805.             IF LEN(What) > firstlinelimit THEN
  806.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  807.                 text$ = RTRIM$(LEFT$(What, textallowed))
  808.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  809.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  810.             ELSE
  811.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  812.                 finished = -1
  813.             END IF
  814.     END SELECT
  815.  
  816.     IF finished THEN
  817.         SELECT CASE TextHandles(Handle).UpdateMethod
  818.             CASE NoUpdate 'We don't update the position at all.
  819.             CASE DoUpdate
  820.                 TextHandles(Handle).Xpos = x + pw
  821.                 TextHandles(Handle).Ypos = y
  822.             CASE NewLine
  823.                 TextHandles(Handle).Ypos = y + 1
  824.                 TextHandles(Handle).Xpos = 1
  825.         END SELECT
  826.         _DEST D: _SOURCE S
  827.         COLOR FG, BG
  828.     END IF
  829.  
  830. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  831.     u = UBOUND(TextHandles)
  832.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  833.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  834.     TextHandles(Handle).TextColor = Foreground
  835.  
  836.  
  837. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  838.     u = UBOUND(TextHandles)
  839.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  840.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  841.     TextHandles(Handle).TextBackgroundColor = Background
  842.  
  843.  
  844.  
  845. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  846.     u = UBOUND(TextHandles)
  847.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  848.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  849.     TextHandles(Handle).TextColor = Foreground
  850.     TextHandles(Handle).TextBackgroundColor = Background
  851.  
  852.  
  853. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  854.     u = UBOUND(TextHandles)
  855.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  856.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  857.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  858.     TextHandles(Handle).UpdateMethod = Method
  859.  
  860.  
  861. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  862.     u = UBOUND(TextHandles)
  863.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  864.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  865.     SELECT CASE Y
  866.         CASE BottomLine
  867.             TextHandles(Handle).VerticalAlignment = -2
  868.         CASE CenterLine
  869.             TextHandles(Handle).VerticalAlignment = -1
  870.         CASE ELSE
  871.             TextHandles(Handle).VerticalAlignment = 0
  872.     END SELECT
  873.     IF X < 1 AND X > -4 THEN
  874.         TextHandles(Handle).Justification = X
  875.     ELSE
  876.         TextHandles(Handle).Xpos = X
  877.     END IF
  878.     IF Y < 1 THEN EXIT SUB
  879.     TextHandles(Handle).Ypos = Y
  880.  
  881. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  882.     u = UBOUND(TextHandles)
  883.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  884.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  885.     IF X < 1 AND X > -4 THEN
  886.         TextHandles(Handle).Justification = X
  887.     ELSE
  888.         TextHandles(Handle).Xpos = X
  889.     END IF
  890.  
  891. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  892.     u = UBOUND(TextHandles)
  893.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  894.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  895.     SELECT CASE Y
  896.         CASE BottomLine
  897.             TextHandles(Handle).VerticalAlignment = -2
  898.         CASE CenterLine
  899.             TextHandles(Handle).VerticalAlignment = -1
  900.         CASE ELSE
  901.             TextHandles(Handle).VerticalAlignment = 0
  902.     END SELECT
  903.     IF Y < 1 THEN EXIT SUB
  904.     TextHandles(Handle).Ypos = Y
  905.  
  906.  
  907. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  908.     u = UBOUND(TextHandles)
  909.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  910.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  911.     GetPrintPositionY = TextHandles(Handle).Ypos
  912.  
  913. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  914.     u = UBOUND(TextHandles)
  915.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  916.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  917.     GetPrintPositionX = TextHandles(Handle).Xpos
  918.  
  919.  
  920.  
  921. FUNCTION WordBreak (text$)
  922.     CONST Breaks = " ;,.?!-"
  923.     FOR i = LEN(text$) TO 0 STEP -1
  924.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  925.         loopcount = loopcount + 1
  926.     NEXT
  927.     IF i = 0 THEN i = LEN(text$)
  928.     WordBreak = i
  929.  
  930.  
  931.  
  932. SUB ClearTextArea (Handle AS INTEGER)
  933.     u = UBOUND(TextHandles)
  934.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  935.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  936.     IF TextHandles(Handle).SavedBackground THEN
  937.         w = TextHandles(Handle).w
  938.         h = TextHandles(Handle).h
  939.         x1 = TextHandles(Handle).ScreenX
  940.         y1 = TextHandles(Handle).ScreenY
  941.         x2 = x1 + w - 1
  942.         y2 = y1 + h - 1
  943.         blend = _BLEND
  944.         _DONTBLEND
  945.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  946.         IF blend THEN _BLEND
  947.     END IF
  948.     DrawTextArea Handle
  949.  
  950.  
  951.  
  952. SUB DrawTextArea (Handle AS INTEGER)
  953.     u = UBOUND(TextHandles)
  954.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  955.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  956.     w = TextHandles(Handle).w
  957.     h = TextHandles(Handle).h
  958.     x1 = TextHandles(Handle).ScreenX
  959.     y1 = TextHandles(Handle).ScreenY
  960.     x2 = x1 + w - 1
  961.     y2 = y1 + h - 1
  962.  
  963.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  964.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  965.  
  966.  
  967.  
  968. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  969.     u = UBOUND(TextHandles)
  970.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  971.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  972.     TextHandles(Handle).FrameColor = FrameColor
  973.     TextHandles(Handle).BackColor = BackColor
  974.  
  975.  
  976.  
  977. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  978.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  979.     x2 = tx2: y2 = ty2
  980.     IF x1 > x2 THEN SWAP x1, x2
  981.     IF y1 > y2 THEN SWAP y1, y2
  982.     w = x2 - x1 + 1
  983.     h = y2 - y1 + 1
  984.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  985.     'Error checking for if the user sends coordinates which are off the screen
  986.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  987.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  988.  
  989.     u = UBOUND(TextHandles)
  990.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  991.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  992.     NEXT
  993.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  994.         Handle = u + 1
  995.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  996.     END IF
  997.     TextHandles(Handle).x1 = x1
  998.     TextHandles(Handle).y1 = y1
  999.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  1000.     TextHandles(Handle).InUse = True
  1001.     TextHandles(Handle).Xpos = 0
  1002.     TextHandles(Handle).Ypos = 1
  1003.     TextHandles(Handle).UpdateMethod = NewLine
  1004.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  1005.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  1006.  
  1007.     IF SaveBackground THEN
  1008.         imagehandle = _NEWIMAGE(w, h, 32)
  1009.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1010.         TextHandles(Handle).SavedBackground = imagehandle
  1011.     END IF
  1012.     TextHandles(Handle).ScreenX = x1
  1013.     TextHandles(Handle).ScreenY = y1
  1014.  
  1015.     NewTextArea% = Handle
  1016.  
  1017. SUB FreeTextArea (Handle AS INTEGER)
  1018.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  1019.         IF TextHandles(Handle).InUse THEN
  1020.             TextHandles(Handle).InUse = False
  1021.             IF TextHandles(Handle).SavedBackground THEN
  1022.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  1023.                     w = TextHandles(Handle).w
  1024.                     h = TextHandles(Handle).h
  1025.                     x1 = TextHandles(Handle).ScreenX
  1026.                     y1 = TextHandles(Handle).ScreenY
  1027.                     x2 = x1 + w - 1
  1028.                     y2 = y1 + h - 1
  1029.                     blend = _BLEND
  1030.                     _DONTBLEND
  1031.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1032.                     IF blend THEN _BLEND
  1033.                 END IF
  1034.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  1035.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  1036.             END IF
  1037.         ELSE
  1038.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  1039.         END IF
  1040.     ELSE
  1041.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  1042.     END IF
  1043.  
  1044. SUB HideFrame (Handle AS INTEGER)
  1045.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  1046.         TextHandles(Handle).HideFrame = -1
  1047.         w = TextHandles(Handle).w
  1048.         h = TextHandles(Handle).h
  1049.         x1 = TextHandles(Handle).ScreenX
  1050.         y1 = TextHandles(Handle).ScreenY
  1051.         x2 = x1 + w - 1
  1052.         y2 = y1 + h - 1
  1053.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1054.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1055.         IF TextHandles(Handle).SavedBackground THEN
  1056.             blend = _BLEND
  1057.             _DONTBLEND
  1058.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1059.             _FREEIMAGE TextHandles(Handle).SavedBackground
  1060.             IF blend THEN _BLEND
  1061.         END IF
  1062.         TextHandles(Handle).SavedBackground = imagehandle
  1063.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  1064.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  1065.     END IF
  1066.  
  1067. SUB RestoreFrame (Handle AS INTEGER)
  1068.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  1069.         TextHandles(Handle).HideFrame = 0
  1070.         w = TextHandles(Handle).w
  1071.         h = TextHandles(Handle).h
  1072.         x1 = TextHandles(Handle).ScreenX
  1073.         y1 = TextHandles(Handle).ScreenY
  1074.         x2 = x1 + w - 1
  1075.         y2 = y1 + h - 1
  1076.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1077.         blend = _BLEND
  1078.         _DONTBLEND
  1079.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1080.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  1081.         _FREEIMAGE TextHandles(Handle).SavedBackground
  1082.         IF blend THEN _BLEND
  1083.         TextHandles(Handle).SavedBackground = imagehandle
  1084.         TextHandles(Handle).x1 = x1 'When the frames are frames are restored, we need to recalculate our print position
  1085.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  1086.     END IF
  1087.  
  1088. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  1089.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  1090.     u = UBOUND(TextHandles)
  1091.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1092.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1093.     HideFrame Handle
  1094.     TextHandles(Handle).ScreenX = x1
  1095.     TextHandles(Handle).ScreenY = y1
  1096.     RestoreFrame Handle
  1097.  

I was working on implementing the custom icon system for when you defeat a monster or find an item (you can choose your own icons and colors to represent them!), when I read your post and thought, "I too don't see as well as I used to back in the day.  It'd be nice to have some sort of system in place so we can scale the screen to make it easier to read and interact with..."

So, I came up with this little concept, which was amazingly simple to implement actually!

Try it out, and then use SHIFT plus the arrow keys to scale the screen.  For now, only the map is zoomable, but I figure changing the fonts to scale as well shouldn't be that difficult to do.  After all, the text frames are pretty self-contained already.  It should be just a simple change to fix them to scale font size up and down for us...

In the end, I imagine Shift-Up and Shift-Down will scale the map, while Shift-left and Shift-Right will scale the text.  There's no reason for us old fogies to strain our eyes when playing a game, when we can just zoom in and make things much more visible for us, as needed!  :D

******************

(Also, if you do kill a monster now, you can see the start of my concept for the icon representation system.  You can't actually choose any icons yet, but you can freeze up the game nice and good while waiting for the next update to the game which will offer the option fully...)
Title: Re: Rogue-Like (work in progress)
Post by: bplus on September 12, 2019, 10:22:07 am
oops! I killed a rat just by wandering around sorry Mr Rat ;-)
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 12, 2019, 10:25:31 am
The scaling routines now works as I envisioned -- we can now scale both the map and the text, by using the SHIFT and ARROW keys.

SHIFT-UP -- increase the screen zoom.
SHIFT-DOWN -- decrease the screen zoom.
SHIFT-LEFT -- increase the text font.
SHIFT-DOWN -- decrease the text font.

Code: QB64: [Select]
  1. DEFLNG A-Z 'default to long instead of single
  2. TYPE TextArea
  3.     InUse AS INTEGER
  4.     x1 AS LONG 'left
  5.     y1 AS LONG 'top
  6.     w AS LONG 'width
  7.     h AS LONG 'height
  8.     FrameColor AS _UNSIGNED LONG
  9.     BackColor AS _UNSIGNED LONG
  10.     Xpos AS INTEGER
  11.     Ypos AS INTEGER
  12.     VerticalAlignment AS INTEGER
  13.     Justification AS INTEGER
  14.     UpdateMethod AS INTEGER
  15.     TextColor AS _UNSIGNED LONG
  16.     TextBackgroundColor AS _UNSIGNED LONG
  17.     SavedBackground AS INTEGER
  18.     HideFrame AS INTEGER
  19.     ScreenX AS INTEGER
  20.     ScreenY AS INTEGER
  21.     Font AS LONG 'NEW! Change fonts for each independent font area
  22.  
  23. REDIM SHARED TextHandles(0) AS TextArea
  24.  
  25. CONST True = -1, False = 0
  26. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  27. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  28. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  29. '********************************************************
  30. '* Text Frames before this line
  31. '********************************************************
  32.  
  33.  
  34.  
  35.  
  36.  
  37.  
  38. _CONSOLE ON 'for debugging purposes while making/testing things
  39.  
  40. TYPE Damage_Type
  41.     Low AS INTEGER
  42.     High AS INTEGER
  43.  
  44. TYPE Light_Type
  45.     Name AS STRING * 20
  46.     Reach AS _UNSIGNED _BYTE
  47.     Left AS _UNSIGNED _BYTE
  48.  
  49. TYPE Weapon_Type
  50.     Name AS STRING * 20
  51.     Reach AS _UNSIGNED _BYTE
  52.     Damage AS Damage_Type
  53.     HitBonus AS _UNSIGNED _BYTE
  54.     DamageBonus AS _UNSIGNED _BYTE
  55.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  56.  
  57. TYPE Armor_Type
  58.     Name AS STRING * 20
  59.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  60.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  61.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  62.  
  63. TYPE Hero_Type
  64.     Name AS STRING * 20
  65.     Life AS Damage_Type
  66.     Level AS _UNSIGNED _BYTE
  67.     EXP_Earned AS LONG
  68.     EXP_Needed AS LONG
  69.     Light AS Light_Type
  70.     Weapon1 AS Weapon_Type
  71.     Weapon2 AS Weapon_Type
  72.     Armor AS Armor_Type
  73.  
  74. TYPE Map_Identifer_TYPE
  75.     Symbol AS _UNSIGNED _BYTE
  76.  
  77. TYPE Monster_TYPE
  78.     Name AS STRING * 20
  79.     Life AS Damage_Type
  80.     Level AS INTEGER
  81.     ExpBonus AS INTEGER
  82.     Sight AS INTEGER
  83.     Hearing AS INTEGER
  84.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  85.     Weapon1 AS Weapon_Type
  86.     Weapon2 AS Weapon_Type
  87.     Armor AS Armor_Type
  88.     ID AS Map_Identifer_TYPE
  89.  
  90. TYPE Encounter_TYPE
  91.     Active AS INTEGER
  92.     X AS INTEGER
  93.     Y AS INTEGER
  94.     M AS INTEGER
  95.     Life AS INTEGER
  96.  
  97. REDIM SHARED Monster(100) AS Monster_TYPE
  98. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  99.  
  100. DIM SHARED Hero AS Hero_Type
  101. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  102. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  103. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  104. DIM SHARED Scale AS _FLOAT, WorkScreen AS LONG, DisplayScreen AS LONG
  105. DIM SHARED TextFont
  106.  
  107. WorkScreen = _NEWIMAGE(800, 600, 32)
  108. DisplayScreen = _NEWIMAGE(800, 700, 32)
  109. SCREEN DisplayScreen
  110. Scale = 1
  111.  
  112. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  113. '1 map is illuminated
  114. '2 map is uncovered
  115. '4 map is a wall
  116. '8 map is a pathway
  117. '16 map is a stairway
  118. '32 map is simply blocked (perhaps with a monster?)
  119. '64 map is secret (can not be uncovered)
  120.  
  121. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  122.  
  123.  
  124.  
  125.  
  126.  
  127. Init
  128. CreateMap 99, 74, 10
  129.  
  130.     DrawMap
  131.     DisplayCharacter
  132.     _DISPLAY
  133.     GetInput
  134.     MonstersTurn
  135.     CheckForHeroGrowth
  136.  
  137. SUB Init
  138.     Hero.Name = "Steve The Tester!"
  139.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  140.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  141.     Hero.Light.Name = "Magic Candle"
  142.     Hero.Light.Reach = 2: Hero.Light.Left = -1 'infinite
  143.     Hero.Weapon1.Name = "Bare Fist"
  144.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  145.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  146.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  147.     Hero.Weapon2.Name = "Magic Candle"
  148.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  149.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  150.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  151.     Hero.Armor.Name = "Naked"
  152.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  153.  
  154.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  155.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  156.     SetTextFont PrintArea, "courbd.ttf", 24
  157.     DrawTextArea PrintArea
  158.     SetPrintPositionX PrintArea, CenterJustify
  159.     SetPrintUpdate PrintArea, NewLine
  160.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  161.     SetTextFont PrintArea, "courbd.ttf", 18
  162.     PrintOut PrintArea, "created by STEVE!"
  163.     PrintOut PrintArea, ""
  164.     SetPrintPositionX PrintArea, LeftJustify
  165.     TextFont = 18
  166.  
  167. SUB CheckForHeroGrowth
  168.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  169.         BEEP
  170.         CLS
  171.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  172.         _DELAY 5
  173.         SYSTEM
  174.     END IF
  175.  
  176.  
  177. SUB DisplayCharacter
  178.     LINE (0, 601)-(229, 799), &HFF000000, BF
  179.     COLOR &HFFFFFFFF, 0
  180.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  181.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  182.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  183.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  184.  
  185.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  186.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  187.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  188.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  189.  
  190. SUB GetInput
  191.     DO
  192.         k = _KEYHIT: valid = -1
  193.         SELECT CASE k
  194.             CASE 18432 'up
  195.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  196.                     Scale = Scale + .1
  197.                     IF Scale > 4 THEN Scale = 4
  198.                 ELSE
  199.                     IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  200.                 END IF
  201.             CASE 19200: 'left
  202.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  203.                     TextFont = TextFont - 1
  204.                     IF TextFont < 8 THEN TextFont = 8
  205.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  206.                     ClearTextArea PrintArea
  207.                     SetPrintPosition PrintArea, 1, 1
  208.                     PrintOut PrintArea, "Font Size Changed"
  209.                 ELSE
  210.                     IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  211.                 END IF
  212.             CASE 20480: 'down
  213.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  214.                     Scale = Scale - .1
  215.                     IF Scale < 1 THEN Scale = 1
  216.                 ELSE
  217.                     IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  218.                 END IF
  219.             CASE 19712: 'right
  220.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  221.                     TextFont = TextFont + 1
  222.                     IF TextFont > 48 THEN TextFont = 48
  223.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  224.                     ClearTextArea PrintArea
  225.                     SetPrintPosition PrintArea, 1, 1
  226.                     PrintOut PrintArea, "Font Size Changed"
  227.                 ELSE
  228.                     IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  229.                 END IF
  230.             CASE 32 'space to just wait and skip a turn
  231.             CASE 60 ' "<" key
  232.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  233.                     Level = Level + 1
  234.                     CreateMap 99, 74, 10
  235.                     PathFind
  236.                 END IF
  237.             CASE ASC("+"), ASC("=")
  238.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  239.             CASE ASC("-"), ASC("_")
  240.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  241.             CASE ELSE
  242.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  243.         END SELECT
  244.         _LIMIT 60
  245.     LOOP UNTIL k AND valid
  246.     _KEYCLEAR 'one keystroke at a time
  247.  
  248. SUB Box (X, Y, Wide, High, Caption AS STRING, Kolor AS _UNSIGNED LONG)
  249.     LINE (X, Y)-STEP(Wide, High), Kolor, BF
  250.     LINE (X, Y)-STEP(Wide, High), &HFFFFFFFF, B
  251.     pw = _PRINTWIDTH(Caption): ph = _FONTHEIGHT
  252.     _PRINTSTRING (X + (Wide - pw) \ 2, Y + (High - ph) \ 2), Caption
  253.  
  254.  
  255. SUB MoveHero (MoveX, MoveY)
  256.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  257.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  258.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  259.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  260.             IF MoveX THEN Hero.X = Hero.X + MoveX
  261.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  262.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  263.             PathFind
  264.         ELSE
  265.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  266.             FOR i = 1 TO EncounterLimit
  267.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  268.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  269.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  270.                         Swing 0, i, 1 'swing with the right hand
  271.                     END IF
  272.                 END IF
  273.             NEXT
  274.         END IF
  275.     END IF
  276.  
  277. SUB Swing (Who, AtWhom, HandUsed)
  278.     M = Encounter(AtWhom).M
  279.     BaseChancetohit = 10 'base 10 chance to hit
  280.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  281.         IF Hero.Weapon1.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  282.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  283.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  284.             totalroll = 0
  285.             DO
  286.                 roll = INT(RND * 20) + 1
  287.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  288.                 IF roll = 20 THEN totalroll = totalroll + 20
  289.                 totalroll = totalroll + roll
  290.             LOOP UNTIL roll <> 1 AND roll <> 20
  291.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  292.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  293.             out$ = _TRIM$(Hero.Name)
  294.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  295.                 SetTextColor PrintArea, &HFFF000F0, 0
  296.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  297.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  298.                 Hero.Life.Low = Hero.Life.Low - damage
  299.             ELSEIF totalroll < Chancetohit THEN
  300.                 SetTextColor PrintArea, &HFFF0F000, 0
  301.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  302.             ELSEIF totalroll > Chancetohit + 20 THEN
  303.                 SetTextColor PrintArea, &HFF00FF00, 0
  304.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  305.                 damage = damage * (totalroll \ 20 + 1)
  306.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  307.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  308.             ELSEIF totalroll >= Chancetohit THEN
  309.                 SetTextColor PrintArea, &HFF00FF00, 0
  310.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  311.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  312.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  313.             END IF
  314.             PrintOut PrintArea, out$
  315.         END IF
  316.         IF Hero.Weapon2.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  317.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  318.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  319.             totalroll = 0
  320.             DO
  321.                 roll = INT(RND * 20) + 1
  322.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  323.                 IF roll = 20 THEN totalroll = totalroll + 20
  324.                 totalroll = totalroll + roll
  325.             LOOP UNTIL roll <> 1 AND roll <> 20
  326.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  327.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  328.             out$ = _TRIM$(Hero.Name)
  329.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  330.                 SetTextColor PrintArea, &HFFF000F0, 0
  331.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  332.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  333.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  334.                 Hero.Life.Low = Hero.Life.Low - damage
  335.             ELSEIF totalroll < Chancetohit THEN
  336.                 SetTextColor PrintArea, &HFFF0F000, 0
  337.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  338.             ELSEIF totalroll > Chancetohit + 20 THEN
  339.                 SetTextColor PrintArea, &HFF00FF00, 0
  340.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  341.                 damage = damage * (totalroll \ 20 + 1)
  342.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  343.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  344.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  345.             ELSEIF totalroll >= Chancetohit THEN
  346.                 SetTextColor PrintArea, &HFF00FF00, 0
  347.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  348.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  349.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  350.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  351.             END IF
  352.             PrintOut PrintArea, out$
  353.         END IF
  354.  
  355.         IF Encounter(AtWhom).Life <= 0 THEN 'the monster died!
  356.             SetTextColor PrintArea, &HFFFF0000, 0
  357.             out$ = _TRIM$(Monster(M).Name) + " died!"
  358.             PrintOut PrintArea, out$
  359.             Encounter(AtWhom).Active = 0
  360.             Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  361.             MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) = MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  362.  
  363.             RedSet = 128: 'GreenSet = 128: BlueSet = 128
  364.             IF Monster(M).ID.Symbol = 63 THEN 'it's a first time kill!
  365.                 TempArea = NewTextArea(200, 100, 600, 500, True)
  366.                 ColorTextArea TempArea, _RGB32(255, 255, 255), _RGB32(128, 128, 128)
  367.                 DrawTextArea TempArea
  368.                 SetPrintPositionX TempArea, CenterJustify
  369.                 SetPrintUpdate TempArea, NewLine
  370.                 SetTextColor TempArea, &HFFFFFFFF, 0
  371.                 out$ = "CONGRATULATIONS!  You just killed " + _TRIM$(Monster(M).Name) + " for the first time!"
  372.                 PrintOut TempArea, out$
  373.                 DO
  374.                     SetTextColor TempArea, _RGB32(RedSet, GreenSet, BlueSet), 0
  375.                     COLOR _RGB32(RedSet, GreenSet, BlueSet), 0
  376.                     FOR y = 0 TO 15
  377.                         FOR x = 0 TO 15
  378.                             Box x * 16 + 275, y * 16 + 150, 16, 16, CHR$(y * 16 + x), _RGB32(200, 200, 200)
  379.                         NEXT
  380.                     NEXT
  381.                     Box 275, 425, 256, 20, "", _RGB32(200, 200, 200)
  382.                     Box 275, 425, RedSet, 20, "", _RGB32(255, 0, 0)
  383.                     Box 275, 450, 256, 20, "", _RGB32(200, 200, 200)
  384.                     Box 275, 450, GreenSet, 20, "", _RGB32(0, 256, 0)
  385.                     Box 275, 475, 256, 20, "", _RGB32(200, 200, 200)
  386.                     Box 275, 475, BlueSet, 20, "", _RGB32(0, 0, 256)
  387.                     _DISPLAY
  388.                     WHILE _MOUSEINPUT: WEND
  389.                     IF _MOUSEBUTTON(1) THEN
  390.                         IF _MOUSEX > 250 AND _MOUSEX < 556 THEN 'we're clicked at the right mouse coordinate
  391.                             SELECT CASE _MOUSEY
  392.                                 CASE 425 TO 445
  393.                                     RedSet = _MOUSEX - 275
  394.                                     IF RedSet < 0 THEN RedSet = 0
  395.                                     IF RedSet > 255 THEN RedSet = 255
  396.                                 CASE 450 TO 470
  397.                                     GreenSet = _MOUSEX - 275
  398.                                     IF GreenSet < 0 THEN GreenSet = 0
  399.                                     IF GreenSet > 255 THEN GreenSet = 255
  400.                                 CASE 475 TO 495
  401.                                     BlueSet = _MOUSEX - 275
  402.                                     IF BlueSet < 0 THEN BlueSet = 0
  403.                                     IF BlueSet > 255 THEN BlueSet = 255
  404.                             END SELECT
  405.                         END IF
  406.                     END IF
  407.                     _LIMIT 60
  408.                 LOOP
  409.  
  410.             END IF
  411.         END IF
  412.     ELSE 'it's a monster attacking
  413.  
  414.     END IF
  415.  
  416.  
  417. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  418.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  419.     D = Distance(MX, MY) 'distance from monster to the hero
  420.     E = Encounter(i).M 'the actual monster in question
  421.  
  422.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  423.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  424.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  425.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  426.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  427.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  428.             MoveMonster = -1
  429.         END IF
  430.     END IF
  431.  
  432.  
  433.  
  434. SUB MonstersTurn
  435.     FOR i = 1 TO EncounterLimit
  436.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  437.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  438.             D = Distance(MX, MY) 'distance from monster to the hero
  439.             E = Encounter(i).M 'the actual monster in question
  440.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  441.  
  442.                 attack = 0
  443.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  444.                     'insert attack code here
  445.  
  446.                     _TITLE "ATTACK!"
  447.                     _CONTINUE
  448.                 END IF
  449.                 IF D <= Monster(E).Weapon2.Reach THEN 'we're in reach for the monster to attack with their off hand.
  450.                     'insert attack code here
  451.                     _CONTINUE
  452.                 END IF
  453.  
  454.                 IF attack = 0 THEN 'if the monster didn't attack, it can now move towards the hero.
  455.                     IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  456.                         IF D > Distance(MX - 1, MY) THEN
  457.                             IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  458.                         END IF
  459.                     END IF
  460.                     IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  461.                         IF D > Distance(MX, MY - 1) THEN
  462.                             IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  463.                         END IF
  464.                     END IF
  465.                     IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  466.                         IF D > Distance(MX + 1, MY) THEN
  467.                             IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  468.                         END IF
  469.                     END IF
  470.                     IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  471.                         IF D > Distance(MX, MY + 1) THEN
  472.                             IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  473.                         END IF
  474.                     END IF
  475.                 END IF
  476.             END IF
  477.         END IF
  478.  
  479.     NEXT
  480.  
  481.  
  482.  
  483.  
  484. SUB DrawMap
  485.     _DEST WorkScreen
  486.     _FONT 8
  487.     LINE (0, 0)-(800, 600), &HFF0000FF, BF 'clear the map
  488.     FOR Y = 0 TO YH
  489.         FOR X = 0 TO XH
  490.             IF Distance(X, Y) <= Hero.Light.Reach THEN 'It's close enough to check for illumination
  491.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  492.             END IF
  493.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  494.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  495.                     COLOR &HFF000000, 0
  496.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  497.                 END IF
  498.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  499.                     COLOR &HFF000000, &HFF777777
  500.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  501.                 END IF
  502.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  503.                     COLOR &HFF00FF00, &HFFFFFF00
  504.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  505.                 END IF
  506.             END IF
  507.             'note: highlighting for the light should come AFTER the map is drawn
  508.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  509.                 COLOR &H40FFFF00, 0
  510.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  511.                 MapArray(X, Y) = MapArray(X, Y) - 1
  512.                 FOR i = 1 TO EncounterLimit
  513.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  514.                         E = Encounter(i).M
  515.                         COLOR Monster(E).ID.Color
  516.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(Monster(E).ID.Symbol)
  517.                     END IF
  518.                 NEXT
  519.  
  520.             END IF
  521.         NEXT
  522.     NEXT
  523.     COLOR &HFFFFFF00, 0 'Yellow Hero
  524.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  525.     XOffset## = 400 / Scale
  526.     YOffset## = 300 / Scale
  527.     CenterX = Hero.X * 8 'convert hero coordinate to grid coordinate
  528.     CenterY = Hero.Y * 8
  529.     _DEST DisplayScreen
  530.     LINE (0, 0)-(800, 600), &HFF0000FF, BF 'clear the map
  531.     _PUTIMAGE (0, 0)-(800, 600), WorkScreen, DisplayScreen, (CenterX - XOffset##, CenterY - YOffset##)-(CenterX + XOffset##, CenterY + YOffset##)
  532.  
  533.  
  534.  
  535.  
  536.  
  537. SUB CreateMap (XLimit, YLimit, Rooms)
  538.     ERASE MapArray 'clear the old map and reset everything to 0
  539.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  540.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  541.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  542.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  543.  
  544.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  545.  
  546.     FOR i = 1 TO Rooms
  547.         DO
  548.             RoomSize = INT(RND * 9) + 2
  549.             RoomX = INT(RND * (XLimit - RoomSize))
  550.             RoomY = INT(RND * (YLimit - RoomSize))
  551.             'test for positioning
  552.             good = -1 'it's good starting out
  553.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  554.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  555.             NEXT X, Y
  556.         LOOP UNTIL good
  557.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  558.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  559.         NEXT X, Y
  560.         RoomCenterX(i) = RoomX + .5 * RoomSize
  561.         RoomCenterY(i) = RoomY + .5 * RoomSize
  562.     NEXT
  563.     FOR i = 1 TO Rooms - 1
  564.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  565.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  566.         DO UNTIL StartX = EndX AND StartY = EndY
  567.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  568.             Meander = 10
  569.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  570.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  571.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  572.                     Ychange = 0
  573.                 ELSE
  574.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  575.                     Ychange = 0
  576.                 END IF
  577.             ELSE
  578.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  579.                     Ychange = SGN(EndY - StartY)
  580.                     XChange = 0
  581.                 ELSE
  582.                     Ychange = INT(RND * 3) - 1
  583.                     XChange = 0
  584.                 END IF
  585.             END IF
  586.             StartX = StartX + XChange
  587.             StartY = StartY + Ychange
  588.             IF StartX < 0 THEN StartX = 0
  589.             IF StartY < 0 THEN StartY = 0
  590.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  591.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  592.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  593.         LOOP
  594.     NEXT
  595.     DO
  596.         Hero.X = INT(RND * XLimit + 1)
  597.         Hero.Y = INT(RND * YLimit + 1)
  598.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  599.     MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  600.     DO
  601.         X = INT(RND * XLimit + 1)
  602.         Y = INT(RND * YLimit + 1)
  603.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  604.     MapArray(X, Y) = MapArray(X, Y) OR 16
  605.     PathFind
  606.     EncounterLimit = INT(RND * 6) + 5
  607.     FOR i = 1 TO EncounterLimit
  608.         Encounter(i).M = RandomMonster
  609.         Encounter(i).Active = -1
  610.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  611.         valid = -1
  612.         DO
  613.             Encounter(i).X = INT(RND * XLimit + 1)
  614.             Encounter(i).Y = INT(RND * YLimit + 1)
  615.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  616.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  617.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  618.     NEXT
  619.  
  620. SUB PathFind
  621.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  622.     DIM pass AS _UNSIGNED _BYTE
  623.     m = _MEM(Distance()): m1 = _MEM(Temp())
  624.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  625.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  626.     Temp(Hero.X, Hero.Y) = 0
  627.     pass = 0
  628.     DO
  629.         changed = 0
  630.         y = 0
  631.         DO
  632.             x = 0
  633.             DO
  634.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  635.                     IF x < XH THEN
  636.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  637.                     END IF
  638.                     IF x > 0 THEN
  639.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  640.                     END IF
  641.                     IF y < YH THEN
  642.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  643.                     END IF
  644.                     IF y > 0 THEN
  645.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  646.                     END IF
  647.                 END IF
  648.                 x = x + 1
  649.             LOOP UNTIL x > XH
  650.             y = y + 1
  651.         LOOP UNTIL y > YH
  652.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  653.         pass = pass + 1
  654.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  655.     Distance(Hero.X, Hero.Y) = 0
  656.  
  657. FUNCTION RandomMonster
  658.     'Shared variable level tells us what level of the dungeon we're on.
  659.     STATIC MC, DS 'monster count and data set
  660.     IF NOT DS THEN
  661.         DS = -1
  662.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  663.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  664.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  665.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  666.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  667.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  668.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  669.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  670.         'Monster(1).Armor.Name = ""
  671.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  672.  
  673.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  674.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  675.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  676.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  677.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  678.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  679.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  680.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  681.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  682.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  683.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  684.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  685.         NEXT
  686.     END IF
  687.     SELECT CASE Level 'the starting level
  688.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  689.     END SELECT
  690.     RandomMonster = INT(RND * MC) + 1
  691.  
  692.  
  693. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  694. '# SUBroutines and FUNCTIONs below #'
  695. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  696. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  697.     u = UBOUND(TextHandles)
  698.     Handle = WhichHandle
  699.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  700.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  701.     Where = TextHandles(Handle).VerticalAlignment
  702.     How = TextHandles(Handle).Justification
  703.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  704.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  705.  
  706.  
  707. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  708.     'WhichHandle is the handle which designates which text area we want to use
  709.     'Where is where we want it to go in that text area
  710.     '  -- Online prints the text to the current print position line in that text area.
  711.     '  -- CenterLine centers the text to the center of that text area.
  712.     '  -- any other value will print to that line positon in that particular box.
  713.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  714.     'What is the text that we want to print in our text area
  715.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  716.  
  717.     D = _DEST: S = _SOURCE
  718.     F = _FONT
  719.  
  720.     u = UBOUND(TextHandles)
  721.     Handle = WhichHandle
  722.  
  723.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  724.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  725.     IF TextHandles(Handle).HideFrame THEN
  726.         _DEST TextHandles(Handle).SavedBackground
  727.         _SOURCE TextHandles(Handle).SavedBackground
  728.     END IF
  729.     _FONT TextHandles(Handle).Font
  730.     fh = _FONTHEIGHT: pw = _PRINTWIDTH(What)
  731.     h = TextHandles(Handle).h - 4: w = TextHandles(Handle).w - 4
  732.  
  733.     SELECT CASE Where
  734.         CASE BottomLine
  735.             y = h \ fh
  736.         CASE OnLine
  737.             y = TextHandles(Handle).Ypos
  738.             IF y = 0 THEN y = 1
  739.         CASE CenterLine
  740.             linesused = 0
  741.             tpw = pw: tw = w: tWhat$ = What
  742.             DO UNTIL tpw <= tw
  743.                 textallowed = WordBreak(LEFT$(tWhat$, w \ _FONTWIDTH))
  744.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  745.                 linesused = linesused + 1
  746.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  747.                 tpw = _PRINTWIDTH(tWhat$)
  748.             LOOP
  749.             linesused = linesused + 1
  750.             py = (h - linesused * fh) \ 2
  751.             y = py \ fh + 1
  752.             IF y < 1 THEN y = 1
  753.         CASE ELSE
  754.             y = Where
  755.     END SELECT
  756.  
  757.     IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  758.     blend = _BLEND
  759.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  760.         'first let's get a temp image handle for the existing area of the screen.
  761.         x1 = TextHandles(Handle).x1 + 2
  762.         y1 = TextHandles(Handle).y1 + 2
  763.         x2 = TextHandles(Handle).x1 + w
  764.         y2 = TextHandles(Handle).y1 + h
  765.         nh = y2 - y1 + 1 - fh
  766.         nw = x2 - x1 + 1
  767.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  768.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  769.         DrawTextArea Handle
  770.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  771.         y = y - 1
  772.     LOOP
  773.     IF blend THEN _BLEND
  774.  
  775.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  776.  
  777.     SELECT CASE How
  778.         CASE LeftJustify
  779.             x = 0
  780.             IF pw > w THEN
  781.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  782.                 text$ = RTRIM$(LEFT$(What, textallowed))
  783.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  784.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  785.             ELSE
  786.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  787.                 finished = -1
  788.             END IF
  789.         CASE CenterJustify
  790.             IF pw > w THEN
  791.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  792.                 text$ = RTRIM$(LEFT$(What, textallowed))
  793.                 x = (w - _PRINTWIDTH(text$)) \ 2
  794.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  795.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  796.             ELSE
  797.                 x = (w - pw) \ 2
  798.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  799.                 finished = -1
  800.             END IF
  801.         CASE RightJustify
  802.             IF pw > w THEN
  803.                 textallowed = WordBreak(LEFT$(What, w \ _FONTWIDTH))
  804.                 text$ = RTRIM$(LEFT$(What, textallowed))
  805.                 x = w - _PRINTWIDTH(text$)
  806.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  807.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  808.             ELSE
  809.                 x = w - pw
  810.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  811.                 finished = -1
  812.             END IF
  813.         CASE NoJustify
  814.             x = TextHandles(Handle).Xpos
  815.             firstlinelimit = (w - x) \ _FONTWIDTH 'the limit of characters on the first line
  816.             IF LEN(What) > firstlinelimit THEN
  817.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  818.                 text$ = RTRIM$(LEFT$(What, textallowed))
  819.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  820.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  821.             ELSE
  822.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  823.                 finished = -1
  824.             END IF
  825.     END SELECT
  826.  
  827.     IF finished THEN
  828.         SELECT CASE TextHandles(Handle).UpdateMethod
  829.             CASE NoUpdate 'We don't update the position at all.
  830.             CASE DoUpdate
  831.                 TextHandles(Handle).Xpos = x + pw
  832.                 TextHandles(Handle).Ypos = y
  833.             CASE NewLine
  834.                 TextHandles(Handle).Ypos = y + 1
  835.                 TextHandles(Handle).Xpos = 1
  836.         END SELECT
  837.         _FONT F
  838.         _DEST D: _SOURCE S
  839.         COLOR FG, BG
  840.     END IF
  841.  
  842. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  843.     u = UBOUND(TextHandles)
  844.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  845.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  846.     TextHandles(Handle).TextColor = Foreground
  847.  
  848.  
  849. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  850.     u = UBOUND(TextHandles)
  851.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  852.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  853.     TextHandles(Handle).TextBackgroundColor = Background
  854.  
  855. SUB SetTextFont (Handle AS INTEGER, FontName AS STRING, FontSize AS INTEGER)
  856.     u = UBOUND(TextHandles)
  857.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  858.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  859.     SELECT CASE TextHandles(Handle).Font
  860.         CASE 8, 9, 14, 15, 16, 17 'In built QB64 fonts.  We don't need to free them.
  861.         CASE IS > 1
  862.             'we have the font already in use
  863.             'REMOVE THIS CONDITION IF NECESSARY, AND MANUALLY FREE/RELEASE FONTS AS ABLE!!!
  864.             _FREEFONT TextHandles(Handle).Font 'if it's in use elsewhere, this *WILL* toss an error.
  865.     END SELECT
  866.  
  867.     temp = _LOADFONT(FontName, FontSize, "monospace")
  868.     IF temp > 1 THEN
  869.         TextHandles(Handle).Font = temp
  870.     ELSE
  871.         TextHandles(Handle).Font = 16 'default to font 16, in case
  872.     END IF
  873.  
  874.  
  875. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  876.     u = UBOUND(TextHandles)
  877.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  878.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  879.     TextHandles(Handle).TextColor = Foreground
  880.     TextHandles(Handle).TextBackgroundColor = Background
  881.  
  882.  
  883. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  884.     u = UBOUND(TextHandles)
  885.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  886.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  887.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  888.     TextHandles(Handle).UpdateMethod = Method
  889.  
  890.  
  891. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  892.     u = UBOUND(TextHandles)
  893.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  894.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  895.     SELECT CASE Y
  896.         CASE BottomLine
  897.             TextHandles(Handle).VerticalAlignment = -2
  898.         CASE CenterLine
  899.             TextHandles(Handle).VerticalAlignment = -1
  900.         CASE ELSE
  901.             TextHandles(Handle).VerticalAlignment = 0
  902.     END SELECT
  903.     IF X < 1 AND X > -4 THEN
  904.         TextHandles(Handle).Justification = X
  905.     ELSE
  906.         TextHandles(Handle).Xpos = X
  907.     END IF
  908.     IF Y < 1 THEN EXIT SUB
  909.     TextHandles(Handle).Ypos = Y
  910.  
  911. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  912.     u = UBOUND(TextHandles)
  913.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  914.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  915.     IF X < 1 AND X > -4 THEN
  916.         TextHandles(Handle).Justification = X
  917.     ELSE
  918.         TextHandles(Handle).Xpos = X
  919.     END IF
  920.  
  921. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  922.     u = UBOUND(TextHandles)
  923.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  924.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  925.     SELECT CASE Y
  926.         CASE BottomLine
  927.             TextHandles(Handle).VerticalAlignment = -2
  928.         CASE CenterLine
  929.             TextHandles(Handle).VerticalAlignment = -1
  930.         CASE ELSE
  931.             TextHandles(Handle).VerticalAlignment = 0
  932.     END SELECT
  933.     IF Y < 1 THEN EXIT SUB
  934.     TextHandles(Handle).Ypos = Y
  935.  
  936.  
  937. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  938.     u = UBOUND(TextHandles)
  939.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  940.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  941.     GetPrintPositionY = TextHandles(Handle).Ypos
  942.  
  943. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  944.     u = UBOUND(TextHandles)
  945.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  946.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  947.     GetPrintPositionX = TextHandles(Handle).Xpos
  948.  
  949.  
  950.  
  951. FUNCTION WordBreak (text$)
  952.     CONST Breaks = " ;,.?!-"
  953.     FOR i = LEN(text$) TO 0 STEP -1
  954.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  955.         loopcount = loopcount + 1
  956.     NEXT
  957.     IF i = 0 THEN i = LEN(text$)
  958.     WordBreak = i
  959.  
  960.  
  961.  
  962. SUB ClearTextArea (Handle AS INTEGER)
  963.     u = UBOUND(TextHandles)
  964.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  965.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  966.     IF TextHandles(Handle).SavedBackground THEN
  967.         w = TextHandles(Handle).w
  968.         h = TextHandles(Handle).h
  969.         x1 = TextHandles(Handle).ScreenX
  970.         y1 = TextHandles(Handle).ScreenY
  971.         x2 = x1 + w - 1
  972.         y2 = y1 + h - 1
  973.         blend = _BLEND
  974.         _DONTBLEND
  975.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  976.         IF blend THEN _BLEND
  977.     END IF
  978.     DrawTextArea Handle
  979.  
  980.  
  981.  
  982. SUB DrawTextArea (Handle AS INTEGER)
  983.     u = UBOUND(TextHandles)
  984.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  985.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  986.     w = TextHandles(Handle).w
  987.     h = TextHandles(Handle).h
  988.     x1 = TextHandles(Handle).ScreenX
  989.     y1 = TextHandles(Handle).ScreenY
  990.     x2 = x1 + w - 1
  991.     y2 = y1 + h - 1
  992.  
  993.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  994.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  995.  
  996.  
  997.  
  998. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  999.     u = UBOUND(TextHandles)
  1000.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1001.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1002.     TextHandles(Handle).FrameColor = FrameColor
  1003.     TextHandles(Handle).BackColor = BackColor
  1004.  
  1005.  
  1006.  
  1007. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  1008.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  1009.     x2 = tx2: y2 = ty2
  1010.     IF x1 > x2 THEN SWAP x1, x2
  1011.     IF y1 > y2 THEN SWAP y1, y2
  1012.     w = x2 - x1 + 1
  1013.     h = y2 - y1 + 1
  1014.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  1015.     'Error checking for if the user sends coordinates which are off the screen
  1016.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  1017.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  1018.  
  1019.     u = UBOUND(TextHandles)
  1020.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  1021.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  1022.     NEXT
  1023.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  1024.         Handle = u + 1
  1025.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  1026.     END IF
  1027.     TextHandles(Handle).x1 = x1
  1028.     TextHandles(Handle).y1 = y1
  1029.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  1030.     TextHandles(Handle).InUse = True
  1031.     TextHandles(Handle).Xpos = 0
  1032.     TextHandles(Handle).Ypos = 1
  1033.     TextHandles(Handle).UpdateMethod = NewLine
  1034.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  1035.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  1036.  
  1037.     IF SaveBackground THEN
  1038.         imagehandle = _NEWIMAGE(w, h, 32)
  1039.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1040.         TextHandles(Handle).SavedBackground = imagehandle
  1041.     END IF
  1042.     TextHandles(Handle).ScreenX = x1
  1043.     TextHandles(Handle).ScreenY = y1
  1044.     TextHandles(Handle).Font = 16 'default to font 16
  1045.     NewTextArea% = Handle
  1046.  
  1047. SUB FreeTextArea (Handle AS INTEGER)
  1048.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  1049.         IF TextHandles(Handle).InUse THEN
  1050.             TextHandles(Handle).InUse = False
  1051.             IF TextHandles(Handle).SavedBackground THEN
  1052.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  1053.                     w = TextHandles(Handle).w
  1054.                     h = TextHandles(Handle).h
  1055.                     x1 = TextHandles(Handle).ScreenX
  1056.                     y1 = TextHandles(Handle).ScreenY
  1057.                     x2 = x1 + w - 1
  1058.                     y2 = y1 + h - 1
  1059.                     blend = _BLEND
  1060.                     _DONTBLEND
  1061.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1062.                     IF blend THEN _BLEND
  1063.                 END IF
  1064.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  1065.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  1066.             END IF
  1067.         ELSE
  1068.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  1069.         END IF
  1070.     ELSE
  1071.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  1072.     END IF
  1073.  
  1074. SUB HideFrame (Handle AS INTEGER)
  1075.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  1076.         TextHandles(Handle).HideFrame = -1
  1077.         w = TextHandles(Handle).w
  1078.         h = TextHandles(Handle).h
  1079.         x1 = TextHandles(Handle).ScreenX
  1080.         y1 = TextHandles(Handle).ScreenY
  1081.         x2 = x1 + w - 1
  1082.         y2 = y1 + h - 1
  1083.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1084.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1085.         IF TextHandles(Handle).SavedBackground THEN
  1086.             blend = _BLEND
  1087.             _DONTBLEND
  1088.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1089.             _FREEIMAGE TextHandles(Handle).SavedBackground
  1090.             IF blend THEN _BLEND
  1091.         END IF
  1092.         TextHandles(Handle).SavedBackground = imagehandle
  1093.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  1094.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  1095.     END IF
  1096.  
  1097. SUB RestoreFrame (Handle AS INTEGER)
  1098.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  1099.         TextHandles(Handle).HideFrame = 0
  1100.         w = TextHandles(Handle).w
  1101.         h = TextHandles(Handle).h
  1102.         x1 = TextHandles(Handle).ScreenX
  1103.         y1 = TextHandles(Handle).ScreenY
  1104.         x2 = x1 + w - 1
  1105.         y2 = y1 + h - 1
  1106.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1107.         blend = _BLEND
  1108.         _DONTBLEND
  1109.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1110.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  1111.         _FREEIMAGE TextHandles(Handle).SavedBackground
  1112.         IF blend THEN _BLEND
  1113.         TextHandles(Handle).SavedBackground = imagehandle
  1114.         TextHandles(Handle).x1 = x1 'When the frames are frames are restored, we need to recalculate our print position
  1115.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  1116.     END IF
  1117.  
  1118. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  1119.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  1120.     u = UBOUND(TextHandles)
  1121.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1122.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1123.     HideFrame Handle
  1124.     TextHandles(Handle).ScreenX = x1
  1125.     TextHandles(Handle).ScreenY = y1
  1126.     RestoreFrame Handle

(I'll need to update the text frame library later so others can make use of the ability to use independent fonts with their independent text frames.  It was a quick addition, and one which I'm shocked that I hadn't made use of already!)



Next is finishing up the symbol association system so it'll stop locking the screen up on us, and we can actually complete the process as intended.  :D
Title: Re: Rogue-Like (work in progress)
Post by: bplus on September 12, 2019, 10:32:05 am
Hi Steve,

I am getting allot of "division by 0" errors in last update, font sizing works nice though (unless that is what is causing errors).
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 12, 2019, 10:44:27 am
Hi Steve,

I am getting allot of "division by 0" errors in last update, font sizing works nice though (unless that is what is causing errors).

Those are in debugging even now.  I dunno when I broke something, but I'll get it sorted out soon(tm), I hope. :)
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 12, 2019, 12:41:48 pm
Those are in debugging even now.  I dunno when I broke something, but I'll get it sorted out soon(tm), I hope. :)

The issue here isn't that I broke something (well, I did, but it was actually AGES ago...), it's that QB64 itself was broken. 

Waaaay back before version 1.0 was even released, I patched _LOADFONT so that us windows users can have it try and load automatically from our default font directory for us.  A lot of folks were having issues even remembering where their font folder was, and since it's usually in "C:\Windows\Fonts\", it seemed a simple enough thing to tell QB64, "If you don't find it with the program, take just a moment and see if it's in that standard default folder before tossing an error code..."

And, it really *is* a simple little thing to do -- the issue is I did it *at the wrong time* inside the source of QB64....

An user passes _LOADFONT a sting to process, like "MONOSPACE, BOLD, ITALIC", and _loadfont then breaks that down section by section to parse it....  By the time we get around to reapplying that "C:\Windows\Fonts" back to the font name, we've parsed our requirement list down to nothing, and the second pass gives us the font without that "MONOSPACE, BOLD, ITALIC" working for us.

The fix to QB64 is rather simple, and I've highlighted how to correct the issue permanently for folks here: https://www.qb64.org/forum/index.php?topic=1704.msg109428#msg109428

The fix for the program is even simpler:  Place the courbd.ttf in your QB64 folder and the issue goes away.

BUT, since I really can't trust that folks will have either of those fixes in place, I went in and altered the code so that we should now be error-proof with our routines.  The only issue here is that there WILL be a slight delay to text printing as we manually check letter by letter to get the max font size, when resizing, if the font size = 0.  If necessary, I'll optimize that out for us, but honestly, I'd prefer to just leave it in there to encourage folks to either patch libqb.cpp, or just put the dang "courbd.ttf" font file in the same folder with the source and be done with it.  :P

Fix (with delay for those who don't want to indulge in either true fix) is below:

Code: QB64: [Select]
  1. DEFLNG A-Z 'default to long instead of single
  2. TYPE TextArea
  3.     InUse AS INTEGER
  4.     x1 AS LONG 'left
  5.     y1 AS LONG 'top
  6.     w AS LONG 'width
  7.     h AS LONG 'height
  8.     FrameColor AS _UNSIGNED LONG
  9.     BackColor AS _UNSIGNED LONG
  10.     Xpos AS INTEGER
  11.     Ypos AS INTEGER
  12.     VerticalAlignment AS INTEGER
  13.     Justification AS INTEGER
  14.     UpdateMethod AS INTEGER
  15.     TextColor AS _UNSIGNED LONG
  16.     TextBackgroundColor AS _UNSIGNED LONG
  17.     SavedBackground AS INTEGER
  18.     HideFrame AS INTEGER
  19.     ScreenX AS INTEGER
  20.     ScreenY AS INTEGER
  21.     Font AS LONG 'NEW! Change fonts for each independent font area
  22.  
  23. REDIM SHARED TextHandles(0) AS TextArea
  24.  
  25. CONST True = -1, False = 0
  26. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  27. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  28. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  29. '********************************************************
  30. '* Text Frames before this line
  31. '********************************************************
  32.  
  33.  
  34.  
  35.  
  36.  
  37.  
  38. _CONSOLE ON 'for debugging purposes while making/testing things
  39.  
  40. TYPE Damage_Type
  41.     Low AS INTEGER
  42.     High AS INTEGER
  43.  
  44. TYPE Light_Type
  45.     Name AS STRING * 20
  46.     Reach AS _UNSIGNED _BYTE
  47.     Left AS _UNSIGNED _BYTE
  48.  
  49. TYPE Weapon_Type
  50.     Name AS STRING * 20
  51.     Reach AS _UNSIGNED _BYTE
  52.     Damage AS Damage_Type
  53.     HitBonus AS _UNSIGNED _BYTE
  54.     DamageBonus AS _UNSIGNED _BYTE
  55.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  56.  
  57. TYPE Armor_Type
  58.     Name AS STRING * 20
  59.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  60.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  61.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  62.  
  63. TYPE Hero_Type
  64.     Name AS STRING * 20
  65.     Life AS Damage_Type
  66.     Level AS _UNSIGNED _BYTE
  67.     EXP_Earned AS LONG
  68.     EXP_Needed AS LONG
  69.     Light AS Light_Type
  70.     Weapon1 AS Weapon_Type
  71.     Weapon2 AS Weapon_Type
  72.     Armor AS Armor_Type
  73.  
  74. TYPE Map_Identifer_TYPE
  75.     Symbol AS _UNSIGNED _BYTE
  76.  
  77. TYPE Monster_TYPE
  78.     Name AS STRING * 20
  79.     Life AS Damage_Type
  80.     Level AS INTEGER
  81.     ExpBonus AS INTEGER
  82.     Sight AS INTEGER
  83.     Hearing AS INTEGER
  84.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  85.     Weapon1 AS Weapon_Type
  86.     Weapon2 AS Weapon_Type
  87.     Armor AS Armor_Type
  88.     ID AS Map_Identifer_TYPE
  89.  
  90. TYPE Encounter_TYPE
  91.     Active AS INTEGER
  92.     X AS INTEGER
  93.     Y AS INTEGER
  94.     M AS INTEGER
  95.     Life AS INTEGER
  96.  
  97. REDIM SHARED Monster(100) AS Monster_TYPE
  98. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  99.  
  100. DIM SHARED Hero AS Hero_Type
  101. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  102. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  103. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  104. DIM SHARED Scale AS _FLOAT, WorkScreen AS LONG, DisplayScreen AS LONG
  105. DIM SHARED TextFont
  106.  
  107. WorkScreen = _NEWIMAGE(800, 600, 32)
  108. DisplayScreen = _NEWIMAGE(800, 700, 32)
  109. SCREEN DisplayScreen
  110. Scale = 1
  111.  
  112. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  113. '1 map is illuminated
  114. '2 map is uncovered
  115. '4 map is a wall
  116. '8 map is a pathway
  117. '16 map is a stairway
  118. '32 map is simply blocked (perhaps with a monster?)
  119. '64 map is secret (can not be uncovered)
  120.  
  121. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  122.  
  123.  
  124.  
  125. ON ERROR GOTO errorhandler
  126.  
  127. GOTO skiperrorhandler
  128. errorhandler:
  129.  
  130. PRINT ERR; "Error on "; _ERRORLINE
  131. _DEST DisplayScreen
  132. skiperrorhandler:
  133.  
  134. Init
  135. CreateMap 99, 74, 10
  136.  
  137.     DrawMap
  138.     DisplayCharacter
  139.     _DISPLAY
  140.     GetInput
  141.     MonstersTurn
  142.     CheckForHeroGrowth
  143.  
  144. SUB Init
  145.     Hero.Name = "Steve The Tester!"
  146.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  147.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  148.     Hero.Light.Name = "Magic Candle"
  149.     Hero.Light.Reach = 2: Hero.Light.Left = -1 'infinite
  150.     Hero.Weapon1.Name = "Bare Fist"
  151.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  152.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  153.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  154.     Hero.Weapon2.Name = "Magic Candle"
  155.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  156.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  157.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  158.     Hero.Armor.Name = "Naked"
  159.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  160.  
  161.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  162.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  163.     SetTextFont PrintArea, "courbd.ttf", 24
  164.     DrawTextArea PrintArea
  165.     SetPrintPositionX PrintArea, CenterJustify
  166.     SetPrintUpdate PrintArea, NewLine
  167.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  168.     SetTextFont PrintArea, "courbd.ttf", 18
  169.     PrintOut PrintArea, "created by STEVE!"
  170.     PrintOut PrintArea, ""
  171.     SetPrintPositionX PrintArea, LeftJustify
  172.     SetTextFont PrintArea, "courbd.ttf", 14
  173.     TextFont = 14
  174.  
  175. SUB CheckForHeroGrowth
  176.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  177.         BEEP
  178.         CLS
  179.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  180.         _DELAY 5
  181.         SYSTEM
  182.     END IF
  183.  
  184.  
  185. SUB DisplayCharacter
  186.     LINE (0, 601)-(229, 799), &HFF000000, BF
  187.     COLOR &HFFFFFFFF, 0
  188.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  189.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  190.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  191.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  192.  
  193.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  194.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  195.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  196.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  197.  
  198. SUB GetInput
  199.     DO
  200.         k = _KEYHIT: valid = -1
  201.         SELECT CASE k
  202.             CASE 18432 'up
  203.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  204.                     Scale = Scale + .1
  205.                     IF Scale > 4 THEN Scale = 4
  206.                 ELSE
  207.                     IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  208.                 END IF
  209.             CASE 19200: 'left
  210.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  211.                     TextFont = TextFont - 1
  212.                     IF TextFont < 8 THEN TextFont = 8
  213.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  214.                     ClearTextArea PrintArea
  215.                     SetPrintPosition PrintArea, 1, 1
  216.                     PrintOut PrintArea, "Font Size Changed"
  217.                 ELSE
  218.                     IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  219.                 END IF
  220.             CASE 20480: 'down
  221.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  222.                     Scale = Scale - .1
  223.                     IF Scale < 1 THEN Scale = 1
  224.                 ELSE
  225.                     IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  226.                 END IF
  227.             CASE 19712: 'right
  228.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  229.                     TextFont = TextFont + 1
  230.                     IF TextFont > 48 THEN TextFont = 48
  231.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  232.                     ClearTextArea PrintArea
  233.                     SetPrintPosition PrintArea, 1, 1
  234.                     PrintOut PrintArea, "Font Size Changed"
  235.                 ELSE
  236.                     IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  237.                 END IF
  238.             CASE 32 'space to just wait and skip a turn
  239.             CASE 60 ' "<" key
  240.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  241.                     Level = Level + 1
  242.                     CreateMap 99, 74, 10
  243.                     PathFind
  244.                 END IF
  245.             CASE ASC("+"), ASC("=")
  246.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  247.             CASE ASC("-"), ASC("_")
  248.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  249.             CASE ELSE
  250.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  251.         END SELECT
  252.         _LIMIT 60
  253.     LOOP UNTIL k AND valid
  254.     _KEYCLEAR 'one keystroke at a time
  255.  
  256. SUB Box (X, Y, Wide, High, Caption AS STRING, Kolor AS _UNSIGNED LONG)
  257.     LINE (X, Y)-STEP(Wide, High), Kolor, BF
  258.     LINE (X, Y)-STEP(Wide, High), &HFFFFFFFF, B
  259.     pw = _PRINTWIDTH(Caption): ph = _FONTHEIGHT
  260.     _PRINTSTRING (X + (Wide - pw) \ 2, Y + (High - ph) \ 2), Caption
  261.  
  262.  
  263. SUB MoveHero (MoveX, MoveY)
  264.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  265.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  266.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  267.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  268.             IF MoveX THEN Hero.X = Hero.X + MoveX
  269.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  270.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  271.             PathFind
  272.         ELSE
  273.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  274.  
  275.             FOR i = 1 TO EncounterLimit
  276.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  277.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  278.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  279.                         Swing 0, i 'hero swings at the monster
  280.                     END IF
  281.                 END IF
  282.             NEXT
  283.         END IF
  284.     END IF
  285.  
  286. SUB Swing (Who, AtWhom)
  287.     M = Encounter(AtWhom).M
  288.     BaseChancetohit = 10 'base 10 chance to hit
  289.  
  290.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  291.         IF Hero.Weapon1.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  292.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  293.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  294.             totalroll = 0
  295.             DO
  296.                 roll = INT(RND * 20) + 1
  297.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  298.                 IF roll = 20 THEN totalroll = totalroll + 20
  299.                 totalroll = totalroll + roll
  300.             LOOP UNTIL roll <> 1 AND roll <> 20
  301.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  302.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  303.             out$ = _TRIM$(Hero.Name)
  304.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  305.                 SetTextColor PrintArea, &HFFF000F0, 0
  306.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  307.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  308.                 Hero.Life.Low = Hero.Life.Low - damage
  309.             ELSEIF totalroll < Chancetohit THEN
  310.                 SetTextColor PrintArea, &HFFF0F000, 0
  311.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  312.             ELSEIF totalroll > Chancetohit + 20 THEN
  313.                 SetTextColor PrintArea, &HFF00FF00, 0
  314.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  315.                 damage = damage * (totalroll / 20 + 1)
  316.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  317.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  318.             ELSEIF totalroll >= Chancetohit THEN
  319.                 SetTextColor PrintArea, &HFF00FF00, 0
  320.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  321.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  322.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  323.             END IF
  324.             PrintOut PrintArea, out$
  325.         END IF
  326.         IF Hero.Weapon2.Reach > 0 THEN 'it's a weapon and not an utility object being held.
  327.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  328.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  329.             totalroll = 0
  330.             DO
  331.                 roll = INT(RND * 20) + 1
  332.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  333.                 IF roll = 20 THEN totalroll = totalroll + 20
  334.                 totalroll = totalroll + roll
  335.             LOOP UNTIL roll <> 1 AND roll <> 20
  336.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  337.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  338.             out$ = _TRIM$(Hero.Name)
  339.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  340.                 SetTextColor PrintArea, &HFFF000F0, 0
  341.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  342.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  343.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  344.                 Hero.Life.Low = Hero.Life.Low - damage
  345.             ELSEIF totalroll < Chancetohit THEN
  346.                 SetTextColor PrintArea, &HFFF0F000, 0
  347.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  348.             ELSEIF totalroll > Chancetohit + 20 THEN
  349.                 SetTextColor PrintArea, &HFF00FF00, 0
  350.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  351.                 damage = damage * (totalroll / 20 + 1)
  352.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  353.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  354.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  355.             ELSEIF totalroll >= Chancetohit THEN
  356.                 SetTextColor PrintArea, &HFF00FF00, 0
  357.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  358.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  359.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  360.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  361.             END IF
  362.             PrintOut PrintArea, out$
  363.         END IF
  364.  
  365.         IF Encounter(AtWhom).Life <= 0 THEN 'the monster died!
  366.             SetTextColor PrintArea, &HFFFF0000, 0
  367.             out$ = _TRIM$(Monster(M).Name) + " died!"
  368.             PrintOut PrintArea, out$
  369.             Encounter(AtWhom).Active = 0
  370.             Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  371.             MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) = MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  372.             IF Monster(M).ID.Symbol = 63 THEN 'it's a first time kill!
  373.                 DIM ub AS _UNSIGNED _BYTE, ul AS _UNSIGNED LONG
  374.                 SetSymbol ub, ul, "CONGRATULATIONS!  You just killed " + _TRIM$(Monster(M).Name) + " for the first time!"
  375.  
  376.             END IF
  377.         END IF
  378.     ELSE 'it's a monster attacking
  379.  
  380.     END IF
  381.  
  382.  
  383. SUB SetSymbol (ReturnSymbol AS _UNSIGNED _BYTE, ReturnColor AS _UNSIGNED LONG, out$)
  384.     'Make certain that the routine that calls this one uses the same variable types so we can pass values back and forth
  385.     RedSet = 128: 'GreenSet = 128: BlueSet = 128
  386.  
  387.     TempArea = NewTextArea(200, 100, 600, 500, True)
  388.     ColorTextArea TempArea, _RGB32(255, 255, 255), _RGB32(128, 128, 128)
  389.     DrawTextArea TempArea
  390.     SetPrintPositionX TempArea, CenterJustify
  391.     SetPrintUpdate TempArea, NewLine
  392.     SetTextColor TempArea, &HFFFFFFFF, 0
  393.     PrintOut TempArea, out$
  394.     SetTextFont TempArea, "courbd.ttf", 8
  395.     DO
  396.         SetTextColor TempArea, _RGB32(RedSet, GreenSet, BlueSet), 0
  397.         COLOR _RGB32(RedSet, GreenSet, BlueSet), 0
  398.         FOR y = 0 TO 15
  399.             FOR x = 0 TO 15
  400.                 Box x * 16 + 275, y * 16 + 150, 16, 16, CHR$(y * 16 + x), _RGB32(200, 200, 200)
  401.             NEXT
  402.         NEXT
  403.         Box 275, 425, 256, 20, "", _RGB32(200, 200, 200)
  404.         Box 275, 425, RedSet, 20, "", _RGB32(255, 0, 0)
  405.         Box 275, 450, 256, 20, "", _RGB32(200, 200, 200)
  406.         Box 275, 450, GreenSet, 20, "", _RGB32(0, 256, 0)
  407.         Box 275, 475, 256, 20, "", _RGB32(200, 200, 200)
  408.         Box 275, 475, BlueSet, 20, "", _RGB32(0, 0, 256)
  409.         _DISPLAY
  410.         WHILE _MOUSEINPUT: WEND
  411.         IF _MOUSEBUTTON(1) THEN
  412.             IF _MOUSEX > 250 AND _MOUSEX < 556 THEN 'we're clicked at the right mouse coordinate
  413.                 SELECT CASE _MOUSEY
  414.                     CASE 425 TO 445
  415.                         RedSet = _MOUSEX - 275
  416.                         IF RedSet < 0 THEN RedSet = 0
  417.                         IF RedSet > 255 THEN RedSet = 255
  418.                     CASE 450 TO 470
  419.                         GreenSet = _MOUSEX - 275
  420.                         IF GreenSet < 0 THEN GreenSet = 0
  421.                         IF GreenSet > 255 THEN GreenSet = 255
  422.                     CASE 475 TO 495
  423.                         BlueSet = _MOUSEX - 275
  424.                         IF BlueSet < 0 THEN BlueSet = 0
  425.                         IF BlueSet > 255 THEN BlueSet = 255
  426.                 END SELECT
  427.             END IF
  428.         END IF
  429.         _LIMIT 60
  430.     LOOP
  431.  
  432.  
  433.  
  434.  
  435. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  436.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  437.     D = Distance(MX, MY) 'distance from monster to the hero
  438.     E = Encounter(i).M 'the actual monster in question
  439.  
  440.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  441.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  442.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  443.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  444.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  445.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  446.             MoveMonster = -1
  447.         END IF
  448.     END IF
  449.  
  450.  
  451.  
  452. SUB MonstersTurn
  453.     FOR i = 1 TO EncounterLimit
  454.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  455.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  456.             D = Distance(MX, MY) 'distance from monster to the hero
  457.             E = Encounter(i).M 'the actual monster in question
  458.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  459.  
  460.                 attack = 0
  461.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  462.                     'insert attack code here
  463.  
  464.                     _TITLE "ATTACK!"
  465.                     _CONTINUE
  466.                 END IF
  467.                 IF D <= Monster(E).Weapon2.Reach THEN 'we're in reach for the monster to attack with their off hand.
  468.                     'insert attack code here
  469.                     _CONTINUE
  470.                 END IF
  471.  
  472.                 IF attack = 0 THEN 'if the monster didn't attack, it can now move towards the hero.
  473.                     IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  474.                         IF D > Distance(MX - 1, MY) THEN
  475.                             IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  476.                         END IF
  477.                     END IF
  478.                     IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  479.                         IF D > Distance(MX, MY - 1) THEN
  480.                             IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  481.                         END IF
  482.                     END IF
  483.                     IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  484.                         IF D > Distance(MX + 1, MY) THEN
  485.                             IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  486.                         END IF
  487.                     END IF
  488.                     IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  489.                         IF D > Distance(MX, MY + 1) THEN
  490.                             IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  491.                         END IF
  492.                     END IF
  493.                 END IF
  494.             END IF
  495.         END IF
  496.  
  497.     NEXT
  498.  
  499.  
  500.  
  501.  
  502. SUB DrawMap
  503.     _DEST WorkScreen
  504.     _FONT 8
  505.     LINE (0, 0)-(800, 600), &HFF0000FF, BF 'clear the map
  506.     FOR Y = 0 TO YH
  507.         FOR X = 0 TO XH
  508.             IF Distance(X, Y) <= Hero.Light.Reach THEN 'It's close enough to check for illumination
  509.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  510.             END IF
  511.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  512.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  513.                     COLOR &HFF000000, 0
  514.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  515.                 END IF
  516.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  517.                     COLOR &HFF000000, &HFF777777
  518.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), "."
  519.                 END IF
  520.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  521.                     COLOR &HFF00FF00, &HFFFFFF00
  522.                     _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(240)
  523.                 END IF
  524.             END IF
  525.             'note: highlighting for the light should come AFTER the map is drawn
  526.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  527.                 COLOR &H40FFFF00, 0
  528.                 _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(219)
  529.                 MapArray(X, Y) = MapArray(X, Y) - 1
  530.                 FOR i = 1 TO EncounterLimit
  531.                     IF X = Encounter(i).X AND Y = Encounter(i).Y AND Encounter(i).Active = -1 THEN
  532.                         E = Encounter(i).M
  533.                         COLOR Monster(E).ID.Color
  534.                         _PRINTSTRING (X * _FONTWIDTH, Y * _FONTHEIGHT), CHR$(Monster(E).ID.Symbol)
  535.                     END IF
  536.                 NEXT
  537.  
  538.             END IF
  539.         NEXT
  540.     NEXT
  541.     COLOR &HFFFFFF00, 0 'Yellow Hero
  542.     _PRINTSTRING (Hero.X * _FONTWIDTH, Hero.Y * _FONTHEIGHT), CHR$(1)
  543.     XOffset## = 400 / Scale
  544.     YOffset## = 300 / Scale
  545.     CenterX = Hero.X * 8 'convert hero coordinate to grid coordinate
  546.     CenterY = Hero.Y * 8
  547.     _DEST DisplayScreen
  548.     LINE (0, 0)-(800, 600), &HFF0000FF, BF 'clear the map
  549.     _PUTIMAGE (0, 0)-(800, 600), WorkScreen, DisplayScreen, (CenterX - XOffset##, CenterY - YOffset##)-(CenterX + XOffset##, CenterY + YOffset##)
  550.  
  551.  
  552.  
  553.  
  554.  
  555. SUB CreateMap (XLimit, YLimit, Rooms)
  556.     ERASE MapArray 'clear the old map and reset everything to 0
  557.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  558.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  559.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  560.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  561.  
  562.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  563.  
  564.     FOR i = 1 TO Rooms
  565.         DO
  566.             RoomSize = INT(RND * 9) + 2
  567.             RoomX = INT(RND * (XLimit - RoomSize))
  568.             RoomY = INT(RND * (YLimit - RoomSize))
  569.             'test for positioning
  570.             good = -1 'it's good starting out
  571.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  572.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  573.             NEXT X, Y
  574.         LOOP UNTIL good
  575.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  576.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  577.         NEXT X, Y
  578.         RoomCenterX(i) = RoomX + .5 * RoomSize
  579.         RoomCenterY(i) = RoomY + .5 * RoomSize
  580.     NEXT
  581.     FOR i = 1 TO Rooms - 1
  582.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  583.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  584.         DO UNTIL StartX = EndX AND StartY = EndY
  585.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  586.             Meander = 10
  587.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  588.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  589.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  590.                     Ychange = 0
  591.                 ELSE
  592.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  593.                     Ychange = 0
  594.                 END IF
  595.             ELSE
  596.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  597.                     Ychange = SGN(EndY - StartY)
  598.                     XChange = 0
  599.                 ELSE
  600.                     Ychange = INT(RND * 3) - 1
  601.                     XChange = 0
  602.                 END IF
  603.             END IF
  604.             StartX = StartX + XChange
  605.             StartY = StartY + Ychange
  606.             IF StartX < 0 THEN StartX = 0
  607.             IF StartY < 0 THEN StartY = 0
  608.             IF StartX > UBOUND(MapArray, 1) THEN StartX = UBOUND(MapArray, 1)
  609.             IF StartY > UBOUND(MapArray, 2) THEN StartY = UBOUND(MapArray, 2)
  610.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8
  611.         LOOP
  612.     NEXT
  613.     DO
  614.         Hero.X = INT(RND * XLimit + 1)
  615.         Hero.Y = INT(RND * YLimit + 1)
  616.     LOOP UNTIL MapArray(Hero.X, Hero.Y) AND 4 'place the hero randomly, until they're in a room somewhere
  617.     MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  618.     DO
  619.         X = INT(RND * XLimit + 1)
  620.         Y = INT(RND * YLimit + 1)
  621.     LOOP UNTIL MapArray(X, Y) AND 4 'get a random spot in a room, for the stairs to the next level
  622.     MapArray(X, Y) = MapArray(X, Y) OR 16
  623.     PathFind
  624.     EncounterLimit = INT(RND * 6) + 5
  625.     FOR i = 1 TO EncounterLimit
  626.         Encounter(i).M = RandomMonster
  627.         Encounter(i).Active = -1
  628.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  629.         valid = -1
  630.         DO
  631.             Encounter(i).X = INT(RND * XLimit + 1)
  632.             Encounter(i).Y = INT(RND * YLimit + 1)
  633.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  634.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  635.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  636.     NEXT
  637.  
  638. SUB PathFind
  639.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  640.     DIM pass AS _UNSIGNED _BYTE
  641.     m = _MEM(Distance()): m1 = _MEM(Temp())
  642.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  643.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  644.     Temp(Hero.X, Hero.Y) = 0
  645.     pass = 0
  646.     DO
  647.         changed = 0
  648.         y = 0
  649.         DO
  650.             x = 0
  651.             DO
  652.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  653.                     IF x < XH THEN
  654.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  655.                     END IF
  656.                     IF x > 0 THEN
  657.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  658.                     END IF
  659.                     IF y < YH THEN
  660.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  661.                     END IF
  662.                     IF y > 0 THEN
  663.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  664.                     END IF
  665.                 END IF
  666.                 x = x + 1
  667.             LOOP UNTIL x > XH
  668.             y = y + 1
  669.         LOOP UNTIL y > YH
  670.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  671.         pass = pass + 1
  672.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  673.     Distance(Hero.X, Hero.Y) = 0
  674.  
  675. FUNCTION RandomMonster
  676.     'Shared variable level tells us what level of the dungeon we're on.
  677.     STATIC MC, DS 'monster count and data set
  678.     IF NOT DS THEN
  679.         DS = -1
  680.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  681.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  682.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  683.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  684.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  685.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  686.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  687.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  688.         'Monster(1).Armor.Name = ""
  689.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  690.  
  691.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  692.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  693.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  694.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  695.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  696.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  697.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  698.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  699.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  700.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  701.         FOR i = 1 TO UBOUND(Monster) 'All monsters first appear as a red question mark on the screen, until battled.
  702.             Monster(i).ID.Symbol = 63: Monster(i).ID.Color = &HFFFF0000
  703.         NEXT
  704.     END IF
  705.     SELECT CASE Level 'the starting level
  706.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  707.     END SELECT
  708.     RandomMonster = INT(RND * MC) + 1
  709.  
  710.  
  711. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  712. '# SUBroutines and FUNCTIONs below #'
  713. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  714. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  715.     u = UBOUND(TextHandles)
  716.     Handle = WhichHandle
  717.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  718.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  719.     Where = TextHandles(Handle).VerticalAlignment
  720.     How = TextHandles(Handle).Justification
  721.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  722.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  723.  
  724.  
  725. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  726.     'WhichHandle is the handle which designates which text area we want to use
  727.     'Where is where we want it to go in that text area
  728.     '  -- Online prints the text to the current print position line in that text area.
  729.     '  -- CenterLine centers the text to the center of that text area.
  730.     '  -- any other value will print to that line positon in that particular box.
  731.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  732.     'What is the text that we want to print in our text area
  733.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  734.  
  735.     D = _DEST: S = _SOURCE
  736.     OldFont = _FONT
  737.  
  738.     u = UBOUND(TextHandles)
  739.     Handle = WhichHandle
  740.  
  741.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  742.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  743.     IF TextHandles(Handle).HideFrame THEN
  744.         _DEST TextHandles(Handle).SavedBackground
  745.         _SOURCE TextHandles(Handle).SavedBackground
  746.     END IF
  747.     _FONT TextHandles(Handle).Font
  748.     fh = _FONTHEIGHT: pw = _PRINTWIDTH(What)
  749.     IF _FONTWIDTH = 0 THEN
  750.         FOR i = 1 TO 255
  751.             IF _PRINTWIDTH(CHR$(i)) > fw THEN fw = _PRINTWIDTH(CHR$(i))
  752.         NEXT
  753.     ELSE
  754.         fw = _FONTWIDTH
  755.     END IF
  756.  
  757.     h = TextHandles(Handle).h - 4: w = TextHandles(Handle).w - 4
  758.  
  759.     SELECT CASE Where
  760.         CASE BottomLine
  761.             y = h \ fh
  762.         CASE OnLine
  763.             y = TextHandles(Handle).Ypos
  764.             IF y = 0 THEN y = 1
  765.         CASE CenterLine
  766.             linesused = 0
  767.             tpw = pw: tw = w: tWhat$ = What
  768.             DO UNTIL tpw <= tw
  769.                 textallowed = WordBreak(LEFT$(tWhat$, w \ fw))
  770.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  771.                 linesused = linesused + 1
  772.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  773.                 tpw = _PRINTWIDTH(tWhat$)
  774.             LOOP
  775.             linesused = linesused + 1
  776.             py = (h - linesused * fh) \ 2
  777.             y = py \ fh + 1
  778.             IF y < 1 THEN y = 1
  779.         CASE ELSE
  780.             y = Where
  781.     END SELECT
  782.  
  783.     'IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  784.     blend = _BLEND
  785.  
  786.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  787.         'first let's get a temp image handle for the existing area of the screen.
  788.         x1 = TextHandles(Handle).x1 + 2
  789.         y1 = TextHandles(Handle).y1 + 2
  790.         x2 = TextHandles(Handle).x1 + w
  791.         y2 = TextHandles(Handle).y1 + h
  792.         nh = y2 - y1 + 1 - fh
  793.         nw = x2 - x1 + 1
  794.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  795.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  796.         DrawTextArea Handle
  797.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  798.         y = y - 1
  799.     LOOP
  800.  
  801.     IF blend THEN _BLEND
  802.  
  803.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  804.  
  805.     SELECT CASE How
  806.         CASE LeftJustify
  807.             x = 0
  808.             IF pw > w THEN
  809.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  810.                 text$ = RTRIM$(LEFT$(What, textallowed))
  811.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  812.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  813.             ELSE
  814.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  815.                 finished = -1
  816.             END IF
  817.         CASE CenterJustify
  818.             IF pw > w THEN
  819.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  820.                 text$ = RTRIM$(LEFT$(What, textallowed))
  821.                 x = (w - _PRINTWIDTH(text$)) \ 2
  822.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  823.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  824.             ELSE
  825.                 x = (w - pw) \ 2
  826.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  827.                 finished = -1
  828.             END IF
  829.         CASE RightJustify
  830.             IF pw > w THEN
  831.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  832.                 text$ = RTRIM$(LEFT$(What, textallowed))
  833.                 x = w - _PRINTWIDTH(text$)
  834.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  835.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  836.             ELSE
  837.                 x = w - pw
  838.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  839.                 finished = -1
  840.             END IF
  841.         CASE NoJustify
  842.             x = TextHandles(Handle).Xpos
  843.             firstlinelimit = (w - x) \ fw 'the limit of characters on the first line
  844.             IF LEN(What) > firstlinelimit THEN
  845.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  846.                 text$ = RTRIM$(LEFT$(What, textallowed))
  847.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  848.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  849.             ELSE
  850.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  851.                 finished = -1
  852.             END IF
  853.     END SELECT
  854.     IF finished THEN
  855.         SELECT CASE TextHandles(Handle).UpdateMethod
  856.             CASE NoUpdate 'We don't update the position at all.
  857.             CASE DoUpdate
  858.                 TextHandles(Handle).Xpos = x + pw
  859.                 TextHandles(Handle).Ypos = y
  860.             CASE NewLine
  861.                 TextHandles(Handle).Ypos = y + 1
  862.                 TextHandles(Handle).Xpos = 1
  863.         END SELECT
  864.         _FONT OldFont
  865.         _DEST D: _SOURCE S
  866.         COLOR FG, BG
  867.     END IF
  868.  
  869. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  870.     u = UBOUND(TextHandles)
  871.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  872.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  873.     TextHandles(Handle).TextColor = Foreground
  874.  
  875.  
  876. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  877.     u = UBOUND(TextHandles)
  878.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  879.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  880.     TextHandles(Handle).TextBackgroundColor = Background
  881.  
  882. SUB SetTextFont (Handle AS INTEGER, FontName AS STRING, FontSize AS INTEGER)
  883.     u = UBOUND(TextHandles)
  884.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  885.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  886.     SELECT CASE TextHandles(Handle).Font
  887.         CASE 8, 9, 14, 15, 16, 17 'In built QB64 fonts.  We don't need to free them.
  888.         CASE IS > 1
  889.             'we have the font already in use
  890.             'REMOVE THIS CONDITION IF NECESSARY, AND MANUALLY FREE/RELEASE FONTS AS ABLE!!!
  891.             _FREEFONT TextHandles(Handle).Font 'if it's in use elsewhere, this *WILL* toss an error.
  892.     END SELECT
  893.  
  894.     temp = _LOADFONT(FontName, FontSize, "MONOSPACE")
  895.     IF temp > 1 THEN
  896.         TextHandles(Handle).Font = temp
  897.     ELSE
  898.         TextHandles(Handle).Font = 16 'default to font 16, in case
  899.     END IF
  900.  
  901.  
  902. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  903.     u = UBOUND(TextHandles)
  904.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  905.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  906.     TextHandles(Handle).TextColor = Foreground
  907.     TextHandles(Handle).TextBackgroundColor = Background
  908.  
  909.  
  910. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  911.     u = UBOUND(TextHandles)
  912.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  913.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  914.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  915.     TextHandles(Handle).UpdateMethod = Method
  916.  
  917.  
  918. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  919.     u = UBOUND(TextHandles)
  920.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  921.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  922.     SELECT CASE Y
  923.         CASE BottomLine
  924.             TextHandles(Handle).VerticalAlignment = -2
  925.         CASE CenterLine
  926.             TextHandles(Handle).VerticalAlignment = -1
  927.         CASE ELSE
  928.             TextHandles(Handle).VerticalAlignment = 0
  929.     END SELECT
  930.     IF X < 1 AND X > -4 THEN
  931.         TextHandles(Handle).Justification = X
  932.     ELSE
  933.         TextHandles(Handle).Xpos = X
  934.     END IF
  935.     IF Y < 1 THEN EXIT SUB
  936.     TextHandles(Handle).Ypos = Y
  937.  
  938. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  939.     u = UBOUND(TextHandles)
  940.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  941.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  942.     IF X < 1 AND X > -4 THEN
  943.         TextHandles(Handle).Justification = X
  944.     ELSE
  945.         TextHandles(Handle).Xpos = X
  946.     END IF
  947.  
  948. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  949.     u = UBOUND(TextHandles)
  950.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  951.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  952.     SELECT CASE Y
  953.         CASE BottomLine
  954.             TextHandles(Handle).VerticalAlignment = -2
  955.         CASE CenterLine
  956.             TextHandles(Handle).VerticalAlignment = -1
  957.         CASE ELSE
  958.             TextHandles(Handle).VerticalAlignment = 0
  959.     END SELECT
  960.     IF Y < 1 THEN EXIT SUB
  961.     TextHandles(Handle).Ypos = Y
  962.  
  963.  
  964. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  965.     u = UBOUND(TextHandles)
  966.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  967.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  968.     GetPrintPositionY = TextHandles(Handle).Ypos
  969.  
  970. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  971.     u = UBOUND(TextHandles)
  972.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  973.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  974.     GetPrintPositionX = TextHandles(Handle).Xpos
  975.  
  976.  
  977.  
  978. FUNCTION WordBreak (text$)
  979.     CONST Breaks = " ;,.?!-"
  980.     FOR i = LEN(text$) TO 0 STEP -1
  981.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  982.         loopcount = loopcount + 1
  983.     NEXT
  984.     IF i = 0 THEN i = LEN(text$)
  985.     WordBreak = i
  986.  
  987.  
  988.  
  989. SUB ClearTextArea (Handle AS INTEGER)
  990.     u = UBOUND(TextHandles)
  991.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  992.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  993.     IF TextHandles(Handle).SavedBackground THEN
  994.         w = TextHandles(Handle).w
  995.         h = TextHandles(Handle).h
  996.         x1 = TextHandles(Handle).ScreenX
  997.         y1 = TextHandles(Handle).ScreenY
  998.         x2 = x1 + w - 1
  999.         y2 = y1 + h - 1
  1000.         blend = _BLEND
  1001.         _DONTBLEND
  1002.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1003.         IF blend THEN _BLEND
  1004.     END IF
  1005.     DrawTextArea Handle
  1006.  
  1007.  
  1008.  
  1009. SUB DrawTextArea (Handle AS INTEGER)
  1010.     u = UBOUND(TextHandles)
  1011.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1012.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1013.     w = TextHandles(Handle).w
  1014.     h = TextHandles(Handle).h
  1015.     x1 = TextHandles(Handle).ScreenX
  1016.     y1 = TextHandles(Handle).ScreenY
  1017.     x2 = x1 + w - 1
  1018.     y2 = y1 + h - 1
  1019.  
  1020.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  1021.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  1022.  
  1023.  
  1024.  
  1025. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  1026.     u = UBOUND(TextHandles)
  1027.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1028.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1029.     TextHandles(Handle).FrameColor = FrameColor
  1030.     TextHandles(Handle).BackColor = BackColor
  1031.  
  1032.  
  1033.  
  1034. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  1035.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  1036.     x2 = tx2: y2 = ty2
  1037.     IF x1 > x2 THEN SWAP x1, x2
  1038.     IF y1 > y2 THEN SWAP y1, y2
  1039.     w = x2 - x1 + 1
  1040.     h = y2 - y1 + 1
  1041.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  1042.     'Error checking for if the user sends coordinates which are off the screen
  1043.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  1044.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  1045.  
  1046.     u = UBOUND(TextHandles)
  1047.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  1048.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  1049.     NEXT
  1050.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  1051.         Handle = u + 1
  1052.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  1053.     END IF
  1054.     TextHandles(Handle).x1 = x1
  1055.     TextHandles(Handle).y1 = y1
  1056.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  1057.     TextHandles(Handle).InUse = True
  1058.     TextHandles(Handle).Xpos = 0
  1059.     TextHandles(Handle).Ypos = 1
  1060.     TextHandles(Handle).UpdateMethod = NewLine
  1061.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  1062.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  1063.  
  1064.     IF SaveBackground THEN
  1065.         imagehandle = _NEWIMAGE(w, h, 32)
  1066.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1067.         TextHandles(Handle).SavedBackground = imagehandle
  1068.     END IF
  1069.     TextHandles(Handle).ScreenX = x1
  1070.     TextHandles(Handle).ScreenY = y1
  1071.     TextHandles(Handle).Font = 16 'default to font 16
  1072.     NewTextArea% = Handle
  1073.  
  1074. SUB FreeTextArea (Handle AS INTEGER)
  1075.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  1076.         IF TextHandles(Handle).InUse THEN
  1077.             TextHandles(Handle).InUse = False
  1078.             IF TextHandles(Handle).SavedBackground THEN
  1079.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  1080.                     w = TextHandles(Handle).w
  1081.                     h = TextHandles(Handle).h
  1082.                     x1 = TextHandles(Handle).ScreenX
  1083.                     y1 = TextHandles(Handle).ScreenY
  1084.                     x2 = x1 + w - 1
  1085.                     y2 = y1 + h - 1
  1086.                     blend = _BLEND
  1087.                     _DONTBLEND
  1088.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1089.                     IF blend THEN _BLEND
  1090.                 END IF
  1091.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  1092.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  1093.             END IF
  1094.         ELSE
  1095.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  1096.         END IF
  1097.     ELSE
  1098.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  1099.     END IF
  1100.  
  1101. SUB HideFrame (Handle AS INTEGER)
  1102.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  1103.         TextHandles(Handle).HideFrame = -1
  1104.         w = TextHandles(Handle).w
  1105.         h = TextHandles(Handle).h
  1106.         x1 = TextHandles(Handle).ScreenX
  1107.         y1 = TextHandles(Handle).ScreenY
  1108.         x2 = x1 + w - 1
  1109.         y2 = y1 + h - 1
  1110.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1111.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1112.         IF TextHandles(Handle).SavedBackground THEN
  1113.             blend = _BLEND
  1114.             _DONTBLEND
  1115.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1116.             _FREEIMAGE TextHandles(Handle).SavedBackground
  1117.             IF blend THEN _BLEND
  1118.         END IF
  1119.         TextHandles(Handle).SavedBackground = imagehandle
  1120.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  1121.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  1122.     END IF
  1123.  
  1124. SUB RestoreFrame (Handle AS INTEGER)
  1125.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  1126.         TextHandles(Handle).HideFrame = 0
  1127.         w = TextHandles(Handle).w
  1128.         h = TextHandles(Handle).h
  1129.         x1 = TextHandles(Handle).ScreenX
  1130.         y1 = TextHandles(Handle).ScreenY
  1131.         x2 = x1 + w - 1
  1132.         y2 = y1 + h - 1
  1133.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1134.         blend = _BLEND
  1135.         _DONTBLEND
  1136.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1137.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  1138.         _FREEIMAGE TextHandles(Handle).SavedBackground
  1139.         IF blend THEN _BLEND
  1140.         TextHandles(Handle).SavedBackground = imagehandle
  1141.         TextHandles(Handle).x1 = x1 'When the frames are frames are restored, we need to recalculate our print position
  1142.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  1143.     END IF
  1144.  
  1145. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  1146.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  1147.     u = UBOUND(TextHandles)
  1148.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1149.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1150.     HideFrame Handle
  1151.     TextHandles(Handle).ScreenX = x1
  1152.     TextHandles(Handle).ScreenY = y1
  1153.     RestoreFrame Handle

Now, if you applied either of the two true fixes, you can run the code, resize your font, and watch as QB64 auto spaces things to make each character the same size, and -- oddly enough -- see where some larger fonts are, in fact, LESS wide than smaller fonts...

Scaling up fonts changes height/width, and apparently due to rounding sometimes, the ratio between height/width isn't always consistent, which can lead to some of the taller fonts actually being narrower than some of the shorter fonts before them.  Who knew?!!
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 13, 2019, 03:16:32 am
2 Steps forward, 1 Step back....   

I've completely ripped a lot of my poor little game apart and rebuilt it from the ground up, so I can use a basic sprite set instead of sticking with text characters for everything.  Just because I'm shooting to create a Rogue-like experience, it doesn't mean I need to stay limited to become a Rogue-clone.  The lighting, map zooming, text sizing, and discovery system are all something which isn't native to Rogue, so I figured, "What the heck -- if I'm going to customize things this much, why not just go ahead and use some  low resolution sprites for things, since they allow a greater range of representation than the old A-Z of monsters in Rogue."

So, what we have now has to be zipped up and placed into a 7z file for sharing, so the resource files stay available -- and you can see it below:
Title: Re: Rogue-Like (work in progress)
Post by: bplus on September 13, 2019, 11:05:10 am
Hi Steve,

I definitely like the graphics improvements!

Say are all those lefts and rights in the paths part of the charm of rogue?

Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 13, 2019, 11:16:37 am
Hi Steve,

I definitely like the graphics improvements!

Say are all those lefts and rights in the paths part of the charm of rogue?

They are.  They get even more charming at deeper levels when you start running into “dark rooms” where your light doesn’t illuminate, and “secret paths” which don’t have any graphic on your map.  (You just look like you’re walking through the walls...). And then you have the completely hidden passages, which you have to search the room before they even appear...

There’s still a lot to go for this little game, but I have to say, I really like the core mechanics which we’re seeing in it already.  I think it’s about time to have the monsters attack back for us.  ;)
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 13, 2019, 08:46:04 pm
Code: QB64: [Select]
  1. DEFLNG A-Z 'default to long instead of single
  2. TYPE TextArea
  3.     InUse AS INTEGER
  4.     x1 AS LONG 'left
  5.     y1 AS LONG 'top
  6.     w AS LONG 'width
  7.     h AS LONG 'height
  8.     FrameColor AS _UNSIGNED LONG
  9.     BackColor AS _UNSIGNED LONG
  10.     Xpos AS INTEGER
  11.     Ypos AS INTEGER
  12.     VerticalAlignment AS INTEGER
  13.     Justification AS INTEGER
  14.     UpdateMethod AS INTEGER
  15.     TextColor AS _UNSIGNED LONG
  16.     TextBackgroundColor AS _UNSIGNED LONG
  17.     SavedBackground AS INTEGER
  18.     HideFrame AS INTEGER
  19.     ScreenX AS INTEGER
  20.     ScreenY AS INTEGER
  21.     Font AS LONG 'NEW! Change fonts for each independent font area
  22.  
  23. REDIM SHARED TextHandles(0) AS TextArea
  24.  
  25. CONST True = -1, False = 0
  26. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  27. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  28. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  29. '********************************************************
  30. '* Text Frames before this line
  31. '********************************************************
  32.  
  33.  
  34.  
  35.  
  36.  
  37.  
  38. _CONSOLE ON 'for debugging purposes while making/testing things
  39.  
  40. TYPE Damage_Type
  41.     Low AS INTEGER
  42.     High AS INTEGER
  43.  
  44. TYPE Light_Type
  45.     Name AS STRING * 20
  46.     Reach AS _UNSIGNED _BYTE
  47.     Left AS _UNSIGNED _BYTE
  48.  
  49. TYPE Weapon_Type
  50.     Name AS STRING * 20
  51.     Reach AS _UNSIGNED _BYTE
  52.     Damage AS Damage_Type
  53.     HitBonus AS _UNSIGNED _BYTE
  54.     DamageBonus AS _UNSIGNED _BYTE
  55.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  56.  
  57. TYPE Armor_Type
  58.     Name AS STRING * 20
  59.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  60.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  61.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  62.  
  63. TYPE Hero_Type
  64.     Name AS STRING * 20
  65.     Life AS Damage_Type
  66.     Level AS _UNSIGNED _BYTE
  67.     EXP_Earned AS LONG
  68.     EXP_Needed AS LONG
  69.     Light AS Light_Type
  70.     Weapon1 AS Weapon_Type
  71.     Weapon2 AS Weapon_Type
  72.     Armor AS Armor_Type
  73.     HealingRate AS _UNSIGNED _BYTE
  74.  
  75. TYPE Monster_TYPE
  76.     Name AS STRING * 20
  77.     Life AS Damage_Type
  78.     Level AS INTEGER
  79.     ExpBonus AS INTEGER
  80.     Sight AS INTEGER
  81.     Hearing AS INTEGER
  82.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  83.     Weapon1 AS Weapon_Type
  84.     Weapon2 AS Weapon_Type
  85.     Armor AS Armor_Type
  86.     Found AS INTEGER
  87.     IconX AS LONG
  88.     IconY AS LONG
  89.  
  90. TYPE Encounter_TYPE
  91.     Active AS INTEGER
  92.     X AS INTEGER
  93.     Y AS INTEGER
  94.     M AS INTEGER
  95.     Life AS INTEGER
  96.  
  97. REDIM SHARED Monster(100) AS Monster_TYPE
  98. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  99.  
  100. DIM SHARED Hero AS Hero_Type
  101. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  102. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  103. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  104. DIM SHARED Scale AS _FLOAT, WorkScreen AS LONG, DisplayScreen AS LONG
  105. DIM SHARED TextFont AS LONG, StepsTaken AS _UNSIGNED _INTEGER64
  106.  
  107. WorkScreen = _NEWIMAGE(3200, 2400, 32)
  108. DisplayScreen = _NEWIMAGE(800, 700, 32)
  109. SCREEN DisplayScreen
  110. Scale = 2
  111.  
  112. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  113. '1 map is illuminated
  114. '2 map is uncovered
  115. '4 map is a wall
  116. '8 map is a pathway
  117. '16 map is a stairway
  118. '32 map is simply blocked (perhaps with a monster?)
  119. '64 map is secret (can not be uncovered)
  120.  
  121. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  122.  
  123.  
  124.  
  125. ON ERROR GOTO errorhandler
  126.  
  127. GOTO skiperrorhandler
  128. errorhandler:
  129.  
  130. PRINT ERR; "Error on "; _ERRORLINE
  131. _DEST DisplayScreen
  132. skiperrorhandler:
  133.  
  134. Init
  135. CreateMap 99, 74, 10
  136.  
  137.     DrawMap
  138.     DisplayCharacter
  139.     _DISPLAY
  140.     GetInput
  141.     MonstersTurn
  142.     CheckForHeroGrowth
  143.  
  144. SUB Init
  145.     Hero.Name = "Steve The Tester!"
  146.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  147.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  148.     Hero.Light.Name = "Magic Candle"
  149.     Hero.Light.Reach = 2: Hero.Light.Left = -1 'infinite
  150.     Hero.Weapon1.Name = "Bare Fist"
  151.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  152.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  153.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  154.     Hero.Weapon2.Name = "Magic Candle"
  155.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  156.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  157.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  158.     Hero.Armor.Name = "Naked"
  159.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  160.     Hero.HealingRate = 20 'the hero heals 1 point of health for every 20 valid turns naturally
  161.  
  162.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  163.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  164.     SetTextFont PrintArea, "courbd.ttf", 24
  165.     DrawTextArea PrintArea
  166.     SetPrintPositionX PrintArea, CenterJustify
  167.     SetPrintUpdate PrintArea, NewLine
  168.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  169.     SetTextFont PrintArea, "courbd.ttf", 18
  170.     PrintOut PrintArea, "created by STEVE!"
  171.     PrintOut PrintArea, ""
  172.     SetPrintPositionX PrintArea, LeftJustify
  173.     SetTextFont PrintArea, "courbd.ttf", 12
  174.     TextFont = 12
  175.  
  176. SUB CheckForHeroGrowth
  177.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  178.         CLS
  179.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  180.         _DISPLAY
  181.         BEEP
  182.         _DELAY 5
  183.         SYSTEM
  184.     END IF
  185.     IF StepsTaken MOD Hero.HealingRate = 0 THEN 'heal the hero naturally over time
  186.         IF Hero.Life.Low < Hero.Life.High THEN Hero.Life.Low = Hero.Life.Low + 1
  187.     END IF
  188.  
  189.  
  190. SUB DisplayCharacter
  191.     LINE (0, 601)-(229, 799), &HFF000000, BF
  192.     COLOR &HFFFFFFFF, 0
  193.     _PRINTSTRING (0, 605), "HERO : " + Hero.Name
  194.     _PRINTSTRING (0, 613), "LEVEL:" + STR$(Hero.Level)
  195.     _PRINTSTRING (0, 621), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  196.     _PRINTSTRING (0, 637), "LIFE :" + STR$(Hero.Life.Low) + " (" + _TRIM$(STR$(Hero.Life.High)) + ")"
  197.  
  198.     _PRINTSTRING (0, 653), "HAND1: " + Hero.Weapon1.Name
  199.     _PRINTSTRING (0, 661), "HAND2: " + Hero.Weapon2.Name
  200.     _PRINTSTRING (0, 669), "ARMOR: " + Hero.Armor.Name
  201.     _PRINTSTRING (0, 685), "LIGHT: " + Hero.Light.Name
  202.  
  203. SUB GetInput
  204.     DO
  205.         k = _KEYHIT: valid = -1
  206.         SELECT CASE k
  207.             CASE 18432 'up
  208.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  209.                     SELECT CASE Scale
  210.                         CASE 1.5: Scale = 2 'It's as small as we go
  211.                         CASE 2: Scale = 3
  212.                         CASE 3: Scale = 4
  213.                         CASE 4: Scale = 6
  214.                         CASE 6: Scale = 8
  215.                         CASE 8: Scale = 12
  216.                     END SELECT
  217.                 ELSE
  218.                     IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  219.                 END IF
  220.             CASE 19200: 'left
  221.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  222.                     TextFont = TextFont - 1
  223.                     IF TextFont < 8 THEN TextFont = 8
  224.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  225.                     ClearTextArea PrintArea
  226.                     SetPrintPosition PrintArea, 1, 1
  227.                     PrintOut PrintArea, "Font Size Changed"
  228.                 ELSE
  229.                     IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  230.                 END IF
  231.             CASE 20480: 'down
  232.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  233.                     SELECT CASE Scale
  234.                         CASE 2: Scale = 1.5 'It's as small as we go
  235.                         CASE 3: Scale = 2
  236.                         CASE 4: Scale = 3
  237.                         CASE 6: Scale = 4
  238.                         CASE 8: Scale = 6
  239.                         CASE 12: Scale = 8
  240.                     END SELECT
  241.                 ELSE
  242.                     IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  243.                 END IF
  244.             CASE 19712: 'right
  245.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  246.                     TextFont = TextFont + 1
  247.                     IF TextFont > 48 THEN TextFont = 48
  248.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  249.                     ClearTextArea PrintArea
  250.                     SetPrintPosition PrintArea, 1, 1
  251.                     PrintOut PrintArea, "Font Size Changed"
  252.                 ELSE
  253.                     IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  254.                 END IF
  255.             CASE 32 'space to just wait and skip a turn
  256.             CASE 60 ' "<" key
  257.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  258.                     Level = Level + 1
  259.                     CreateMap 99, 74, 10
  260.                     PathFind
  261.                 END IF
  262.             CASE ASC("+"), ASC("=")
  263.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  264.             CASE ASC("-"), ASC("_")
  265.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  266.             CASE ELSE
  267.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  268.         END SELECT
  269.         _LIMIT 60
  270.     LOOP UNTIL k AND valid
  271.     _KEYCLEAR 'one keystroke at a time
  272.     StepsTaken = StepsTaken + 1
  273.  
  274. SUB Box (X, Y, Wide, High, Caption AS STRING, Kolor AS _UNSIGNED LONG)
  275.     LINE (X, Y)-STEP(Wide, High), Kolor, BF
  276.     LINE (X, Y)-STEP(Wide, High), &HFFFFFFFF, B
  277.     pw = _PRINTWIDTH(Caption): ph = _FONTHEIGHT
  278.     _PRINTSTRING (X + (Wide - pw) \ 2, Y + (High - ph) \ 2), Caption
  279.  
  280.  
  281. SUB MoveHero (MoveX, MoveY)
  282.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  283.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  284.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  285.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  286.             IF MoveX THEN Hero.X = Hero.X + MoveX
  287.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  288.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  289.             PathFind
  290.         ELSE
  291.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  292.             FOR i = 1 TO EncounterLimit
  293.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  294.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  295.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  296.                         Swing 0, i 'hero swings at the monster
  297.                     END IF
  298.                 END IF
  299.             NEXT
  300.         END IF
  301.     END IF
  302.  
  303. SUB Swing (Who, AtWhom)
  304.  
  305.     BaseChancetohit = 10 'base 10 chance to hit
  306.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  307.         M = Encounter(AtWhom).M
  308.         IF Hero.Weapon1.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  309.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  310.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  311.             totalroll = 0
  312.             DO
  313.                 roll = INT(RND * 20) + 1
  314.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  315.                 IF roll = 20 THEN totalroll = totalroll + 20
  316.                 totalroll = totalroll + roll
  317.             LOOP UNTIL roll <> 1 AND roll <> 20
  318.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  319.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  320.             out$ = _TRIM$(Hero.Name)
  321.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  322.                 SetTextColor PrintArea, &HFFF000F0, 0
  323.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  324.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  325.                 Hero.Life.Low = Hero.Life.Low - damage
  326.             ELSEIF totalroll < Chancetohit THEN
  327.                 SetTextColor PrintArea, &HFFF0F000, 0
  328.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  329.             ELSEIF totalroll > Chancetohit + 20 THEN
  330.                 SetTextColor PrintArea, &HFF00FF00, 0
  331.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  332.                 damage = damage * (totalroll / 20 + 1)
  333.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  334.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  335.             ELSEIF totalroll >= Chancetohit THEN
  336.                 SetTextColor PrintArea, &HFF00FF00, 0
  337.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  338.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  339.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  340.             END IF
  341.         ELSEIF Hero.Weapon1.Reach > 0 THEN
  342.             SetTextColor PrintArea, &HFFF000F0, 0
  343.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon1.Name) + "!"
  344.         ELSE
  345.             out$ = ""
  346.         END IF
  347.         IF out$ <> "" THEN PrintOut PrintArea, out$
  348.         IF Hero.Weapon2.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  349.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  350.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  351.             totalroll = 0
  352.             DO
  353.                 roll = INT(RND * 20) + 1
  354.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  355.                 IF roll = 20 THEN totalroll = totalroll + 20
  356.                 totalroll = totalroll + roll
  357.             LOOP UNTIL roll <> 1 AND roll <> 20
  358.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  359.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  360.             out$ = _TRIM$(Hero.Name)
  361.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  362.                 SetTextColor PrintArea, &HFFF000F0, 0
  363.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  364.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  365.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  366.                 Hero.Life.Low = Hero.Life.Low - damage
  367.             ELSEIF totalroll < Chancetohit THEN
  368.                 SetTextColor PrintArea, &HFFF0F000, 0
  369.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  370.             ELSEIF totalroll > Chancetohit + 20 THEN
  371.                 SetTextColor PrintArea, &HFF00FF00, 0
  372.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  373.                 damage = damage * (totalroll / 20 + 1)
  374.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  375.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  376.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  377.             ELSEIF totalroll >= Chancetohit THEN
  378.                 SetTextColor PrintArea, &HFF00FF00, 0
  379.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  380.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  381.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  382.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  383.             END IF
  384.         ELSEIF Hero.Weapon2.Reach > 0 THEN
  385.             SetTextColor PrintArea, &HFFF000F0, 0
  386.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon2.Name) + "!"
  387.         ELSE
  388.             out$ = ""
  389.         END IF
  390.         IF out$ <> "" THEN PrintOut PrintArea, out$
  391.         IF Encounter(AtWhom).Life <= 0 THEN 'the monster died!
  392.             SetTextColor PrintArea, &HFFFF0000, 0
  393.             out$ = _TRIM$(Monster(M).Name) + " died!  You earned " + _TRIM$(STR$(Monster(M).Level + Monster(M).ExpBonus)) + " experience."
  394.             PrintOut PrintArea, out$
  395.             Encounter(AtWhom).Active = 0
  396.             Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  397.             MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) = MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  398.             IF Monster(M).Found = 0 THEN
  399.                 Monster(M).Found = -1 'it's a first time kill!
  400.                 SetTextColor PrintArea, &HFFFFFF00, &HFFFF0000
  401.                 out$ = "Congratulations!  You killed a " + _TRIM$(Monster(M).Name) + " for the first time!"
  402.                 PrintOut PrintArea, out$
  403.             END IF
  404.         END IF
  405.     ELSE 'it's a monster attacking
  406.         M = Encounter(Who).M
  407.         IF Monster(M).Weapon1.Reach >= Distance(Encounter(Who).X, Encounter(Who).Y) THEN 'it's a weapon and not an utility object being held.
  408.             Chancetohit = BaseChancetohit + Monster(M).Weapon1.HitBonus 'add in the weapon's hit bonus
  409.             Chancetohit = Chancetohit - Hero.Armor.PD 'subtract the hero's armor/ natural dodge
  410.             totalroll = 0
  411.             DO
  412.                 roll = INT(RND * 20) + 1
  413.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  414.                 IF roll = 20 THEN totalroll = totalroll + 20
  415.                 totalroll = totalroll + roll
  416.             LOOP UNTIL roll <> 1 AND roll <> 20
  417.             damage = INT(RND * (Monster(M).Weapon1.Damage.High - Monster(M).Weapon1.Damage.Low + 1)) + Monster(M).Weapon1.Damage.Low 'random damage for the hit
  418.             damage = damage + Monster(M).Weapon1.DamageBonus 'add in the weapon's damage bonus
  419.             out$ = _TRIM$(Monster(M).Name)
  420.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  421.                 SetTextColor PrintArea, &HFFF000F0, 0
  422.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  423.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  424.                 Monster(M).Life.Low = Monster(M).Life.Low - damage
  425.             ELSEIF totalroll < Chancetohit THEN
  426.                 SetTextColor PrintArea, &HFFF0F000, 0
  427.                 out$ = out$ + " missed " + _TRIM$(Hero.Name) + ", with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  428.             ELSEIF totalroll > Chancetohit + 20 THEN
  429.                 SetTextColor PrintArea, &HFF00FFFF, 0
  430.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Hero.Name) + " for"
  431.                 damage = damage * (totalroll / 20 + 1)
  432.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  433.                 Hero.Life.Low = Hero.Life.Low - damage
  434.             ELSEIF totalroll >= Chancetohit THEN
  435.                 SetTextColor PrintArea, &HFF00FFFF, 0
  436.                 out$ = out$ + " hit " + _TRIM$(Hero.Name) + " for"
  437.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "."
  438.                 Hero.Life.Low = Hero.Life.Low - damage
  439.             END IF
  440.         ELSEIF Monster(M).Weapon1.Reach > 0 THEN
  441.             SetTextColor PrintArea, &HFFF000F0, 0
  442.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Monster(M).Weapon2.Name) + "!"
  443.         ELSE
  444.             out$ = ""
  445.         END IF
  446.         IF out$ <> "" THEN PrintOut PrintArea, out$
  447.     END IF
  448.  
  449.  
  450. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  451.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  452.     D = Distance(MX, MY) 'distance from monster to the hero
  453.     E = Encounter(i).M 'the actual monster in question
  454.  
  455.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  456.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  457.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  458.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  459.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  460.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  461.             MoveMonster = -1
  462.         END IF
  463.     END IF
  464.  
  465.  
  466.  
  467. SUB MonstersTurn
  468.     FOR i = 1 TO EncounterLimit
  469.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  470.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  471.             D = Distance(MX, MY) 'distance from monster to the hero
  472.             E = Encounter(i).M 'the actual monster in question
  473.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  474.  
  475.                 attack = 0
  476.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  477.                     'insert attack code here
  478.                     Swing i, 0
  479.                     _CONTINUE
  480.                 END IF
  481.  
  482.                 'if the monster didn't attack, it can now move towards the hero.
  483.                 IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  484.                     IF D > Distance(MX - 1, MY) THEN
  485.                         IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  486.                     END IF
  487.                 END IF
  488.                 IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  489.                     IF D > Distance(MX, MY - 1) THEN
  490.                         IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  491.                     END IF
  492.                 END IF
  493.                 IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  494.                     IF D > Distance(MX + 1, MY) THEN
  495.                         IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  496.                     END IF
  497.                 END IF
  498.                 IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  499.                     IF D > Distance(MX, MY + 1) THEN
  500.                         IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  501.                     END IF
  502.                 END IF
  503.             END IF
  504.         END IF
  505.  
  506.     NEXT
  507.  
  508.  
  509.  
  510.  
  511. SUB DrawMap
  512.     STATIC Icons AS LONG
  513.     IF Icons = 0 THEN
  514.         temp = _NEWIMAGE(480, 480, 32)
  515.         _DEST temp: _CONTROLCHR OFF
  516.         COLOR &HFFFFFF00, 0 'Yellow Hero
  517.         _PRINTSTRING (0, 0), CHR$(1) 'the hero
  518.         COLOR &HFFFF0000, 0 'Red Question Mark
  519.         _PRINTSTRING (16, 0), "?" 'a question mark
  520.         Icons = _LOADIMAGE("Sprites.png", 32)
  521.         _PUTIMAGE (2016, 1504)-STEP(32, 32), temp, Icons, (0, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  522.         _PUTIMAGE (1984, 1504)-STEP(32, 32), temp, Icons, (16, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  523.         '        SCREEN Icons: DO: SLEEP: LOOP
  524.     END IF
  525.     _DEST WorkScreen
  526.     CLS
  527.     'LINE (0, 0)-(3200, 2400), &HFF000000, BF 'clear the map
  528.     FOR Y = 0 TO YH
  529.         FOR X = 0 TO XH
  530.             IF Distance(X, Y) <= Hero.Light.Reach THEN 'It's close enough to check for illumination
  531.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  532.             END IF
  533.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  534.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  535.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF303030, BF
  536.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (6 * 32, 18 * 32)-STEP(31, 31)
  537.                 END IF
  538.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  539.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (36 * 32, 13 * 32)-STEP(31, 31)
  540.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF707070, BF
  541.                 END IF
  542.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  543.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (4 * 32, 45 * 32)-STEP(31, 31)
  544.                 END IF
  545.             END IF
  546.             'note: highlighting for the light should come AFTER the map is drawn
  547.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  548.                 LINE (X * 32, Y * 32)-STEP(32, 32), &H40FFFF00, BF
  549.                 MapArray(X, Y) = MapArray(X, Y) - 1
  550.                 FOR I = 1 TO EncounterLimit
  551.                     IF X = Encounter(I).X AND Y = Encounter(I).Y AND Encounter(I).Active = -1 THEN
  552.                         E = Encounter(I).M
  553.                         IF Monster(E).Found THEN
  554.                             _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Monster(E).IconX, Monster(E).IconY)-STEP(31, 31)
  555.                         ELSE
  556.                             _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (1984, 1504)-STEP(31, 31)
  557.                         END IF
  558.                     END IF
  559.                 NEXT
  560.  
  561.             END IF
  562.         NEXT
  563.     NEXT
  564.     COLOR &HFFFFFF00, 0 'Yellow Hero
  565.     _PUTIMAGE (Hero.X * 32, Hero.Y * 32)-STEP(32, 32), Icons, WorkScreen, (2016, 1504)-STEP(31, 31)
  566.     XOffset## = 1600 / Scale
  567.     YOffset## = 1200 / Scale
  568.     CenterX = Hero.X * 32 'convert hero coordinate to grid coordinate
  569.     CenterY = Hero.Y * 32
  570.     _DEST DisplayScreen
  571.     LINE (0, 0)-(800, 600), &HFF000000, BF 'clear the map
  572.     _PUTIMAGE (0, 0)-(800, 600), WorkScreen, DisplayScreen, (CenterX - XOffset##, CenterY - YOffset##)-(CenterX + XOffset##, CenterY + YOffset##)
  573.  
  574.  
  575.  
  576.  
  577.  
  578. SUB CreateMap (XLimit, YLimit, Rooms)
  579.     ERASE MapArray 'clear the old map and reset everything to 0
  580.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  581.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  582.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  583.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  584.  
  585.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  586.  
  587.     StairRoom = INT(RND * Rooms) + 1
  588.     FOR i = 1 TO Rooms
  589.         DO
  590.             RoomSize = INT(RND * 9) + 2
  591.             RoomX = INT(RND * (XLimit - RoomSize))
  592.             RoomY = INT(RND * (YLimit - RoomSize))
  593.             'test for positioning
  594.             good = -1 'it's good starting out
  595.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  596.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  597.             NEXT X, Y
  598.         LOOP UNTIL good
  599.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  600.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  601.         NEXT X, Y
  602.         RoomCenterX(i) = RoomX + .5 * RoomSize
  603.         RoomCenterY(i) = RoomY + .5 * RoomSize
  604.         IF i = 1 THEN 'place the hero in the first room  (which can be anywhere randomly on our map)
  605.             Hero.X = RoomX + INT(RND * RoomSize)
  606.             Hero.Y = RoomY + INT(RND * RoomSize)
  607.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  608.         END IF
  609.         IF i = StairRoom THEN 'place the stairs in one of the random rooms
  610.             DO 'But lets not place the stairs directly on top of the hero to begin with
  611.                 StairX = RoomX + INT(RND * RoomSize)
  612.                 StairY = RoomY + INT(RND * RoomSize)
  613.             LOOP UNTIL StairX <> Hero.X AND StairY <> Hero.Y
  614.             MapArray(StairX, StairY) = MapArray(StairX, StairY) OR 16
  615.         END IF
  616.     NEXT
  617.     FOR i = 1 TO Rooms - 1
  618.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  619.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  620.         DO UNTIL StartX = EndX AND StartY = EndY
  621.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  622.             Meander = 10
  623.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  624.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  625.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  626.                     Ychange = 0
  627.                 ELSE
  628.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  629.                     Ychange = 0
  630.                 END IF
  631.             ELSE
  632.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  633.                     Ychange = SGN(EndY - StartY)
  634.                     XChange = 0
  635.                 ELSE
  636.                     Ychange = INT(RND * 3) - 1
  637.                     XChange = 0
  638.                 END IF
  639.             END IF
  640.             StartX = StartX + XChange
  641.             StartY = StartY + Ychange
  642.             IF StartX < 0 THEN StartX = 0 'Make certain we move inside the bounds of our map dimensions
  643.             IF StartY < 0 THEN StartY = 0
  644.             IF StartX > XH THEN StartX = XH
  645.             IF StartY > YH THEN StartY = YH
  646.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8 'place a path where we moved to
  647.         LOOP
  648.     NEXT
  649.     PathFind
  650.     EncounterLimit = INT(RND * 6) + 5
  651.     FOR i = 1 TO EncounterLimit
  652.         Encounter(i).M = RandomMonster
  653.         Encounter(i).Active = -1
  654.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  655.         valid = -1: EndlessLoopExit = 0
  656.         DO
  657.             EndlessLoopExit = EndlessLoopExit + 1
  658.             Encounter(i).X = INT(RND * XLimit + 1)
  659.             Encounter(i).Y = INT(RND * YLimit + 1)
  660.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  661.             IF EndlessLoopExit = 1000 THEN EXIT DO 'if we can't place the monster in a room after 1000 tries, just place it wherever and call it a "wandering monster".
  662.             'Of course, "wandering monsters" may end up inside a wall, in which case they simply become "lost monsters" and do nothing to affect the level.  It's the same as if they never existed at all.
  663.             'BUT, we *should* generally be able to place a monster after 1000 tries.  This segment is just in the off-chance that the Random Number Gods are out to get us and to prevent any chance for an endless loop.
  664.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  665.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  666.     NEXT
  667.  
  668. SUB PathFind
  669.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  670.     DIM pass AS _UNSIGNED _BYTE
  671.     m = _MEM(Distance()): m1 = _MEM(Temp())
  672.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  673.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  674.     Temp(Hero.X, Hero.Y) = 0
  675.     pass = 0
  676.     DO
  677.         changed = 0
  678.         y = 0
  679.         DO
  680.             x = 0
  681.             DO
  682.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  683.                     IF x < XH THEN
  684.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  685.                     END IF
  686.                     IF x > 0 THEN
  687.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  688.                     END IF
  689.                     IF y < YH THEN
  690.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  691.                     END IF
  692.                     IF y > 0 THEN
  693.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  694.                     END IF
  695.                 END IF
  696.                 x = x + 1
  697.             LOOP UNTIL x > XH
  698.             y = y + 1
  699.         LOOP UNTIL y > YH
  700.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  701.         pass = pass + 1
  702.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  703.     Distance(Hero.X, Hero.Y) = 0
  704.  
  705. FUNCTION RandomMonster
  706.     'Shared variable level tells us what level of the dungeon we're on.
  707.     STATIC MC, DS 'monster count and data set
  708.     IF NOT DS THEN
  709.         DS = -1
  710.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  711.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  712.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  713.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  714.         Monster(1).IconX = 44 * 32: Monster(1).IconY = 3 * 32 'position 44,3 on the sprite sheet
  715.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  716.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  717.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  718.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  719.         'Monster(1).Armor.Name = ""
  720.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  721.  
  722.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  723.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  724.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  725.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  726.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  727.         Monster(2).IconX = 23 * 32: Monster(2).IconY = 4 * 32 'position 44,3 on the sprite sheet
  728.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  729.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  730.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  731.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  732.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  733.         Monster(3).IconX = 37 * 32: Monster(3).IconY = 4 * 32 'position 44,3 on the sprite sheet
  734.     END IF
  735.     SELECT CASE Level 'the starting level
  736.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  737.     END SELECT
  738.     RandomMonster = INT(RND * MC) + 1
  739.  
  740.  
  741. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  742. '# SUBroutines and FUNCTIONs below #'
  743. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  744. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  745.     u = UBOUND(TextHandles)
  746.     Handle = WhichHandle
  747.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  748.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  749.     Where = TextHandles(Handle).VerticalAlignment
  750.     How = TextHandles(Handle).Justification
  751.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  752.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  753.  
  754.  
  755. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  756.     'WhichHandle is the handle which designates which text area we want to use
  757.     'Where is where we want it to go in that text area
  758.     '  -- Online prints the text to the current print position line in that text area.
  759.     '  -- CenterLine centers the text to the center of that text area.
  760.     '  -- any other value will print to that line positon in that particular box.
  761.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  762.     'What is the text that we want to print in our text area
  763.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  764.  
  765.     D = _DEST: S = _SOURCE
  766.     OldFont = _FONT
  767.  
  768.     u = UBOUND(TextHandles)
  769.     Handle = WhichHandle
  770.  
  771.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  772.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  773.     IF TextHandles(Handle).HideFrame THEN
  774.         _DEST TextHandles(Handle).SavedBackground
  775.         _SOURCE TextHandles(Handle).SavedBackground
  776.     END IF
  777.     _FONT TextHandles(Handle).Font
  778.     fh = _FONTHEIGHT: pw = _PRINTWIDTH(What)
  779.     IF _FONTWIDTH = 0 THEN
  780.         FOR i = 1 TO 255
  781.             IF _PRINTWIDTH(CHR$(i)) > fw THEN fw = _PRINTWIDTH(CHR$(i))
  782.         NEXT
  783.     ELSE
  784.         fw = _FONTWIDTH
  785.     END IF
  786.  
  787.     h = TextHandles(Handle).h - 4: w = TextHandles(Handle).w - 4
  788.  
  789.     SELECT CASE Where
  790.         CASE BottomLine
  791.             y = h \ fh
  792.         CASE OnLine
  793.             y = TextHandles(Handle).Ypos
  794.             IF y = 0 THEN y = 1
  795.         CASE CenterLine
  796.             linesused = 0
  797.             tpw = pw: tw = w: tWhat$ = What
  798.             DO UNTIL tpw <= tw
  799.                 textallowed = WordBreak(LEFT$(tWhat$, w \ fw))
  800.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  801.                 linesused = linesused + 1
  802.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  803.                 tpw = _PRINTWIDTH(tWhat$)
  804.             LOOP
  805.             linesused = linesused + 1
  806.             py = (h - linesused * fh) \ 2
  807.             y = py \ fh + 1
  808.             IF y < 1 THEN y = 1
  809.         CASE ELSE
  810.             y = Where
  811.     END SELECT
  812.  
  813.     'IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  814.     blend = _BLEND
  815.  
  816.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  817.         'first let's get a temp image handle for the existing area of the screen.
  818.         x1 = TextHandles(Handle).x1 + 2
  819.         y1 = TextHandles(Handle).y1 + 2
  820.         x2 = TextHandles(Handle).x1 + w
  821.         y2 = TextHandles(Handle).y1 + h
  822.         nh = y2 - y1 + 1 - fh
  823.         nw = x2 - x1 + 1
  824.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  825.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  826.         DrawTextArea Handle
  827.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  828.         y = y - 1
  829.     LOOP
  830.  
  831.     IF blend THEN _BLEND
  832.  
  833.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  834.  
  835.     SELECT CASE How
  836.         CASE LeftJustify
  837.             x = 0
  838.             IF pw > w THEN
  839.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  840.                 text$ = RTRIM$(LEFT$(What, textallowed))
  841.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  842.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  843.             ELSE
  844.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  845.                 finished = -1
  846.             END IF
  847.         CASE CenterJustify
  848.             IF pw > w THEN
  849.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  850.                 text$ = RTRIM$(LEFT$(What, textallowed))
  851.                 x = (w - _PRINTWIDTH(text$)) \ 2
  852.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  853.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  854.             ELSE
  855.                 x = (w - pw) \ 2
  856.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  857.                 finished = -1
  858.             END IF
  859.         CASE RightJustify
  860.             IF pw > w THEN
  861.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  862.                 text$ = RTRIM$(LEFT$(What, textallowed))
  863.                 x = w - _PRINTWIDTH(text$)
  864.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  865.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  866.             ELSE
  867.                 x = w - pw
  868.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  869.                 finished = -1
  870.             END IF
  871.         CASE NoJustify
  872.             x = TextHandles(Handle).Xpos
  873.             firstlinelimit = (w - x) \ fw 'the limit of characters on the first line
  874.             IF LEN(What) > firstlinelimit THEN
  875.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  876.                 text$ = RTRIM$(LEFT$(What, textallowed))
  877.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  878.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  879.             ELSE
  880.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  881.                 finished = -1
  882.             END IF
  883.     END SELECT
  884.     IF finished THEN
  885.         SELECT CASE TextHandles(Handle).UpdateMethod
  886.             CASE NoUpdate 'We don't update the position at all.
  887.             CASE DoUpdate
  888.                 TextHandles(Handle).Xpos = x + pw
  889.                 TextHandles(Handle).Ypos = y
  890.             CASE NewLine
  891.                 TextHandles(Handle).Ypos = y + 1
  892.                 TextHandles(Handle).Xpos = 1
  893.         END SELECT
  894.         _FONT OldFont
  895.         _DEST D: _SOURCE S
  896.         COLOR FG, BG
  897.     END IF
  898.  
  899. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  900.     u = UBOUND(TextHandles)
  901.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  902.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  903.     TextHandles(Handle).TextColor = Foreground
  904.  
  905.  
  906. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  907.     u = UBOUND(TextHandles)
  908.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  909.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  910.     TextHandles(Handle).TextBackgroundColor = Background
  911.  
  912. SUB SetTextFont (Handle AS INTEGER, FontName AS STRING, FontSize AS INTEGER)
  913.     u = UBOUND(TextHandles)
  914.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  915.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  916.     SELECT CASE TextHandles(Handle).Font
  917.         CASE 8, 9, 14, 15, 16, 17 'In built QB64 fonts.  We don't need to free them.
  918.         CASE IS > 1
  919.             'we have the font already in use
  920.             'REMOVE THIS CONDITION IF NECESSARY, AND MANUALLY FREE/RELEASE FONTS AS ABLE!!!
  921.             _FREEFONT TextHandles(Handle).Font 'if it's in use elsewhere, this *WILL* toss an error.
  922.     END SELECT
  923.  
  924.     temp = _LOADFONT(FontName, FontSize, "MONOSPACE")
  925.     IF temp > 1 THEN
  926.         TextHandles(Handle).Font = temp
  927.     ELSE
  928.         TextHandles(Handle).Font = 16 'default to font 16, in case
  929.     END IF
  930.  
  931.  
  932. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  933.     u = UBOUND(TextHandles)
  934.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  935.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  936.     TextHandles(Handle).TextColor = Foreground
  937.     TextHandles(Handle).TextBackgroundColor = Background
  938.  
  939.  
  940. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  941.     u = UBOUND(TextHandles)
  942.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  943.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  944.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  945.     TextHandles(Handle).UpdateMethod = Method
  946.  
  947.  
  948. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  949.     u = UBOUND(TextHandles)
  950.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  951.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  952.     SELECT CASE Y
  953.         CASE BottomLine
  954.             TextHandles(Handle).VerticalAlignment = -2
  955.         CASE CenterLine
  956.             TextHandles(Handle).VerticalAlignment = -1
  957.         CASE ELSE
  958.             TextHandles(Handle).VerticalAlignment = 0
  959.     END SELECT
  960.     IF X < 1 AND X > -4 THEN
  961.         TextHandles(Handle).Justification = X
  962.     ELSE
  963.         TextHandles(Handle).Xpos = X
  964.     END IF
  965.     IF Y < 1 THEN EXIT SUB
  966.     TextHandles(Handle).Ypos = Y
  967.  
  968. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  969.     u = UBOUND(TextHandles)
  970.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  971.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  972.     IF X < 1 AND X > -4 THEN
  973.         TextHandles(Handle).Justification = X
  974.     ELSE
  975.         TextHandles(Handle).Xpos = X
  976.     END IF
  977.  
  978. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  979.     u = UBOUND(TextHandles)
  980.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  981.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  982.     SELECT CASE Y
  983.         CASE BottomLine
  984.             TextHandles(Handle).VerticalAlignment = -2
  985.         CASE CenterLine
  986.             TextHandles(Handle).VerticalAlignment = -1
  987.         CASE ELSE
  988.             TextHandles(Handle).VerticalAlignment = 0
  989.     END SELECT
  990.     IF Y < 1 THEN EXIT SUB
  991.     TextHandles(Handle).Ypos = Y
  992.  
  993.  
  994. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  995.     u = UBOUND(TextHandles)
  996.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  997.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  998.     GetPrintPositionY = TextHandles(Handle).Ypos
  999.  
  1000. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  1001.     u = UBOUND(TextHandles)
  1002.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1003.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1004.     GetPrintPositionX = TextHandles(Handle).Xpos
  1005.  
  1006.  
  1007.  
  1008. FUNCTION WordBreak (text$)
  1009.     CONST Breaks = " ;,.?!-"
  1010.     FOR i = LEN(text$) TO 0 STEP -1
  1011.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  1012.         loopcount = loopcount + 1
  1013.     NEXT
  1014.     IF i = 0 THEN i = LEN(text$)
  1015.     WordBreak = i
  1016.  
  1017.  
  1018.  
  1019. SUB ClearTextArea (Handle AS INTEGER)
  1020.     u = UBOUND(TextHandles)
  1021.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1022.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1023.     IF TextHandles(Handle).SavedBackground THEN
  1024.         w = TextHandles(Handle).w
  1025.         h = TextHandles(Handle).h
  1026.         x1 = TextHandles(Handle).ScreenX
  1027.         y1 = TextHandles(Handle).ScreenY
  1028.         x2 = x1 + w - 1
  1029.         y2 = y1 + h - 1
  1030.         blend = _BLEND
  1031.         _DONTBLEND
  1032.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1033.         IF blend THEN _BLEND
  1034.     END IF
  1035.     DrawTextArea Handle
  1036.  
  1037.  
  1038.  
  1039. SUB DrawTextArea (Handle AS INTEGER)
  1040.     u = UBOUND(TextHandles)
  1041.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1042.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1043.     w = TextHandles(Handle).w
  1044.     h = TextHandles(Handle).h
  1045.     x1 = TextHandles(Handle).ScreenX
  1046.     y1 = TextHandles(Handle).ScreenY
  1047.     x2 = x1 + w - 1
  1048.     y2 = y1 + h - 1
  1049.  
  1050.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  1051.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  1052.  
  1053.  
  1054.  
  1055. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  1056.     u = UBOUND(TextHandles)
  1057.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1058.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1059.     TextHandles(Handle).FrameColor = FrameColor
  1060.     TextHandles(Handle).BackColor = BackColor
  1061.  
  1062.  
  1063.  
  1064. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  1065.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  1066.     x2 = tx2: y2 = ty2
  1067.     IF x1 > x2 THEN SWAP x1, x2
  1068.     IF y1 > y2 THEN SWAP y1, y2
  1069.     w = x2 - x1 + 1
  1070.     h = y2 - y1 + 1
  1071.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  1072.     'Error checking for if the user sends coordinates which are off the screen
  1073.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  1074.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  1075.  
  1076.     u = UBOUND(TextHandles)
  1077.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  1078.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  1079.     NEXT
  1080.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  1081.         Handle = u + 1
  1082.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  1083.     END IF
  1084.     TextHandles(Handle).x1 = x1
  1085.     TextHandles(Handle).y1 = y1
  1086.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  1087.     TextHandles(Handle).InUse = True
  1088.     TextHandles(Handle).Xpos = 0
  1089.     TextHandles(Handle).Ypos = 1
  1090.     TextHandles(Handle).UpdateMethod = NewLine
  1091.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  1092.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  1093.  
  1094.     IF SaveBackground THEN
  1095.         imagehandle = _NEWIMAGE(w, h, 32)
  1096.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1097.         TextHandles(Handle).SavedBackground = imagehandle
  1098.     END IF
  1099.     TextHandles(Handle).ScreenX = x1
  1100.     TextHandles(Handle).ScreenY = y1
  1101.     TextHandles(Handle).Font = 16 'default to font 16
  1102.     NewTextArea% = Handle
  1103.  
  1104. SUB FreeTextArea (Handle AS INTEGER)
  1105.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  1106.         IF TextHandles(Handle).InUse THEN
  1107.             TextHandles(Handle).InUse = False
  1108.             IF TextHandles(Handle).SavedBackground THEN
  1109.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  1110.                     w = TextHandles(Handle).w
  1111.                     h = TextHandles(Handle).h
  1112.                     x1 = TextHandles(Handle).ScreenX
  1113.                     y1 = TextHandles(Handle).ScreenY
  1114.                     x2 = x1 + w - 1
  1115.                     y2 = y1 + h - 1
  1116.                     blend = _BLEND
  1117.                     _DONTBLEND
  1118.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1119.                     IF blend THEN _BLEND
  1120.                 END IF
  1121.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  1122.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  1123.             END IF
  1124.         ELSE
  1125.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  1126.         END IF
  1127.     ELSE
  1128.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  1129.     END IF
  1130.  
  1131. SUB HideFrame (Handle AS INTEGER)
  1132.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  1133.         TextHandles(Handle).HideFrame = -1
  1134.         w = TextHandles(Handle).w
  1135.         h = TextHandles(Handle).h
  1136.         x1 = TextHandles(Handle).ScreenX
  1137.         y1 = TextHandles(Handle).ScreenY
  1138.         x2 = x1 + w - 1
  1139.         y2 = y1 + h - 1
  1140.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1141.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1142.         IF TextHandles(Handle).SavedBackground THEN
  1143.             blend = _BLEND
  1144.             _DONTBLEND
  1145.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1146.             _FREEIMAGE TextHandles(Handle).SavedBackground
  1147.             IF blend THEN _BLEND
  1148.         END IF
  1149.         TextHandles(Handle).SavedBackground = imagehandle
  1150.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  1151.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  1152.     END IF
  1153.  
  1154. SUB RestoreFrame (Handle AS INTEGER)
  1155.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  1156.         TextHandles(Handle).HideFrame = 0
  1157.         w = TextHandles(Handle).w
  1158.         h = TextHandles(Handle).h
  1159.         x1 = TextHandles(Handle).ScreenX
  1160.         y1 = TextHandles(Handle).ScreenY
  1161.         x2 = x1 + w - 1
  1162.         y2 = y1 + h - 1
  1163.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1164.         blend = _BLEND
  1165.         _DONTBLEND
  1166.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1167.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  1168.         _FREEIMAGE TextHandles(Handle).SavedBackground
  1169.         IF blend THEN _BLEND
  1170.         TextHandles(Handle).SavedBackground = imagehandle
  1171.         TextHandles(Handle).x1 = x1 'When the frames are frames are restored, we need to recalculate our print position
  1172.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  1173.     END IF
  1174.  
  1175. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  1176.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  1177.     u = UBOUND(TextHandles)
  1178.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1179.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1180.     HideFrame Handle
  1181.     TextHandles(Handle).ScreenX = x1
  1182.     TextHandles(Handle).ScreenY = y1
  1183.     RestoreFrame Handle
  1184.  

Several improvements into the game now.

Attacks are now limited by range, and not just by a monster blocking movement, which allows me to implement longer ranged weapons and manual attacks into the game in a future update.  (A sword may only have a reach of 1 and hit the tile in front of the hero, but a spear could have a reach of 2, and a bow a range of 10 and shoot across a room.)

Enemies now attack back.  You can now get injured, and not just by critically failing an attack and hitting yourself!

Since you now get injured, you also now heal slowly over time, as you rest or explore the map.



At this point, I'd say the combat system is now more-or-less in place and working as intended.  Various weapons and gear can be swapped in and out to change attack values/damage ranges and such in the future, but the core of the battle system is now up and working as intended.

So what's coming next??

The hero needs the ability to level up from all the experience they can earn now...

Food and hunger needs to be introduced into the game, so the hero can starve to death if they just find a far corner and sit and let 300 rounds pass while they heal back to full health.  (Food management was always an important part of Rogue, and is something which I think is essential to manage when making a Rogue-like.)

It's about time to get rid of the adjustable magic candle and start adding some other light sources into the game, such as torches or lanterns....

... which means the inventory system is going to have to be implemented sometime soon...

... So, that's basically where we're at so far, and a general idea of where I'll be going to with the next stages of development.
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 14, 2019, 11:20:59 am
Hero's are now leveling up, and I've overhauled the display system a bit more.  I think things are starting to shape up nicely, as you can see from the attached screen shot.

Code: QB64: [Select]
  1. DEFLNG A-Z 'default to long instead of single
  2. TYPE TextArea
  3.     InUse AS INTEGER
  4.     x1 AS LONG 'left
  5.     y1 AS LONG 'top
  6.     w AS LONG 'width
  7.     h AS LONG 'height
  8.     FrameColor AS _UNSIGNED LONG
  9.     BackColor AS _UNSIGNED LONG
  10.     Xpos AS INTEGER
  11.     Ypos AS INTEGER
  12.     VerticalAlignment AS INTEGER
  13.     Justification AS INTEGER
  14.     UpdateMethod AS INTEGER
  15.     TextColor AS _UNSIGNED LONG
  16.     TextBackgroundColor AS _UNSIGNED LONG
  17.     SavedBackground AS INTEGER
  18.     HideFrame AS INTEGER
  19.     ScreenX AS INTEGER
  20.     ScreenY AS INTEGER
  21.     Font AS LONG 'NEW! Change fonts for each independent font area
  22.  
  23. REDIM SHARED TextHandles(0) AS TextArea
  24.  
  25. CONST True = -1, False = 0
  26. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  27. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  28. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  29. '********************************************************
  30. '* Text Frames before this line
  31. '********************************************************
  32.  
  33.  
  34.  
  35.  
  36.  
  37. _CONSOLE ON 'for debugging purposes while making/testing things
  38.  
  39. TYPE Damage_Type
  40.     Low AS INTEGER
  41.     High AS INTEGER
  42.  
  43. TYPE Light_Type
  44.     Name AS STRING * 20
  45.     Reach AS _UNSIGNED _BYTE
  46.     Left AS _UNSIGNED _BYTE
  47.  
  48. TYPE Weapon_Type
  49.     Name AS STRING * 20
  50.     Reach AS _UNSIGNED _BYTE
  51.     Damage AS Damage_Type
  52.     HitBonus AS _UNSIGNED _BYTE
  53.     DamageBonus AS _UNSIGNED _BYTE
  54.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  55.  
  56. TYPE Armor_Type
  57.     Name AS STRING * 20
  58.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  59.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  60.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  61.  
  62. TYPE Hero_Type
  63.     Name AS STRING * 20
  64.     Life AS Damage_Type
  65.     Mana AS Damage_Type
  66.     Level AS _UNSIGNED _BYTE
  67.     EXP_Earned AS LONG
  68.     EXP_Needed AS LONG
  69.     Light AS Light_Type
  70.     Weapon1 AS Weapon_Type
  71.     Weapon2 AS Weapon_Type
  72.     Armor AS Armor_Type
  73.     HealingRate AS _UNSIGNED _BYTE
  74.     Hunger AS SINGLE
  75.     HungerRate AS SINGLE
  76.  
  77. TYPE Monster_TYPE
  78.     Name AS STRING * 20
  79.     Life AS Damage_Type
  80.     Level AS INTEGER
  81.     ExpBonus AS INTEGER
  82.     Sight AS INTEGER
  83.     Hearing AS INTEGER
  84.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  85.     Weapon1 AS Weapon_Type
  86.     Weapon2 AS Weapon_Type
  87.     Armor AS Armor_Type
  88.     Found AS INTEGER
  89.     IconX AS LONG
  90.     IconY AS LONG
  91.  
  92. TYPE Encounter_TYPE
  93.     Active AS INTEGER
  94.     X AS INTEGER
  95.     Y AS INTEGER
  96.     M AS INTEGER
  97.     Life AS INTEGER
  98.  
  99. REDIM SHARED Monster(100) AS Monster_TYPE
  100. REDIM SHARED Encounter(100) AS Encounter_TYPE, EncounterLimit AS INTEGER
  101.  
  102. DIM SHARED Hero AS Hero_Type
  103. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  104. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  105. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  106. DIM SHARED Scale AS _FLOAT, WorkScreen AS LONG, DisplayScreen AS LONG, Icons AS LONG
  107. DIM SHARED TextFont AS LONG, StepsTaken AS _UNSIGNED _INTEGER64
  108.  
  109. WorkScreen = _NEWIMAGE(3200, 2400, 32)
  110. DisplayScreen = _NEWIMAGE(800, 700, 32)
  111. SCREEN DisplayScreen
  112. Scale = 2
  113.  
  114. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  115. '1 map is illuminated
  116. '2 map is uncovered
  117. '4 map is a wall
  118. '8 map is a pathway
  119. '16 map is a stairway
  120. '32 map is simply blocked (perhaps with a monster?)
  121. '64 map is secret (can not be uncovered)
  122.  
  123. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  124.  
  125.  
  126.  
  127. ON ERROR GOTO errorhandler
  128.  
  129. GOTO skiperrorhandler
  130. errorhandler:
  131.  
  132. PRINT ERR; "Error on "; _ERRORLINE
  133. _DEST DisplayScreen
  134. skiperrorhandler:
  135.  
  136. Init
  137. CreateMap 99, 74, 10
  138.  
  139.     DrawMap
  140.     DisplayCharacter
  141.     _DISPLAY
  142.     GetInput
  143.     MonstersTurn
  144.     CheckForHeroGrowth
  145.  
  146. SUB Init
  147.     D = _DEST
  148.     Hero.Name = "Steve The Tester!"
  149.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  150.     Hero.Mana.Low = 10: Hero.Mana.High = 10
  151.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  152.     Hero.Light.Name = "Magic Candle"
  153.     Hero.Light.Reach = 2: Hero.Light.Left = -1 'infinite
  154.     Hero.Weapon1.Name = "Bare Fist"
  155.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  156.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  157.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  158.     Hero.Weapon2.Name = "Magic Candle"
  159.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  160.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  161.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  162.     Hero.Armor.Name = "Naked"
  163.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  164.     Hero.HealingRate = 20 'the hero heals 1 point of health for every 20 valid turns naturally
  165.     Hero.HungerRate = 0.1 'Let's start heros out with a full belly and a low hunger growth rate
  166.  
  167.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  168.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  169.     SetTextFont PrintArea, "courbd.ttf", 24
  170.     DrawTextArea PrintArea
  171.     SetPrintPositionX PrintArea, CenterJustify
  172.     SetPrintUpdate PrintArea, NewLine
  173.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  174.     SetTextFont PrintArea, "courbd.ttf", 18
  175.     PrintOut PrintArea, "created by STEVE!"
  176.     PrintOut PrintArea, ""
  177.     SetPrintPositionX PrintArea, LeftJustify
  178.     SetTextFont PrintArea, "courbd.ttf", 12
  179.     TextFont = 12
  180.  
  181.  
  182.  
  183.     temp = _NEWIMAGE(480, 480, 32)
  184.     _DEST temp: _CONTROLCHR OFF
  185.     COLOR &HFFFFFF00, 0 'Yellow Hero
  186.     _PRINTSTRING (0, 0), CHR$(1) 'the hero
  187.     COLOR &HFFFF0000, 0 'Red Question Mark
  188.     _PRINTSTRING (16, 0), "?" 'a question mark
  189.     Icons = _LOADIMAGE("Sprites.png", 32)
  190.     _PUTIMAGE (2016, 1504)-STEP(32, 32), temp, Icons, (0, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  191.     _PUTIMAGE (1984, 1504)-STEP(32, 32), temp, Icons, (16, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  192.     '        SCREEN Icons: DO: SLEEP: LOOP
  193.  
  194.  
  195.     _DEST D
  196.  
  197. SUB CheckForHeroGrowth
  198.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  199.         CLS
  200.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  201.         _DISPLAY
  202.         BEEP
  203.         _DELAY 5
  204.         SYSTEM
  205.     END IF
  206.     IF Hero.EXP_Earned >= Hero.EXP_Needed THEN 'let's check to see if the hero has leveled up
  207.         PrintOut PrintArea, "Congratulations!  You have gained a level!"
  208.         DO
  209.             r = INT(RND * 6) + 1
  210.             lifegained = lifegained + r
  211.         LOOP UNTIL r <> 6
  212.         Hero.Life.Low = Hero.Life.Low + r
  213.         Hero.Life.High = Hero.Life.High + r
  214.         Hero.EXP_Earned = 0
  215.         Hero.Level = Hero.Level + 1
  216.         Hero.EXP_Needed = Hero.EXP_Needed + Hero.Level + 1
  217.     END IF
  218.     IF StepsTaken MOD Hero.HealingRate = 0 THEN 'heal the hero naturally over time
  219.         IF Hero.Life.Low < Hero.Life.High THEN Hero.Life.Low = Hero.Life.Low + 1
  220.     END IF
  221.     Hero.Hunger = Hero.Hunger + Hero.HungerRate
  222.  
  223.  
  224.  
  225. SUB DisplayCharacter
  226.     LINE (0, 601)-(229, 799), &HFF000000, BF
  227.     COLOR -1, 0
  228.  
  229.     Box 0, 601, 229, 62, 0, 0, "", Silver, 0
  230.     Box 0, 601, 229, 12, Black, 0, _TRIM$(Hero.Name), Silver, 0
  231.     Box 0, 626, 229 * Hero.Life.Low / Hero.Life.High, 12, 0, 0, "", Red, Black
  232.     Box 0, 639, 229 * Hero.Mana.Low / Hero.Mana.High, 12, 0, 0, "", Blue, Black
  233.     Box 0, 652, 229 * Hero.EXP_Earned / Hero.EXP_Needed, 12, 0, 0, "", Green, Black
  234.     _PRINTSTRING (10, 616), "LEVEL:" + STR$(Hero.Level)
  235.     _PRINTSTRING (10, 629), "LIFE :" + STR$(Hero.Life.Low) + " / " + _TRIM$(STR$(Hero.Life.High))
  236.     _PRINTSTRING (10, 642), "MANA :" + STR$(Hero.Mana.Low) + " / " + _TRIM$(STR$(Hero.Mana.High))
  237.     _PRINTSTRING (10, 655), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  238.  
  239.     FOR i = 0 TO 5 'six boxes for information : left hand, right hand, armor, and 3 more for later....
  240.         Box 36 * i + 8, 665, 34, 34, 0, 0, "", Black, Silver
  241.     NEXT
  242.     FOR i = 0 TO 1
  243.         IF i = 0 THEN WeaponName$ = _TRIM$(Hero.Weapon1.Name) ELSE WeaponName$ = _TRIM$(Hero.Weapon2.Name)
  244.  
  245.         SELECT CASE WeaponName$
  246.             CASE "Bare Fist": X = 2 * 32: Y = 47 * 32
  247.             CASE "Magic Candle": X = 52 * 32: Y = 42 * 32
  248.             CASE ELSE: X = -100: Y = -100
  249.         END SELECT
  250.         _PUTIMAGE (9 + 36 * i, 666)-STEP(32, 32), Icons, DisplayScreen, (X, Y)-STEP(32, 32)
  251.     NEXT
  252.     SELECT CASE _TRIM$(Hero.Armor.Name)
  253.         CASE "Naked": X = 46 * 32: Y = 42 * 32
  254.         CASE ELSE: X = -100: Y = -100
  255.     END SELECT
  256.     _PUTIMAGE (81, 666)-STEP(32, 32), Icons, DisplayScreen, (X, Y)-STEP(32, 32)
  257.  
  258.  
  259. SUB GetInput
  260.     DO
  261.         k = _KEYHIT: valid = -1
  262.         SELECT CASE k
  263.             CASE 18432 'up
  264.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  265.                     SELECT CASE Scale
  266.                         CASE 1.5: Scale = 2 'It's as small as we go
  267.                         CASE 2: Scale = 3
  268.                         CASE 3: Scale = 4
  269.                         CASE 4: Scale = 6
  270.                         CASE 6: Scale = 8
  271.                         CASE 8: Scale = 12
  272.                     END SELECT
  273.                 ELSE
  274.                     IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  275.                 END IF
  276.             CASE 19200: 'left
  277.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  278.                     TextFont = TextFont - 1
  279.                     IF TextFont < 8 THEN TextFont = 8
  280.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  281.                     ClearTextArea PrintArea
  282.                     SetPrintPosition PrintArea, 1, 1
  283.                     PrintOut PrintArea, "Font Size Changed"
  284.                 ELSE
  285.                     IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  286.                 END IF
  287.             CASE 20480: 'down
  288.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  289.                     SELECT CASE Scale
  290.                         CASE 2: Scale = 1.5 'It's as small as we go
  291.                         CASE 3: Scale = 2
  292.                         CASE 4: Scale = 3
  293.                         CASE 6: Scale = 4
  294.                         CASE 8: Scale = 6
  295.                         CASE 12: Scale = 8
  296.                     END SELECT
  297.                 ELSE
  298.                     IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  299.                 END IF
  300.             CASE 19712: 'right
  301.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  302.                     TextFont = TextFont + 1
  303.                     IF TextFont > 48 THEN TextFont = 48
  304.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  305.                     ClearTextArea PrintArea
  306.                     SetPrintPosition PrintArea, 1, 1
  307.                     PrintOut PrintArea, "Font Size Changed"
  308.                 ELSE
  309.                     IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  310.                 END IF
  311.             CASE 32 'space to just wait and skip a turn
  312.             CASE 60 ' "<" key
  313.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  314.                     Level = Level + 1
  315.                     CreateMap 99, 74, 10
  316.                     PathFind
  317.                 END IF
  318.             CASE ASC("+"), ASC("=")
  319.                 IF Hero.Light.Reach < 25 THEN Hero.Light.Reach = Hero.Light.Reach + 1
  320.             CASE ASC("-"), ASC("_")
  321.                 IF Hero.Light.Reach > 1 THEN Hero.Light.Reach = Hero.Light.Reach - 1
  322.             CASE ELSE
  323.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  324.         END SELECT
  325.         _LIMIT 60
  326.     LOOP UNTIL k AND valid
  327.     _KEYCLEAR 'one keystroke at a time
  328.     StepsTaken = StepsTaken + 1
  329.  
  330. SUB Box (X, Y, Wide, High, FontColor as _unsigned long, _
  331.          FontBackGround as _unsigned long, Caption AS STRING, Kolor AS _UNSIGNED LONG, BackGround AS _UNSIGNED LONG)
  332.     COLOR FontColor, FontBackGround
  333.     LINE (X, Y)-STEP(Wide, High), Kolor, BF
  334.     LINE (X, Y)-STEP(Wide, High), BackGround, B
  335.     pw = _PRINTWIDTH(Caption): ph = _FONTHEIGHT
  336.     _PRINTSTRING (X + (Wide - pw + 1) \ 2, Y + (High - ph + 1) \ 2), Caption
  337.     COLOR DC, BG
  338.  
  339.  
  340. SUB MoveHero (MoveX, MoveY)
  341.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  342.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  343.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  344.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  345.             IF MoveX THEN Hero.X = Hero.X + MoveX
  346.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  347.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  348.             PathFind
  349.         ELSE
  350.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  351.             FOR i = 1 TO EncounterLimit
  352.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  353.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  354.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  355.                         Swing 0, i 'hero swings at the monster
  356.                     END IF
  357.                 END IF
  358.             NEXT
  359.         END IF
  360.     END IF
  361.  
  362. SUB Swing (Who, AtWhom)
  363.  
  364.     BaseChancetohit = 10 'base 10 chance to hit
  365.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  366.         M = Encounter(AtWhom).M
  367.         IF Hero.Weapon1.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  368.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  369.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  370.             totalroll = 0
  371.             DO
  372.                 roll = INT(RND * 20) + 1
  373.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  374.                 IF roll = 20 THEN totalroll = totalroll + 20
  375.                 totalroll = totalroll + roll
  376.             LOOP UNTIL roll <> 1 AND roll <> 20
  377.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  378.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  379.             out$ = _TRIM$(Hero.Name)
  380.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  381.                 SetTextColor PrintArea, &HFFF000F0, 0
  382.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  383.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  384.                 Hero.Life.Low = Hero.Life.Low - damage
  385.             ELSEIF totalroll < Chancetohit THEN
  386.                 SetTextColor PrintArea, &HFFF0F000, 0
  387.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  388.             ELSEIF totalroll > Chancetohit + 20 THEN
  389.                 SetTextColor PrintArea, &HFF00FF00, 0
  390.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  391.                 damage = damage * (totalroll / 20 + 1)
  392.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  393.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  394.             ELSEIF totalroll >= Chancetohit THEN
  395.                 SetTextColor PrintArea, &HFF00FF00, 0
  396.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  397.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  398.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  399.             END IF
  400.         ELSEIF Hero.Weapon1.Reach > 0 THEN
  401.             SetTextColor PrintArea, &HFFF000F0, 0
  402.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon1.Name) + "!"
  403.         ELSE
  404.             out$ = ""
  405.         END IF
  406.         IF out$ <> "" THEN PrintOut PrintArea, out$
  407.         IF Hero.Weapon2.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  408.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  409.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  410.             totalroll = 0
  411.             DO
  412.                 roll = INT(RND * 20) + 1
  413.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  414.                 IF roll = 20 THEN totalroll = totalroll + 20
  415.                 totalroll = totalroll + roll
  416.             LOOP UNTIL roll <> 1 AND roll <> 20
  417.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  418.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  419.             out$ = _TRIM$(Hero.Name)
  420.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  421.                 SetTextColor PrintArea, &HFFF000F0, 0
  422.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  423.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  424.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  425.                 Hero.Life.Low = Hero.Life.Low - damage
  426.             ELSEIF totalroll < Chancetohit THEN
  427.                 SetTextColor PrintArea, &HFFF0F000, 0
  428.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  429.             ELSEIF totalroll > Chancetohit + 20 THEN
  430.                 SetTextColor PrintArea, &HFF00FF00, 0
  431.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  432.                 damage = damage * (totalroll / 20 + 1)
  433.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  434.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  435.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  436.             ELSEIF totalroll >= Chancetohit THEN
  437.                 SetTextColor PrintArea, &HFF00FF00, 0
  438.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  439.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  440.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  441.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  442.             END IF
  443.         ELSEIF Hero.Weapon2.Reach > 0 THEN
  444.             SetTextColor PrintArea, &HFFF000F0, 0
  445.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon2.Name) + "!"
  446.         ELSE
  447.             out$ = ""
  448.         END IF
  449.         IF out$ <> "" THEN PrintOut PrintArea, out$
  450.         IF Encounter(AtWhom).Life <= 0 THEN 'the monster died!
  451.             SetTextColor PrintArea, &HFFFF0000, 0
  452.             out$ = _TRIM$(Monster(M).Name) + " died!  You earned " + _TRIM$(STR$(Monster(M).Level + Monster(M).ExpBonus)) + " experience."
  453.             PrintOut PrintArea, out$
  454.             Encounter(AtWhom).Active = 0
  455.             Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  456.             MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) = MapArray(Encounter(AtWhom).X, Encounter(AtWhom).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  457.             IF Monster(M).Found = 0 THEN
  458.                 Monster(M).Found = -1 'it's a first time kill!
  459.                 SetTextColor PrintArea, &HFFFFFF00, &HFFFF0000
  460.                 out$ = "Congratulations!  You killed a " + _TRIM$(Monster(M).Name) + " for the first time!"
  461.                 PrintOut PrintArea, out$
  462.             END IF
  463.         END IF
  464.     ELSE 'it's a monster attacking
  465.         M = Encounter(Who).M
  466.         IF Monster(M).Weapon1.Reach >= Distance(Encounter(Who).X, Encounter(Who).Y) THEN 'it's a weapon and not an utility object being held.
  467.             Chancetohit = BaseChancetohit + Monster(M).Weapon1.HitBonus 'add in the weapon's hit bonus
  468.             Chancetohit = Chancetohit - Hero.Armor.PD 'subtract the hero's armor/ natural dodge
  469.             totalroll = 0
  470.             DO
  471.                 roll = INT(RND * 20) + 1
  472.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  473.                 IF roll = 20 THEN totalroll = totalroll + 20
  474.                 totalroll = totalroll + roll
  475.             LOOP UNTIL roll <> 1 AND roll <> 20
  476.             damage = INT(RND * (Monster(M).Weapon1.Damage.High - Monster(M).Weapon1.Damage.Low + 1)) + Monster(M).Weapon1.Damage.Low 'random damage for the hit
  477.             damage = damage + Monster(M).Weapon1.DamageBonus 'add in the weapon's damage bonus
  478.             out$ = _TRIM$(Monster(M).Name)
  479.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  480.                 SetTextColor PrintArea, &HFFF000F0, 0
  481.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  482.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  483.                 Monster(M).Life.Low = Monster(M).Life.Low - damage
  484.             ELSEIF totalroll < Chancetohit THEN
  485.                 SetTextColor PrintArea, &HFFF0F000, 0
  486.                 out$ = out$ + " missed " + _TRIM$(Hero.Name) + ", with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  487.             ELSEIF totalroll > Chancetohit + 20 THEN
  488.                 SetTextColor PrintArea, &HFF00FFFF, 0
  489.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Hero.Name) + " for"
  490.                 damage = damage * (totalroll / 20 + 1)
  491.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  492.                 Hero.Life.Low = Hero.Life.Low - damage
  493.             ELSEIF totalroll >= Chancetohit THEN
  494.                 SetTextColor PrintArea, &HFF00FFFF, 0
  495.                 out$ = out$ + " hit " + _TRIM$(Hero.Name) + " for"
  496.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "."
  497.                 Hero.Life.Low = Hero.Life.Low - damage
  498.             END IF
  499.         ELSEIF Monster(M).Weapon1.Reach > 0 THEN
  500.             SetTextColor PrintArea, &HFFF000F0, 0
  501.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Monster(M).Weapon2.Name) + "!"
  502.         ELSE
  503.             out$ = ""
  504.         END IF
  505.         IF out$ <> "" THEN PrintOut PrintArea, out$
  506.     END IF
  507.  
  508.  
  509. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  510.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  511.     D = Distance(MX, MY) 'distance from monster to the hero
  512.     E = Encounter(i).M 'the actual monster in question
  513.  
  514.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  515.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  516.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  517.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  518.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  519.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  520.             MoveMonster = -1
  521.         END IF
  522.     END IF
  523.  
  524.  
  525.  
  526. SUB MonstersTurn
  527.     FOR i = 1 TO EncounterLimit
  528.         IF Encounter(i).Active THEN 'Only if the monster is still alive and active do we need to actually do anything else.
  529.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  530.             D = Distance(MX, MY) 'distance from monster to the hero
  531.             E = Encounter(i).M 'the actual monster in question
  532.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  533.  
  534.                 attack = 0
  535.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  536.                     'insert attack code here
  537.                     Swing i, 0
  538.                     _CONTINUE
  539.                 END IF
  540.  
  541.                 'if the monster didn't attack, it can now move towards the hero.
  542.                 IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  543.                     IF D > Distance(MX - 1, MY) THEN
  544.                         IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  545.                     END IF
  546.                 END IF
  547.                 IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  548.                     IF D > Distance(MX, MY - 1) THEN
  549.                         IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  550.                     END IF
  551.                 END IF
  552.                 IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  553.                     IF D > Distance(MX + 1, MY) THEN
  554.                         IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  555.                     END IF
  556.                 END IF
  557.                 IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  558.                     IF D > Distance(MX, MY + 1) THEN
  559.                         IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  560.                     END IF
  561.                 END IF
  562.             END IF
  563.         END IF
  564.  
  565.     NEXT
  566.  
  567.  
  568.  
  569.  
  570. SUB DrawMap
  571.     _DEST WorkScreen
  572.     CLS
  573.     'LINE (0, 0)-(3200, 2400), &HFF000000, BF 'clear the map
  574.     FOR Y = 0 TO YH
  575.         FOR X = 0 TO XH
  576.             IF Distance(X, Y) <= Hero.Light.Reach THEN 'It's close enough to check for illumination
  577.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  578.             END IF
  579.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  580.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  581.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF303030, BF
  582.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (6 * 32, 18 * 32)-STEP(31, 31)
  583.                 END IF
  584.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  585.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (36 * 32, 13 * 32)-STEP(31, 31)
  586.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF707070, BF
  587.                 END IF
  588.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  589.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (4 * 32, 45 * 32)-STEP(31, 31)
  590.                 END IF
  591.             END IF
  592.             'note: highlighting for the light should come AFTER the map is drawn
  593.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  594.                 LINE (X * 32, Y * 32)-STEP(32, 32), &H40FFFF00, BF
  595.                 MapArray(X, Y) = MapArray(X, Y) - 1
  596.                 FOR I = 1 TO EncounterLimit
  597.                     IF X = Encounter(I).X AND Y = Encounter(I).Y AND Encounter(I).Active = -1 THEN
  598.                         E = Encounter(I).M
  599.                         IF Monster(E).Found THEN
  600.                             _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Monster(E).IconX, Monster(E).IconY)-STEP(31, 31)
  601.                         ELSE
  602.                             _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (1984, 1504)-STEP(31, 31)
  603.                         END IF
  604.                     END IF
  605.                 NEXT
  606.  
  607.             END IF
  608.         NEXT
  609.     NEXT
  610.     COLOR &HFFFFFF00, 0 'Yellow Hero
  611.     _PUTIMAGE (Hero.X * 32, Hero.Y * 32)-STEP(32, 32), Icons, WorkScreen, (2016, 1504)-STEP(31, 31)
  612.     XOffset## = 1600 / Scale
  613.     YOffset## = 1200 / Scale
  614.     CenterX = Hero.X * 32 'convert hero coordinate to grid coordinate
  615.     CenterY = Hero.Y * 32
  616.     _DEST DisplayScreen
  617.     LINE (0, 0)-(800, 600), &HFF000000, BF 'clear the map
  618.     _PUTIMAGE (0, 0)-(800, 600), WorkScreen, DisplayScreen, (CenterX - XOffset##, CenterY - YOffset##)-(CenterX + XOffset##, CenterY + YOffset##)
  619.  
  620.  
  621.  
  622.  
  623.  
  624. SUB CreateMap (XLimit, YLimit, Rooms)
  625.     ERASE MapArray 'clear the old map and reset everything to 0
  626.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  627.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  628.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  629.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  630.  
  631.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  632.  
  633.     StairRoom = INT(RND * Rooms) + 1
  634.     FOR i = 1 TO Rooms
  635.         DO
  636.             RoomSize = INT(RND * 9) + 2
  637.             RoomX = INT(RND * (XLimit - RoomSize))
  638.             RoomY = INT(RND * (YLimit - RoomSize))
  639.             'test for positioning
  640.             good = -1 'it's good starting out
  641.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  642.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  643.             NEXT X, Y
  644.         LOOP UNTIL good
  645.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  646.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  647.         NEXT X, Y
  648.         RoomCenterX(i) = RoomX + .5 * RoomSize
  649.         RoomCenterY(i) = RoomY + .5 * RoomSize
  650.         IF i = 1 THEN 'place the hero in the first room  (which can be anywhere randomly on our map)
  651.             Hero.X = RoomX + INT(RND * RoomSize)
  652.             Hero.Y = RoomY + INT(RND * RoomSize)
  653.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  654.         END IF
  655.         IF i = StairRoom THEN 'place the stairs in one of the random rooms
  656.             DO 'But lets not place the stairs directly on top of the hero to begin with
  657.                 StairX = RoomX + INT(RND * RoomSize)
  658.                 StairY = RoomY + INT(RND * RoomSize)
  659.             LOOP UNTIL StairX <> Hero.X AND StairY <> Hero.Y
  660.             MapArray(StairX, StairY) = MapArray(StairX, StairY) OR 16
  661.         END IF
  662.     NEXT
  663.     FOR i = 1 TO Rooms - 1
  664.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  665.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  666.         DO UNTIL StartX = EndX AND StartY = EndY
  667.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  668.             Meander = 10
  669.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  670.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  671.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  672.                     Ychange = 0
  673.                 ELSE
  674.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  675.                     Ychange = 0
  676.                 END IF
  677.             ELSE
  678.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  679.                     Ychange = SGN(EndY - StartY)
  680.                     XChange = 0
  681.                 ELSE
  682.                     Ychange = INT(RND * 3) - 1
  683.                     XChange = 0
  684.                 END IF
  685.             END IF
  686.             StartX = StartX + XChange
  687.             StartY = StartY + Ychange
  688.             IF StartX < 0 THEN StartX = 0 'Make certain we move inside the bounds of our map dimensions
  689.             IF StartY < 0 THEN StartY = 0
  690.             IF StartX > XH THEN StartX = XH
  691.             IF StartY > YH THEN StartY = YH
  692.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8 'place a path where we moved to
  693.         LOOP
  694.     NEXT
  695.     PathFind
  696.     EncounterLimit = INT(RND * 6) + 5
  697.     FOR i = 1 TO EncounterLimit
  698.         Encounter(i).M = RandomMonster
  699.         Encounter(i).Active = -1
  700.         Encounter(i).Life = INT(RND * Monster(Encounter(i).M).Life.High - Monster(Encounter(i).M).Life.Low + 1) + Monster(Encounter(i).M).Life.Low
  701.         valid = -1: EndlessLoopExit = 0
  702.         DO
  703.             EndlessLoopExit = EndlessLoopExit + 1
  704.             Encounter(i).X = INT(RND * XLimit + 1)
  705.             Encounter(i).Y = INT(RND * YLimit + 1)
  706.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  707.             IF EndlessLoopExit = 1000 THEN EXIT DO 'if we can't place the monster in a room after 1000 tries, just place it wherever and call it a "wandering monster".
  708.             'Of course, "wandering monsters" may end up inside a wall, in which case they simply become "lost monsters" and do nothing to affect the level.  It's the same as if they never existed at all.
  709.             'BUT, we *should* generally be able to place a monster after 1000 tries.  This segment is just in the off-chance that the Random Number Gods are out to get us and to prevent any chance for an endless loop.
  710.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  711.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  712.     NEXT
  713.  
  714. SUB PathFind
  715.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  716.     DIM pass AS _UNSIGNED _BYTE
  717.     m = _MEM(Distance()): m1 = _MEM(Temp())
  718.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  719.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  720.     Temp(Hero.X, Hero.Y) = 0
  721.     pass = 0
  722.     DO
  723.         changed = 0
  724.         y = 0
  725.         DO
  726.             x = 0
  727.             DO
  728.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  729.                     IF x < XH THEN
  730.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  731.                     END IF
  732.                     IF x > 0 THEN
  733.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  734.                     END IF
  735.                     IF y < YH THEN
  736.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  737.                     END IF
  738.                     IF y > 0 THEN
  739.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  740.                     END IF
  741.                 END IF
  742.                 x = x + 1
  743.             LOOP UNTIL x > XH
  744.             y = y + 1
  745.         LOOP UNTIL y > YH
  746.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  747.         pass = pass + 1
  748.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  749.     Distance(Hero.X, Hero.Y) = 0
  750.  
  751. FUNCTION RandomMonster
  752.     'Shared variable level tells us what level of the dungeon we're on.
  753.     STATIC MC, DS 'monster count and data set
  754.     IF NOT DS THEN
  755.         DS = -1
  756.         Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  757.         Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  758.         Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  759.         Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  760.         Monster(1).IconX = 44 * 32: Monster(1).IconY = 3 * 32 'position 44,3 on the sprite sheet
  761.         'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  762.         'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  763.         'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  764.         'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  765.         'Monster(1).Armor.Name = ""
  766.         'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  767.  
  768.         Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  769.         Monster(2).Level = 1: Monster(2).ExpBonus = 0
  770.         Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  771.         Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  772.         Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  773.         Monster(2).IconX = 23 * 32: Monster(2).IconY = 4 * 32 'position 44,3 on the sprite sheet
  774.         Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  775.         Monster(3).Level = 1: Monster(3).ExpBonus = 0
  776.         Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  777.         Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  778.         Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  779.         Monster(3).IconX = 37 * 32: Monster(3).IconY = 4 * 32 'position 44,3 on the sprite sheet
  780.     END IF
  781.     SELECT CASE Level 'the starting level
  782.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  783.     END SELECT
  784.     RandomMonster = INT(RND * MC) + 1
  785.  
  786.  
  787. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  788. '# SUBroutines and FUNCTIONs below #'
  789. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  790. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  791.     u = UBOUND(TextHandles)
  792.     Handle = WhichHandle
  793.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  794.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  795.     Where = TextHandles(Handle).VerticalAlignment
  796.     How = TextHandles(Handle).Justification
  797.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  798.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  799.  
  800.  
  801. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  802.     'WhichHandle is the handle which designates which text area we want to use
  803.     'Where is where we want it to go in that text area
  804.     '  -- Online prints the text to the current print position line in that text area.
  805.     '  -- CenterLine centers the text to the center of that text area.
  806.     '  -- any other value will print to that line positon in that particular box.
  807.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  808.     'What is the text that we want to print in our text area
  809.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  810.  
  811.     D = _DEST: S = _SOURCE
  812.     OldFont = _FONT
  813.  
  814.     u = UBOUND(TextHandles)
  815.     Handle = WhichHandle
  816.  
  817.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  818.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  819.     IF TextHandles(Handle).HideFrame THEN
  820.         _DEST TextHandles(Handle).SavedBackground
  821.         _SOURCE TextHandles(Handle).SavedBackground
  822.     END IF
  823.     _FONT TextHandles(Handle).Font
  824.     fh = _FONTHEIGHT: pw = _PRINTWIDTH(What)
  825.     IF _FONTWIDTH = 0 THEN
  826.         FOR i = 1 TO 255
  827.             IF _PRINTWIDTH(CHR$(i)) > fw THEN fw = _PRINTWIDTH(CHR$(i))
  828.         NEXT
  829.     ELSE
  830.         fw = _FONTWIDTH
  831.     END IF
  832.  
  833.     h = TextHandles(Handle).h - 4: w = TextHandles(Handle).w - 4
  834.  
  835.     SELECT CASE Where
  836.         CASE BottomLine
  837.             y = h \ fh
  838.         CASE OnLine
  839.             y = TextHandles(Handle).Ypos
  840.             IF y = 0 THEN y = 1
  841.         CASE CenterLine
  842.             linesused = 0
  843.             tpw = pw: tw = w: tWhat$ = What
  844.             DO UNTIL tpw <= tw
  845.                 textallowed = WordBreak(LEFT$(tWhat$, w \ fw))
  846.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  847.                 linesused = linesused + 1
  848.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  849.                 tpw = _PRINTWIDTH(tWhat$)
  850.             LOOP
  851.             linesused = linesused + 1
  852.             py = (h - linesused * fh) \ 2
  853.             y = py \ fh + 1
  854.             IF y < 1 THEN y = 1
  855.         CASE ELSE
  856.             y = Where
  857.     END SELECT
  858.  
  859.     'IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  860.     blend = _BLEND
  861.  
  862.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  863.         'first let's get a temp image handle for the existing area of the screen.
  864.         x1 = TextHandles(Handle).x1 + 2
  865.         y1 = TextHandles(Handle).y1 + 2
  866.         x2 = TextHandles(Handle).x1 + w
  867.         y2 = TextHandles(Handle).y1 + h
  868.         nh = y2 - y1 + 1 - fh
  869.         nw = x2 - x1 + 1
  870.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  871.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  872.         DrawTextArea Handle
  873.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  874.         y = y - 1
  875.     LOOP
  876.  
  877.     IF blend THEN _BLEND
  878.  
  879.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  880.  
  881.     SELECT CASE How
  882.         CASE LeftJustify
  883.             x = 0
  884.             IF pw > w THEN
  885.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  886.                 text$ = RTRIM$(LEFT$(What, textallowed))
  887.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  888.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  889.             ELSE
  890.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  891.                 finished = -1
  892.             END IF
  893.         CASE CenterJustify
  894.             IF pw > w THEN
  895.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  896.                 text$ = RTRIM$(LEFT$(What, textallowed))
  897.                 x = (w - _PRINTWIDTH(text$)) \ 2
  898.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  899.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  900.             ELSE
  901.                 x = (w - pw) \ 2
  902.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  903.                 finished = -1
  904.             END IF
  905.         CASE RightJustify
  906.             IF pw > w THEN
  907.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  908.                 text$ = RTRIM$(LEFT$(What, textallowed))
  909.                 x = w - _PRINTWIDTH(text$)
  910.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  911.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  912.             ELSE
  913.                 x = w - pw
  914.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  915.                 finished = -1
  916.             END IF
  917.         CASE NoJustify
  918.             x = TextHandles(Handle).Xpos
  919.             firstlinelimit = (w - x) \ fw 'the limit of characters on the first line
  920.             IF LEN(What) > firstlinelimit THEN
  921.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  922.                 text$ = RTRIM$(LEFT$(What, textallowed))
  923.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  924.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  925.             ELSE
  926.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  927.                 finished = -1
  928.             END IF
  929.     END SELECT
  930.     IF finished THEN
  931.         SELECT CASE TextHandles(Handle).UpdateMethod
  932.             CASE NoUpdate 'We don't update the position at all.
  933.             CASE DoUpdate
  934.                 TextHandles(Handle).Xpos = x + pw
  935.                 TextHandles(Handle).Ypos = y
  936.             CASE NewLine
  937.                 TextHandles(Handle).Ypos = y + 1
  938.                 TextHandles(Handle).Xpos = 1
  939.         END SELECT
  940.         _FONT OldFont
  941.         _DEST D: _SOURCE S
  942.         COLOR FG, BG
  943.     END IF
  944.  
  945. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  946.     u = UBOUND(TextHandles)
  947.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  948.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  949.     TextHandles(Handle).TextColor = Foreground
  950.  
  951.  
  952. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  953.     u = UBOUND(TextHandles)
  954.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  955.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  956.     TextHandles(Handle).TextBackgroundColor = Background
  957.  
  958. SUB SetTextFont (Handle AS INTEGER, FontName AS STRING, FontSize AS INTEGER)
  959.     u = UBOUND(TextHandles)
  960.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  961.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  962.     SELECT CASE TextHandles(Handle).Font
  963.         CASE 8, 9, 14, 15, 16, 17 'In built QB64 fonts.  We don't need to free them.
  964.         CASE IS > 1
  965.             'we have the font already in use
  966.             'REMOVE THIS CONDITION IF NECESSARY, AND MANUALLY FREE/RELEASE FONTS AS ABLE!!!
  967.             _FREEFONT TextHandles(Handle).Font 'if it's in use elsewhere, this *WILL* toss an error.
  968.     END SELECT
  969.  
  970.     temp = _LOADFONT(FontName, FontSize, "MONOSPACE")
  971.     IF temp > 1 THEN
  972.         TextHandles(Handle).Font = temp
  973.     ELSE
  974.         TextHandles(Handle).Font = 16 'default to font 16, in case
  975.     END IF
  976.  
  977.  
  978. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  979.     u = UBOUND(TextHandles)
  980.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  981.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  982.     TextHandles(Handle).TextColor = Foreground
  983.     TextHandles(Handle).TextBackgroundColor = Background
  984.  
  985.  
  986. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  987.     u = UBOUND(TextHandles)
  988.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  989.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  990.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  991.     TextHandles(Handle).UpdateMethod = Method
  992.  
  993.  
  994. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  995.     u = UBOUND(TextHandles)
  996.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  997.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  998.     SELECT CASE Y
  999.         CASE BottomLine
  1000.             TextHandles(Handle).VerticalAlignment = -2
  1001.         CASE CenterLine
  1002.             TextHandles(Handle).VerticalAlignment = -1
  1003.         CASE ELSE
  1004.             TextHandles(Handle).VerticalAlignment = 0
  1005.     END SELECT
  1006.     IF X < 1 AND X > -4 THEN
  1007.         TextHandles(Handle).Justification = X
  1008.     ELSE
  1009.         TextHandles(Handle).Xpos = X
  1010.     END IF
  1011.     IF Y < 1 THEN EXIT SUB
  1012.     TextHandles(Handle).Ypos = Y
  1013.  
  1014. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  1015.     u = UBOUND(TextHandles)
  1016.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1017.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1018.     IF X < 1 AND X > -4 THEN
  1019.         TextHandles(Handle).Justification = X
  1020.     ELSE
  1021.         TextHandles(Handle).Xpos = X
  1022.     END IF
  1023.  
  1024. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  1025.     u = UBOUND(TextHandles)
  1026.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1027.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1028.     SELECT CASE Y
  1029.         CASE BottomLine
  1030.             TextHandles(Handle).VerticalAlignment = -2
  1031.         CASE CenterLine
  1032.             TextHandles(Handle).VerticalAlignment = -1
  1033.         CASE ELSE
  1034.             TextHandles(Handle).VerticalAlignment = 0
  1035.     END SELECT
  1036.     IF Y < 1 THEN EXIT SUB
  1037.     TextHandles(Handle).Ypos = Y
  1038.  
  1039.  
  1040. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  1041.     u = UBOUND(TextHandles)
  1042.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1043.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1044.     GetPrintPositionY = TextHandles(Handle).Ypos
  1045.  
  1046. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  1047.     u = UBOUND(TextHandles)
  1048.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1049.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1050.     GetPrintPositionX = TextHandles(Handle).Xpos
  1051.  
  1052.  
  1053.  
  1054. FUNCTION WordBreak (text$)
  1055.     CONST Breaks = " ;,.?!-"
  1056.     FOR i = LEN(text$) TO 0 STEP -1
  1057.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  1058.         loopcount = loopcount + 1
  1059.     NEXT
  1060.     IF i = 0 THEN i = LEN(text$)
  1061.     WordBreak = i
  1062.  
  1063.  
  1064.  
  1065. SUB ClearTextArea (Handle AS INTEGER)
  1066.     u = UBOUND(TextHandles)
  1067.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1068.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1069.     IF TextHandles(Handle).SavedBackground THEN
  1070.         w = TextHandles(Handle).w
  1071.         h = TextHandles(Handle).h
  1072.         x1 = TextHandles(Handle).ScreenX
  1073.         y1 = TextHandles(Handle).ScreenY
  1074.         x2 = x1 + w - 1
  1075.         y2 = y1 + h - 1
  1076.         blend = _BLEND
  1077.         _DONTBLEND
  1078.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1079.         IF blend THEN _BLEND
  1080.     END IF
  1081.     DrawTextArea Handle
  1082.  
  1083.  
  1084.  
  1085. SUB DrawTextArea (Handle AS INTEGER)
  1086.     u = UBOUND(TextHandles)
  1087.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1088.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1089.     w = TextHandles(Handle).w
  1090.     h = TextHandles(Handle).h
  1091.     x1 = TextHandles(Handle).ScreenX
  1092.     y1 = TextHandles(Handle).ScreenY
  1093.     x2 = x1 + w - 1
  1094.     y2 = y1 + h - 1
  1095.  
  1096.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  1097.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  1098.  
  1099.  
  1100.  
  1101. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  1102.     u = UBOUND(TextHandles)
  1103.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1104.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1105.     TextHandles(Handle).FrameColor = FrameColor
  1106.     TextHandles(Handle).BackColor = BackColor
  1107.  
  1108.  
  1109.  
  1110. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  1111.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  1112.     x2 = tx2: y2 = ty2
  1113.     IF x1 > x2 THEN SWAP x1, x2
  1114.     IF y1 > y2 THEN SWAP y1, y2
  1115.     w = x2 - x1 + 1
  1116.     h = y2 - y1 + 1
  1117.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  1118.     'Error checking for if the user sends coordinates which are off the screen
  1119.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  1120.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  1121.  
  1122.     u = UBOUND(TextHandles)
  1123.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  1124.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  1125.     NEXT
  1126.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  1127.         Handle = u + 1
  1128.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  1129.     END IF
  1130.     TextHandles(Handle).x1 = x1
  1131.     TextHandles(Handle).y1 = y1
  1132.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  1133.     TextHandles(Handle).InUse = True
  1134.     TextHandles(Handle).Xpos = 0
  1135.     TextHandles(Handle).Ypos = 1
  1136.     TextHandles(Handle).UpdateMethod = NewLine
  1137.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  1138.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  1139.  
  1140.     IF SaveBackground THEN
  1141.         imagehandle = _NEWIMAGE(w, h, 32)
  1142.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1143.         TextHandles(Handle).SavedBackground = imagehandle
  1144.     END IF
  1145.     TextHandles(Handle).ScreenX = x1
  1146.     TextHandles(Handle).ScreenY = y1
  1147.     TextHandles(Handle).Font = 16 'default to font 16
  1148.     NewTextArea% = Handle
  1149.  
  1150. SUB FreeTextArea (Handle AS INTEGER)
  1151.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  1152.         IF TextHandles(Handle).InUse THEN
  1153.             TextHandles(Handle).InUse = False
  1154.             IF TextHandles(Handle).SavedBackground THEN
  1155.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  1156.                     w = TextHandles(Handle).w
  1157.                     h = TextHandles(Handle).h
  1158.                     x1 = TextHandles(Handle).ScreenX
  1159.                     y1 = TextHandles(Handle).ScreenY
  1160.                     x2 = x1 + w - 1
  1161.                     y2 = y1 + h - 1
  1162.                     blend = _BLEND
  1163.                     _DONTBLEND
  1164.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1165.                     IF blend THEN _BLEND
  1166.                 END IF
  1167.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  1168.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  1169.             END IF
  1170.         ELSE
  1171.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  1172.         END IF
  1173.     ELSE
  1174.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  1175.     END IF
  1176.  
  1177. SUB HideFrame (Handle AS INTEGER)
  1178.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  1179.         TextHandles(Handle).HideFrame = -1
  1180.         w = TextHandles(Handle).w
  1181.         h = TextHandles(Handle).h
  1182.         x1 = TextHandles(Handle).ScreenX
  1183.         y1 = TextHandles(Handle).ScreenY
  1184.         x2 = x1 + w - 1
  1185.         y2 = y1 + h - 1
  1186.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1187.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1188.         IF TextHandles(Handle).SavedBackground THEN
  1189.             blend = _BLEND
  1190.             _DONTBLEND
  1191.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1192.             _FREEIMAGE TextHandles(Handle).SavedBackground
  1193.             IF blend THEN _BLEND
  1194.         END IF
  1195.         TextHandles(Handle).SavedBackground = imagehandle
  1196.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  1197.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  1198.     END IF
  1199.  
  1200. SUB RestoreFrame (Handle AS INTEGER)
  1201.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  1202.         TextHandles(Handle).HideFrame = 0
  1203.         w = TextHandles(Handle).w
  1204.         h = TextHandles(Handle).h
  1205.         x1 = TextHandles(Handle).ScreenX
  1206.         y1 = TextHandles(Handle).ScreenY
  1207.         x2 = x1 + w - 1
  1208.         y2 = y1 + h - 1
  1209.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1210.         blend = _BLEND
  1211.         _DONTBLEND
  1212.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1213.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  1214.         _FREEIMAGE TextHandles(Handle).SavedBackground
  1215.         IF blend THEN _BLEND
  1216.         TextHandles(Handle).SavedBackground = imagehandle
  1217.         TextHandles(Handle).x1 = x1 'When the frames are frames are restored, we need to recalculate our print position
  1218.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  1219.     END IF
  1220.  
  1221. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  1222.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  1223.     u = UBOUND(TextHandles)
  1224.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1225.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1226.     HideFrame Handle
  1227.     TextHandles(Handle).ScreenX = x1
  1228.     TextHandles(Handle).ScreenY = y1
  1229.     RestoreFrame Handle
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 14, 2019, 10:45:07 pm
Once again, an almost complete overhaul to the way I was doing things.  I really am taking two steps forwards and one step back with this little project.  /sigh

Code: QB64: [Select]
  1. DEFLNG A-Z 'default to long instead of single
  2. TYPE TextArea
  3.     InUse AS INTEGER
  4.     x1 AS LONG 'left
  5.     y1 AS LONG 'top
  6.     w AS LONG 'width
  7.     h AS LONG 'height
  8.     FrameColor AS _UNSIGNED LONG
  9.     BackColor AS _UNSIGNED LONG
  10.     Xpos AS INTEGER
  11.     Ypos AS INTEGER
  12.     VerticalAlignment AS INTEGER
  13.     Justification AS INTEGER
  14.     UpdateMethod AS INTEGER
  15.     TextColor AS _UNSIGNED LONG
  16.     TextBackgroundColor AS _UNSIGNED LONG
  17.     SavedBackground AS INTEGER
  18.     HideFrame AS INTEGER
  19.     ScreenX AS INTEGER
  20.     ScreenY AS INTEGER
  21.     Font AS LONG 'NEW! Change fonts for each independent font area
  22.  
  23. REDIM SHARED TextHandles(0) AS TextArea
  24.  
  25. CONST True = -1, False = 0
  26. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  27. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  28. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  29. '********************************************************
  30. '* Text Frames before this line
  31. '********************************************************
  32.  
  33.  
  34.  
  35.  
  36.  
  37. _CONSOLE ON 'for debugging purposes while making/testing things
  38.  
  39. TYPE Damage_Type
  40.     Low AS INTEGER
  41.     High AS INTEGER
  42.  
  43. TYPE Light_Type
  44.     Reach AS _UNSIGNED _BYTE
  45.     Left AS _UNSIGNED _BYTE
  46.  
  47. TYPE Weapon_Type
  48.     Name AS STRING * 20
  49.     Reach AS _UNSIGNED _BYTE
  50.     Damage AS Damage_Type
  51.     HitBonus AS _UNSIGNED _BYTE
  52.     DamageBonus AS _UNSIGNED _BYTE
  53.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  54.     IconX AS LONG
  55.     IconY AS LONG
  56.     Light AS Light_Type
  57.  
  58. TYPE Armor_Type
  59.     Name AS STRING * 20
  60.     PD AS _UNSIGNED _BYTE 'Passive Defense (dodge)
  61.     DR AS _UNSIGNED _BYTE 'Damage Resistance (absorption)
  62.     Left AS _UNSIGNED _BYTE 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  63.     IconX AS LONG
  64.     IconY AS LONG
  65.  
  66. TYPE Food_Type
  67.     Name AS STRING * 20
  68.     HungerFill AS SINGLE 'how much said food fills one's stomach
  69.     HungerRate AS SINGLE 'how fast it digests before we get hunry again
  70.     IconX AS LONG
  71.     IconY AS LONG
  72.  
  73.  
  74. TYPE Hero_Type
  75.     Name AS STRING * 20
  76.     Life AS Damage_Type
  77.     Mana AS Damage_Type
  78.     Level AS _UNSIGNED _BYTE
  79.     EXP_Earned AS LONG
  80.     EXP_Needed AS LONG
  81.     Light AS Light_Type
  82.     Weapon1 AS Weapon_Type
  83.     Weapon2 AS Weapon_Type
  84.     Armor AS Armor_Type
  85.     HealingRate AS _UNSIGNED _BYTE
  86.     Hunger AS SINGLE
  87.     HungerRate AS SINGLE
  88.  
  89. TYPE Treasure_TYPE
  90.     Chance AS SINGLE
  91.     Type AS _UNSIGNED _BYTE '1 weapon, 2 armor, 3 food, 4 item
  92.     Index AS INTEGER 'the number of the type... Weapon(0) = "Bare Fist", so tyoe = 1, index = 0.
  93.  
  94. TYPE Monster_TYPE
  95.     Name AS STRING * 20
  96.     Life AS Damage_Type
  97.     Level AS INTEGER
  98.     ExpBonus AS INTEGER
  99.     Sight AS INTEGER
  100.     Hearing AS INTEGER
  101.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  102.     Weapon1 AS Weapon_Type
  103.     Weapon2 AS Weapon_Type
  104.     Armor AS Armor_Type
  105.     Found AS INTEGER
  106.     IconX AS LONG
  107.     IconY AS LONG
  108.     Loot1 AS Treasure_TYPE
  109.     Loot2 AS Treasure_TYPE
  110.     loot3 AS Treasure_TYPE
  111.  
  112. TYPE Encounter_TYPE
  113.     Active AS INTEGER
  114.     X AS INTEGER
  115.     Y AS INTEGER
  116.     Type AS INTEGER '0 monster, 1 weapon, 2 armor, 3 food, 4 item
  117.     Index AS INTEGER
  118.     Life AS INTEGER
  119.  
  120. REDIM SHARED Monster(100) AS Monster_TYPE
  121. REDIM SHARED Encounter(1000) AS Encounter_TYPE, EncounterLimit AS INTEGER
  122.  
  123. 'These are all base items and weapons.  The sets which the character actually carries will vary significantly from wear/tear, enchantments, and other in-game factors.
  124. REDIM SHARED Weapon(100) AS Weapon_Type
  125. REDIM SHARED Armor(100) AS Armor_Type
  126. REDIM SHARED Food(100) AS Food_Type
  127.  
  128. DIM SHARED Hero AS Hero_Type
  129. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  130. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  131. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  132. DIM SHARED Scale AS _FLOAT, WorkScreen AS LONG, DisplayScreen AS LONG, Icons AS LONG
  133. DIM SHARED TextFont AS LONG, StepsTaken AS _UNSIGNED _INTEGER64
  134.  
  135. WorkScreen = _NEWIMAGE(3200, 2400, 32)
  136. DisplayScreen = _NEWIMAGE(800, 700, 32)
  137. SCREEN DisplayScreen
  138. Scale = 2
  139.  
  140. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  141. '1 map is illuminated
  142. '2 map is uncovered
  143. '4 map is a wall
  144. '8 map is a pathway
  145. '16 map is a stairway
  146. '32 map is simply blocked (perhaps with a monster?)
  147. '64 map is secret (can not be uncovered)
  148. '128 is an item on the ground
  149.  
  150. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  151.  
  152.  
  153.  
  154. ON ERROR GOTO errorhandler
  155.  
  156. GOTO skiperrorhandler
  157. errorhandler:
  158.  
  159. PRINT ERR; "Error on "; _ERRORLINE
  160. _DEST DisplayScreen
  161. skiperrorhandler:
  162.  
  163. Init
  164. CreateMap 99, 74, 10
  165.     DrawMap
  166.     DisplayCharacter
  167.     _DISPLAY
  168.     GetInput
  169.     MonstersTurn
  170.     CheckForHeroGrowth
  171.  
  172. SUB Init
  173.     D = _DEST
  174.     Hero.Name = "Steve The Tester!"
  175.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  176.     Hero.Mana.Low = 10: Hero.Mana.High = 10
  177.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  178.     Hero.Weapon1.Name = "Bare Fist"
  179.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  180.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  181.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  182.     Hero.Weapon1.IconX = 2 * 32: Hero.Weapon1.IconY = 47 * 32
  183.     Hero.Weapon2.Name = "Magic Candle"
  184.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  185.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  186.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  187.     Hero.Weapon2.IconX = 52 * 32: Hero.Weapon2.IconY = 42 * 32
  188.     Hero.Weapon2.Light.Reach = 2: Hero.Weapon2.Light.Left = -1 'infinite
  189.     Hero.Armor.Name = "Naked"
  190.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  191.     Hero.Armor.IconX = 46 * 32: Hero.Armor.IconY = 42 * 32
  192.     Hero.HealingRate = 20 'the hero heals 1 point of health for every 20 valid turns naturally
  193.     Hero.HungerRate = 0.1 'Let's start heros out with a full belly and a low hunger growth rate
  194.  
  195.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  196.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  197.     SetTextFont PrintArea, "courbd.ttf", 24
  198.     DrawTextArea PrintArea
  199.     SetPrintPositionX PrintArea, CenterJustify
  200.     SetPrintUpdate PrintArea, NewLine
  201.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  202.     SetTextFont PrintArea, "courbd.ttf", 18
  203.     PrintOut PrintArea, "created by STEVE!"
  204.     PrintOut PrintArea, ""
  205.     SetPrintPositionX PrintArea, LeftJustify
  206.     SetTextFont PrintArea, "courbd.ttf", 12
  207.     TextFont = 12
  208.  
  209.     temp = _NEWIMAGE(480, 480, 32)
  210.     _DEST temp: _CONTROLCHR OFF
  211.     COLOR &HFFFFFF00, 0 'Yellow Hero
  212.     _PRINTSTRING (0, 0), CHR$(1) 'the hero
  213.     COLOR &HFFFF0000, 0 'Red Question Mark
  214.     _PRINTSTRING (16, 0), "?" 'a question mark
  215.     Icons = _LOADIMAGE("Sprites.png", 32)
  216.     _PUTIMAGE (2016, 1504)-STEP(32, 32), temp, Icons, (0, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  217.     _PUTIMAGE (1984, 1504)-STEP(32, 32), temp, Icons, (16, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  218.     '        SCREEN Icons: DO: SLEEP: LOOP
  219.     _DEST D
  220.  
  221.     'Init some basic weapons which we can find
  222.  
  223.     'These first two should always be available for us to use/enjoy.
  224.     Weapon(0).Name = "Bare Fist"
  225.     Weapon(0).Reach = 1: Weapon(0).Damage.Low = 1: Weapon(0).Damage.High = 2
  226.     Weapon(0).HitBonus = 0: Weapon(0).DamageBonus = 0
  227.     Weapon(0).Left = -1 'your fist is indestructible!
  228.     Weapon(0).IconX = 2 * 32: Weapon(0).IconY = 47 * 32
  229.     Weapon(0).Light.Reach = 0: Weapon(0).Light.Left = 0
  230.     Weapon(1).Name = "Magic Candle"
  231.     Weapon(1).Reach = 0: Weapon(1).Damage.Low = 0: Weapon(1).Damage.High = 0
  232.     Weapon(1).HitBonus = 0: Weapon(1).DamageBonus = 0
  233.     Weapon(1).Left = 0 'you can't attack with a candle
  234.     Weapon(1).IconX = 52 * 32: Weapon(1).IconY = 42 * 32
  235.     Weapon(1).Light.Reach = 2: Weapon(1).Light.Left = -1 'infinite
  236.  
  237.     Armor(0).Name = "Naked"
  238.     Armor(0).PD = 0: Armor(0).DR = 0: Armor(0).Left = -1 'you might be naked, but at least you can't break your armor!
  239.     Armor(0).IconX = 46 * 32: Armor(0).IconY = 42 * 32
  240.  
  241.     Food(1).Name = "Bat Meat"
  242.     Food(1).HungerFill = 10: Food(1).HungerRate = .1
  243.     Food(1).IconX = 36 * 32: Food(1).IconY = 23 * 32
  244.  
  245.     Food(2).Name = "Rat Meat"
  246.     Food(2).HungerFill = 10: Food(2).HungerRate = .1
  247.     Food(2).IconX = 36 * 32: Food(2).IconY = 23 * 32
  248.  
  249.     Food(3).Name = "Snake Meat"
  250.     Food(3).HungerFill = 10: Food(3).HungerRate = .1
  251.     Food(3).IconX = 36 * 32: Food(3).IconY = 23 * 32
  252.  
  253.     InitMonsters
  254.  
  255. SUB InitMonsters
  256.  
  257.     Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  258.     Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  259.     Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  260.     Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  261.     Monster(1).IconX = 44 * 32: Monster(1).IconY = 3 * 32 'position 44,3 on the sprite sheet
  262.     Monster(1).Loot1.Chance = 95: Monster(1).Loot1.Type = 3: Monster(1).Loot1.Index = 1
  263.     'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  264.     'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  265.     'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  266.     'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  267.     'Monster(1).Armor.Name = ""
  268.     'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  269.     Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  270.     Monster(2).Level = 1: Monster(2).ExpBonus = 0
  271.     Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  272.     Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  273.     Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  274.     Monster(2).IconX = 23 * 32: Monster(2).IconY = 4 * 32 'position 44,3 on the sprite sheet
  275.     Monster(2).Loot1.Chance = 25: Monster(2).Loot1.Type = 3: Monster(2).Loot1.Index = 2
  276.     Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  277.     Monster(3).Level = 1: Monster(3).ExpBonus = 0
  278.     Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  279.     Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  280.     Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  281.     Monster(3).IconX = 37 * 32: Monster(3).IconY = 4 * 32 'position 44,3 on the sprite sheet
  282.     Monster(3).Loot1.Chance = 25: Monster(3).Loot1.Type = 3: Monster(3).Loot1.Index = 3
  283.  
  284.  
  285. SUB CheckForHeroGrowth
  286.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  287.         CLS
  288.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  289.         _DISPLAY
  290.         BEEP
  291.         _DELAY 5
  292.         SYSTEM
  293.     END IF
  294.     IF Hero.EXP_Earned >= Hero.EXP_Needed THEN 'let's check to see if the hero has leveled up
  295.         PrintOut PrintArea, "Congratulations!  You have gained a level!"
  296.         DO
  297.             r = INT(RND * 6) + 1
  298.             lifegained = lifegained + r
  299.         LOOP UNTIL r <> 6
  300.         Hero.Life.Low = Hero.Life.Low + r
  301.         Hero.Life.High = Hero.Life.High + r
  302.         Hero.EXP_Earned = 0
  303.         Hero.Level = Hero.Level + 1
  304.         Hero.EXP_Needed = Hero.EXP_Needed + Hero.Level + 1
  305.     END IF
  306.     IF StepsTaken MOD Hero.HealingRate = 0 THEN 'heal the hero naturally over time
  307.         IF Hero.Life.Low < Hero.Life.High THEN Hero.Life.Low = Hero.Life.Low + 1
  308.     END IF
  309.     Hero.Hunger = Hero.Hunger + Hero.HungerRate
  310.     IF Hero.Weapon1.Light.Left > -1 THEN Hero.Weapon1.Light.Left = Hero.Weapon1.Light.Left - 1 'durability on our light sources wear down over time
  311.     IF Hero.Weapon2.Light.Left > -1 THEN Hero.Weapon2.Light.Left = Hero.Weapon2.Light.Left - 1
  312.  
  313.  
  314.  
  315. SUB DisplayCharacter
  316.     LINE (0, 601)-(229, 799), &HFF000000, BF
  317.     COLOR -1, 0
  318.     Box 0, 601, 229, 62, 0, 0, "", Silver, 0
  319.     Box 0, 601, 229, 12, Black, 0, _TRIM$(Hero.Name), Silver, 0
  320.     Box 0, 626, 229 * Hero.Life.Low / Hero.Life.High, 12, 0, 0, "", Red, Black
  321.     Box 0, 639, 229 * Hero.Mana.Low / Hero.Mana.High, 12, 0, 0, "", Blue, Black
  322.     Box 0, 652, 229 * Hero.EXP_Earned / Hero.EXP_Needed, 12, 0, 0, "", Green, Black
  323.     _PRINTSTRING (10, 616), "LEVEL:" + STR$(Hero.Level)
  324.     _PRINTSTRING (10, 629), "LIFE :" + STR$(Hero.Life.Low) + " / " + _TRIM$(STR$(Hero.Life.High))
  325.     _PRINTSTRING (10, 642), "MANA :" + STR$(Hero.Mana.Low) + " / " + _TRIM$(STR$(Hero.Mana.High))
  326.     _PRINTSTRING (10, 655), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  327.     FOR i = 0 TO 5 'six boxes for information : left hand, right hand, armor, and 3 more for later....
  328.         Box 36 * i + 8, 665, 34, 34, 0, 0, "", Black, Silver
  329.     NEXT
  330.     _PUTIMAGE (9, 666)-STEP(32, 32), Icons, DisplayScreen, (Hero.Weapon1.IconX, Hero.Weapon1.IconY)-STEP(32, 32)
  331.     _PUTIMAGE (45, 666)-STEP(32, 32), Icons, DisplayScreen, (Hero.Weapon2.IconX, Hero.Weapon2.IconY)-STEP(32, 32)
  332.     _PUTIMAGE (81, 666)-STEP(32, 32), Icons, DisplayScreen, (Hero.Armor.IconX, Hero.Armor.IconY)-STEP(32, 32)
  333.  
  334. SUB GetInput
  335.     DO
  336.         k = _KEYHIT: valid = -1
  337.         SELECT CASE k
  338.             CASE 18432 'up
  339.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  340.                     SELECT CASE Scale
  341.                         CASE 1.5: Scale = 2 'It's as small as we go
  342.                         CASE 2: Scale = 3
  343.                         CASE 3: Scale = 4
  344.                         CASE 4: Scale = 6
  345.                         CASE 6: Scale = 8
  346.                         CASE 8: Scale = 12
  347.                     END SELECT
  348.                 ELSE
  349.                     IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  350.                 END IF
  351.             CASE 19200: 'left
  352.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  353.                     TextFont = TextFont - 1
  354.                     IF TextFont < 8 THEN TextFont = 8
  355.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  356.                     ClearTextArea PrintArea
  357.                     SetPrintPosition PrintArea, 1, 1
  358.                     PrintOut PrintArea, "Font Size Changed"
  359.                 ELSE
  360.                     IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  361.                 END IF
  362.             CASE 20480: 'down
  363.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  364.                     SELECT CASE Scale
  365.                         CASE 2: Scale = 1.5 'It's as small as we go
  366.                         CASE 3: Scale = 2
  367.                         CASE 4: Scale = 3
  368.                         CASE 6: Scale = 4
  369.                         CASE 8: Scale = 6
  370.                         CASE 12: Scale = 8
  371.                     END SELECT
  372.                 ELSE
  373.                     IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  374.                 END IF
  375.             CASE 19712: 'right
  376.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  377.                     TextFont = TextFont + 1
  378.                     IF TextFont > 48 THEN TextFont = 48
  379.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  380.                     ClearTextArea PrintArea
  381.                     SetPrintPosition PrintArea, 1, 1
  382.                     PrintOut PrintArea, "Font Size Changed"
  383.                 ELSE
  384.                     IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  385.                 END IF
  386.             CASE 32 'space to just wait and skip a turn
  387.             CASE 60 ' "<" key
  388.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  389.                     Level = Level + 1
  390.                     CreateMap 99, 74, 10
  391.                     PathFind
  392.                 END IF
  393.             CASE ASC("+"), ASC("=")
  394.                 IF Hero.Weapon2.Light.Reach < 25 THEN Hero.Weapon2.Light.Reach = Hero.Weapon2.Light.Reach + 1
  395.             CASE ASC("-"), ASC("_")
  396.                 IF Hero.Weapon2.Light.Reach > -1 THEN Hero.Weapon2.Light.Reach = Hero.Weapon2.Light.Reach - 1
  397.             CASE ELSE
  398.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  399.         END SELECT
  400.         _LIMIT 60
  401.     LOOP UNTIL k AND valid
  402.     _KEYCLEAR 'one keystroke at a time
  403.     StepsTaken = StepsTaken + 1
  404.  
  405. SUB Box (X, Y, Wide, High, FontColor as _unsigned long, _
  406.          FontBackGround as _unsigned long, Caption AS STRING, Kolor AS _UNSIGNED LONG, BackGround AS _UNSIGNED LONG)
  407.     COLOR FontColor, FontBackGround
  408.     LINE (X, Y)-STEP(Wide, High), Kolor, BF
  409.     LINE (X, Y)-STEP(Wide, High), BackGround, B
  410.     pw = _PRINTWIDTH(Caption): ph = _FONTHEIGHT
  411.     _PRINTSTRING (X + (Wide - pw + 1) \ 2, Y + (High - ph + 1) \ 2), Caption
  412.     COLOR DC, BG
  413.  
  414.  
  415. SUB MoveHero (MoveX, MoveY)
  416.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  417.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  418.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  419.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  420.             IF MoveX THEN Hero.X = Hero.X + MoveX
  421.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  422.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  423.             PathFind
  424.         ELSE
  425.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  426.             FOR i = 1 TO EncounterLimit
  427.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  428.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  429.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  430.                         Swing 0, i 'hero swings at the monster
  431.                     END IF
  432.                 END IF
  433.             NEXT
  434.         END IF
  435.     END IF
  436.  
  437. SUB Swing (Who, AtWhom)
  438.  
  439.     BaseChancetohit = 10 'base 10 chance to hit
  440.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  441.         M = Encounter(AtWhom).Index
  442.         IF Hero.Weapon1.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  443.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  444.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  445.             totalroll = 0
  446.             DO
  447.                 roll = INT(RND * 20) + 1
  448.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  449.                 IF roll = 20 THEN totalroll = totalroll + 20
  450.                 totalroll = totalroll + roll
  451.             LOOP UNTIL roll <> 1 AND roll <> 20
  452.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  453.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  454.             out$ = _TRIM$(Hero.Name)
  455.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  456.                 SetTextColor PrintArea, &HFFF000F0, 0
  457.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  458.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  459.                 Hero.Life.Low = Hero.Life.Low - damage
  460.             ELSEIF totalroll < Chancetohit THEN
  461.                 SetTextColor PrintArea, &HFFF0F000, 0
  462.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  463.             ELSEIF totalroll > Chancetohit + 20 THEN
  464.                 SetTextColor PrintArea, &HFF00FF00, 0
  465.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  466.                 damage = damage * (totalroll / 20 + 1)
  467.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  468.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  469.             ELSEIF totalroll >= Chancetohit THEN
  470.                 SetTextColor PrintArea, &HFF00FF00, 0
  471.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  472.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  473.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  474.             END IF
  475.         ELSEIF Hero.Weapon1.Reach > 0 THEN
  476.             SetTextColor PrintArea, &HFFF000F0, 0
  477.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon1.Name) + "!"
  478.         ELSE
  479.             out$ = ""
  480.         END IF
  481.         IF out$ <> "" THEN PrintOut PrintArea, out$
  482.         IF Hero.Weapon2.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  483.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  484.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  485.             totalroll = 0
  486.             DO
  487.                 roll = INT(RND * 20) + 1
  488.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  489.                 IF roll = 20 THEN totalroll = totalroll + 20
  490.                 totalroll = totalroll + roll
  491.             LOOP UNTIL roll <> 1 AND roll <> 20
  492.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  493.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  494.             out$ = _TRIM$(Hero.Name)
  495.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  496.                 SetTextColor PrintArea, &HFFF000F0, 0
  497.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  498.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  499.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  500.                 Hero.Life.Low = Hero.Life.Low - damage
  501.             ELSEIF totalroll < Chancetohit THEN
  502.                 SetTextColor PrintArea, &HFFF0F000, 0
  503.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  504.             ELSEIF totalroll > Chancetohit + 20 THEN
  505.                 SetTextColor PrintArea, &HFF00FF00, 0
  506.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  507.                 damage = damage * (totalroll / 20 + 1)
  508.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  509.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  510.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  511.             ELSEIF totalroll >= Chancetohit THEN
  512.                 SetTextColor PrintArea, &HFF00FF00, 0
  513.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  514.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  515.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  516.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  517.             END IF
  518.         ELSEIF Hero.Weapon2.Reach > 0 THEN
  519.             SetTextColor PrintArea, &HFFF000F0, 0
  520.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon2.Name) + "!"
  521.         ELSE
  522.             out$ = ""
  523.         END IF
  524.         IF out$ <> "" THEN PrintOut PrintArea, out$
  525.         IF Encounter(AtWhom).Life <= 0 THEN MonsterDied (AtWhom) 'the monster died!
  526.     ELSE 'it's a monster attacking
  527.         M = Encounter(Who).Index
  528.         IF Monster(M).Weapon1.Reach >= Distance(Encounter(Who).X, Encounter(Who).Y) THEN 'it's a weapon and not an utility object being held.
  529.             Chancetohit = BaseChancetohit + Monster(M).Weapon1.HitBonus 'add in the weapon's hit bonus
  530.             Chancetohit = Chancetohit - Hero.Armor.PD 'subtract the hero's armor/ natural dodge
  531.             totalroll = 0
  532.             DO
  533.                 roll = INT(RND * 20) + 1
  534.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  535.                 IF roll = 20 THEN totalroll = totalroll + 20
  536.                 totalroll = totalroll + roll
  537.             LOOP UNTIL roll <> 1 AND roll <> 20
  538.             damage = INT(RND * (Monster(M).Weapon1.Damage.High - Monster(M).Weapon1.Damage.Low + 1)) + Monster(M).Weapon1.Damage.Low 'random damage for the hit
  539.             damage = damage + Monster(M).Weapon1.DamageBonus 'add in the weapon's damage bonus
  540.             out$ = _TRIM$(Monster(M).Name)
  541.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  542.                 SetTextColor PrintArea, &HFFF000F0, 0
  543.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  544.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  545.                 Monster(M).Life.Low = Monster(M).Life.Low - damage
  546.             ELSEIF totalroll < Chancetohit THEN
  547.                 SetTextColor PrintArea, &HFFF0F000, 0
  548.                 out$ = out$ + " missed " + _TRIM$(Hero.Name) + ", with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  549.             ELSEIF totalroll > Chancetohit + 20 THEN
  550.                 SetTextColor PrintArea, &HFF00FFFF, 0
  551.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Hero.Name) + " for"
  552.                 damage = damage * (totalroll / 20 + 1)
  553.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  554.                 Hero.Life.Low = Hero.Life.Low - damage
  555.             ELSEIF totalroll >= Chancetohit THEN
  556.                 SetTextColor PrintArea, &HFF00FFFF, 0
  557.                 out$ = out$ + " hit " + _TRIM$(Hero.Name) + " for"
  558.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "."
  559.                 Hero.Life.Low = Hero.Life.Low - damage
  560.             END IF
  561.         ELSEIF Monster(M).Weapon1.Reach > 0 THEN
  562.             SetTextColor PrintArea, &HFFF000F0, 0
  563.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Monster(M).Weapon2.Name) + "!"
  564.         ELSE
  565.             out$ = ""
  566.         END IF
  567.         IF out$ <> "" THEN PrintOut PrintArea, out$
  568.     END IF
  569.  
  570. SUB MonsterDied (Who)
  571.     M = Encounter(Who).Index
  572.     SetTextColor PrintArea, &HFFFF0000, 0
  573.     out$ = _TRIM$(Monster(M).Name) + " died!  You earned " + _TRIM$(STR$(Monster(M).Level + Monster(M).ExpBonus)) + " experience."
  574.     PrintOut PrintArea, out$
  575.     Encounter(Who).Active = 0
  576.     Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  577.     MapArray(Encounter(Who).X, Encounter(Who).Y) = MapArray(Encounter(Who).X, Encounter(Who).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  578.     IF Monster(M).Found = 0 THEN
  579.         Monster(M).Found = -1 'it's a first time kill!
  580.         SetTextColor PrintArea, &HFFFFFF00, &HFFFF0000
  581.         out$ = "Congratulations!  You killed a " + _TRIM$(Monster(M).Name) + " for the first time!"
  582.         PrintOut PrintArea, out$
  583.     END IF
  584.  
  585.     'monster loot!!
  586.     IF UBOUND(Encounter) <= EncounterLimit THEN REDIM _PRESERVE Encounter(EncounterLimit + 100) AS Encounter_TYPE 'make certain our array is large enough to hold all the loot on the map.
  587.     'if the player starts dumping items onto the ground, we could concievably fill the screen with tons of loots.
  588.     R# = RND * 100
  589.     IF R# < Monster(M).Loot1.Chance THEN
  590.         GOSUB addloot
  591.         Encounter(E).Type = Monster(M).Loot1.Type
  592.         Encounter(E).Index = Monster(M).Loot1.Index
  593.     ELSEIF R# < Monster(M).Loot2.Chance THEN
  594.         GOSUB addloot
  595.     ELSEIF R# < Monster(M).loot3.Chance THEN
  596.         GOSUB addloot
  597.     END IF
  598.  
  599.     EXIT SUB
  600.  
  601.     addloot: 'a small sub proceedure to reduce copy/paste code
  602.     EncounterLimit = EncounterLimit + 1
  603.     E = EncounterLimit 'just for ease of typing below
  604.     Encounter(E).Active = -1
  605.     Encounter(E).X = Encounter(Who).X
  606.     Encounter(E).Y = Encounter(Who).Y
  607.     PrintOut PrintArea, _TRIM$(Monster(M).Name) + " left something behind."
  608.     RETURN
  609.  
  610.  
  611. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  612.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  613.     D = Distance(MX, MY) 'distance from monster to the hero
  614.     E = Encounter(i).Index 'the actual monster in question
  615.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  616.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  617.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  618.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  619.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  620.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  621.             MoveMonster = -1
  622.         END IF
  623.     END IF
  624.  
  625.  
  626.  
  627. SUB MonstersTurn
  628.     FOR i = 1 TO EncounterLimit
  629.         IF Encounter(i).Active AND (Encounter(i).Type = 0) THEN 'Only if it's a monster, and the monster is still alive and active do we need to actually do anything else.
  630.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  631.             D = Distance(MX, MY) 'distance from monster to the hero
  632.             E = Encounter(i).Index 'the actual monster in question
  633.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  634.                 attack = 0
  635.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  636.                     'insert attack code here
  637.                     Swing i, 0
  638.                     _CONTINUE
  639.                 END IF
  640.                 'if the monster didn't attack, it can now move towards the hero.
  641.                 IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  642.                     IF D > Distance(MX - 1, MY) THEN
  643.                         IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  644.                     END IF
  645.                 END IF
  646.                 IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  647.                     IF D > Distance(MX, MY - 1) THEN
  648.                         IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  649.                     END IF
  650.                 END IF
  651.                 IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  652.                     IF D > Distance(MX + 1, MY) THEN
  653.                         IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  654.                     END IF
  655.                 END IF
  656.                 IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  657.                     IF D > Distance(MX, MY + 1) THEN
  658.                         IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  659.                     END IF
  660.                 END IF
  661.             END IF
  662.         END IF
  663.  
  664.     NEXT
  665.  
  666.  
  667.  
  668.  
  669. SUB DrawMap
  670.     _DEST WorkScreen
  671.     CLS
  672.     'LINE (0, 0)-(3200, 2400), &HFF000000, BF 'clear the map
  673.     IF Hero.Weapon1.Light.Reach > Hero.Weapon2.Light.Reach THEN LightReach = Hero.Weapon1.Light.Reach ELSE LightReach = Hero.Weapon2.Light.Reach
  674.     FOR Y = 0 TO YH
  675.         FOR X = 0 TO XH
  676.             IF Distance(X, Y) <= LightReach THEN 'It's close enough to check for illumination
  677.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  678.             END IF
  679.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  680.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  681.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF303030, BF
  682.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (6 * 32, 18 * 32)-STEP(31, 31)
  683.                 END IF
  684.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  685.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (36 * 32, 13 * 32)-STEP(31, 31)
  686.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF707070, BF
  687.                 END IF
  688.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  689.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (4 * 32, 45 * 32)-STEP(31, 31)
  690.                 END IF
  691.             END IF
  692.             'note: highlighting for the light should come AFTER the map is drawn
  693.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  694.                 LINE (X * 32, Y * 32)-STEP(32, 32), &H40FFFF00, BF
  695.                 MapArray(X, Y) = MapArray(X, Y) - 1
  696.                 FOR I = 1 TO EncounterLimit
  697.                     IF X = Encounter(I).X AND Y = Encounter(I).Y AND Encounter(I).Active = -1 THEN
  698.                         E = Encounter(I).Index
  699.                         T = Encounter(I).Type
  700.                         SELECT CASE T
  701.                             CASE 0 'it's a monster
  702.                                 IF Monster(E).Found THEN
  703.                                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Monster(E).IconX, Monster(E).IconY)-STEP(31, 31)
  704.                                 ELSE
  705.                                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (1984, 1504)-STEP(31, 31)
  706.                                 END IF
  707.                             CASE 1 'weapon
  708.                                 _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Weapon(E).IconX, Weapon(E).IconY)-STEP(31, 31)
  709.                             CASE 2 'armor
  710.                                 _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Armor(E).IconX, Armor(E).IconY)-STEP(31, 31)
  711.                             CASE 3 'food
  712.                                 _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Food(E).IconX, Food(E).IconY)-STEP(31, 31)
  713.                             CASE 4 'item
  714.                         END SELECT
  715.                     END IF
  716.                 NEXT
  717.  
  718.             END IF
  719.         NEXT
  720.     NEXT
  721.     COLOR &HFFFFFF00, 0 'Yellow Hero
  722.     _PUTIMAGE (Hero.X * 32, Hero.Y * 32)-STEP(32, 32), Icons, WorkScreen, (2016, 1504)-STEP(31, 31)
  723.     XOffset## = 1600 / Scale
  724.     YOffset## = 1200 / Scale
  725.     CenterX = Hero.X * 32 'convert hero coordinate to grid coordinate
  726.     CenterY = Hero.Y * 32
  727.     _DEST DisplayScreen
  728.     LINE (0, 0)-(800, 600), &HFF000000, BF 'clear the map
  729.     _PUTIMAGE (0, 0)-(800, 600), WorkScreen, DisplayScreen, (CenterX - XOffset##, CenterY - YOffset##)-(CenterX + XOffset##, CenterY + YOffset##)
  730.  
  731.  
  732.  
  733.  
  734.  
  735. SUB CreateMap (XLimit, YLimit, Rooms)
  736.     ERASE MapArray 'clear the old map and reset everything to 0
  737.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  738.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  739.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  740.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  741.  
  742.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  743.  
  744.     StairRoom = INT(RND * Rooms) + 1
  745.     FOR i = 1 TO Rooms
  746.         DO
  747.             RoomSize = INT(RND * 9) + 2
  748.             RoomX = INT(RND * (XLimit - RoomSize))
  749.             RoomY = INT(RND * (YLimit - RoomSize))
  750.             'test for positioning
  751.             good = -1 'it's good starting out
  752.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  753.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  754.             NEXT X, Y
  755.         LOOP UNTIL good
  756.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  757.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  758.         NEXT X, Y
  759.         RoomCenterX(i) = RoomX + .5 * RoomSize
  760.         RoomCenterY(i) = RoomY + .5 * RoomSize
  761.         IF i = 1 THEN 'place the hero in the first room  (which can be anywhere randomly on our map)
  762.             Hero.X = RoomX + INT(RND * RoomSize)
  763.             Hero.Y = RoomY + INT(RND * RoomSize)
  764.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  765.         END IF
  766.         IF i = StairRoom THEN 'place the stairs in one of the random rooms
  767.             DO 'But lets not place the stairs directly on top of the hero to begin with
  768.                 StairX = RoomX + INT(RND * RoomSize)
  769.                 StairY = RoomY + INT(RND * RoomSize)
  770.             LOOP UNTIL StairX <> Hero.X AND StairY <> Hero.Y
  771.             MapArray(StairX, StairY) = MapArray(StairX, StairY) OR 16
  772.         END IF
  773.     NEXT
  774.     FOR i = 1 TO Rooms - 1
  775.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  776.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  777.         DO UNTIL StartX = EndX AND StartY = EndY
  778.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  779.             Meander = 10
  780.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  781.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  782.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  783.                     Ychange = 0
  784.                 ELSE
  785.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  786.                     Ychange = 0
  787.                 END IF
  788.             ELSE
  789.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  790.                     Ychange = SGN(EndY - StartY)
  791.                     XChange = 0
  792.                 ELSE
  793.                     Ychange = INT(RND * 3) - 1
  794.                     XChange = 0
  795.                 END IF
  796.             END IF
  797.             StartX = StartX + XChange
  798.             StartY = StartY + Ychange
  799.             IF StartX < 0 THEN StartX = 0 'Make certain we move inside the bounds of our map dimensions
  800.             IF StartY < 0 THEN StartY = 0
  801.             IF StartX > XH THEN StartX = XH
  802.             IF StartY > YH THEN StartY = YH
  803.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8 'place a path where we moved to
  804.         LOOP
  805.     NEXT
  806.     PathFind
  807.     EncounterLimit = INT(RND * 6) + 5
  808.     FOR i = 1 TO EncounterLimit
  809.         Encounter(i).Type = 0 'type 0 is a monster
  810.         Encounter(i).Index = RandomMonster
  811.         Encounter(i).Active = -1
  812.         M = Encounter(i).Index
  813.         Encounter(i).Life = INT(RND * Monster(M).Life.High - Monster(M).Life.Low + 1) + Monster(M).Life.Low
  814.         valid = -1: EndlessLoopExit = 0
  815.         DO
  816.             EndlessLoopExit = EndlessLoopExit + 1
  817.             Encounter(i).X = INT(RND * XLimit + 1)
  818.             Encounter(i).Y = INT(RND * YLimit + 1)
  819.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  820.             IF EndlessLoopExit = 1000 THEN EXIT DO 'if we can't place the monster in a room after 1000 tries, just place it wherever and call it a "wandering monster".
  821.             'Of course, "wandering monsters" may end up inside a wall, in which case they simply become "lost monsters" and do nothing to affect the level.  It's the same as if they never existed at all.
  822.             'BUT, we *should* generally be able to place a monster after 1000 tries.  This segment is just in the off-chance that the Random Number Gods are out to get us and to prevent any chance for an endless loop.
  823.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  824.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  825.     NEXT
  826.     LootLimit = 0 'no loot on the map at this time.  Too bad for joo!
  827.  
  828. SUB PathFind
  829.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  830.     DIM pass AS _UNSIGNED _BYTE
  831.     m = _MEM(Distance()): m1 = _MEM(Temp())
  832.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  833.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  834.     Temp(Hero.X, Hero.Y) = 0
  835.     pass = 0
  836.     DO
  837.         changed = 0
  838.         y = 0
  839.         DO
  840.             x = 0
  841.             DO
  842.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  843.                     IF x < XH THEN
  844.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  845.                     END IF
  846.                     IF x > 0 THEN
  847.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  848.                     END IF
  849.                     IF y < YH THEN
  850.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  851.                     END IF
  852.                     IF y > 0 THEN
  853.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  854.                     END IF
  855.                 END IF
  856.                 x = x + 1
  857.             LOOP UNTIL x > XH
  858.             y = y + 1
  859.         LOOP UNTIL y > YH
  860.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  861.         pass = pass + 1
  862.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  863.     Distance(Hero.X, Hero.Y) = 0
  864.  
  865. FUNCTION RandomMonster
  866.     STATIC MC 'monster count
  867.     SELECT CASE Level 'the starting level
  868.         CASE 1: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  869.     END SELECT
  870.     RandomMonster = INT(RND * MC) + 1
  871.  
  872.  
  873. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  874. '# SUBroutines and FUNCTIONs below #'
  875. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  876. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  877.     u = UBOUND(TextHandles)
  878.     Handle = WhichHandle
  879.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  880.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  881.     Where = TextHandles(Handle).VerticalAlignment
  882.     How = TextHandles(Handle).Justification
  883.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  884.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  885.  
  886.  
  887. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  888.     'WhichHandle is the handle which designates which text area we want to use
  889.     'Where is where we want it to go in that text area
  890.     '  -- Online prints the text to the current print position line in that text area.
  891.     '  -- CenterLine centers the text to the center of that text area.
  892.     '  -- any other value will print to that line positon in that particular box.
  893.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  894.     'What is the text that we want to print in our text area
  895.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  896.  
  897.     D = _DEST: S = _SOURCE
  898.     OldFont = _FONT
  899.  
  900.     u = UBOUND(TextHandles)
  901.     Handle = WhichHandle
  902.  
  903.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  904.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  905.     IF TextHandles(Handle).HideFrame THEN
  906.         _DEST TextHandles(Handle).SavedBackground
  907.         _SOURCE TextHandles(Handle).SavedBackground
  908.     END IF
  909.     _FONT TextHandles(Handle).Font
  910.     fh = _FONTHEIGHT: pw = _PRINTWIDTH(What)
  911.     IF _FONTWIDTH = 0 THEN
  912.         FOR i = 1 TO 255
  913.             IF _PRINTWIDTH(CHR$(i)) > fw THEN fw = _PRINTWIDTH(CHR$(i))
  914.         NEXT
  915.     ELSE
  916.         fw = _FONTWIDTH
  917.     END IF
  918.  
  919.     h = TextHandles(Handle).h - 4: w = TextHandles(Handle).w - 4
  920.  
  921.     SELECT CASE Where
  922.         CASE BottomLine
  923.             y = h \ fh
  924.         CASE OnLine
  925.             y = TextHandles(Handle).Ypos
  926.             IF y = 0 THEN y = 1
  927.         CASE CenterLine
  928.             linesused = 0
  929.             tpw = pw: tw = w: tWhat$ = What
  930.             DO UNTIL tpw <= tw
  931.                 textallowed = WordBreak(LEFT$(tWhat$, w \ fw))
  932.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  933.                 linesused = linesused + 1
  934.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  935.                 tpw = _PRINTWIDTH(tWhat$)
  936.             LOOP
  937.             linesused = linesused + 1
  938.             py = (h - linesused * fh) \ 2
  939.             y = py \ fh + 1
  940.             IF y < 1 THEN y = 1
  941.         CASE ELSE
  942.             y = Where
  943.     END SELECT
  944.  
  945.     'IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  946.     blend = _BLEND
  947.  
  948.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  949.         'first let's get a temp image handle for the existing area of the screen.
  950.         x1 = TextHandles(Handle).x1 + 2
  951.         y1 = TextHandles(Handle).y1 + 2
  952.         x2 = TextHandles(Handle).x1 + w
  953.         y2 = TextHandles(Handle).y1 + h
  954.         nh = y2 - y1 + 1 - fh
  955.         nw = x2 - x1 + 1
  956.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  957.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  958.         DrawTextArea Handle
  959.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  960.         y = y - 1
  961.     LOOP
  962.  
  963.     IF blend THEN _BLEND
  964.  
  965.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  966.  
  967.     SELECT CASE How
  968.         CASE LeftJustify
  969.             x = 0
  970.             IF pw > w THEN
  971.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  972.                 text$ = RTRIM$(LEFT$(What, textallowed))
  973.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  974.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  975.             ELSE
  976.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  977.                 finished = -1
  978.             END IF
  979.         CASE CenterJustify
  980.             IF pw > w THEN
  981.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  982.                 text$ = RTRIM$(LEFT$(What, textallowed))
  983.                 x = (w - _PRINTWIDTH(text$)) \ 2
  984.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  985.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  986.             ELSE
  987.                 x = (w - pw) \ 2
  988.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  989.                 finished = -1
  990.             END IF
  991.         CASE RightJustify
  992.             IF pw > w THEN
  993.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  994.                 text$ = RTRIM$(LEFT$(What, textallowed))
  995.                 x = w - _PRINTWIDTH(text$)
  996.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  997.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  998.             ELSE
  999.                 x = w - pw
  1000.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1001.                 finished = -1
  1002.             END IF
  1003.         CASE NoJustify
  1004.             x = TextHandles(Handle).Xpos
  1005.             firstlinelimit = (w - x) \ fw 'the limit of characters on the first line
  1006.             IF LEN(What) > firstlinelimit THEN
  1007.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  1008.                 text$ = RTRIM$(LEFT$(What, textallowed))
  1009.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  1010.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  1011.             ELSE
  1012.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1013.                 finished = -1
  1014.             END IF
  1015.     END SELECT
  1016.     IF finished THEN
  1017.         SELECT CASE TextHandles(Handle).UpdateMethod
  1018.             CASE NoUpdate 'We don't update the position at all.
  1019.             CASE DoUpdate
  1020.                 TextHandles(Handle).Xpos = x + pw
  1021.                 TextHandles(Handle).Ypos = y
  1022.             CASE NewLine
  1023.                 TextHandles(Handle).Ypos = y + 1
  1024.                 TextHandles(Handle).Xpos = 1
  1025.         END SELECT
  1026.         _FONT OldFont
  1027.         _DEST D: _SOURCE S
  1028.         COLOR FG, BG
  1029.     END IF
  1030.  
  1031. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  1032.     u = UBOUND(TextHandles)
  1033.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1034.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1035.     TextHandles(Handle).TextColor = Foreground
  1036.  
  1037.  
  1038. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  1039.     u = UBOUND(TextHandles)
  1040.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1041.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1042.     TextHandles(Handle).TextBackgroundColor = Background
  1043.  
  1044. SUB SetTextFont (Handle AS INTEGER, FontName AS STRING, FontSize AS INTEGER)
  1045.     u = UBOUND(TextHandles)
  1046.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1047.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1048.     SELECT CASE TextHandles(Handle).Font
  1049.         CASE 8, 9, 14, 15, 16, 17 'In built QB64 fonts.  We don't need to free them.
  1050.         CASE IS > 1
  1051.             'we have the font already in use
  1052.             'REMOVE THIS CONDITION IF NECESSARY, AND MANUALLY FREE/RELEASE FONTS AS ABLE!!!
  1053.             _FREEFONT TextHandles(Handle).Font 'if it's in use elsewhere, this *WILL* toss an error.
  1054.     END SELECT
  1055.  
  1056.     temp = _LOADFONT(FontName, FontSize, "MONOSPACE")
  1057.     IF temp > 1 THEN
  1058.         TextHandles(Handle).Font = temp
  1059.     ELSE
  1060.         TextHandles(Handle).Font = 16 'default to font 16, in case
  1061.     END IF
  1062.  
  1063.  
  1064. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  1065.     u = UBOUND(TextHandles)
  1066.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1067.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1068.     TextHandles(Handle).TextColor = Foreground
  1069.     TextHandles(Handle).TextBackgroundColor = Background
  1070.  
  1071.  
  1072. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  1073.     u = UBOUND(TextHandles)
  1074.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1075.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1076.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  1077.     TextHandles(Handle).UpdateMethod = Method
  1078.  
  1079.  
  1080. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  1081.     u = UBOUND(TextHandles)
  1082.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1083.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1084.     SELECT CASE Y
  1085.         CASE BottomLine
  1086.             TextHandles(Handle).VerticalAlignment = -2
  1087.         CASE CenterLine
  1088.             TextHandles(Handle).VerticalAlignment = -1
  1089.         CASE ELSE
  1090.             TextHandles(Handle).VerticalAlignment = 0
  1091.     END SELECT
  1092.     IF X < 1 AND X > -4 THEN
  1093.         TextHandles(Handle).Justification = X
  1094.     ELSE
  1095.         TextHandles(Handle).Xpos = X
  1096.     END IF
  1097.     IF Y < 1 THEN EXIT SUB
  1098.     TextHandles(Handle).Ypos = Y
  1099.  
  1100. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  1101.     u = UBOUND(TextHandles)
  1102.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1103.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1104.     IF X < 1 AND X > -4 THEN
  1105.         TextHandles(Handle).Justification = X
  1106.     ELSE
  1107.         TextHandles(Handle).Xpos = X
  1108.     END IF
  1109.  
  1110. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  1111.     u = UBOUND(TextHandles)
  1112.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1113.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1114.     SELECT CASE Y
  1115.         CASE BottomLine
  1116.             TextHandles(Handle).VerticalAlignment = -2
  1117.         CASE CenterLine
  1118.             TextHandles(Handle).VerticalAlignment = -1
  1119.         CASE ELSE
  1120.             TextHandles(Handle).VerticalAlignment = 0
  1121.     END SELECT
  1122.     IF Y < 1 THEN EXIT SUB
  1123.     TextHandles(Handle).Ypos = Y
  1124.  
  1125.  
  1126. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  1127.     u = UBOUND(TextHandles)
  1128.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1129.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1130.     GetPrintPositionY = TextHandles(Handle).Ypos
  1131.  
  1132. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  1133.     u = UBOUND(TextHandles)
  1134.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1135.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1136.     GetPrintPositionX = TextHandles(Handle).Xpos
  1137.  
  1138.  
  1139.  
  1140. FUNCTION WordBreak (text$)
  1141.     CONST Breaks = " ;,.?!-"
  1142.     FOR i = LEN(text$) TO 0 STEP -1
  1143.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  1144.         loopcount = loopcount + 1
  1145.     NEXT
  1146.     IF i = 0 THEN i = LEN(text$)
  1147.     WordBreak = i
  1148.  
  1149.  
  1150.  
  1151. SUB ClearTextArea (Handle AS INTEGER)
  1152.     u = UBOUND(TextHandles)
  1153.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1154.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1155.     IF TextHandles(Handle).SavedBackground THEN
  1156.         w = TextHandles(Handle).w
  1157.         h = TextHandles(Handle).h
  1158.         x1 = TextHandles(Handle).ScreenX
  1159.         y1 = TextHandles(Handle).ScreenY
  1160.         x2 = x1 + w - 1
  1161.         y2 = y1 + h - 1
  1162.         blend = _BLEND
  1163.         _DONTBLEND
  1164.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1165.         IF blend THEN _BLEND
  1166.     END IF
  1167.     DrawTextArea Handle
  1168.  
  1169.  
  1170.  
  1171. SUB DrawTextArea (Handle AS INTEGER)
  1172.     u = UBOUND(TextHandles)
  1173.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1174.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1175.     w = TextHandles(Handle).w
  1176.     h = TextHandles(Handle).h
  1177.     x1 = TextHandles(Handle).ScreenX
  1178.     y1 = TextHandles(Handle).ScreenY
  1179.     x2 = x1 + w - 1
  1180.     y2 = y1 + h - 1
  1181.  
  1182.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  1183.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  1184.  
  1185.  
  1186.  
  1187. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  1188.     u = UBOUND(TextHandles)
  1189.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1190.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1191.     TextHandles(Handle).FrameColor = FrameColor
  1192.     TextHandles(Handle).BackColor = BackColor
  1193.  
  1194.  
  1195.  
  1196. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  1197.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  1198.     x2 = tx2: y2 = ty2
  1199.     IF x1 > x2 THEN SWAP x1, x2
  1200.     IF y1 > y2 THEN SWAP y1, y2
  1201.     w = x2 - x1 + 1
  1202.     h = y2 - y1 + 1
  1203.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  1204.     'Error checking for if the user sends coordinates which are off the screen
  1205.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  1206.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  1207.  
  1208.     u = UBOUND(TextHandles)
  1209.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  1210.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  1211.     NEXT
  1212.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  1213.         Handle = u + 1
  1214.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  1215.     END IF
  1216.     TextHandles(Handle).x1 = x1
  1217.     TextHandles(Handle).y1 = y1
  1218.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  1219.     TextHandles(Handle).InUse = True
  1220.     TextHandles(Handle).Xpos = 0
  1221.     TextHandles(Handle).Ypos = 1
  1222.     TextHandles(Handle).UpdateMethod = NewLine
  1223.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  1224.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  1225.  
  1226.     IF SaveBackground THEN
  1227.         imagehandle = _NEWIMAGE(w, h, 32)
  1228.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1229.         TextHandles(Handle).SavedBackground = imagehandle
  1230.     END IF
  1231.     TextHandles(Handle).ScreenX = x1
  1232.     TextHandles(Handle).ScreenY = y1
  1233.     TextHandles(Handle).Font = 16 'default to font 16
  1234.     NewTextArea% = Handle
  1235.  
  1236. SUB FreeTextArea (Handle AS INTEGER)
  1237.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  1238.         IF TextHandles(Handle).InUse THEN
  1239.             TextHandles(Handle).InUse = False
  1240.             IF TextHandles(Handle).SavedBackground THEN
  1241.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  1242.                     w = TextHandles(Handle).w
  1243.                     h = TextHandles(Handle).h
  1244.                     x1 = TextHandles(Handle).ScreenX
  1245.                     y1 = TextHandles(Handle).ScreenY
  1246.                     x2 = x1 + w - 1
  1247.                     y2 = y1 + h - 1
  1248.                     blend = _BLEND
  1249.                     _DONTBLEND
  1250.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1251.                     IF blend THEN _BLEND
  1252.                 END IF
  1253.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  1254.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  1255.             END IF
  1256.         ELSE
  1257.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  1258.         END IF
  1259.     ELSE
  1260.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  1261.     END IF
  1262.  
  1263. SUB HideFrame (Handle AS INTEGER)
  1264.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  1265.         TextHandles(Handle).HideFrame = -1
  1266.         w = TextHandles(Handle).w
  1267.         h = TextHandles(Handle).h
  1268.         x1 = TextHandles(Handle).ScreenX
  1269.         y1 = TextHandles(Handle).ScreenY
  1270.         x2 = x1 + w - 1
  1271.         y2 = y1 + h - 1
  1272.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1273.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1274.         IF TextHandles(Handle).SavedBackground THEN
  1275.             blend = _BLEND
  1276.             _DONTBLEND
  1277.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1278.             _FREEIMAGE TextHandles(Handle).SavedBackground
  1279.             IF blend THEN _BLEND
  1280.         END IF
  1281.         TextHandles(Handle).SavedBackground = imagehandle
  1282.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  1283.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  1284.     END IF
  1285.  
  1286. SUB RestoreFrame (Handle AS INTEGER)
  1287.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  1288.         TextHandles(Handle).HideFrame = 0
  1289.         w = TextHandles(Handle).w
  1290.         h = TextHandles(Handle).h
  1291.         x1 = TextHandles(Handle).ScreenX
  1292.         y1 = TextHandles(Handle).ScreenY
  1293.         x2 = x1 + w - 1
  1294.         y2 = y1 + h - 1
  1295.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1296.         blend = _BLEND
  1297.         _DONTBLEND
  1298.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1299.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  1300.         _FREEIMAGE TextHandles(Handle).SavedBackground
  1301.         IF blend THEN _BLEND
  1302.         TextHandles(Handle).SavedBackground = imagehandle
  1303.         TextHandles(Handle).x1 = x1 'When the frames are restored, we need to recalculate our print position
  1304.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  1305.     END IF
  1306.  
  1307. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  1308.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  1309.     u = UBOUND(TextHandles)
  1310.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1311.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1312.     HideFrame Handle
  1313.     TextHandles(Handle).ScreenX = x1
  1314.     TextHandles(Handle).ScreenY = y1
  1315.     RestoreFrame Handle
  1316.  

So what did all the ripping out, restructuring, and rebuilding accomplish for me??

Monsters are now starting to drop loot and place it on the ground for us!  YAY!

.
.
.
... Too bad we still can't pick it up and make use of it in any meaningful way.  Right now, the monsters you fight just happen to sometimes poop meat which you can't pick up or interact with in any way.   Yaaaay, meat poopers!!

But, now that I (am soon to) have a means to help replenish the food that a player can consume, I can start having them actually need to eat and get hungry.  (Well, we've been going hungry all along; we've just been completely ignoring all messages or effects from hunger, since there wasn't any means to actually eat implemented into the game.)

I suppose the next step now is to work on implementing the inventory system so players can actually pick up those chunks of meat which poop out when a creature dies, and save them for use later, when they're hungry...

Who the heck knew that sorting out something as simple as "Does it drop food or not?" would be so complicated to implement.  :P
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 15, 2019, 12:27:11 pm
The inventory system has now been implemented into the game, though we still don't have commands yet to actually pick up or interact with any of the (only food) items in the game so far.   /sigh...   So much to do, and so far to go!

Code: QB64: [Select]
  1. DEFLNG A-Z 'default to long instead of single
  2. TYPE TextArea
  3.     InUse AS INTEGER
  4.     x1 AS LONG 'left
  5.     y1 AS LONG 'top
  6.     w AS LONG 'width
  7.     h AS LONG 'height
  8.     FrameColor AS _UNSIGNED LONG
  9.     BackColor AS _UNSIGNED LONG
  10.     Xpos AS INTEGER
  11.     Ypos AS INTEGER
  12.     VerticalAlignment AS INTEGER
  13.     Justification AS INTEGER
  14.     UpdateMethod AS INTEGER
  15.     TextColor AS _UNSIGNED LONG
  16.     TextBackgroundColor AS _UNSIGNED LONG
  17.     SavedBackground AS INTEGER
  18.     HideFrame AS INTEGER
  19.     ScreenX AS INTEGER
  20.     ScreenY AS INTEGER
  21.     Font AS LONG 'NEW! Change fonts for each independent font area
  22.  
  23. REDIM SHARED TextHandles(0) AS TextArea
  24.  
  25. CONST True = -1, False = 0
  26. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  27. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  28. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  29. '********************************************************
  30. '* Text Frames before this line
  31. '********************************************************
  32.  
  33.  
  34.  
  35.  
  36.  
  37. _CONSOLE ON 'for debugging purposes while making/testing things
  38.  
  39. TYPE Damage_Type
  40.     Low AS INTEGER
  41.     High AS INTEGER
  42.  
  43. TYPE Light_Type
  44.     Reach AS INTEGER
  45.     Left AS INTEGER
  46.  
  47. TYPE Weapon_Type
  48.     Identified AS _UNSIGNED _BYTE
  49.     Name AS STRING * 20
  50.     DisplayedName AS STRING * 20
  51.     Reach AS INTEGER
  52.     Damage AS Damage_Type
  53.     HitBonus AS INTEGER
  54.     DamageBonus AS INTEGER
  55.     Left AS INTEGER 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  56.     IconX AS LONG
  57.     IconY AS LONG
  58.     Light AS Light_Type
  59.  
  60. TYPE Armor_Type
  61.     Identified AS _UNSIGNED _BYTE
  62.     Name AS STRING * 20
  63.     DisplayedName AS STRING * 20
  64.     PD AS INTEGER 'Passive Defense (dodge)
  65.     DR AS INTEGER 'Damage Resistance (absorption)
  66.     Left AS INTEGER 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  67.     IconX AS LONG
  68.     IconY AS LONG
  69.  
  70. TYPE Food_Type
  71.     Identified AS _UNSIGNED _BYTE
  72.     Name AS STRING * 20
  73.     DisplayedName AS STRING * 20
  74.     HungerFill AS SINGLE 'how much said food fills one's stomach
  75.     HungerRate AS SINGLE 'how fast it digests before we get hunry again
  76.     IconX AS LONG
  77.     IconY AS LONG
  78.  
  79.  
  80. TYPE Hero_Type
  81.     Name AS STRING * 20
  82.     Life AS Damage_Type
  83.     Mana AS Damage_Type
  84.     Level AS _UNSIGNED _BYTE
  85.     EXP_Earned AS LONG
  86.     EXP_Needed AS LONG
  87.     Light AS Light_Type
  88.     Weapon1 AS Weapon_Type
  89.     Weapon2 AS Weapon_Type
  90.     Armor AS Armor_Type
  91.     HealingRate AS INTEGER 'number of turns before the hero heals a point
  92.     Hunger AS SINGLE
  93.     HungerRate AS SINGLE
  94.  
  95. TYPE Treasure_TYPE
  96.     Chance AS SINGLE
  97.     Type AS _UNSIGNED _BYTE '1 weapon, 2 armor, 3 food, 4 item
  98.     Index AS INTEGER 'the number of the type... Weapon(0) = "Bare Fist", so tyoe = 1, index = 0.
  99.  
  100. TYPE Monster_TYPE
  101.     Name AS STRING * 20
  102.     Life AS Damage_Type
  103.     Level AS INTEGER
  104.     ExpBonus AS INTEGER
  105.     Sight AS INTEGER
  106.     Hearing AS INTEGER
  107.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  108.     Weapon1 AS Weapon_Type
  109.     Weapon2 AS Weapon_Type
  110.     Armor AS Armor_Type
  111.     Found AS INTEGER
  112.     IconX AS LONG
  113.     IconY AS LONG
  114.     Loot1 AS Treasure_TYPE
  115.     Loot2 AS Treasure_TYPE
  116.     loot3 AS Treasure_TYPE
  117.  
  118. TYPE Encounter_TYPE
  119.     Active AS INTEGER
  120.     X AS INTEGER
  121.     Y AS INTEGER
  122.     Type AS INTEGER '0 monster, 1 weapon, 2 armor, 3 food, 4 item
  123.     Index AS INTEGER
  124.     Life AS INTEGER
  125.  
  126. TYPE Inventory_Type
  127.     Weapon AS Weapon_Type
  128.     Armor AS Armor_Type
  129.     Food AS Food_Type
  130.     'Item as item_type 'to come later, once general items get added
  131.  
  132.  
  133. REDIM SHARED Monster(100) AS Monster_TYPE
  134. REDIM SHARED Encounter(1000) AS Encounter_TYPE, EncounterLimit AS INTEGER
  135.  
  136. 'These are all base items and weapons.  The sets which the character actually carries will vary significantly from wear/tear, enchantments, and other in-game factors.
  137. REDIM SHARED Weapon(100) AS Weapon_Type
  138. REDIM SHARED Armor(100) AS Armor_Type
  139. REDIM SHARED Food(100) AS Food_Type
  140.  
  141. DIM SHARED Hero AS Hero_Type
  142. REDIM SHARED Inventory(103) AS Inventory_Type '103 slots each to carry weapons/armor/food/items
  143. DIM SHARED WeaponsOwned AS _UNSIGNED _BYTE, ArmorOwned AS _UNSIGNED _BYTE, FoodOwned AS _UNSIGNED _BYTE, ItemsOwned AS _UNSIGNED _BYTE 'a count of each which we own.  Should always be a value between 0 and 103.
  144. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  145. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  146. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  147. DIM SHARED Scale AS _FLOAT, WorkScreen AS LONG, DisplayScreen AS LONG, Icons AS LONG
  148. DIM SHARED TextFont AS LONG, StepsTaken AS _UNSIGNED _INTEGER64
  149.  
  150. WorkScreen = _NEWIMAGE(3200, 2400, 32)
  151. DisplayScreen = _NEWIMAGE(800, 700, 32)
  152. SCREEN DisplayScreen
  153. Scale = 2
  154.  
  155. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  156. '1 map is illuminated
  157. '2 map is uncovered
  158. '4 map is a wall
  159. '8 map is a pathway
  160. '16 map is a stairway
  161. '32 map is simply blocked (perhaps with a monster?)
  162. '64 map is secret (can not be uncovered)
  163.  
  164. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  165.  
  166.  
  167. Init
  168. CreateMap 99, 74, 10
  169.     DrawMap
  170.     DisplayCharacter
  171.     _DISPLAY
  172.     GetInput
  173.     MonstersTurn
  174.     CheckForHeroGrowth
  175.  
  176. SUB Init
  177.     D = _DEST
  178.     Hero.Name = "Steve The Tester!"
  179.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  180.     Hero.Mana.Low = 10: Hero.Mana.High = 10
  181.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  182.     Hero.Weapon1.Name = "Bare Fist"
  183.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  184.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  185.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  186.     Hero.Weapon1.IconX = 2 * 32: Hero.Weapon1.IconY = 47 * 32
  187.     Hero.Weapon2.Name = "Magic Candle"
  188.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  189.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  190.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  191.     Hero.Weapon2.IconX = 52 * 32: Hero.Weapon2.IconY = 42 * 32
  192.     Hero.Weapon2.Light.Reach = 2: Hero.Weapon2.Light.Left = -1 'infinite
  193.     Hero.Armor.Name = "Naked"
  194.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  195.     Hero.Armor.IconX = 46 * 32: Hero.Armor.IconY = 42 * 32
  196.     Hero.HealingRate = 20 'the hero heals 1 point of health for every 20 valid turns naturally
  197.     Hero.HungerRate = 0.1 'Let's start heros out with a full belly and a low hunger growth rate
  198.  
  199.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  200.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  201.     SetTextFont PrintArea, "courbd.ttf", 24
  202.     DrawTextArea PrintArea
  203.     SetPrintPositionX PrintArea, CenterJustify
  204.     SetPrintUpdate PrintArea, NewLine
  205.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  206.     SetTextFont PrintArea, "courbd.ttf", 18
  207.     PrintOut PrintArea, "created by STEVE!"
  208.     PrintOut PrintArea, ""
  209.     SetPrintPositionX PrintArea, LeftJustify
  210.     SetTextFont PrintArea, "courbd.ttf", 12
  211.     TextFont = 12
  212.  
  213.     temp = _NEWIMAGE(480, 480, 32)
  214.     _DEST temp: _CONTROLCHR OFF
  215.     COLOR &HFFFFFF00, 0 'Yellow Hero
  216.     _PRINTSTRING (0, 0), CHR$(1) 'the hero
  217.     COLOR &HFFFF0000, 0 'Red Question Mark
  218.     _PRINTSTRING (16, 0), "?" 'a question mark
  219.     Icons = _LOADIMAGE("Sprites.png", 32)
  220.     _PUTIMAGE (2016, 1504)-STEP(32, 32), temp, Icons, (0, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  221.     _PUTIMAGE (1984, 1504)-STEP(32, 32), temp, Icons, (16, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  222.     '        SCREEN Icons: DO: SLEEP: LOOP
  223.     _DEST D
  224.  
  225.     'Init some basic weapons which we can find
  226.  
  227.     'These first two should always be available for us to use/enjoy.
  228.     Weapon(0).Name = "Bare Fist"
  229.     Weapon(0).Reach = 1: Weapon(0).Damage.Low = 1: Weapon(0).Damage.High = 2
  230.     Weapon(0).HitBonus = 0: Weapon(0).DamageBonus = 0
  231.     Weapon(0).Left = -1 'your fist is indestructible!
  232.     Weapon(0).IconX = 2 * 32: Weapon(0).IconY = 47 * 32
  233.     Weapon(0).Light.Reach = 0: Weapon(0).Light.Left = 0
  234.     Weapon(1).Name = "Magic Candle"
  235.     Weapon(1).Reach = 0: Weapon(1).Damage.Low = 0: Weapon(1).Damage.High = 0
  236.     Weapon(1).HitBonus = 0: Weapon(1).DamageBonus = 0
  237.     Weapon(1).Left = 0 'you can't attack with a candle
  238.     Weapon(1).IconX = 52 * 32: Weapon(1).IconY = 42 * 32
  239.     Weapon(1).Light.Reach = 2: Weapon(1).Light.Left = -1 'infinite
  240.  
  241.     Armor(0).Name = "Naked"
  242.     Armor(0).PD = 0: Armor(0).DR = 0: Armor(0).Left = -1 'you might be naked, but at least you can't break your armor!
  243.     Armor(0).IconX = 46 * 32: Armor(0).IconY = 42 * 32
  244.  
  245.     Food(1).Name = "Bat Meat"
  246.     Food(1).HungerFill = 10: Food(1).HungerRate = .1
  247.     Food(1).IconX = 36 * 32: Food(1).IconY = 23 * 32
  248.  
  249.     Food(2).Name = "Rat Meat"
  250.     Food(2).HungerFill = 10: Food(2).HungerRate = .1
  251.     Food(2).IconX = 36 * 32: Food(2).IconY = 23 * 32
  252.  
  253.     Food(3).Name = "Snake Meat"
  254.     Food(3).HungerFill = 10: Food(3).HungerRate = .1
  255.     Food(3).IconX = 36 * 32: Food(3).IconY = 23 * 32
  256.  
  257.     WeaponsOwned = 2
  258.     Inventory(0).Weapon.Identified = -1
  259.     Inventory(0).Weapon.Name = "Bare Fist"
  260.     Inventory(0).Weapon.Reach = 1: Inventory(0).Weapon.Damage.Low = 1: Inventory(0).Weapon.Damage.High = 2
  261.     Inventory(0).Weapon.HitBonus = 0: Inventory(0).Weapon.DamageBonus = 0
  262.     Inventory(0).Weapon.Left = -1 'your fist is indestructible!
  263.     Inventory(0).Weapon.IconX = 2 * 32: Inventory(0).Weapon.IconY = 47 * 32
  264.     Inventory(0).Weapon.Light.Reach = 0: Inventory(0).Weapon.Light.Left = -1
  265.     Inventory(1).Weapon.Identified = -1
  266.     Inventory(1).Weapon.Name = "Magic Candle"
  267.     Inventory(1).Weapon.Reach = 0: Inventory(1).Weapon.Damage.Low = 0: Inventory(1).Weapon.Damage.High = 0
  268.     Inventory(1).Weapon.HitBonus = 0: Inventory(1).Weapon.DamageBonus = 0
  269.     Inventory(1).Weapon.Left = -1 'magic candle is indestructible
  270.     Inventory(1).Weapon.IconX = 52 * 32: Inventory(1).Weapon.IconY = 42 * 32
  271.     Inventory(1).Weapon.Light.Reach = 2: Inventory(1).Weapon.Light.Left = -1 'infinite
  272.  
  273.     ArmorOwnded = 1
  274.     Inventory(0).Armor.Identified = -1
  275.     Inventory(0).Armor.Name = "Naked"
  276.     Inventory(0).Armor.PD = 0: Inventory(0).Armor.DR = 0: Inventory(0).Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  277.     Inventory(0).Armor.IconX = 46 * 32: Inventory(0).Armor.IconY = 42 * 32
  278.  
  279.     InitMonsters
  280.  
  281. SUB InitMonsters
  282.  
  283.     Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  284.     Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  285.     Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  286.     Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  287.     Monster(1).IconX = 44 * 32: Monster(1).IconY = 3 * 32 'position 44,3 on the sprite sheet
  288.     Monster(1).Loot1.Chance = 95: Monster(1).Loot1.Type = 3: Monster(1).Loot1.Index = 1
  289.     'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  290.     'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  291.     'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  292.     'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  293.     'Monster(1).Armor.Name = ""
  294.     'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  295.     Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  296.     Monster(2).Level = 1: Monster(2).ExpBonus = 0
  297.     Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  298.     Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  299.     Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  300.     Monster(2).IconX = 23 * 32: Monster(2).IconY = 4 * 32 'position 44,3 on the sprite sheet
  301.     Monster(2).Loot1.Chance = 25: Monster(2).Loot1.Type = 3: Monster(2).Loot1.Index = 2
  302.     Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  303.     Monster(3).Level = 1: Monster(3).ExpBonus = 0
  304.     Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  305.     Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  306.     Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  307.     Monster(3).IconX = 37 * 32: Monster(3).IconY = 4 * 32 'position 44,3 on the sprite sheet
  308.     Monster(3).Loot1.Chance = 25: Monster(3).Loot1.Type = 3: Monster(3).Loot1.Index = 3
  309.  
  310.  
  311. SUB CheckForHeroGrowth
  312.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  313.         CLS
  314.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  315.         _DISPLAY
  316.         BEEP
  317.         _DELAY 5
  318.         SYSTEM
  319.     END IF
  320.     IF Hero.EXP_Earned >= Hero.EXP_Needed THEN 'let's check to see if the hero has leveled up
  321.         PrintOut PrintArea, "Congratulations!  You have gained a level!"
  322.         DO
  323.             r = INT(RND * 6) + 1
  324.             lifegained = lifegained + r
  325.         LOOP UNTIL r <> 6
  326.         Hero.Life.Low = Hero.Life.Low + r
  327.         Hero.Life.High = Hero.Life.High + r
  328.         Hero.EXP_Earned = 0
  329.         Hero.Level = Hero.Level + 1
  330.         Hero.EXP_Needed = Hero.EXP_Needed + Hero.Level + 1
  331.     END IF
  332.     IF StepsTaken MOD Hero.HealingRate = 0 THEN 'heal the hero naturally over time
  333.         IF Hero.Life.Low < Hero.Life.High THEN Hero.Life.Low = Hero.Life.Low + 1
  334.     END IF
  335.     Hero.Hunger = Hero.Hunger + Hero.HungerRate
  336.     IF Hero.Weapon1.Light.Left > -1 THEN Hero.Weapon1.Light.Left = Hero.Weapon1.Light.Left - 1 'durability on our light sources wear down over time
  337.     IF Hero.Weapon2.Light.Left > -1 THEN Hero.Weapon2.Light.Left = Hero.Weapon2.Light.Left - 1
  338.  
  339.  
  340.  
  341. SUB DisplayCharacter
  342.     LINE (0, 601)-(229, 799), &HFF000000, BF
  343.     COLOR -1, 0
  344.     Box 0, 601, 229, 62, 0, 0, "", Silver, 0
  345.     Box 0, 601, 229, 12, Black, 0, _TRIM$(Hero.Name), Silver, 0
  346.     Box 0, 626, 229 * Hero.Life.Low / Hero.Life.High, 12, 0, 0, "", Red, Black
  347.     Box 0, 639, 229 * Hero.Mana.Low / Hero.Mana.High, 12, 0, 0, "", Blue, Black
  348.     Box 0, 652, 229 * Hero.EXP_Earned / Hero.EXP_Needed, 12, 0, 0, "", Green, Black
  349.     _PRINTSTRING (10, 616), "LEVEL:" + STR$(Hero.Level)
  350.     _PRINTSTRING (10, 629), "LIFE :" + STR$(Hero.Life.Low) + " / " + _TRIM$(STR$(Hero.Life.High))
  351.     _PRINTSTRING (10, 642), "MANA :" + STR$(Hero.Mana.Low) + " / " + _TRIM$(STR$(Hero.Mana.High))
  352.     _PRINTSTRING (10, 655), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  353.     FOR i = 0 TO 5 'six boxes for information : left hand, right hand, armor, and 3 more for later....
  354.         Box 36 * i + 8, 665, 34, 34, 0, 0, "", Black, Silver
  355.     NEXT
  356.     _PUTIMAGE (9, 666)-STEP(32, 32), Icons, DisplayScreen, (Hero.Weapon1.IconX, Hero.Weapon1.IconY)-STEP(32, 32)
  357.     _PUTIMAGE (45, 666)-STEP(32, 32), Icons, DisplayScreen, (Hero.Weapon2.IconX, Hero.Weapon2.IconY)-STEP(32, 32)
  358.     _PUTIMAGE (81, 666)-STEP(32, 32), Icons, DisplayScreen, (Hero.Armor.IconX, Hero.Armor.IconY)-STEP(32, 32)
  359.  
  360. SUB ManageInventory
  361.     STATIC Header AS LONG, MainFont AS LONG, ItemNameFont AS LONG
  362.     STATIC Selection AS INTEGER, Page AS INTEGER, Item AS INTEGER
  363.     PCOPY 0, 1
  364.     D = _DEST: S = _SOURCE
  365.     OldFont = _FONT
  366.     IF Header = 0 THEN Header = _LOADFONT("courbd.ttf", 24, "monospace")
  367.     IF MainFont = 0 THEN MainFont = _LOADFONT("courbd.ttf", 14, "monospace")
  368.     IF ItemNameFont = 0 THEN ItemNameFont = _LOADFONT("courbd.ttf", 18, "monospace")
  369.     IF Selection = 0 THEN Selection = 1 'there's 4 categories which we can choose from (Weapon, Armor, Food, Item)
  370.     'Page = 0 'there's 4 pages of info for each category, from 0 to 3
  371.     'Item = 0 'and there's 26 items on each page, from 0 to 25.
  372.     DO
  373.         _LIMIT 30
  374.         valid = -1
  375.         LINE (50, 50)-STEP(700, 500), SkyBlue, BF 'erase the background
  376.         LINE (300, 106)-(725, 450), LightGray, BF
  377.         _FONT Header
  378.         SELECT CASE Selection 'redraw the selection we're working with
  379.             CASE 1
  380.                 Box 51, 51, 174, 50, Black, 0, "Weapons", SkyBlue, SkyBlue
  381.                 Box 225, 51, 174, 50, Black, 0, "Armors", LightGray, Black
  382.                 Box 400, 51, 174, 50, Black, 0, "Food", LightGray, Black
  383.                 Box 575, 51, 174, 50, Black, 0, "Items", LightGray, Black
  384.                 _FONT MainFont
  385.                 FOR i = 26 * Page TO 26 * Page + 25
  386.                     out$ = CHR$(65 + i MOD 26) + ")" + Inventory(i).Weapon.Name
  387.                     _PRINTSTRING (56, 106 + _FONTHEIGHT * (i MOD 26)), out$
  388.                 NEXT
  389.                 S = Item MOD 26
  390.                 Box 54 + _FONTWIDTH * 2, 106 + _FONTHEIGHT * Item, _FONTWIDTH * 20 + 4, _FONTHEIGHT, 0, 0, "", LightGray, Black
  391.                 out$ = CHR$(65 + S) + ")" + Inventory(Item).Weapon.Name
  392.                 _PRINTSTRING (56, 106 + _FONTHEIGHT * S), out$
  393.  
  394.                 COLOR Black, 0
  395.                 _FONT ItemNameFont
  396.                 IF Inventory(Item).Weapon.Identified THEN
  397.                     Box 300, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Weapon.Name), 0, 0
  398.                     _FONT 8
  399.                     _PRINTSTRING (305, 145), "Reach  :" + STR$(Inventory(Item).Weapon.Reach)
  400.                     _PRINTSTRING (305, 165), "Min Dmg:" + STR$(Inventory(Item).Weapon.Damage.Low)
  401.                     _PRINTSTRING (305, 175), "Max Dmg:" + STR$(Inventory(Item).Weapon.Damage.High)
  402.                     _PRINTSTRING (305, 185), "Hit Mod:" + STR$(Inventory(Item).Weapon.HitBonus)
  403.                     _PRINTSTRING (305, 195), "Dmg Mod:" + STR$(Inventory(Item).Weapon.DamageBonus)
  404.                     _PRINTSTRING (305, 205), "Light  :" + STR$(Inventory(Item).Weapon.Light.Reach)
  405.                 ELSE
  406.                     Box 350, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Weapon.DisplayedName), 0, 0
  407.                     _FONT 8
  408.                     _PRINTSTRING (305, 145), "Reach  : ???"
  409.                     _PRINTSTRING (305, 165), "Min Dmg: ???"
  410.                     _PRINTSTRING (305, 175), "Max Dmg: ???"
  411.                     _PRINTSTRING (305, 185), "Hit Mod: ???"
  412.                     _PRINTSTRING (305, 195), "Dmg Mod: ???"
  413.                     _PRINTSTRING (305, 205), "Light  : ???"
  414.                 END IF
  415.                 out$ = "Cond   : "
  416.                 SELECT CASE Inventory(Item).Weapon.Left 'the durability left on a weapon
  417.                     CASE -1: out$ = out$ + "Ind."
  418.                     CASE 0: out$ = out$ + "Broken"
  419.                     CASE IS < 26: out$ = out$ + "Worn"
  420.                     CASE IS < 50: out$ = out$ + "Used"
  421.                     CASE IS < 75: out$ = out$ + "Good"
  422.                     CASE IS < 90: out$ = out$ + "Mint"
  423.                     CASE ELSE: out$ = out$ + "New"
  424.                 END SELECT
  425.                 _PRINTSTRING (305, 155), out$
  426.                 LINE (422, 134)-STEP(130, 130), Black, BF
  427.                 _PUTIMAGE (423, 135)-STEP(128, 128), Icons, DisplayScreen, (Inventory(Item).Weapon.IconX, Inventory(Item).Weapon.IconY)-STEP(31, 31)
  428.             CASE 2
  429.                 Box 225, 51, 174, 50, Black, 0, "Armors", SkyBlue, SkyBlue
  430.                 Box 51, 51, 174, 50, Black, 0, "Weapons", LightGray, Black
  431.                 Box 400, 51, 174, 50, Black, 0, "Food", LightGray, Black
  432.                 Box 575, 51, 174, 50, Black, 0, "Items", LightGray, Black
  433.                 _FONT MainFont
  434.                 FOR i = 0 TO 25
  435.                     out$ = CHR$(65 + i) + ")" + Inventory(i).Armor.Name
  436.                     _PRINTSTRING (56, 106 + _FONTHEIGHT * i), out$
  437.                 NEXT
  438.                 Box 54 + _FONTWIDTH * 2, 106 + _FONTHEIGHT * Item, _FONTWIDTH * 20 + 4, _FONTHEIGHT, 0, 0, "", LightGray, Black
  439.                 out$ = CHR$(65 + Item MOD 26) + ")" + Inventory(Item).Armor.Name
  440.                 _PRINTSTRING (56, 106 + _FONTHEIGHT * (Item MOD 26)), out$
  441.  
  442.                 COLOR Black, 0
  443.                 _FONT ItemNameFont
  444.                 IF Inventory(Item).Armor.Identified THEN
  445.                     Box 300, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Armor.Name), 0, 0
  446.                     _FONT 8
  447.                     _PRINTSTRING (305, 145), "PD   :" + STR$(Inventory(Item).Armor.PD)
  448.                     _PRINTSTRING (305, 155), "DR   :" + STR$(Inventory(Item).Armor.DR)
  449.                 ELSE
  450.                     Box 350, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Armor.DisplayedName), 0, 0
  451.                     _FONT 8
  452.                     _PRINTSTRING (305, 145), "PD   : ???"
  453.                     _PRINTSTRING (305, 155), "DR   : ???"
  454.                 END IF
  455.                 out$ = "Cond : "
  456.                 SELECT CASE Inventory(Item).Armor.Left 'the durability left on armor
  457.                     CASE -1: out$ = out$ + "Ind."
  458.                     CASE 0: out$ = out$ + "Broken"
  459.                     CASE IS < 26: out$ = out$ + "Worn"
  460.                     CASE IS < 50: out$ = out$ + "Used"
  461.                     CASE IS < 75: out$ = out$ + "Good"
  462.                     CASE IS < 90: out$ = out$ + "Mint"
  463.                     CASE ELSE: out$ = out$ + "New"
  464.                 END SELECT
  465.                 _PRINTSTRING (305, 175), out$
  466.                 LINE (422, 134)-STEP(130, 130), Black, BF
  467.                 _PUTIMAGE (423, 135)-STEP(128, 128), Icons, DisplayScreen, (Inventory(Item).Armor.IconX, Inventory(Item).Armor.IconY)-STEP(31, 31)
  468.             CASE 3
  469.                 Box 400, 51, 174, 50, Black, 0, "Food", SkyBlue, SkyBlue
  470.                 Box 51, 51, 174, 50, Black, 0, "Weapons", LightGray, Black
  471.                 Box 225, 51, 174, 50, Black, 0, "Armors", LightGray, Black
  472.                 Box 575, 51, 174, 50, Black, 0, "Items", LightGray, Black
  473.                 _FONT MainFont
  474.                 FOR i = 0 TO 25
  475.                     out$ = CHR$(65 + i) + ")" + Inventory(i).Food.Name
  476.                     _PRINTSTRING (56, 106 + _FONTHEIGHT * i), out$
  477.                 NEXT
  478.                 Box 54 + _FONTWIDTH * 2, 106 + _FONTHEIGHT * Item, _FONTWIDTH * 20 + 4, _FONTHEIGHT, 0, 0, "", LightGray, Black
  479.                 out$ = CHR$(65 + Item MOD 26) + ")" + Inventory(Item).Food.Name
  480.                 _PRINTSTRING (56, 106 + _FONTHEIGHT * (Item MOD 26)), out$
  481.  
  482.                 COLOR Black, 0
  483.                 _FONT ItemNameFont
  484.                 IF Inventory(Item).Food.Identified THEN
  485.                     Box 300, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Food.Name), 0, 0
  486.                 ELSE
  487.                     Box 350, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Food.DisplayedName), 0, 0
  488.                 END IF
  489.                 LINE (422, 134)-STEP(130, 130), Black, BF
  490.                 _PUTIMAGE (423, 135)-STEP(128, 128), Icons, DisplayScreen, (Inventory(Item).Food.IconX, Inventory(Item).Food.IconY)-STEP(31, 31)
  491.             CASE 4
  492.                 Box 575, 51, 174, 50, Black, 0, "Items", SkyBlue, SkyBlue
  493.                 Box 51, 51, 174, 50, Black, 0, "Weapons", LightGray, Black
  494.                 Box 225, 51, 174, 50, Black, 0, "Armors", LightGray, Black
  495.                 Box 400, 51, 174, 50, Black, 0, "Food", LightGray, Black
  496.                 _FONT MainFont
  497.                 FOR i = 0 TO 25
  498.                     out$ = CHR$(65 + i) + ")" '+ Inventory(i).Item.Name 'not defined yet
  499.                     _PRINTSTRING (56, 106 + _FONTHEIGHT * i), out$
  500.                 NEXT
  501.                 Box 54 + _FONTWIDTH * 2, 106 + _FONTHEIGHT * Item, _FONTWIDTH * 20 + 4, _FONTHEIGHT, 0, 0, "", LightGray, Black
  502.                 out$ = CHR$(65 + Item MOD 26) + ")" '+ Inventory(Item).Item.Name
  503.                 _PRINTSTRING (56, 106 + _FONTHEIGHT * (Item MOD 26)), out$
  504.         END SELECT
  505.         _FONT MainFont
  506.         Box 55, 485, 95, 50, Black, 0, "Page" + STR$(Page + 1), 0, 0
  507.         Box 170, 485, 95, 50, Black, 0, "(D)rop", LightGray, Black
  508.         Box 285, 485, 95, 50, Black, 0, "(E)quip", LightGray, Black
  509.         Box 410, 485, 95, 50, Black, 0, "(U)se", LightGray, Black
  510.         Box 525, 485, 95, 50, Black, 0, "nothing", LightGray, Black
  511.         Box 640, 485, 95, 50, Black, 0, "(C)lose", LightGray, Black
  512.         k = _KEYHIT
  513.         SELECT CASE k
  514.             CASE 18432 'up
  515.                 Item = Item - 1
  516.                 IF Item < 0 THEN Item = 25
  517.             CASE 19200 'left
  518.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  519.                     Page = Page - 1
  520.                     IF Page < 0 THEN Page = 3
  521.                 ELSE
  522.                     Selection = Selection - 1
  523.                     IF Selection < 1 THEN Selection = 4
  524.                 END IF
  525.             CASE 20480 'down
  526.                 Item = Item + 1
  527.                 IF Item > 25 THEN Item = 0
  528.             CASE 19712 'right
  529.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  530.                     Page = Page + 1
  531.                     IF Page > 3 THEN Page = 0
  532.                 ELSE
  533.                     Selection = Selection + 1
  534.                     IF Selection > 4 THEN Selection = 1
  535.                 END IF
  536.             CASE ASC("C"), ASC("c"), 27
  537.                 EXIT DO
  538.             CASE ELSE
  539.                 valid = 0
  540.         END SELECT
  541.         _DISPLAY
  542.         PCOPY 1, 0
  543.     LOOP
  544.  
  545.     _FONT OldFont
  546.     _DEST D: _SOURCE S
  547.  
  548.  
  549.  
  550.  
  551. SUB GetInput
  552.     DO
  553.         k = _KEYHIT: valid = -1
  554.         SELECT CASE k
  555.             CASE 18432 'up
  556.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  557.                     SELECT CASE Scale
  558.                         CASE 1.5: Scale = 2 'It's as small as we go
  559.                         CASE 2: Scale = 3
  560.                         CASE 3: Scale = 4
  561.                         CASE 4: Scale = 6
  562.                         CASE 6: Scale = 8
  563.                         CASE 8: Scale = 12
  564.                     END SELECT
  565.                 ELSE
  566.                     IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  567.                 END IF
  568.             CASE 19200: 'left
  569.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  570.                     TextFont = TextFont - 1
  571.                     IF TextFont < 8 THEN TextFont = 8
  572.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  573.                     ClearTextArea PrintArea
  574.                     SetPrintPosition PrintArea, 1, 1
  575.                     PrintOut PrintArea, "Font Size Changed"
  576.                 ELSE
  577.                     IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  578.                 END IF
  579.             CASE 20480: 'down
  580.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  581.                     SELECT CASE Scale
  582.                         CASE 2: Scale = 1.5 'It's as small as we go
  583.                         CASE 3: Scale = 2
  584.                         CASE 4: Scale = 3
  585.                         CASE 6: Scale = 4
  586.                         CASE 8: Scale = 6
  587.                         CASE 12: Scale = 8
  588.                     END SELECT
  589.                 ELSE
  590.                     IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  591.                 END IF
  592.             CASE 19712: 'right
  593.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  594.                     TextFont = TextFont + 1
  595.                     IF TextFont > 48 THEN TextFont = 48
  596.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  597.                     ClearTextArea PrintArea
  598.                     SetPrintPosition PrintArea, 1, 1
  599.                     PrintOut PrintArea, "Font Size Changed"
  600.                 ELSE
  601.                     IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  602.                 END IF
  603.             CASE 32 'space to just wait and skip a turn
  604.             CASE 60 ' "<" key
  605.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  606.                     Level = Level + 1
  607.                     CreateMap 99, 74, 10
  608.                     PathFind
  609.                 END IF
  610.             CASE ASC("I"), ASC("i")
  611.                 ManageInventory
  612.             CASE ASC("+"), ASC("=")
  613.                 IF Hero.Weapon2.Light.Reach < 25 THEN Hero.Weapon2.Light.Reach = Hero.Weapon2.Light.Reach + 1
  614.             CASE ASC("-"), ASC("_")
  615.                 IF Hero.Weapon2.Light.Reach > -1 THEN Hero.Weapon2.Light.Reach = Hero.Weapon2.Light.Reach - 1
  616.             CASE ELSE
  617.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  618.         END SELECT
  619.         _LIMIT 60
  620.     LOOP UNTIL k AND valid
  621.     _KEYCLEAR 'one keystroke at a time
  622.     StepsTaken = StepsTaken + 1
  623.  
  624. SUB Box (X, Y, Wide, High, FontColor as _unsigned long, _
  625.          FontBackGround as _unsigned long, Caption AS STRING, Kolor AS _UNSIGNED LONG, BackGround AS _UNSIGNED LONG)
  626.     COLOR FontColor, FontBackGround
  627.     LINE (X, Y)-STEP(Wide, High), Kolor, BF
  628.     LINE (X, Y)-STEP(Wide, High), BackGround, B
  629.     pw = _PRINTWIDTH(Caption): ph = _FONTHEIGHT
  630.     _PRINTSTRING (X + (Wide - pw + 1) \ 2, Y + (High - ph + 1) \ 2), Caption
  631.     COLOR DC, BG
  632.  
  633.  
  634. SUB MoveHero (MoveX, MoveY)
  635.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  636.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  637.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  638.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  639.             IF MoveX THEN Hero.X = Hero.X + MoveX
  640.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  641.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  642.             PathFind
  643.         ELSE
  644.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  645.             FOR i = 1 TO EncounterLimit
  646.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  647.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  648.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  649.                         Swing 0, i 'hero swings at the monster
  650.                     END IF
  651.                 END IF
  652.             NEXT
  653.         END IF
  654.     END IF
  655.  
  656. SUB Swing (Who, AtWhom)
  657.  
  658.     BaseChancetohit = 10 'base 10 chance to hit
  659.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  660.         M = Encounter(AtWhom).Index
  661.         IF Hero.Weapon1.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  662.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  663.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  664.             totalroll = 0
  665.             DO
  666.                 roll = INT(RND * 20) + 1
  667.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  668.                 IF roll = 20 THEN totalroll = totalroll + 20
  669.                 totalroll = totalroll + roll
  670.             LOOP UNTIL roll <> 1 AND roll <> 20
  671.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  672.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  673.             out$ = _TRIM$(Hero.Name)
  674.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  675.                 SetTextColor PrintArea, &HFFF000F0, 0
  676.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  677.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  678.                 Hero.Life.Low = Hero.Life.Low - damage
  679.             ELSEIF totalroll < Chancetohit THEN
  680.                 SetTextColor PrintArea, &HFFF0F000, 0
  681.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  682.             ELSEIF totalroll > Chancetohit + 20 THEN
  683.                 SetTextColor PrintArea, &HFF00FF00, 0
  684.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  685.                 damage = damage * (totalroll / 20 + 1)
  686.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  687.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  688.             ELSEIF totalroll >= Chancetohit THEN
  689.                 SetTextColor PrintArea, &HFF00FF00, 0
  690.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  691.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  692.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  693.             END IF
  694.         ELSEIF Hero.Weapon1.Reach > 0 THEN
  695.             SetTextColor PrintArea, &HFFF000F0, 0
  696.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon1.Name) + "!"
  697.         ELSE
  698.             out$ = ""
  699.         END IF
  700.         IF out$ <> "" THEN PrintOut PrintArea, out$
  701.         IF Hero.Weapon2.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  702.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  703.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  704.             totalroll = 0
  705.             DO
  706.                 roll = INT(RND * 20) + 1
  707.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  708.                 IF roll = 20 THEN totalroll = totalroll + 20
  709.                 totalroll = totalroll + roll
  710.             LOOP UNTIL roll <> 1 AND roll <> 20
  711.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  712.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  713.             out$ = _TRIM$(Hero.Name)
  714.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  715.                 SetTextColor PrintArea, &HFFF000F0, 0
  716.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  717.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  718.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  719.                 Hero.Life.Low = Hero.Life.Low - damage
  720.             ELSEIF totalroll < Chancetohit THEN
  721.                 SetTextColor PrintArea, &HFFF0F000, 0
  722.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  723.             ELSEIF totalroll > Chancetohit + 20 THEN
  724.                 SetTextColor PrintArea, &HFF00FF00, 0
  725.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  726.                 damage = damage * (totalroll / 20 + 1)
  727.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  728.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  729.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  730.             ELSEIF totalroll >= Chancetohit THEN
  731.                 SetTextColor PrintArea, &HFF00FF00, 0
  732.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  733.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  734.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  735.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  736.             END IF
  737.         ELSEIF Hero.Weapon2.Reach > 0 THEN
  738.             SetTextColor PrintArea, &HFFF000F0, 0
  739.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon2.Name) + "!"
  740.         ELSE
  741.             out$ = ""
  742.         END IF
  743.         IF out$ <> "" THEN PrintOut PrintArea, out$
  744.         IF Encounter(AtWhom).Life <= 0 THEN MonsterDied (AtWhom) 'the monster died!
  745.     ELSE 'it's a monster attacking
  746.         M = Encounter(Who).Index
  747.         IF Monster(M).Weapon1.Reach >= Distance(Encounter(Who).X, Encounter(Who).Y) THEN 'it's a weapon and not an utility object being held.
  748.             Chancetohit = BaseChancetohit + Monster(M).Weapon1.HitBonus 'add in the weapon's hit bonus
  749.             Chancetohit = Chancetohit - Hero.Armor.PD 'subtract the hero's armor/ natural dodge
  750.             totalroll = 0
  751.             DO
  752.                 roll = INT(RND * 20) + 1
  753.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  754.                 IF roll = 20 THEN totalroll = totalroll + 20
  755.                 totalroll = totalroll + roll
  756.             LOOP UNTIL roll <> 1 AND roll <> 20
  757.             damage = INT(RND * (Monster(M).Weapon1.Damage.High - Monster(M).Weapon1.Damage.Low + 1)) + Monster(M).Weapon1.Damage.Low 'random damage for the hit
  758.             damage = damage + Monster(M).Weapon1.DamageBonus 'add in the weapon's damage bonus
  759.             out$ = _TRIM$(Monster(M).Name)
  760.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  761.                 SetTextColor PrintArea, &HFFF000F0, 0
  762.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  763.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  764.                 Monster(M).Life.Low = Monster(M).Life.Low - damage
  765.             ELSEIF totalroll < Chancetohit THEN
  766.                 SetTextColor PrintArea, &HFFF0F000, 0
  767.                 out$ = out$ + " missed " + _TRIM$(Hero.Name) + ", with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  768.             ELSEIF totalroll > Chancetohit + 20 THEN
  769.                 SetTextColor PrintArea, &HFF00FFFF, 0
  770.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Hero.Name) + " for"
  771.                 damage = damage * (totalroll / 20 + 1)
  772.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  773.                 Hero.Life.Low = Hero.Life.Low - damage
  774.             ELSEIF totalroll >= Chancetohit THEN
  775.                 SetTextColor PrintArea, &HFF00FFFF, 0
  776.                 out$ = out$ + " hit " + _TRIM$(Hero.Name) + " for"
  777.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "."
  778.                 Hero.Life.Low = Hero.Life.Low - damage
  779.             END IF
  780.         ELSEIF Monster(M).Weapon1.Reach > 0 THEN
  781.             SetTextColor PrintArea, &HFFF000F0, 0
  782.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Monster(M).Weapon2.Name) + "!"
  783.         ELSE
  784.             out$ = ""
  785.         END IF
  786.         IF out$ <> "" THEN PrintOut PrintArea, out$
  787.     END IF
  788.  
  789. SUB MonsterDied (Who)
  790.     M = Encounter(Who).Index
  791.     SetTextColor PrintArea, &HFFFF0000, 0
  792.     out$ = _TRIM$(Monster(M).Name) + " died!  You earned " + _TRIM$(STR$(Monster(M).Level + Monster(M).ExpBonus)) + " experience."
  793.     PrintOut PrintArea, out$
  794.     Encounter(Who).Active = 0
  795.     Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  796.     MapArray(Encounter(Who).X, Encounter(Who).Y) = MapArray(Encounter(Who).X, Encounter(Who).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  797.     IF Monster(M).Found = 0 THEN
  798.         Monster(M).Found = -1 'it's a first time kill!
  799.         SetTextColor PrintArea, &HFFFFFF00, &HFFFF0000
  800.         out$ = "Congratulations!  You killed a " + _TRIM$(Monster(M).Name) + " for the first time!"
  801.         PrintOut PrintArea, out$
  802.     END IF
  803.  
  804.     'monster loot!!
  805.     IF UBOUND(Encounter) <= EncounterLimit THEN REDIM _PRESERVE Encounter(EncounterLimit + 100) AS Encounter_TYPE 'make certain our array is large enough to hold all the loot on the map.
  806.     'if the player starts dumping items onto the ground, we could concievably fill the screen with tons of loots.
  807.     R# = RND * 100
  808.     IF R# < Monster(M).Loot1.Chance THEN
  809.         GOSUB addloot
  810.         Encounter(E).Type = Monster(M).Loot1.Type
  811.         Encounter(E).Index = Monster(M).Loot1.Index
  812.     ELSEIF R# < Monster(M).Loot2.Chance THEN
  813.         GOSUB addloot
  814.     ELSEIF R# < Monster(M).loot3.Chance THEN
  815.         GOSUB addloot
  816.     END IF
  817.  
  818.     EXIT SUB
  819.  
  820.     addloot: 'a small sub proceedure to reduce copy/paste code
  821.     EncounterLimit = EncounterLimit + 1
  822.     E = EncounterLimit 'just for ease of typing below
  823.     Encounter(E).Active = -1
  824.     Encounter(E).X = Encounter(Who).X
  825.     Encounter(E).Y = Encounter(Who).Y
  826.     PrintOut PrintArea, _TRIM$(Monster(M).Name) + " left something behind."
  827.     RETURN
  828.  
  829.  
  830. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  831.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  832.     D = Distance(MX, MY) 'distance from monster to the hero
  833.     E = Encounter(i).Index 'the actual monster in question
  834.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  835.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  836.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  837.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  838.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  839.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  840.             MoveMonster = -1
  841.         END IF
  842.     END IF
  843.  
  844.  
  845.  
  846. SUB MonstersTurn
  847.     FOR i = 1 TO EncounterLimit
  848.         IF Encounter(i).Active AND (Encounter(i).Type = 0) THEN 'Only if it's a monster, and the monster is still alive and active do we need to actually do anything else.
  849.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  850.             D = Distance(MX, MY) 'distance from monster to the hero
  851.             E = Encounter(i).Index 'the actual monster in question
  852.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  853.                 attack = 0
  854.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  855.                     'insert attack code here
  856.                     Swing i, 0
  857.                     _CONTINUE
  858.                 END IF
  859.                 'if the monster didn't attack, it can now move towards the hero.
  860.                 IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  861.                     IF D > Distance(MX - 1, MY) THEN
  862.                         IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  863.                     END IF
  864.                 END IF
  865.                 IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  866.                     IF D > Distance(MX, MY - 1) THEN
  867.                         IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  868.                     END IF
  869.                 END IF
  870.                 IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  871.                     IF D > Distance(MX + 1, MY) THEN
  872.                         IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  873.                     END IF
  874.                 END IF
  875.                 IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  876.                     IF D > Distance(MX, MY + 1) THEN
  877.                         IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  878.                     END IF
  879.                 END IF
  880.             END IF
  881.         END IF
  882.     NEXT
  883.  
  884.  
  885.  
  886. SUB DrawMap
  887.     _DEST WorkScreen
  888.     CLS
  889.     'LINE (0, 0)-(3200, 2400), &HFF000000, BF 'clear the map
  890.     IF Hero.Weapon1.Light.Reach > Hero.Weapon2.Light.Reach THEN LightReach = Hero.Weapon1.Light.Reach ELSE LightReach = Hero.Weapon2.Light.Reach
  891.     FOR Y = 0 TO YH
  892.         FOR X = 0 TO XH
  893.             IF Distance(X, Y) <= LightReach THEN 'It's close enough to check for illumination
  894.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  895.             END IF
  896.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  897.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  898.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF303030, BF
  899.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (6 * 32, 18 * 32)-STEP(31, 31)
  900.                 END IF
  901.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  902.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (36 * 32, 13 * 32)-STEP(31, 31)
  903.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF707070, BF
  904.                 END IF
  905.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  906.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (4 * 32, 45 * 32)-STEP(31, 31)
  907.                 END IF
  908.             END IF
  909.             'note: highlighting for the light should come AFTER the map is drawn
  910.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  911.                 LINE (X * 32, Y * 32)-STEP(32, 32), &H40FFFF00, BF
  912.                 MapArray(X, Y) = MapArray(X, Y) - 1
  913.                 FOR I = 1 TO EncounterLimit
  914.                     IF X = Encounter(I).X AND Y = Encounter(I).Y AND Encounter(I).Active = -1 THEN
  915.                         E = Encounter(I).Index
  916.                         T = Encounter(I).Type
  917.                         SELECT CASE T
  918.                             CASE 0 'it's a monster
  919.                                 IF Monster(E).Found THEN
  920.                                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Monster(E).IconX, Monster(E).IconY)-STEP(31, 31)
  921.                                 ELSE
  922.                                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (1984, 1504)-STEP(31, 31)
  923.                                 END IF
  924.                             CASE 1 'weapon
  925.                                 _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Weapon(E).IconX, Weapon(E).IconY)-STEP(31, 31)
  926.                             CASE 2 'armor
  927.                                 _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Armor(E).IconX, Armor(E).IconY)-STEP(31, 31)
  928.                             CASE 3 'food
  929.                                 _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Food(E).IconX, Food(E).IconY)-STEP(31, 31)
  930.                             CASE 4 'item
  931.                         END SELECT
  932.                     END IF
  933.                 NEXT
  934.  
  935.             END IF
  936.         NEXT
  937.     NEXT
  938.     COLOR &HFFFFFF00, 0 'Yellow Hero
  939.     _PUTIMAGE (Hero.X * 32, Hero.Y * 32)-STEP(32, 32), Icons, WorkScreen, (2016, 1504)-STEP(31, 31)
  940.     XOffset## = 1600 / Scale
  941.     YOffset## = 1200 / Scale
  942.     CenterX = Hero.X * 32 'convert hero coordinate to grid coordinate
  943.     CenterY = Hero.Y * 32
  944.     _DEST DisplayScreen
  945.     LINE (0, 0)-(800, 600), &HFF000000, BF 'clear the map
  946.     _PUTIMAGE (0, 0)-(800, 600), WorkScreen, DisplayScreen, (CenterX - XOffset##, CenterY - YOffset##)-(CenterX + XOffset##, CenterY + YOffset##)
  947.  
  948.  
  949.  
  950.  
  951.  
  952. SUB CreateMap (XLimit, YLimit, Rooms)
  953.     ERASE MapArray 'clear the old map and reset everything to 0
  954.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  955.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  956.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  957.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  958.  
  959.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  960.  
  961.     StairRoom = INT(RND * Rooms) + 1
  962.     FOR i = 1 TO Rooms
  963.         DO
  964.             RoomSize = INT(RND * 9) + 2
  965.             RoomX = INT(RND * (XLimit - RoomSize))
  966.             RoomY = INT(RND * (YLimit - RoomSize))
  967.             'test for positioning
  968.             good = -1 'it's good starting out
  969.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  970.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  971.             NEXT X, Y
  972.         LOOP UNTIL good
  973.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  974.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  975.         NEXT X, Y
  976.         RoomCenterX(i) = RoomX + .5 * RoomSize
  977.         RoomCenterY(i) = RoomY + .5 * RoomSize
  978.         IF i = 1 THEN 'place the hero in the first room  (which can be anywhere randomly on our map)
  979.             Hero.X = RoomX + INT(RND * RoomSize)
  980.             Hero.Y = RoomY + INT(RND * RoomSize)
  981.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  982.         END IF
  983.         IF i = StairRoom THEN 'place the stairs in one of the random rooms
  984.             DO 'But lets not place the stairs directly on top of the hero to begin with
  985.                 StairX = RoomX + INT(RND * RoomSize)
  986.                 StairY = RoomY + INT(RND * RoomSize)
  987.             LOOP UNTIL StairX <> Hero.X AND StairY <> Hero.Y
  988.             MapArray(StairX, StairY) = MapArray(StairX, StairY) OR 16
  989.         END IF
  990.     NEXT
  991.     FOR i = 1 TO Rooms - 1
  992.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  993.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  994.         DO UNTIL StartX = EndX AND StartY = EndY
  995.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  996.             Meander = 10
  997.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  998.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  999.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  1000.                     Ychange = 0
  1001.                 ELSE
  1002.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  1003.                     Ychange = 0
  1004.                 END IF
  1005.             ELSE
  1006.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  1007.                     Ychange = SGN(EndY - StartY)
  1008.                     XChange = 0
  1009.                 ELSE
  1010.                     Ychange = INT(RND * 3) - 1
  1011.                     XChange = 0
  1012.                 END IF
  1013.             END IF
  1014.             StartX = StartX + XChange
  1015.             StartY = StartY + Ychange
  1016.             IF StartX < 0 THEN StartX = 0 'Make certain we move inside the bounds of our map dimensions
  1017.             IF StartY < 0 THEN StartY = 0
  1018.             IF StartX > XH THEN StartX = XH
  1019.             IF StartY > YH THEN StartY = YH
  1020.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8 'place a path where we moved to
  1021.         LOOP
  1022.     NEXT
  1023.     PathFind
  1024.     EncounterLimit = INT(RND * 6) + 5
  1025.     FOR i = 1 TO EncounterLimit
  1026.         Encounter(i).Type = 0 'type 0 is a monster
  1027.         Encounter(i).Index = RandomMonster
  1028.         Encounter(i).Active = -1
  1029.         M = Encounter(i).Index
  1030.         Encounter(i).Life = INT(RND * Monster(M).Life.High - Monster(M).Life.Low + 1) + Monster(M).Life.Low
  1031.         valid = -1: EndlessLoopExit = 0
  1032.         DO
  1033.             EndlessLoopExit = EndlessLoopExit + 1
  1034.             Encounter(i).X = INT(RND * XLimit + 1)
  1035.             Encounter(i).Y = INT(RND * YLimit + 1)
  1036.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  1037.             IF EndlessLoopExit = 1000 THEN EXIT DO 'if we can't place the monster in a room after 1000 tries, just place it wherever and call it a "wandering monster".
  1038.             'Of course, "wandering monsters" may end up inside a wall, in which case they simply become "lost monsters" and do nothing to affect the level.  It's the same as if they never existed at all.
  1039.             'BUT, we *should* generally be able to place a monster after 1000 tries.  This segment is just in the off-chance that the Random Number Gods are out to get us and to prevent any chance for an endless loop.
  1040.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  1041.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  1042.     NEXT
  1043.     LootLimit = 0 'no loot on the map at this time.  Too bad for joo!
  1044.  
  1045. SUB PathFind
  1046.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  1047.     DIM pass AS _UNSIGNED _BYTE
  1048.     m = _MEM(Distance()): m1 = _MEM(Temp())
  1049.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  1050.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  1051.     Temp(Hero.X, Hero.Y) = 0
  1052.     pass = 0
  1053.     DO
  1054.         changed = 0
  1055.         y = 0
  1056.         DO
  1057.             x = 0
  1058.             DO
  1059.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  1060.                     IF x < XH THEN
  1061.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  1062.                     END IF
  1063.                     IF x > 0 THEN
  1064.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  1065.                     END IF
  1066.                     IF y < YH THEN
  1067.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  1068.                     END IF
  1069.                     IF y > 0 THEN
  1070.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  1071.                     END IF
  1072.                 END IF
  1073.                 x = x + 1
  1074.             LOOP UNTIL x > XH
  1075.             y = y + 1
  1076.         LOOP UNTIL y > YH
  1077.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  1078.         pass = pass + 1
  1079.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  1080.     Distance(Hero.X, Hero.Y) = 0
  1081.  
  1082. FUNCTION RandomMonster
  1083.     SELECT CASE Level 'the level we're on
  1084.         CASE 1 TO 3: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  1085.         CASE ELSE: MC = 3 'since there's only 3 whole monsters in our monster database at the moment, don't expect to find a ton of them to choose from yet!
  1086.     END SELECT
  1087.     RandomMonster = INT(RND * MC) + 1
  1088.  
  1089.  
  1090. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  1091. '# SUBroutines and FUNCTIONs below #'
  1092. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  1093. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  1094.     u = UBOUND(TextHandles)
  1095.     Handle = WhichHandle
  1096.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1097.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1098.     Where = TextHandles(Handle).VerticalAlignment
  1099.     How = TextHandles(Handle).Justification
  1100.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  1101.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  1102.  
  1103.  
  1104. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  1105.     'WhichHandle is the handle which designates which text area we want to use
  1106.     'Where is where we want it to go in that text area
  1107.     '  -- Online prints the text to the current print position line in that text area.
  1108.     '  -- CenterLine centers the text to the center of that text area.
  1109.     '  -- any other value will print to that line positon in that particular box.
  1110.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  1111.     'What is the text that we want to print in our text area
  1112.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  1113.  
  1114.     D = _DEST: S = _SOURCE
  1115.     OldFont = _FONT
  1116.  
  1117.     u = UBOUND(TextHandles)
  1118.     Handle = WhichHandle
  1119.  
  1120.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1121.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1122.     IF TextHandles(Handle).HideFrame THEN
  1123.         _DEST TextHandles(Handle).SavedBackground
  1124.         _SOURCE TextHandles(Handle).SavedBackground
  1125.     END IF
  1126.     _FONT TextHandles(Handle).Font
  1127.     fh = _FONTHEIGHT: pw = _PRINTWIDTH(What)
  1128.     IF _FONTWIDTH = 0 THEN
  1129.         FOR i = 1 TO 255
  1130.             IF _PRINTWIDTH(CHR$(i)) > fw THEN fw = _PRINTWIDTH(CHR$(i))
  1131.         NEXT
  1132.     ELSE
  1133.         fw = _FONTWIDTH
  1134.     END IF
  1135.  
  1136.     h = TextHandles(Handle).h - 4: w = TextHandles(Handle).w - 4
  1137.  
  1138.     SELECT CASE Where
  1139.         CASE BottomLine
  1140.             y = h \ fh
  1141.         CASE OnLine
  1142.             y = TextHandles(Handle).Ypos
  1143.             IF y = 0 THEN y = 1
  1144.         CASE CenterLine
  1145.             linesused = 0
  1146.             tpw = pw: tw = w: tWhat$ = What
  1147.             DO UNTIL tpw <= tw
  1148.                 textallowed = WordBreak(LEFT$(tWhat$, w \ fw))
  1149.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  1150.                 linesused = linesused + 1
  1151.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  1152.                 tpw = _PRINTWIDTH(tWhat$)
  1153.             LOOP
  1154.             linesused = linesused + 1
  1155.             py = (h - linesused * fh) \ 2
  1156.             y = py \ fh + 1
  1157.             IF y < 1 THEN y = 1
  1158.         CASE ELSE
  1159.             y = Where
  1160.     END SELECT
  1161.  
  1162.     'IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  1163.     blend = _BLEND
  1164.  
  1165.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  1166.         'first let's get a temp image handle for the existing area of the screen.
  1167.         x1 = TextHandles(Handle).x1 + 2
  1168.         y1 = TextHandles(Handle).y1 + 2
  1169.         x2 = TextHandles(Handle).x1 + w
  1170.         y2 = TextHandles(Handle).y1 + h
  1171.         nh = y2 - y1 + 1 - fh
  1172.         nw = x2 - x1 + 1
  1173.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  1174.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  1175.         DrawTextArea Handle
  1176.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  1177.         y = y - 1
  1178.     LOOP
  1179.  
  1180.     IF blend THEN _BLEND
  1181.  
  1182.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  1183.  
  1184.     SELECT CASE How
  1185.         CASE LeftJustify
  1186.             x = 0
  1187.             IF pw > w THEN
  1188.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  1189.                 text$ = RTRIM$(LEFT$(What, textallowed))
  1190.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  1191.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  1192.             ELSE
  1193.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1194.                 finished = -1
  1195.             END IF
  1196.         CASE CenterJustify
  1197.             IF pw > w THEN
  1198.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  1199.                 text$ = RTRIM$(LEFT$(What, textallowed))
  1200.                 x = (w - _PRINTWIDTH(text$)) \ 2
  1201.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  1202.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  1203.             ELSE
  1204.                 x = (w - pw) \ 2
  1205.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1206.                 finished = -1
  1207.             END IF
  1208.         CASE RightJustify
  1209.             IF pw > w THEN
  1210.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  1211.                 text$ = RTRIM$(LEFT$(What, textallowed))
  1212.                 x = w - _PRINTWIDTH(text$)
  1213.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  1214.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  1215.             ELSE
  1216.                 x = w - pw
  1217.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1218.                 finished = -1
  1219.             END IF
  1220.         CASE NoJustify
  1221.             x = TextHandles(Handle).Xpos
  1222.             firstlinelimit = (w - x) \ fw 'the limit of characters on the first line
  1223.             IF LEN(What) > firstlinelimit THEN
  1224.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  1225.                 text$ = RTRIM$(LEFT$(What, textallowed))
  1226.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  1227.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  1228.             ELSE
  1229.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1230.                 finished = -1
  1231.             END IF
  1232.     END SELECT
  1233.     IF finished THEN
  1234.         SELECT CASE TextHandles(Handle).UpdateMethod
  1235.             CASE NoUpdate 'We don't update the position at all.
  1236.             CASE DoUpdate
  1237.                 TextHandles(Handle).Xpos = x + pw
  1238.                 TextHandles(Handle).Ypos = y
  1239.             CASE NewLine
  1240.                 TextHandles(Handle).Ypos = y + 1
  1241.                 TextHandles(Handle).Xpos = 1
  1242.         END SELECT
  1243.         _FONT OldFont
  1244.         _DEST D: _SOURCE S
  1245.         COLOR FG, BG
  1246.     END IF
  1247.  
  1248. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  1249.     u = UBOUND(TextHandles)
  1250.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1251.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1252.     TextHandles(Handle).TextColor = Foreground
  1253.  
  1254.  
  1255. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  1256.     u = UBOUND(TextHandles)
  1257.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1258.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1259.     TextHandles(Handle).TextBackgroundColor = Background
  1260.  
  1261. SUB SetTextFont (Handle AS INTEGER, FontName AS STRING, FontSize AS INTEGER)
  1262.     u = UBOUND(TextHandles)
  1263.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1264.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1265.     SELECT CASE TextHandles(Handle).Font
  1266.         CASE 8, 9, 14, 15, 16, 17 'In built QB64 fonts.  We don't need to free them.
  1267.         CASE IS > 1
  1268.             'we have the font already in use
  1269.             'REMOVE THIS CONDITION IF NECESSARY, AND MANUALLY FREE/RELEASE FONTS AS ABLE!!!
  1270.             _FREEFONT TextHandles(Handle).Font 'if it's in use elsewhere, this *WILL* toss an error.
  1271.     END SELECT
  1272.  
  1273.     temp = _LOADFONT(FontName, FontSize, "MONOSPACE")
  1274.     IF temp > 1 THEN
  1275.         TextHandles(Handle).Font = temp
  1276.     ELSE
  1277.         TextHandles(Handle).Font = 16 'default to font 16, in case
  1278.     END IF
  1279.  
  1280.  
  1281. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  1282.     u = UBOUND(TextHandles)
  1283.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1284.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1285.     TextHandles(Handle).TextColor = Foreground
  1286.     TextHandles(Handle).TextBackgroundColor = Background
  1287.  
  1288.  
  1289. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  1290.     u = UBOUND(TextHandles)
  1291.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1292.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1293.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  1294.     TextHandles(Handle).UpdateMethod = Method
  1295.  
  1296.  
  1297. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  1298.     u = UBOUND(TextHandles)
  1299.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1300.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1301.     SELECT CASE Y
  1302.         CASE BottomLine
  1303.             TextHandles(Handle).VerticalAlignment = -2
  1304.         CASE CenterLine
  1305.             TextHandles(Handle).VerticalAlignment = -1
  1306.         CASE ELSE
  1307.             TextHandles(Handle).VerticalAlignment = 0
  1308.     END SELECT
  1309.     IF X < 1 AND X > -4 THEN
  1310.         TextHandles(Handle).Justification = X
  1311.     ELSE
  1312.         TextHandles(Handle).Xpos = X
  1313.     END IF
  1314.     IF Y < 1 THEN EXIT SUB
  1315.     TextHandles(Handle).Ypos = Y
  1316.  
  1317. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  1318.     u = UBOUND(TextHandles)
  1319.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1320.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1321.     IF X < 1 AND X > -4 THEN
  1322.         TextHandles(Handle).Justification = X
  1323.     ELSE
  1324.         TextHandles(Handle).Xpos = X
  1325.     END IF
  1326.  
  1327. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  1328.     u = UBOUND(TextHandles)
  1329.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1330.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1331.     SELECT CASE Y
  1332.         CASE BottomLine
  1333.             TextHandles(Handle).VerticalAlignment = -2
  1334.         CASE CenterLine
  1335.             TextHandles(Handle).VerticalAlignment = -1
  1336.         CASE ELSE
  1337.             TextHandles(Handle).VerticalAlignment = 0
  1338.     END SELECT
  1339.     IF Y < 1 THEN EXIT SUB
  1340.     TextHandles(Handle).Ypos = Y
  1341.  
  1342.  
  1343. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  1344.     u = UBOUND(TextHandles)
  1345.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1346.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1347.     GetPrintPositionY = TextHandles(Handle).Ypos
  1348.  
  1349. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  1350.     u = UBOUND(TextHandles)
  1351.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1352.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1353.     GetPrintPositionX = TextHandles(Handle).Xpos
  1354.  
  1355.  
  1356.  
  1357. FUNCTION WordBreak (text$)
  1358.     CONST Breaks = " ;,.?!-"
  1359.     FOR i = LEN(text$) TO 0 STEP -1
  1360.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  1361.         loopcount = loopcount + 1
  1362.     NEXT
  1363.     IF i = 0 THEN i = LEN(text$)
  1364.     WordBreak = i
  1365.  
  1366.  
  1367.  
  1368. SUB ClearTextArea (Handle AS INTEGER)
  1369.     u = UBOUND(TextHandles)
  1370.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1371.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1372.     IF TextHandles(Handle).SavedBackground THEN
  1373.         w = TextHandles(Handle).w
  1374.         h = TextHandles(Handle).h
  1375.         x1 = TextHandles(Handle).ScreenX
  1376.         y1 = TextHandles(Handle).ScreenY
  1377.         x2 = x1 + w - 1
  1378.         y2 = y1 + h - 1
  1379.         blend = _BLEND
  1380.         _DONTBLEND
  1381.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1382.         IF blend THEN _BLEND
  1383.     END IF
  1384.     DrawTextArea Handle
  1385.  
  1386.  
  1387.  
  1388. SUB DrawTextArea (Handle AS INTEGER)
  1389.     u = UBOUND(TextHandles)
  1390.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1391.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1392.     w = TextHandles(Handle).w
  1393.     h = TextHandles(Handle).h
  1394.     x1 = TextHandles(Handle).ScreenX
  1395.     y1 = TextHandles(Handle).ScreenY
  1396.     x2 = x1 + w - 1
  1397.     y2 = y1 + h - 1
  1398.  
  1399.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  1400.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  1401.  
  1402.  
  1403.  
  1404. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  1405.     u = UBOUND(TextHandles)
  1406.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1407.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1408.     TextHandles(Handle).FrameColor = FrameColor
  1409.     TextHandles(Handle).BackColor = BackColor
  1410.  
  1411.  
  1412.  
  1413. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  1414.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  1415.     x2 = tx2: y2 = ty2
  1416.     IF x1 > x2 THEN SWAP x1, x2
  1417.     IF y1 > y2 THEN SWAP y1, y2
  1418.     w = x2 - x1 + 1
  1419.     h = y2 - y1 + 1
  1420.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  1421.     'Error checking for if the user sends coordinates which are off the screen
  1422.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  1423.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  1424.  
  1425.     u = UBOUND(TextHandles)
  1426.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  1427.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  1428.     NEXT
  1429.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  1430.         Handle = u + 1
  1431.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  1432.     END IF
  1433.     TextHandles(Handle).x1 = x1
  1434.     TextHandles(Handle).y1 = y1
  1435.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  1436.     TextHandles(Handle).InUse = True
  1437.     TextHandles(Handle).Xpos = 0
  1438.     TextHandles(Handle).Ypos = 1
  1439.     TextHandles(Handle).UpdateMethod = NewLine
  1440.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  1441.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  1442.  
  1443.     IF SaveBackground THEN
  1444.         imagehandle = _NEWIMAGE(w, h, 32)
  1445.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1446.         TextHandles(Handle).SavedBackground = imagehandle
  1447.     END IF
  1448.     TextHandles(Handle).ScreenX = x1
  1449.     TextHandles(Handle).ScreenY = y1
  1450.     TextHandles(Handle).Font = 16 'default to font 16
  1451.     NewTextArea% = Handle
  1452.  
  1453. SUB FreeTextArea (Handle AS INTEGER)
  1454.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  1455.         IF TextHandles(Handle).InUse THEN
  1456.             TextHandles(Handle).InUse = False
  1457.             IF TextHandles(Handle).SavedBackground THEN
  1458.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  1459.                     w = TextHandles(Handle).w
  1460.                     h = TextHandles(Handle).h
  1461.                     x1 = TextHandles(Handle).ScreenX
  1462.                     y1 = TextHandles(Handle).ScreenY
  1463.                     x2 = x1 + w - 1
  1464.                     y2 = y1 + h - 1
  1465.                     blend = _BLEND
  1466.                     _DONTBLEND
  1467.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1468.                     IF blend THEN _BLEND
  1469.                 END IF
  1470.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  1471.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  1472.             END IF
  1473.         ELSE
  1474.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  1475.         END IF
  1476.     ELSE
  1477.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  1478.     END IF
  1479.  
  1480. SUB HideFrame (Handle AS INTEGER)
  1481.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  1482.         TextHandles(Handle).HideFrame = -1
  1483.         w = TextHandles(Handle).w
  1484.         h = TextHandles(Handle).h
  1485.         x1 = TextHandles(Handle).ScreenX
  1486.         y1 = TextHandles(Handle).ScreenY
  1487.         x2 = x1 + w - 1
  1488.         y2 = y1 + h - 1
  1489.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1490.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1491.         IF TextHandles(Handle).SavedBackground THEN
  1492.             blend = _BLEND
  1493.             _DONTBLEND
  1494.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1495.             _FREEIMAGE TextHandles(Handle).SavedBackground
  1496.             IF blend THEN _BLEND
  1497.         END IF
  1498.         TextHandles(Handle).SavedBackground = imagehandle
  1499.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  1500.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  1501.     END IF
  1502.  
  1503. SUB RestoreFrame (Handle AS INTEGER)
  1504.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  1505.         TextHandles(Handle).HideFrame = 0
  1506.         w = TextHandles(Handle).w
  1507.         h = TextHandles(Handle).h
  1508.         x1 = TextHandles(Handle).ScreenX
  1509.         y1 = TextHandles(Handle).ScreenY
  1510.         x2 = x1 + w - 1
  1511.         y2 = y1 + h - 1
  1512.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1513.         blend = _BLEND
  1514.         _DONTBLEND
  1515.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1516.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  1517.         _FREEIMAGE TextHandles(Handle).SavedBackground
  1518.         IF blend THEN _BLEND
  1519.         TextHandles(Handle).SavedBackground = imagehandle
  1520.         TextHandles(Handle).x1 = x1 'When the frames are restored, we need to recalculate our print position
  1521.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  1522.     END IF
  1523.  
  1524. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  1525.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  1526.     u = UBOUND(TextHandles)
  1527.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1528.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1529.     HideFrame Handle
  1530.     TextHandles(Handle).ScreenX = x1
  1531.     TextHandles(Handle).ScreenY = y1
  1532.     RestoreFrame Handle

One step at a time to develop this thing, I suppose.  :P

A screenshot of the inventory system is attached below, and I think folks should find it simple enough to interact with that it won't need much explanation. Left/Right arrows keys swap between the tabs up top.  Up/Down arrow keys select an item.  Shift-Left/Shift-Right will change pages (characters can have up to 104 of each category type in their inventory, at the moment).

Now, as I slowly add various drops to the monsters in the game, and implement loot to be found, I'll add to my list of available actions and commands, and the game should get ever more complex and vibrant as I go.  ;D
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on September 16, 2019, 02:45:28 am
And we can now start picking up the food that the monsters drop!  (Which was actually one of the most complicated little additions to the game engine which I've worked on so far!)

Code: QB64: [Select]
  1. DEFLNG A-Z 'default to long instead of single
  2. TYPE TextArea
  3.     InUse AS INTEGER
  4.     x1 AS LONG 'left
  5.     y1 AS LONG 'top
  6.     w AS LONG 'width
  7.     h AS LONG 'height
  8.     FrameColor AS _UNSIGNED LONG
  9.     BackColor AS _UNSIGNED LONG
  10.     Xpos AS INTEGER
  11.     Ypos AS INTEGER
  12.     VerticalAlignment AS INTEGER
  13.     Justification AS INTEGER
  14.     UpdateMethod AS INTEGER
  15.     TextColor AS _UNSIGNED LONG
  16.     TextBackgroundColor AS _UNSIGNED LONG
  17.     SavedBackground AS INTEGER
  18.     HideFrame AS INTEGER
  19.     ScreenX AS INTEGER
  20.     ScreenY AS INTEGER
  21.     Font AS LONG 'NEW! Change fonts for each independent font area
  22.  
  23. REDIM SHARED TextHandles(0) AS TextArea
  24.  
  25. CONST True = -1, False = 0
  26. CONST LeftJustify = -1, CenterJustify = -2, RightJustify = -3, NoJustify = 0
  27. CONST OnLine = 0, CenterLine = -1, TopLine = 1, BottomLine = -2
  28. CONST NoUpdate = 0, DoUpdate = 1, NewLine = 2
  29. '********************************************************
  30. '* Text Frames before this line
  31. '********************************************************
  32.  
  33.  
  34.  
  35.  
  36.  
  37. _CONSOLE ON 'for debugging purposes while making/testing things
  38.  
  39. TYPE Damage_Type
  40.     Low AS INTEGER
  41.     High AS INTEGER
  42.  
  43. TYPE Light_Type
  44.     Reach AS INTEGER
  45.     Left AS INTEGER
  46.  
  47. TYPE Weapon_Type
  48.     Identified AS _UNSIGNED _BYTE
  49.     Name AS STRING * 20
  50.     DisplayedName AS STRING * 20
  51.     Reach AS INTEGER
  52.     Damage AS Damage_Type
  53.     HitBonus AS INTEGER
  54.     DamageBonus AS INTEGER
  55.     Left AS INTEGER 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  56.     IconX AS LONG
  57.     IconY AS LONG
  58.     Light AS Light_Type
  59.  
  60. TYPE Armor_Type
  61.     Identified AS _UNSIGNED _BYTE
  62.     Name AS STRING * 20
  63.     DisplayedName AS STRING * 20
  64.     PD AS INTEGER 'Passive Defense (dodge)
  65.     DR AS INTEGER 'Damage Resistance (absorption)
  66.     Left AS INTEGER 'life left on the weapon, AKA "durability", but easier and cheaper to spell
  67.     IconX AS LONG
  68.     IconY AS LONG
  69.  
  70. TYPE Food_Type
  71.     Count AS INTEGER 'food is stackable
  72.     Identified AS _UNSIGNED _BYTE
  73.     Name AS STRING * 20
  74.     DisplayedName AS STRING * 20
  75.     HungerFill AS SINGLE 'how much said food fills one's stomach
  76.     HungerRate AS SINGLE 'how fast it digests before we get hunry again
  77.     IconX AS LONG
  78.     IconY AS LONG
  79.  
  80.  
  81. TYPE Hero_Type
  82.     Name AS STRING * 20
  83.     Life AS Damage_Type
  84.     Mana AS Damage_Type
  85.     Level AS _UNSIGNED _BYTE
  86.     EXP_Earned AS LONG
  87.     EXP_Needed AS LONG
  88.     Light AS Light_Type
  89.     Weapon1 AS Weapon_Type
  90.     Weapon2 AS Weapon_Type
  91.     Armor AS Armor_Type
  92.     HealingRate AS INTEGER 'number of turns before the hero heals a point
  93.     Hunger AS SINGLE
  94.     HungerRate AS SINGLE
  95.  
  96. TYPE Treasure_TYPE
  97.     Chance AS SINGLE
  98.     Type AS _UNSIGNED _BYTE '1 weapon, 2 armor, 3 food, 4 item
  99.     Index AS INTEGER 'the number of the type... Weapon(0) = "Bare Fist", so tyoe = 1, index = 0.
  100.  
  101. TYPE Monster_TYPE
  102.     Name AS STRING * 20
  103.     Life AS Damage_Type
  104.     Level AS INTEGER
  105.     ExpBonus AS INTEGER
  106.     Sight AS INTEGER
  107.     Hearing AS INTEGER
  108.     Detection AS INTEGER 'in case it has some sort of magic "sixth sense" to detect characters, not related to sight nor sound.
  109.     Weapon1 AS Weapon_Type
  110.     Weapon2 AS Weapon_Type
  111.     Armor AS Armor_Type
  112.     Found AS INTEGER
  113.     IconX AS LONG
  114.     IconY AS LONG
  115.     Loot1 AS Treasure_TYPE
  116.     Loot2 AS Treasure_TYPE
  117.     loot3 AS Treasure_TYPE
  118.  
  119. TYPE Encounter_TYPE
  120.     Active AS INTEGER
  121.     X AS INTEGER
  122.     Y AS INTEGER
  123.     Type AS INTEGER '0 monster, 1 weapon, 2 armor, 3 food, 4 item
  124.     Index AS INTEGER
  125.     Life AS INTEGER
  126.  
  127. TYPE Inventory_Type
  128.     Weapon AS Weapon_Type
  129.     Armor AS Armor_Type
  130.     Food AS Food_Type
  131.     'Item as item_type 'to come later, once general items get added
  132.  
  133.  
  134. REDIM SHARED Monster(100) AS Monster_TYPE
  135. REDIM SHARED Encounter(1000) AS Encounter_TYPE, EncounterLimit AS INTEGER
  136.  
  137. 'These are all base items and weapons.  The sets which the character actually carries will vary significantly from wear/tear, enchantments, and other in-game factors.
  138. REDIM SHARED Weapon(100) AS Weapon_Type
  139. REDIM SHARED Armor(100) AS Armor_Type
  140. REDIM SHARED Food(100) AS Food_Type
  141.  
  142. DIM SHARED Hero AS Hero_Type
  143. REDIM SHARED Inventory(103) AS Inventory_Type '103 slots each to carry weapons/armor/food/items
  144. DIM SHARED WeaponsInGame AS INTEGER, ArmorInGame AS INTEGER, FoodInGame AS INTEGER, ItemsInGame AS INTEGER
  145. DIM SHARED WeaponsOwned AS _UNSIGNED _BYTE, ArmorOwned AS _UNSIGNED _BYTE, FoodOwned AS _UNSIGNED _BYTE, ItemsOwned AS _UNSIGNED _BYTE 'a count of each which we own.  Should always be a value between 0 and 103.
  146. DIM SHARED Level AS _UNSIGNED _BYTE: Level = 1
  147. DIM SHARED XL, XH, YL, YH 'the map X/Y low/high array limits.
  148. DIM SHARED PrintArea AS LONG 'the handle to our text frame print area for game results.
  149. DIM SHARED Scale AS _FLOAT, WorkScreen AS LONG, DisplayScreen AS LONG, Icons AS LONG
  150. DIM SHARED TextFont AS LONG, StepsTaken AS _UNSIGNED _INTEGER64
  151.  
  152. WorkScreen = _NEWIMAGE(3200, 2400, 32)
  153. DisplayScreen = _NEWIMAGE(800, 700, 32)
  154. SCREEN DisplayScreen
  155. Scale = 2
  156.  
  157. REDIM SHARED MapArray(0, 0) AS _UNSIGNED _BYTE
  158. '1 map is illuminated
  159. '2 map is uncovered
  160. '4 map is a wall
  161. '8 map is a pathway
  162. '16 map is a stairway
  163. '32 map is simply blocked (perhaps with a monster?)
  164. '64 map is secret (can not be uncovered)
  165.  
  166. REDIM SHARED Distance(0, 0) AS _UNSIGNED _BYTE
  167.  
  168.  
  169. Init
  170. CreateMap 99, 74, 10
  171.     DrawMap
  172.     DisplayCharacter
  173.     _DISPLAY
  174.     GetInput
  175.     MonstersTurn
  176.     CheckForHeroGrowth
  177.  
  178. SUB Init
  179.     D = _DEST
  180.     Hero.Name = "Steve The Tester!"
  181.     Hero.Life.Low = 10: Hero.Life.High = 10: Hero.Level = 1
  182.     Hero.Mana.Low = 10: Hero.Mana.High = 10
  183.     Hero.EXP_Earned = 0: Hero.EXP_Needed = 2
  184.     Hero.Weapon1.Name = "Bare Fist"
  185.     Hero.Weapon1.Reach = 1: Hero.Weapon1.Damage.Low = 1: Hero.Weapon1.Damage.High = 2
  186.     Hero.Weapon1.HitBonus = 0: Hero.Weapon1.DamageBonus = 0
  187.     Hero.Weapon1.Left = -1 'your fist is indestructible!
  188.     Hero.Weapon1.IconX = 2 * 32: Hero.Weapon1.IconY = 47 * 32
  189.     Hero.Weapon2.Name = "Magic Candle"
  190.     Hero.Weapon2.Reach = 0: Hero.Weapon2.Damage.Low = 0: Hero.Weapon2.Damage.High = 0
  191.     Hero.Weapon2.HitBonus = 0: Hero.Weapon2.DamageBonus = 0
  192.     Hero.Weapon2.Left = 0 'you can't attack with a candle
  193.     Hero.Weapon2.IconX = 52 * 32: Hero.Weapon2.IconY = 42 * 32
  194.     Hero.Weapon2.Light.Reach = 2: Hero.Weapon2.Light.Left = -1 'infinite
  195.     Hero.Armor.Name = "Naked"
  196.     Hero.Armor.PD = 0: Hero.Armor.DR = 0: Hero.Armor.Left = -1 'you might be naked, but at least you can't break your armor!
  197.     Hero.Armor.IconX = 46 * 32: Hero.Armor.IconY = 42 * 32
  198.     Hero.HealingRate = 20 'the hero heals 1 point of health for every 20 valid turns naturally
  199.     Hero.HungerRate = 0.1 'Let's start heros out with a full belly and a low hunger growth rate
  200.  
  201.     PrintArea = NewTextArea(230, 601, 799, 699, False)
  202.     ColorTextArea PrintArea, _RGB32(255, 255, 255), _RGB32(0, 0, 128)
  203.     SetTextFont PrintArea, "courbd.ttf", 24
  204.     DrawTextArea PrintArea
  205.     SetPrintPositionX PrintArea, CenterJustify
  206.     SetPrintUpdate PrintArea, NewLine
  207.     PrintOut PrintArea, "WELCOME TO (almost) ROGUE"
  208.     SetTextFont PrintArea, "courbd.ttf", 18
  209.     PrintOut PrintArea, "created by STEVE!"
  210.     PrintOut PrintArea, ""
  211.     SetPrintPositionX PrintArea, LeftJustify
  212.     SetTextFont PrintArea, "courbd.ttf", 12
  213.     TextFont = 12
  214.  
  215.     temp = _NEWIMAGE(480, 480, 32)
  216.     _DEST temp: _CONTROLCHR OFF
  217.     COLOR &HFFFFFF00, 0 'Yellow Hero
  218.     _PRINTSTRING (0, 0), CHR$(1) 'the hero
  219.     COLOR &HFFFF0000, 0 'Red Question Mark
  220.     _PRINTSTRING (16, 0), "?" 'a question mark
  221.     Icons = _LOADIMAGE("Sprites.png", 32)
  222.     _PUTIMAGE (2016, 1504)-STEP(32, 32), temp, Icons, (0, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  223.     _PUTIMAGE (1984, 1504)-STEP(32, 32), temp, Icons, (16, 0)-STEP(_FONTWIDTH, _FONTHEIGHT)
  224.     '        SCREEN Icons: DO: SLEEP: LOOP
  225.     _DEST D
  226.  
  227.     'Init some basic weapons which we can find
  228.  
  229.     'These first two should always be available for us to use/enjoy.
  230.     WeaponOwned = 1: ArmorOwned = 1: FoodOwned = 1
  231.     Weapon(0).Name = "Bare Fist": Weapon(0).Identified = -1
  232.     Weapon(0).Reach = 1: Weapon(0).Damage.Low = 1: Weapon(0).Damage.High = 2
  233.     Weapon(0).HitBonus = 0: Weapon(0).DamageBonus = 0
  234.     Weapon(0).Left = -1 'your fist is indestructible!
  235.     Weapon(0).IconX = 2 * 32: Weapon(0).IconY = 47 * 32
  236.     Weapon(0).Light.Reach = 0: Weapon(0).Light.Left = 0
  237.     Weapon(1).Name = "Magic Candle": Weapon(1).Identified = -1
  238.     Weapon(1).Reach = 0: Weapon(1).Damage.Low = 0: Weapon(1).Damage.High = 0
  239.     Weapon(1).HitBonus = 0: Weapon(1).DamageBonus = 0
  240.     Weapon(1).Left = 0 'you can't attack with a candle
  241.     Weapon(1).IconX = 52 * 32: Weapon(1).IconY = 42 * 32
  242.     Weapon(1).Light.Reach = 2: Weapon(1).Light.Left = -1 'infinite
  243.  
  244.     Armor(0).Name = "Naked": Armor(0).Identified = -1
  245.     Armor(0).PD = 0: Armor(0).DR = 0: Armor(0).Left = -1 'you might be naked, but at least you can't break your armor!
  246.     Armor(0).IconX = 46 * 32: Armor(0).IconY = 42 * 32
  247.  
  248.     Food(0).Name = "Nothing": Food(0).Identified = -1
  249.     Food(0).HungerFill = 10: Food(0).HungerRate = 0
  250.     Food(0).IconX = 0: Food(0).IconY = 0
  251.  
  252.     Food(1).Name = "Bat Meat"
  253.     Food(1).HungerFill = 10: Food(1).HungerRate = .1
  254.     Food(1).IconX = 36 * 32: Food(1).IconY = 23 * 32
  255.  
  256.     Food(2).Name = "Rat Meat"
  257.     Food(2).HungerFill = 10: Food(2).HungerRate = .1
  258.     Food(2).IconX = 36 * 32: Food(2).IconY = 23 * 32
  259.  
  260.     Food(3).Name = "Snake Meat"
  261.     Food(3).HungerFill = 10: Food(3).HungerRate = .1
  262.     Food(3).IconX = 36 * 32: Food(3).IconY = 23 * 32
  263.  
  264.     WeaponsOwned = 1: ArmorOwnded = 0: FoodOwned = 0 'You start the game with a magic candle and nothing else.
  265.     Inventory(0).Weapon = Weapon(0): Inventory(1).Weapon = Weapon(1)
  266.     Inventory(0).Armor = Armor(0)
  267.     Inventory(0).Food = Food(0)
  268.  
  269.     WeaponsInGame = 1: ArmorInGame = 0: FoodInGame = 3
  270.     InitMonsters
  271.  
  272. SUB InitMonsters
  273.  
  274.     Monster(1).Name = "Bat": Monster(1).Life.Low = 1: Monster(1).Life.High = 4: Monster(1).Level = 1: Monster(1).ExpBonus = 0
  275.     Monster(1).Sight = 2: Monster(1).Hearing = 4: Monster(1).Detection = 0
  276.     Monster(1).Weapon1.Name = "Bite": Monster(1).Weapon1.Reach = 1
  277.     Monster(1).Weapon1.Damage.Low = 1: Monster(1).Weapon1.Damage.High = 2
  278.     Monster(1).IconX = 44 * 32: Monster(1).IconY = 3 * 32 'position 44,3 on the sprite sheet
  279.     Monster(1).Loot1.Chance = 95: Monster(1).Loot1.Type = 3: Monster(1).Loot1.Index = 1
  280.     'Monster(1).Weapon1.HitBonus = 0: Monster(1).Weapon1.DamageBonus = 0: Monster(1).Weapon1.Left = 0
  281.     'Monster(1).Weapon2.Name = "": Monster(1).Weapon2.Reach = 0
  282.     'Monster(1).Weapon2.Damage.Low = 0: Monster(1).Weapon2.Damage.High = 0
  283.     'Monster(1).Weapon2.HitBonus = 0: Monster(1).Weapon2.DamageBonus = 0: Monster(1).Weapon2.Left = 0
  284.     'Monster(1).Armor.Name = ""
  285.     'Monster(1).Armor.PD = 0: Monster(1).Armor.DR = 0: Monster(1).Armor.Left = 0
  286.     Monster(2).Name = "Rat": Monster(2).Life.Low = 1: Monster(2).Life.High = 4
  287.     Monster(2).Level = 1: Monster(2).ExpBonus = 0
  288.     Monster(2).Sight = 2: Monster(2).Hearing = 4: Monster(2).Detection = 0
  289.     Monster(2).Weapon1.Name = "Bite": Monster(2).Weapon1.Reach = 1
  290.     Monster(2).Weapon1.Damage.Low = 1: Monster(2).Weapon1.Damage.High = 2
  291.     Monster(2).IconX = 23 * 32: Monster(2).IconY = 4 * 32 'position 44,3 on the sprite sheet
  292.     Monster(2).Loot1.Chance = 25: Monster(2).Loot1.Type = 3: Monster(2).Loot1.Index = 2
  293.     Monster(3).Name = "Snake": Monster(3).Life.Low = 1: Monster(3).Life.High = 4
  294.     Monster(3).Level = 1: Monster(3).ExpBonus = 0
  295.     Monster(3).Sight = 2: Monster(3).Hearing = 4: Monster(3).Detection = 0
  296.     Monster(3).Weapon1.Name = "Bite": Monster(3).Weapon1.Reach = 1
  297.     Monster(3).Weapon1.Damage.Low = 1: Monster(3).Weapon1.Damage.High = 2
  298.     Monster(3).IconX = 37 * 32: Monster(3).IconY = 4 * 32 'position 44,3 on the sprite sheet
  299.     Monster(3).Loot1.Chance = 25: Monster(3).Loot1.Type = 3: Monster(3).Loot1.Index = 3
  300.  
  301.  
  302. SUB CheckForHeroGrowth
  303.     IF Hero.Life.Low < 1 THEN 'first, let's check to see if we died...
  304.         CLS
  305.         PRINT "YOU DIED!  HAHAHAHA!! (Better ending coming later...)"
  306.         _DISPLAY
  307.         BEEP
  308.         _DELAY 5
  309.         SYSTEM
  310.     END IF
  311.     IF Hero.EXP_Earned >= Hero.EXP_Needed THEN 'let's check to see if the hero has leveled up
  312.         PrintOut PrintArea, "Congratulations!  You have gained a level!"
  313.         DO
  314.             r = INT(RND * 6) + 1
  315.             lifegained = lifegained + r
  316.         LOOP UNTIL r <> 6
  317.         Hero.Life.Low = Hero.Life.Low + r
  318.         Hero.Life.High = Hero.Life.High + r
  319.         Hero.EXP_Earned = 0
  320.         Hero.Level = Hero.Level + 1
  321.         Hero.EXP_Needed = Hero.EXP_Needed + Hero.Level + 1
  322.     END IF
  323.     IF StepsTaken MOD Hero.HealingRate = 0 THEN 'heal the hero naturally over time
  324.         IF Hero.Life.Low < Hero.Life.High THEN Hero.Life.Low = Hero.Life.Low + 1
  325.     END IF
  326.     Hero.Hunger = Hero.Hunger + Hero.HungerRate
  327.     IF Hero.Weapon1.Light.Left > -1 THEN Hero.Weapon1.Light.Left = Hero.Weapon1.Light.Left - 1 'durability on our light sources wear down over time
  328.     IF Hero.Weapon2.Light.Left > -1 THEN Hero.Weapon2.Light.Left = Hero.Weapon2.Light.Left - 1
  329.  
  330.  
  331.  
  332. SUB DisplayCharacter
  333.     LINE (0, 601)-(229, 799), &HFF000000, BF
  334.     COLOR -1, 0
  335.     Box 0, 601, 229, 62, 0, 0, "", Silver, 0
  336.     Box 0, 601, 229, 12, Black, 0, _TRIM$(Hero.Name), Silver, 0
  337.     Box 0, 626, 229 * Hero.Life.Low / Hero.Life.High, 12, 0, 0, "", Red, Black
  338.     Box 0, 639, 229 * Hero.Mana.Low / Hero.Mana.High, 12, 0, 0, "", Blue, Black
  339.     Box 0, 652, 229 * Hero.EXP_Earned / Hero.EXP_Needed, 12, 0, 0, "", Green, Black
  340.     _PRINTSTRING (10, 616), "LEVEL:" + STR$(Hero.Level)
  341.     _PRINTSTRING (10, 629), "LIFE :" + STR$(Hero.Life.Low) + " / " + _TRIM$(STR$(Hero.Life.High))
  342.     _PRINTSTRING (10, 642), "MANA :" + STR$(Hero.Mana.Low) + " / " + _TRIM$(STR$(Hero.Mana.High))
  343.     _PRINTSTRING (10, 655), "EXP  :" + STR$(Hero.EXP_Earned) + " (" + _TRIM$(STR$(Hero.EXP_Needed)) + ")"
  344.     FOR i = 0 TO 5 'six boxes for information : left hand, right hand, armor, and 3 more for later....
  345.         Box 36 * i + 8, 665, 34, 34, 0, 0, "", Black, Silver
  346.     NEXT
  347.     _PUTIMAGE (9, 666)-STEP(32, 32), Icons, DisplayScreen, (Hero.Weapon1.IconX, Hero.Weapon1.IconY)-STEP(32, 32)
  348.     _PUTIMAGE (45, 666)-STEP(32, 32), Icons, DisplayScreen, (Hero.Weapon2.IconX, Hero.Weapon2.IconY)-STEP(32, 32)
  349.     _PUTIMAGE (81, 666)-STEP(32, 32), Icons, DisplayScreen, (Hero.Armor.IconX, Hero.Armor.IconY)-STEP(32, 32)
  350.  
  351. SUB ManageInventory
  352.     STATIC Header AS LONG, MainFont AS LONG, ItemNameFont AS LONG
  353.     STATIC Selection AS INTEGER, Page AS INTEGER, Item AS INTEGER
  354.     PCOPY 0, 1
  355.     D = _DEST: S = _SOURCE
  356.     OldFont = _FONT
  357.     IF Header = 0 THEN Header = _LOADFONT("courbd.ttf", 24, "monospace")
  358.     IF MainFont = 0 THEN MainFont = _LOADFONT("courbd.ttf", 14, "monospace")
  359.     IF ItemNameFont = 0 THEN ItemNameFont = _LOADFONT("courbd.ttf", 18, "monospace")
  360.     IF Selection = 0 THEN Selection = 1 'there's 4 categories which we can choose from (Weapon, Armor, Food, Item)
  361.     'Page = 0 'there's 4 pages of info for each category, from 0 to 3
  362.     'Item = 0 'and there's 26 items on each page, from 0 to 25.
  363.     DO
  364.         _LIMIT 30
  365.         valid = -1
  366.         LINE (50, 50)-STEP(700, 500), SkyBlue, BF 'erase the background
  367.         LINE (300, 106)-(725, 450), LightGray, BF
  368.         _FONT Header
  369.         SELECT CASE Selection 'redraw the selection we're working with
  370.             CASE 1
  371.                 Box 51, 51, 174, 50, Black, 0, "Weapons", SkyBlue, SkyBlue
  372.                 Box 225, 51, 174, 50, Black, 0, "Armors", LightGray, Black
  373.                 Box 400, 51, 174, 50, Black, 0, "Food", LightGray, Black
  374.                 Box 575, 51, 174, 50, Black, 0, "Items", LightGray, Black
  375.                 _FONT MainFont
  376.                 FOR i = 26 * Page TO 26 * Page + 25
  377.                     out$ = CHR$(65 + i MOD 26) + ")" + Inventory(i).Weapon.Name
  378.                     _PRINTSTRING (56, 106 + _FONTHEIGHT * (i MOD 26)), out$
  379.                 NEXT
  380.                 S = Item MOD 26
  381.                 Box 54 + _FONTWIDTH * 2, 106 + _FONTHEIGHT * Item, _FONTWIDTH * 20 + 4, _FONTHEIGHT, 0, 0, "", LightGray, Black
  382.                 out$ = CHR$(65 + S) + ")" + Inventory(Item).Weapon.Name
  383.                 _PRINTSTRING (56, 106 + _FONTHEIGHT * S), out$
  384.  
  385.                 COLOR Black, 0
  386.                 _FONT ItemNameFont
  387.                 IF Inventory(Item).Weapon.Identified THEN
  388.                     Box 300, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Weapon.Name), 0, 0
  389.                     _FONT 8
  390.                     _PRINTSTRING (305, 145), "Reach  :" + STR$(Inventory(Item).Weapon.Reach)
  391.                     _PRINTSTRING (305, 165), "Min Dmg:" + STR$(Inventory(Item).Weapon.Damage.Low)
  392.                     _PRINTSTRING (305, 175), "Max Dmg:" + STR$(Inventory(Item).Weapon.Damage.High)
  393.                     _PRINTSTRING (305, 185), "Hit Mod:" + STR$(Inventory(Item).Weapon.HitBonus)
  394.                     _PRINTSTRING (305, 195), "Dmg Mod:" + STR$(Inventory(Item).Weapon.DamageBonus)
  395.                     _PRINTSTRING (305, 205), "Light  :" + STR$(Inventory(Item).Weapon.Light.Reach)
  396.                 ELSE
  397.                     Box 350, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Weapon.DisplayedName), 0, 0
  398.                     _FONT 8
  399.                     _PRINTSTRING (305, 145), "Reach  : ???"
  400.                     _PRINTSTRING (305, 165), "Min Dmg: ???"
  401.                     _PRINTSTRING (305, 175), "Max Dmg: ???"
  402.                     _PRINTSTRING (305, 185), "Hit Mod: ???"
  403.                     _PRINTSTRING (305, 195), "Dmg Mod: ???"
  404.                     _PRINTSTRING (305, 205), "Light  : ???"
  405.                 END IF
  406.                 out$ = "Cond   : "
  407.                 SELECT CASE Inventory(Item).Weapon.Left 'the durability left on a weapon
  408.                     CASE -1: out$ = out$ + "Ind."
  409.                     CASE 0: out$ = out$ + "Broken"
  410.                     CASE IS < 26: out$ = out$ + "Worn"
  411.                     CASE IS < 50: out$ = out$ + "Used"
  412.                     CASE IS < 75: out$ = out$ + "Good"
  413.                     CASE IS < 90: out$ = out$ + "Mint"
  414.                     CASE ELSE: out$ = out$ + "New"
  415.                 END SELECT
  416.                 _PRINTSTRING (305, 155), out$
  417.                 LINE (422, 134)-STEP(130, 130), Black, BF
  418.                 _PUTIMAGE (423, 135)-STEP(128, 128), Icons, DisplayScreen, (Inventory(Item).Weapon.IconX, Inventory(Item).Weapon.IconY)-STEP(31, 31)
  419.             CASE 2
  420.                 Box 225, 51, 174, 50, Black, 0, "Armors", SkyBlue, SkyBlue
  421.                 Box 51, 51, 174, 50, Black, 0, "Weapons", LightGray, Black
  422.                 Box 400, 51, 174, 50, Black, 0, "Food", LightGray, Black
  423.                 Box 575, 51, 174, 50, Black, 0, "Items", LightGray, Black
  424.                 _FONT MainFont
  425.                 FOR i = 0 TO 25
  426.                     out$ = CHR$(65 + i) + ")" + Inventory(i).Armor.Name
  427.                     _PRINTSTRING (56, 106 + _FONTHEIGHT * i), out$
  428.                 NEXT
  429.                 Box 54 + _FONTWIDTH * 2, 106 + _FONTHEIGHT * Item, _FONTWIDTH * 20 + 4, _FONTHEIGHT, 0, 0, "", LightGray, Black
  430.                 out$ = CHR$(65 + Item MOD 26) + ")" + Inventory(Item).Armor.Name
  431.                 _PRINTSTRING (56, 106 + _FONTHEIGHT * (Item MOD 26)), out$
  432.  
  433.                 COLOR Black, 0
  434.                 _FONT ItemNameFont
  435.                 IF Inventory(Item).Armor.Identified THEN
  436.                     Box 300, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Armor.Name), 0, 0
  437.                     _FONT 8
  438.                     _PRINTSTRING (305, 145), "PD   :" + STR$(Inventory(Item).Armor.PD)
  439.                     _PRINTSTRING (305, 155), "DR   :" + STR$(Inventory(Item).Armor.DR)
  440.                 ELSE
  441.                     Box 350, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Armor.DisplayedName), 0, 0
  442.                     _FONT 8
  443.                     _PRINTSTRING (305, 145), "PD   : ???"
  444.                     _PRINTSTRING (305, 155), "DR   : ???"
  445.                 END IF
  446.                 out$ = "Cond : "
  447.                 SELECT CASE Inventory(Item).Armor.Left 'the durability left on armor
  448.                     CASE -1: out$ = out$ + "Ind."
  449.                     CASE 0: out$ = out$ + "Broken"
  450.                     CASE IS < 26: out$ = out$ + "Worn"
  451.                     CASE IS < 50: out$ = out$ + "Used"
  452.                     CASE IS < 75: out$ = out$ + "Good"
  453.                     CASE IS < 90: out$ = out$ + "Mint"
  454.                     CASE ELSE: out$ = out$ + "New"
  455.                 END SELECT
  456.                 _PRINTSTRING (305, 175), out$
  457.                 LINE (422, 134)-STEP(130, 130), Black, BF
  458.                 _PUTIMAGE (423, 135)-STEP(128, 128), Icons, DisplayScreen, (Inventory(Item).Armor.IconX, Inventory(Item).Armor.IconY)-STEP(31, 31)
  459.             CASE 3
  460.                 Box 400, 51, 174, 50, Black, 0, "Food", SkyBlue, SkyBlue
  461.                 Box 51, 51, 174, 50, Black, 0, "Weapons", LightGray, Black
  462.                 Box 225, 51, 174, 50, Black, 0, "Armors", LightGray, Black
  463.                 Box 575, 51, 174, 50, Black, 0, "Items", LightGray, Black
  464.                 _FONT MainFont
  465.                 FOR i = 0 TO 25
  466.                     IF Inventory(i).Food.Identified THEN
  467.                         out$ = CHR$(65 + i MOD 26) + ")" + Inventory(i).Food.Name
  468.                     ELSE
  469.                         out$ = CHR$(65 + i MOD 26) + ")" + Inventory(i).Food.DisplayedName
  470.                     END IF
  471.                     _PRINTSTRING (56, 106 + _FONTHEIGHT * i), out$
  472.                 NEXT
  473.                 Box 54 + _FONTWIDTH * 2, 106 + _FONTHEIGHT * Item, _FONTWIDTH * 20 + 4, _FONTHEIGHT, 0, 0, "", LightGray, Black
  474.  
  475.                 IF Inventory(Item).Food.Identified THEN
  476.                     out$ = CHR$(65 + Item MOD 26) + ")" + Inventory(Item).Food.Name
  477.                 ELSE
  478.                     out$ = CHR$(65 + Item MOD 26) + ")" + Inventory(Item).Food.DisplayedName
  479.                 END IF
  480.                 IF Inventory(Item).Food.Count > 1 THEN out$ = out$ + "(" + _TRIM$(STR$(Inventory(Item).Food.Count)) + ")"
  481.                 _PRINTSTRING (56, 106 + _FONTHEIGHT * (Item MOD 26)), out$
  482.  
  483.                 COLOR Black, 0
  484.                 _FONT ItemNameFont
  485.                 IF Inventory(Item).Food.Identified THEN
  486.                     Box 300, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Food.Name), 0, 0
  487.                 ELSE
  488.                     Box 350, 110, 375, 28, Black, 0, _TRIM$(Inventory(Item).Food.DisplayedName), 0, 0
  489.                 END IF
  490.                 LINE (422, 134)-STEP(130, 130), Black, BF
  491.                 _PUTIMAGE (423, 135)-STEP(128, 128), Icons, DisplayScreen, (Inventory(Item).Food.IconX, Inventory(Item).Food.IconY)-STEP(31, 31)
  492.             CASE 4
  493.                 Box 575, 51, 174, 50, Black, 0, "Items", SkyBlue, SkyBlue
  494.                 Box 51, 51, 174, 50, Black, 0, "Weapons", LightGray, Black
  495.                 Box 225, 51, 174, 50, Black, 0, "Armors", LightGray, Black
  496.                 Box 400, 51, 174, 50, Black, 0, "Food", LightGray, Black
  497.                 _FONT MainFont
  498.                 FOR i = 0 TO 25
  499.                     out$ = CHR$(65 + i) + ")" '+ Inventory(i).Item.Name 'not defined yet
  500.                     _PRINTSTRING (56, 106 + _FONTHEIGHT * i), out$
  501.                 NEXT
  502.                 Box 54 + _FONTWIDTH * 2, 106 + _FONTHEIGHT * Item, _FONTWIDTH * 20 + 4, _FONTHEIGHT, 0, 0, "", LightGray, Black
  503.                 out$ = CHR$(65 + Item MOD 26) + ")" '+ Inventory(Item).Item.Name
  504.                 _PRINTSTRING (56, 106 + _FONTHEIGHT * (Item MOD 26)), out$
  505.         END SELECT
  506.         _FONT MainFont
  507.         Box 55, 485, 95, 50, Black, 0, "Page" + STR$(Page + 1), 0, 0
  508.         Box 170, 485, 95, 50, Black, 0, "(D)rop", LightGray, Black
  509.         Box 285, 485, 95, 50, Black, 0, "(E)quip", LightGray, Black
  510.         Box 410, 485, 95, 50, Black, 0, "(U)se", LightGray, Black
  511.         Box 525, 485, 95, 50, Black, 0, "nothing", LightGray, Black
  512.         Box 640, 485, 95, 50, Black, 0, "(C)lose", LightGray, Black
  513.         k = _KEYHIT
  514.         SELECT CASE k
  515.             CASE 18432 'up
  516.                 Item = Item - 1
  517.                 IF Item < 0 THEN Item = 25
  518.             CASE 19200 'left
  519.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  520.                     Page = Page - 1
  521.                     IF Page < 0 THEN Page = 3
  522.                 ELSE
  523.                     Selection = Selection - 1
  524.                     IF Selection < 1 THEN Selection = 4
  525.                 END IF
  526.             CASE 20480 'down
  527.                 Item = Item + 1
  528.                 IF Item > 25 THEN Item = 0
  529.             CASE 19712 'right
  530.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  531.                     Page = Page + 1
  532.                     IF Page > 3 THEN Page = 0
  533.                 ELSE
  534.                     Selection = Selection + 1
  535.                     IF Selection > 4 THEN Selection = 1
  536.                 END IF
  537.             CASE ASC("C"), ASC("c"), 27
  538.                 EXIT DO
  539.             CASE ELSE
  540.                 valid = 0
  541.         END SELECT
  542.         _DISPLAY
  543.         PCOPY 1, 0
  544.     LOOP
  545.  
  546.     _FONT OldFont
  547.     _DEST D: _SOURCE S
  548.  
  549.  
  550.  
  551.  
  552. SUB GetInput
  553.     DO
  554.         k = _KEYHIT: valid = -1
  555.         SELECT CASE k
  556.             CASE 18432 'up
  557.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  558.                     SELECT CASE Scale
  559.                         CASE 1.5: Scale = 2 'It's as small as we go
  560.                         CASE 2: Scale = 3
  561.                         CASE 3: Scale = 4
  562.                         CASE 4: Scale = 6
  563.                         CASE 6: Scale = 8
  564.                         CASE 8: Scale = 12
  565.                     END SELECT
  566.                 ELSE
  567.                     IF Hero.Y > YL THEN MoveHero 0, -1 'if we can move up
  568.                 END IF
  569.             CASE 19200: 'left
  570.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  571.                     TextFont = TextFont - 1
  572.                     IF TextFont < 8 THEN TextFont = 8
  573.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  574.                     ClearTextArea PrintArea
  575.                     SetPrintPosition PrintArea, 1, 1
  576.                     PrintOut PrintArea, "Font Size Changed"
  577.                 ELSE
  578.                     IF Hero.X > XL THEN MoveHero -1, 0 'if we can move left
  579.                 END IF
  580.             CASE 20480: 'down
  581.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  582.                     SELECT CASE Scale
  583.                         CASE 2: Scale = 1.5 'It's as small as we go
  584.                         CASE 3: Scale = 2
  585.                         CASE 4: Scale = 3
  586.                         CASE 6: Scale = 4
  587.                         CASE 8: Scale = 6
  588.                         CASE 12: Scale = 8
  589.                     END SELECT
  590.                 ELSE
  591.                     IF Hero.Y < YH THEN MoveHero 0, 1 'if we can move down
  592.                 END IF
  593.             CASE 19712: 'right
  594.                 IF _KEYDOWN(100303) OR _KEYDOWN(100304) THEN 'shift arrow
  595.                     TextFont = TextFont + 1
  596.                     IF TextFont > 48 THEN TextFont = 48
  597.                     SetTextFont PrintArea, "courbd.ttf", TextFont
  598.                     ClearTextArea PrintArea
  599.                     SetPrintPosition PrintArea, 1, 1
  600.                     PrintOut PrintArea, "Font Size Changed"
  601.                 ELSE
  602.                     IF Hero.X < XH THEN MoveHero 1, 0 'if we can move right
  603.                 END IF
  604.             CASE 27 'escape key
  605.                 SYSTEM
  606.             CASE 32 'space to just wait and skip a turn
  607.             CASE 60 ' "<" key
  608.                 IF MapArray(Hero.X, Hero.Y) AND 16 THEN
  609.                     Level = Level + 1
  610.                     CreateMap 99, 74, 10
  611.                     PathFind
  612.                 END IF
  613.             CASE ASC("I"), ASC("i")
  614.                 ManageInventory
  615.             CASE ASC("P"), ASC("p")
  616.                 PickUpSomething
  617.             CASE ASC("+"), ASC("=")
  618.                 IF Hero.Weapon2.Light.Reach < 25 THEN Hero.Weapon2.Light.Reach = Hero.Weapon2.Light.Reach + 1
  619.             CASE ASC("-"), ASC("_")
  620.                 IF Hero.Weapon2.Light.Reach > -1 THEN Hero.Weapon2.Light.Reach = Hero.Weapon2.Light.Reach - 1
  621.             CASE ELSE
  622.                 valid = 0 'it's a key press which we don't recognize.  Ignore it
  623.         END SELECT
  624.         _LIMIT 60
  625.     LOOP UNTIL k AND valid
  626.     _KEYCLEAR 'one keystroke at a time
  627.     StepsTaken = StepsTaken + 1
  628.  
  629. SUB Box (X, Y, Wide, High, FontColor as _unsigned long, _
  630.          FontBackGround as _unsigned long, Caption AS STRING, Kolor AS _UNSIGNED LONG, BackGround AS _UNSIGNED LONG)
  631.     COLOR FontColor, FontBackGround
  632.     LINE (X, Y)-STEP(Wide, High), Kolor, BF
  633.     LINE (X, Y)-STEP(Wide, High), BackGround, B
  634.     pw = _PRINTWIDTH(Caption): ph = _FONTHEIGHT
  635.     _PRINTSTRING (X + (Wide - pw + 1) \ 2, Y + (High - ph + 1) \ 2), Caption
  636.     COLOR DC, BG
  637.  
  638.  
  639. SUB MoveHero (MoveX, MoveY)
  640.     TestX = Hero.X + MoveX: TestY = Hero.Y + MoveY
  641.     IF MapArray(TestX, TestY) AND (4 OR 8) THEN 'and it's a room or passageway
  642.         IF (MapArray(TestX, TestY) AND 32) = 0 THEN 'and it's not blocked for some reason
  643.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) AND NOT 32 'unblock where the hero is
  644.             IF MoveX THEN Hero.X = Hero.X + MoveX
  645.             IF MoveY THEN Hero.Y = Hero.Y + MoveY
  646.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'and block where the hero is now that he moved
  647.             PathFind
  648.         ELSE
  649.             'chances are it's blocked by a monster.  Since we're one step away from it, let's see which monster it is and attack it!
  650.             FOR i = 1 TO EncounterLimit
  651.                 IF Encounter(i).Active THEN 'Check for active/alive monsters only
  652.                     MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  653.                     IF MX = TestX AND MY = TestY THEN 'yep, we found our monster!
  654.                         Swing 0, i 'hero swings at the monster
  655.                     END IF
  656.                 END IF
  657.             NEXT
  658.         END IF
  659.     END IF
  660.  
  661. SUB Swing (Who, AtWhom)
  662.  
  663.     BaseChancetohit = 10 'base 10 chance to hit
  664.     IF Who = 0 THEN 'it's the hero attacking, add his attack bonuses
  665.         M = Encounter(AtWhom).Index
  666.         IF Hero.Weapon1.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  667.             Chancetohit = BaseChancetohit + Hero.Weapon1.HitBonus 'add in the weapon's hit bonus
  668.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  669.             totalroll = 0
  670.             DO
  671.                 roll = INT(RND * 20) + 1
  672.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  673.                 IF roll = 20 THEN totalroll = totalroll + 20
  674.                 totalroll = totalroll + roll
  675.             LOOP UNTIL roll <> 1 AND roll <> 20
  676.             damage = INT(RND * (Hero.Weapon1.Damage.High - Hero.Weapon1.Damage.Low + 1)) + Hero.Weapon1.Damage.Low 'random damage for the hit
  677.             damage = damage + Hero.Weapon1.DamageBonus 'add in the weapon's damage bonus
  678.             out$ = _TRIM$(Hero.Name)
  679.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  680.                 SetTextColor PrintArea, &HFFF000F0, 0
  681.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  682.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  683.                 Hero.Life.Low = Hero.Life.Low - damage
  684.             ELSEIF totalroll < Chancetohit THEN
  685.                 SetTextColor PrintArea, &HFFF0F000, 0
  686.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  687.             ELSEIF totalroll > Chancetohit + 20 THEN
  688.                 SetTextColor PrintArea, &HFF00FF00, 0
  689.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  690.                 damage = damage * (totalroll / 20 + 1)
  691.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "!"
  692.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  693.             ELSEIF totalroll >= Chancetohit THEN
  694.                 SetTextColor PrintArea, &HFF00FF00, 0
  695.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  696.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon1.Name) + "."
  697.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  698.             END IF
  699.         ELSEIF Hero.Weapon1.Reach > 0 THEN
  700.             SetTextColor PrintArea, &HFFF000F0, 0
  701.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon1.Name) + "!"
  702.         ELSE
  703.             out$ = ""
  704.         END IF
  705.         IF out$ <> "" THEN PrintOut PrintArea, out$
  706.         IF Hero.Weapon2.Reach >= Distance(Encounter(AtWhom).X, Encounter(AtWhom).Y) THEN 'it's a weapon and not an utility object being held.
  707.             Chancetohit = BaseChancetohit + Hero.Weapon2.HitBonus 'add in the weapon's hit bonus
  708.             Chancetohit = Chancetohit - Monster(AtWhom).Armor.PD 'subtract the monster's armor/ natural dodge
  709.             totalroll = 0
  710.             DO
  711.                 roll = INT(RND * 20) + 1
  712.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  713.                 IF roll = 20 THEN totalroll = totalroll + 20
  714.                 totalroll = totalroll + roll
  715.             LOOP UNTIL roll <> 1 AND roll <> 20
  716.             damage = INT(RND * (Hero.Weapon2.Damage.High - Hero.Weapon2.Damage.Low + 1)) + Hero.Weapon2.Damage.Low 'random damage for the hit
  717.             damage = damage + Hero.Weapon2.DamageBonus 'add in the weapon's damage bonus
  718.             out$ = _TRIM$(Hero.Name)
  719.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  720.                 SetTextColor PrintArea, &HFFF000F0, 0
  721.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  722.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  723.                 damage = damage - Hero.Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us
  724.                 Hero.Life.Low = Hero.Life.Low - damage
  725.             ELSEIF totalroll < Chancetohit THEN
  726.                 SetTextColor PrintArea, &HFFF0F000, 0
  727.                 out$ = out$ + " missed " + _TRIM$(Monster(M).Name) + ", with " + _TRIM$(Hero.Weapon1.Name) + "!"
  728.             ELSEIF totalroll > Chancetohit + 20 THEN
  729.                 SetTextColor PrintArea, &HFF00FF00, 0
  730.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Monster(M).Name) + " for"
  731.                 damage = damage * (totalroll / 20 + 1)
  732.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "!"
  733.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  734.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  735.             ELSEIF totalroll >= Chancetohit THEN
  736.                 SetTextColor PrintArea, &HFF00FF00, 0
  737.                 out$ = out$ + " hit " + _TRIM$(Monster(M).Name) + " for"
  738.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Hero.Weapon2.Name) + "."
  739.                 damage = damage - Monster(M).Armor.PD: IF damage < 0 THEN damage = 0 'armor absorbs some damage for us"
  740.                 Encounter(AtWhom).Life = Encounter(AtWhom).Life - damage
  741.             END IF
  742.         ELSEIF Hero.Weapon2.Reach > 0 THEN
  743.             SetTextColor PrintArea, &HFFF000F0, 0
  744.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Hero.Weapon2.Name) + "!"
  745.         ELSE
  746.             out$ = ""
  747.         END IF
  748.         IF out$ <> "" THEN PrintOut PrintArea, out$
  749.         IF Encounter(AtWhom).Life <= 0 THEN MonsterDied (AtWhom) 'the monster died!
  750.     ELSE 'it's a monster attacking
  751.         M = Encounter(Who).Index
  752.         IF Monster(M).Weapon1.Reach >= Distance(Encounter(Who).X, Encounter(Who).Y) THEN 'it's a weapon and not an utility object being held.
  753.             Chancetohit = BaseChancetohit + Monster(M).Weapon1.HitBonus 'add in the weapon's hit bonus
  754.             Chancetohit = Chancetohit - Hero.Armor.PD 'subtract the hero's armor/ natural dodge
  755.             totalroll = 0
  756.             DO
  757.                 roll = INT(RND * 20) + 1
  758.                 IF roll = 1 THEN totalroll = totalroll - 20 'critical failure
  759.                 IF roll = 20 THEN totalroll = totalroll + 20
  760.                 totalroll = totalroll + roll
  761.             LOOP UNTIL roll <> 1 AND roll <> 20
  762.             damage = INT(RND * (Monster(M).Weapon1.Damage.High - Monster(M).Weapon1.Damage.Low + 1)) + Monster(M).Weapon1.Damage.Low 'random damage for the hit
  763.             damage = damage + Monster(M).Weapon1.DamageBonus 'add in the weapon's damage bonus
  764.             out$ = _TRIM$(Monster(M).Name)
  765.             IF totalroll < Chancetohit - 20 THEN 'you critically failed!
  766.                 SetTextColor PrintArea, &HFFF000F0, 0
  767.                 out$ = out$ + " CRITICALLY FAILED attacking.  They hit themselves for"
  768.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  769.                 Monster(M).Life.Low = Monster(M).Life.Low - damage
  770.             ELSEIF totalroll < Chancetohit THEN
  771.                 SetTextColor PrintArea, &HFFF0F000, 0
  772.                 out$ = out$ + " missed " + _TRIM$(Hero.Name) + ", with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  773.             ELSEIF totalroll > Chancetohit + 20 THEN
  774.                 SetTextColor PrintArea, &HFF00FFFF, 0
  775.                 out$ = out$ + " CRITICALLY hit " + _TRIM$(Hero.Name) + " for"
  776.                 damage = damage * (totalroll / 20 + 1)
  777.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "!"
  778.                 Hero.Life.Low = Hero.Life.Low - damage
  779.             ELSEIF totalroll >= Chancetohit THEN
  780.                 SetTextColor PrintArea, &HFF00FFFF, 0
  781.                 out$ = out$ + " hit " + _TRIM$(Hero.Name) + " for"
  782.                 out$ = out$ + STR$(damage) + " damage, with " + _TRIM$(Monster(M).Weapon1.Name) + "."
  783.                 Hero.Life.Low = Hero.Life.Low - damage
  784.             END IF
  785.         ELSEIF Monster(M).Weapon1.Reach > 0 THEN
  786.             SetTextColor PrintArea, &HFFF000F0, 0
  787.             out$ = _TRIM$(Monster(M).Name) + " is too far away to attack with a " + _TRIM$(Monster(M).Weapon2.Name) + "!"
  788.         ELSE
  789.             out$ = ""
  790.         END IF
  791.         IF out$ <> "" THEN PrintOut PrintArea, out$
  792.     END IF
  793.  
  794. SUB PickUpSomething
  795.     FOR i = 1 TO EncounterLimit
  796.         IF Encounter(i).Active THEN 'Check for active/alive monsters only
  797.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  798.             IF MX = Hero.X AND MY = Hero.Y THEN 'we found something active to interact with
  799.                 N = Encounter(i).Index 'for easy of typing
  800.                 SELECT CASE Encounter(i).Type
  801.                     CASE 0 'it's a monster
  802.                         PrintOut PrintArea, "WTF?! How the hell did you step on an active monster?  You found a game bug.  Please cry a lot for me!"
  803.                         EXIT SUB
  804.                     CASE 1 'it's a weapon
  805.                     CASE 2 'it's armor
  806.                     CASE 3 'it's food
  807.                         'first see if it matches any of our existing food stocks.
  808.                         FOR j = 0 TO FoodOwned
  809.                             IF Food(N).Name = Inventory(j).Food.Name THEN 'our food matches some existing
  810.                                 Inventory(j).Food.Count = Inventory(j).Food.Count + 1
  811.                                 Encounter(i).Active = 0
  812.                                 GOTO exitchecks 'no need to check any further; we just add to the existing count of that type
  813.                             END IF
  814.                             'At this point, we can assume it's not an existing food we're carrying.
  815.                             IF FoodOwned < 103 THEN 'then we have room for more food
  816.                                 FoodOwned = FoodOwned + 1
  817.                                 Inventory(FoodOwned).Food = Food(N)
  818.                                 out$ = "You picked up a " + _TRIM$(Food(N).DisplayedName) + "."
  819.                                 PrintOut PrintArea, out$
  820.                                 Encounter(i).Active = 0
  821.                                 GOTO exitchecks 'we have now added that food to our inventory.
  822.                             ELSE
  823.                                 out$ = "You try to pick up a " + _TRIM$(Food(N).DisplayedName) + ", but have no room to carry it."
  824.                                 PrintOut PrintArea, out$
  825.                                 GOTO exitchecks 'we don't have room to loot the item currently.
  826.                             END IF
  827.                         NEXT
  828.                     CASE 4 'its an item (which hasn't been added into the game yet)
  829.                 END SELECT
  830.             END IF
  831.         END IF
  832.         exitchecks:
  833.     NEXT
  834.  
  835.  
  836. SUB MonsterDied (Who)
  837.     M = Encounter(Who).Index
  838.     SetTextColor PrintArea, &HFFFF0000, 0
  839.     out$ = _TRIM$(Monster(M).Name) + " died!  You earned " + _TRIM$(STR$(Monster(M).Level + Monster(M).ExpBonus)) + " experience."
  840.     PrintOut PrintArea, out$
  841.     Encounter(Who).Active = 0
  842.     Hero.EXP_Earned = Hero.EXP_Earned + Monster(M).Level + Monster(M).ExpBonus
  843.     MapArray(Encounter(Who).X, Encounter(Who).Y) = MapArray(Encounter(Who).X, Encounter(Who).Y) AND NOT 32 'the way is no longer blocked once we kill the monster!
  844.     IF Monster(M).Found = 0 THEN
  845.         Monster(M).Found = -1 'it's a first time kill!
  846.         SetTextColor PrintArea, &HFFFFFF00, &HFFFF0000
  847.         out$ = "Congratulations!  You killed a " + _TRIM$(Monster(M).Name) + " for the first time!"
  848.         PrintOut PrintArea, out$
  849.     END IF
  850.  
  851.     'monster loot!!
  852.     R# = RND * 100
  853.     IF R# < Monster(M).Loot1.Chance THEN
  854.         GOSUB addloot
  855.  
  856.         Encounter(EncounterLimit).Type = Monster(M).Loot1.Type
  857.         IF Monster(M).Loot1.Type = 3 THEN 'Food can have various states when found
  858.             '(Other things will in time as well, but I haven't gotten to adding any them into the game yet.)
  859.             R1# = RND * 100
  860.             SELECT CASE R1#
  861.                 CASE IS < 10: cond$ = "Sickly "
  862.                 CASE IS < 25: cond$ = "Nasty "
  863.                 CASE IS < 75: cond$ = "Normal "
  864.                 CASE IS < 90: cond$ = "Good "
  865.                 CASE ELSE: cond$ = "Tasty "
  866.             END SELECT
  867.             DIM foodname AS STRING * 20
  868.             foodname = cond$ + _TRIM$(Food(Monster(M).Loot1.Index).Name)
  869.  
  870.             FOR i = 0 TO FoodInGame
  871.                 IF foodname = Food(i).Name THEN 'the food already exists
  872.                     Encounter(EncounterLimit).Index = i
  873.                     EXIT SUB
  874.                 END IF
  875.             NEXT
  876.             'At this point, we're adding a new food to our index
  877.             FoodInGame = FoodInGame + 1
  878.             F = FoodInGame
  879.             Food(F) = Food(Monster(M).Loot1.Index) 'take the base values of our food
  880.             Food(F).Name = foodname
  881.             foodname = "Unknown " + _TRIM$(Food(Monster(M).Loot1.Index).Name)
  882.             Food(F).DisplayedName = foodname
  883.             Encounter(EncounterLimit).Index = FoodInGame
  884.         ELSE
  885.             Encounter(EncounterLimit).Index = Monster(M).Loot1.Index
  886.         END IF
  887.  
  888.  
  889.  
  890.     ELSEIF R# < Monster(M).Loot2.Chance THEN
  891.         GOSUB addloot
  892.     ELSEIF R# < Monster(M).loot3.Chance THEN
  893.         GOSUB addloot
  894.     END IF
  895.  
  896.     EXIT SUB
  897.  
  898.     addloot: 'a small sub proceedure to reduce copy/paste code
  899.     EncounterLimit = EncounterLimit + 1
  900.     E = EncounterLimit 'just for ease of typing below
  901.     IF UBOUND(Encounter) < E THEN REDIM _PRESERVE Encounter(E + 100) AS Encounter_TYPE 'on the off chance that we need to increase our encounter limits for a floor
  902.     Encounter(E).Active = -1
  903.     Encounter(E).X = Encounter(Who).X
  904.     Encounter(E).Y = Encounter(Who).Y
  905.  
  906.  
  907.     PrintOut PrintArea, _TRIM$(Monster(M).Name) + " left something behind."
  908.     RETURN
  909.  
  910.  
  911. FUNCTION MoveMonster (Monster, MoveX, MoveY)
  912.     MX = Encounter(Monster).X: MY = Encounter(Monster).Y 'monster x, monster y position
  913.     D = Distance(MX, MY) 'distance from monster to the hero
  914.     E = Encounter(i).Index 'the actual monster in question
  915.     IF D > Distance(MX + MoveX, MY + MoveY) THEN
  916.         IF (MapArray(MX + MoveX, MY + MoveY) AND 32) = 0 THEN 'where we're trying to move isn't blocked
  917.             MapArray(MX, MY) = MapArray(MX, MY) AND NOT 32 'unblock where the monster is
  918.             Encounter(Monster).X = Encounter(Monster).X + MoveX
  919.             Encounter(Monster).Y = Encounter(Monster).Y + MoveY
  920.             MapArray(MX + MoveX, MY + MoveY) = MapArray(MX + MoveX, MY + MoveY) OR 32 'block where the monster moved to
  921.             MoveMonster = -1
  922.         END IF
  923.     END IF
  924.  
  925.  
  926.  
  927. SUB MonstersTurn
  928.     FOR i = 1 TO EncounterLimit
  929.         IF Encounter(i).Active AND (Encounter(i).Type = 0) THEN 'Only if it's a monster, and the monster is still alive and active do we need to actually do anything else.
  930.             MX = Encounter(i).X: MY = Encounter(i).Y 'monster x, monster y position
  931.             D = Distance(MX, MY) 'distance from monster to the hero
  932.             E = Encounter(i).Index 'the actual monster in question
  933.             IF D < Monster(E).Sight OR D <= Monster(E).Hearing OR D <= Monster(E).Detection THEN
  934.                 attack = 0
  935.                 IF D <= Monster(E).Weapon1.Reach THEN 'we're in reach for the monster to attack with their main hand.
  936.                     'insert attack code here
  937.                     Swing i, 0
  938.                     _CONTINUE
  939.                 END IF
  940.                 'if the monster didn't attack, it can now move towards the hero.
  941.                 IF MX > 0 THEN 'check to see if moving left moves us towards the hero.
  942.                     IF D > Distance(MX - 1, MY) THEN
  943.                         IF MoveMonster(i, -1, 0) THEN _CONTINUE 'move left
  944.                     END IF
  945.                 END IF
  946.                 IF MY > 0 THEN 'check to see if moving up moves us towards the hero.
  947.                     IF D > Distance(MX, MY - 1) THEN
  948.                         IF MoveMonster(i, 0, -1) THEN _CONTINUE 'move up
  949.                     END IF
  950.                 END IF
  951.                 IF MX < XH THEN 'check to see if moving right moves us towards the hero.
  952.                     IF D > Distance(MX + 1, MY) THEN
  953.                         IF MoveMonster(i, 1, 0) THEN _CONTINUE 'move right
  954.                     END IF
  955.                 END IF
  956.                 IF MY < YH THEN 'check to see if moving down moves us towards the hero.
  957.                     IF D > Distance(MX, MY + 1) THEN
  958.                         IF MoveMonster(i, 0, 1) THEN _CONTINUE 'move down
  959.                     END IF
  960.                 END IF
  961.             END IF
  962.         END IF
  963.     NEXT
  964.  
  965.  
  966.  
  967. SUB DrawMap
  968.     _DEST WorkScreen
  969.     CLS
  970.     'LINE (0, 0)-(3200, 2400), &HFF000000, BF 'clear the map
  971.     IF Hero.Weapon1.Light.Reach > Hero.Weapon2.Light.Reach THEN LightReach = Hero.Weapon1.Light.Reach ELSE LightReach = Hero.Weapon2.Light.Reach
  972.     FOR Y = 0 TO YH
  973.         FOR X = 0 TO XH
  974.             IF Distance(X, Y) <= LightReach THEN 'It's close enough to check for illumination
  975.                 IF MapArray(X, Y) <> 0 THEN MapArray(X, Y) = MapArray(X, Y) OR 1 OR 2
  976.             END IF
  977.             IF MapArray(X, Y) AND 2 THEN 'It's an uncovered part of the map, draw it
  978.                 IF MapArray(X, Y) AND 4 THEN 'it's a visible room
  979.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF303030, BF
  980.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (6 * 32, 18 * 32)-STEP(31, 31)
  981.                 END IF
  982.                 IF MapArray(X, Y) AND 8 THEN 'it's a visible path
  983.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (36 * 32, 13 * 32)-STEP(31, 31)
  984.                     '                    LINE (X * 32, Y * 32)-STEP(32, 32), &HFF707070, BF
  985.                 END IF
  986.                 IF MapArray(X, Y) AND 16 THEN 'it's the stairs to the next level
  987.                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (4 * 32, 45 * 32)-STEP(31, 31)
  988.                 END IF
  989.             END IF
  990.             'note: highlighting for the light should come AFTER the map is drawn
  991.             IF MapArray(X, Y) AND 1 THEN 'it's currently illuminated by the lightsource
  992.                 LINE (X * 32, Y * 32)-STEP(32, 32), &H40FFFF00, BF
  993.                 MapArray(X, Y) = MapArray(X, Y) - 1
  994.                 FOR I = 1 TO EncounterLimit
  995.                     IF X = Encounter(I).X AND Y = Encounter(I).Y AND Encounter(I).Active = -1 THEN
  996.                         E = Encounter(I).Index
  997.                         T = Encounter(I).Type
  998.                         SELECT CASE T
  999.                             CASE 0 'it's a monster
  1000.                                 IF Monster(E).Found THEN
  1001.                                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Monster(E).IconX, Monster(E).IconY)-STEP(31, 31)
  1002.                                 ELSE
  1003.                                     _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (1984, 1504)-STEP(31, 31)
  1004.                                 END IF
  1005.                             CASE 1 'weapon
  1006.                                 _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Weapon(E).IconX, Weapon(E).IconY)-STEP(31, 31)
  1007.                             CASE 2 'armor
  1008.                                 _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Armor(E).IconX, Armor(E).IconY)-STEP(31, 31)
  1009.                             CASE 3 'food
  1010.                                 _PUTIMAGE (X * 32, Y * 32)-STEP(32, 32), Icons, WorkScreen, (Food(E).IconX, Food(E).IconY)-STEP(31, 31)
  1011.                             CASE 4 'item
  1012.                         END SELECT
  1013.                     END IF
  1014.                 NEXT
  1015.  
  1016.             END IF
  1017.         NEXT
  1018.     NEXT
  1019.     COLOR &HFFFFFF00, 0 'Yellow Hero
  1020.     _PUTIMAGE (Hero.X * 32, Hero.Y * 32)-STEP(32, 32), Icons, WorkScreen, (2016, 1504)-STEP(31, 31)
  1021.     XOffset## = 1600 / Scale
  1022.     YOffset## = 1200 / Scale
  1023.     CenterX = Hero.X * 32 'convert hero coordinate to grid coordinate
  1024.     CenterY = Hero.Y * 32
  1025.     _DEST DisplayScreen
  1026.     LINE (0, 0)-(800, 600), &HFF000000, BF 'clear the map
  1027.     _PUTIMAGE (0, 0)-(800, 600), WorkScreen, DisplayScreen, (CenterX - XOffset##, CenterY - YOffset##)-(CenterX + XOffset##, CenterY + YOffset##)
  1028.  
  1029.  
  1030.  
  1031.  
  1032.  
  1033. SUB CreateMap (XLimit, YLimit, Rooms)
  1034.     ERASE MapArray 'clear the old map and reset everything to 0
  1035.     REDIM MapArray(XLimit, YLimit) AS _UNSIGNED _BYTE
  1036.     REDIM Distance(XLimit, YLimit) AS _UNSIGNED _BYTE
  1037.     REDIM Temp(XLimit, YLimit) AS _UNSIGNED _BYTE
  1038.     XL = 0: XH = XLimit: YL = 0: YH = YLimit 'global values to pass along our map ultimate dimensions
  1039.  
  1040.     DIM RoomCenterX(Rooms) AS _UNSIGNED _BYTE, RoomCenterY(Rooms) AS _UNSIGNED _BYTE
  1041.  
  1042.     StairRoom = INT(RND * Rooms) + 1
  1043.     FOR i = 1 TO Rooms
  1044.         DO
  1045.             RoomSize = INT(RND * 9) + 2
  1046.             RoomX = INT(RND * (XLimit - RoomSize))
  1047.             RoomY = INT(RND * (YLimit - RoomSize))
  1048.             'test for positioning
  1049.             good = -1 'it's good starting out
  1050.             FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  1051.                     IF MapArray(RoomX + X, RoomY + Y) = 4 THEN good = 0: EXIT FOR 'don't draw a room on a room
  1052.             NEXT X, Y
  1053.         LOOP UNTIL good
  1054.         FOR Y = 0 TO RoomSize: FOR X = 0 TO RoomSize
  1055.                 MapArray(RoomX + X, RoomY + Y) = 4 'go ahead and draw a room
  1056.         NEXT X, Y
  1057.         RoomCenterX(i) = RoomX + .5 * RoomSize
  1058.         RoomCenterY(i) = RoomY + .5 * RoomSize
  1059.         IF i = 1 THEN 'place the hero in the first room  (which can be anywhere randomly on our map)
  1060.             Hero.X = RoomX + INT(RND * RoomSize)
  1061.             Hero.Y = RoomY + INT(RND * RoomSize)
  1062.             MapArray(Hero.X, Hero.Y) = MapArray(Hero.X, Hero.Y) OR 32 'block the map where the hero stands
  1063.         END IF
  1064.         IF i = StairRoom THEN 'place the stairs in one of the random rooms
  1065.             DO 'But lets not place the stairs directly on top of the hero to begin with
  1066.                 StairX = RoomX + INT(RND * RoomSize)
  1067.                 StairY = RoomY + INT(RND * RoomSize)
  1068.             LOOP UNTIL StairX <> Hero.X AND StairY <> Hero.Y
  1069.             MapArray(StairX, StairY) = MapArray(StairX, StairY) OR 16
  1070.         END IF
  1071.     NEXT
  1072.     FOR i = 1 TO Rooms - 1
  1073.         StartX = RoomCenterX(i): StartY = RoomCenterY(i)
  1074.         EndX = RoomCenterX(i + 1): EndY = RoomCenterY(i + 1)
  1075.         DO UNTIL StartX = EndX AND StartY = EndY
  1076.             CoinToss = INT(RND * 100) 'Coin toss to move left/right or up/down, to go towards room, or wander a bit.
  1077.             Meander = 10
  1078.             IF CoinToss MOD 2 THEN 'even or odd, so we only walk vertical or hortizontal and not diagional
  1079.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  1080.                     XChange = SGN(EndX - StartX) '-1,0,1, drawn always towards the mouse
  1081.                     Ychange = 0
  1082.                 ELSE
  1083.                     XChange = INT(RND * 3) - 1 '-1, 0, or 1, drawn in a random direction to let the lightning wander
  1084.                     Ychange = 0
  1085.                 END IF
  1086.             ELSE
  1087.                 IF CoinToss < 100 - Meander THEN 'Lower values meander less and go directly to the target.
  1088.                     Ychange = SGN(EndY - StartY)
  1089.                     XChange = 0
  1090.                 ELSE
  1091.                     Ychange = INT(RND * 3) - 1
  1092.                     XChange = 0
  1093.                 END IF
  1094.             END IF
  1095.             StartX = StartX + XChange
  1096.             StartY = StartY + Ychange
  1097.             IF StartX < 0 THEN StartX = 0 'Make certain we move inside the bounds of our map dimensions
  1098.             IF StartY < 0 THEN StartY = 0
  1099.             IF StartX > XH THEN StartX = XH
  1100.             IF StartY > YH THEN StartY = YH
  1101.             IF MapArray(StartX, StartY) = 0 THEN MapArray(StartX, StartY) = 8 'place a path where we moved to
  1102.         LOOP
  1103.     NEXT
  1104.     PathFind
  1105.     EncounterLimit = INT(RND * 6) + 5
  1106.     FOR i = 1 TO EncounterLimit
  1107.         Encounter(i).Type = 0 'type 0 is a monster
  1108.         Encounter(i).Index = RandomMonster
  1109.         Encounter(i).Active = -1
  1110.         M = Encounter(i).Index
  1111.         Encounter(i).Life = INT(RND * Monster(M).Life.High - Monster(M).Life.Low + 1) + Monster(M).Life.Low
  1112.         valid = -1: EndlessLoopExit = 0
  1113.         DO
  1114.             EndlessLoopExit = EndlessLoopExit + 1
  1115.             Encounter(i).X = INT(RND * XLimit + 1)
  1116.             Encounter(i).Y = INT(RND * YLimit + 1)
  1117.             IF MapArray(Encounter(i).X, Encounter(i).Y) AND 32 THEN valid = 0 'the spot where we're wanting to place our monster is invalid.  (Another monster or the hero is probably there.)
  1118.             IF EndlessLoopExit = 1000 THEN EXIT DO 'if we can't place the monster in a room after 1000 tries, just place it wherever and call it a "wandering monster".
  1119.             'Of course, "wandering monsters" may end up inside a wall, in which case they simply become "lost monsters" and do nothing to affect the level.  It's the same as if they never existed at all.
  1120.             'BUT, we *should* generally be able to place a monster after 1000 tries.  This segment is just in the off-chance that the Random Number Gods are out to get us and to prevent any chance for an endless loop.
  1121.         LOOP UNTIL MapArray(Encounter(i).X, Encounter(i).Y) AND 4 AND valid 'monsters only spawn in rooms to begin with.
  1122.         MapArray(Encounter(i).X, Encounter(i).Y) = MapArray(Encounter(i).X, Encounter(i).Y) OR 32
  1123.     NEXT
  1124.     LootLimit = 0 'no loot on the map at this time.  Too bad for joo!
  1125.  
  1126. SUB PathFind
  1127.     STATIC m AS _MEM, m1 AS _MEM 'no need to keep initializing and freeing these blocks over and over.  Just reuse them...
  1128.     DIM pass AS _UNSIGNED _BYTE
  1129.     m = _MEM(Distance()): m1 = _MEM(Temp())
  1130.     _MEMFILL m1, m1.OFFSET, m1.SIZE, 255 AS _UNSIGNED _BYTE 'flush distance with 255 values until we see how far things actually are from the hero
  1131.     _MEMFILL m, m.OFFSET, m.SIZE, 255 AS _UNSIGNED _BYTE
  1132.     Temp(Hero.X, Hero.Y) = 0
  1133.     pass = 0
  1134.     DO
  1135.         changed = 0
  1136.         y = 0
  1137.         DO
  1138.             x = 0
  1139.             DO
  1140.                 IF Distance(x, y) = 255 AND MapArray(x, y) <> 0 THEN
  1141.                     IF x < XH THEN
  1142.                         IF Temp(x + 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  1143.                     END IF
  1144.                     IF x > 0 THEN
  1145.                         IF Temp(x - 1, y) = pass THEN Distance(x, y) = pass + 1: changed = -1
  1146.                     END IF
  1147.                     IF y < YH THEN
  1148.                         IF Temp(x, y + 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  1149.                     END IF
  1150.                     IF y > 0 THEN
  1151.                         IF Temp(x, y - 1) = pass THEN Distance(x, y) = pass + 1: changed = -1
  1152.                     END IF
  1153.                 END IF
  1154.                 x = x + 1
  1155.             LOOP UNTIL x > XH
  1156.             y = y + 1
  1157.         LOOP UNTIL y > YH
  1158.         _MEMCOPY m, m.OFFSET, m.SIZE TO m1, m1.OFFSET
  1159.         pass = pass + 1
  1160.     LOOP UNTIL changed = 0 OR pass = 255 'if we're more than 255 steps from the hero, we don't need to know where the hell we're at.  We're off the map as far as the hero is concerned!
  1161.     Distance(Hero.X, Hero.Y) = 0
  1162.  
  1163. FUNCTION RandomMonster
  1164.     SELECT CASE Level 'the level we're on
  1165.         CASE 1 TO 3: MC = 3 'the monster count which we can randomly run into and battle from on the current floor
  1166.         CASE ELSE: MC = 3 'since there's only 3 whole monsters in our monster database at the moment, don't expect to find a ton of them to choose from yet!
  1167.     END SELECT
  1168.     RandomMonster = INT(RND * MC) + 1
  1169.  
  1170.  
  1171. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  1172. '# SUBroutines and FUNCTIONs below #'
  1173. ' ----------------------------------------------------------------------------------------------------------------------------------------------------------- '
  1174. SUB PrintOut (WhichHandle AS INTEGER, What AS STRING)
  1175.     u = UBOUND(TextHandles)
  1176.     Handle = WhichHandle
  1177.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1178.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1179.     Where = TextHandles(Handle).VerticalAlignment
  1180.     How = TextHandles(Handle).Justification
  1181.     UpdatePrintPosition = TextHandles(Handle).UpdateMethod
  1182.     PlaceText Handle, Where, How, What, UpdatePrintPosition
  1183.  
  1184.  
  1185. SUB PlaceText (WhichHandle AS INTEGER, Where AS INTEGER, How AS INTEGER, What AS STRING, UpdatePrintPosition AS INTEGER)
  1186.     'WhichHandle is the handle which designates which text area we want to use
  1187.     'Where is where we want it to go in that text area
  1188.     '  -- Online prints the text to the current print position line in that text area.
  1189.     '  -- CenterLine centers the text to the center of that text area.
  1190.     '  -- any other value will print to that line positon in that particular box.
  1191.     'How tells us how we want to place that text (LeftJustified, RightJustified,CenterJustified, or NoJustify)
  1192.     'What is the text that we want to print in our text area
  1193.     'UpdatePrintPosition lets us know if we need to move to a newline or stay on the same line.  (Think PRINT with a semicolon vs PRINT without a semicolon).
  1194.  
  1195.     D = _DEST: S = _SOURCE
  1196.     OldFont = _FONT
  1197.  
  1198.     u = UBOUND(TextHandles)
  1199.     Handle = WhichHandle
  1200.  
  1201.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1202.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1203.     IF TextHandles(Handle).HideFrame THEN
  1204.         _DEST TextHandles(Handle).SavedBackground
  1205.         _SOURCE TextHandles(Handle).SavedBackground
  1206.     END IF
  1207.     _FONT TextHandles(Handle).Font
  1208.     fh = _FONTHEIGHT: pw = _PRINTWIDTH(What)
  1209.     IF _FONTWIDTH = 0 THEN
  1210.         FOR i = 1 TO 255
  1211.             IF _PRINTWIDTH(CHR$(i)) > fw THEN fw = _PRINTWIDTH(CHR$(i))
  1212.         NEXT
  1213.     ELSE
  1214.         fw = _FONTWIDTH
  1215.     END IF
  1216.  
  1217.     h = TextHandles(Handle).h - 4: w = TextHandles(Handle).w - 4
  1218.  
  1219.     SELECT CASE Where
  1220.         CASE BottomLine
  1221.             y = h \ fh
  1222.         CASE OnLine
  1223.             y = TextHandles(Handle).Ypos
  1224.             IF y = 0 THEN y = 1
  1225.         CASE CenterLine
  1226.             linesused = 0
  1227.             tpw = pw: tw = w: tWhat$ = What
  1228.             DO UNTIL tpw <= tw
  1229.                 textallowed = WordBreak(LEFT$(tWhat$, w \ fw))
  1230.                 text$ = RTRIM$(LEFT$(tWhat$, textallowed))
  1231.                 linesused = linesused + 1
  1232.                 tWhat$ = MID$(tWhat$, textallowed + 1)
  1233.                 tpw = _PRINTWIDTH(tWhat$)
  1234.             LOOP
  1235.             linesused = linesused + 1
  1236.             py = (h - linesused * fh) \ 2
  1237.             y = py \ fh + 1
  1238.             IF y < 1 THEN y = 1
  1239.         CASE ELSE
  1240.             y = Where
  1241.     END SELECT
  1242.  
  1243.     'IF y < 1 THEN ERROR 5: EXIT FUNCTION 'We don't print above the allocated text area.
  1244.     blend = _BLEND
  1245.  
  1246.     DO UNTIL y * fh < h 'We need to scroll the text area up, if someone is trying to print below it.
  1247.         'first let's get a temp image handle for the existing area of the screen.
  1248.         x1 = TextHandles(Handle).x1 + 2
  1249.         y1 = TextHandles(Handle).y1 + 2
  1250.         x2 = TextHandles(Handle).x1 + w
  1251.         y2 = TextHandles(Handle).y1 + h
  1252.         nh = y2 - y1 + 1 - fh
  1253.         nw = x2 - x1 + 1
  1254.         tempimage = _NEWIMAGE(nw, nh, 32) 'Really, I should swap this to a routine to pick which screen mode the user is in, but I'll come back to that later.
  1255.         _PUTIMAGE , , tempimage, (x1, y1 + fh)-(x2, y2)
  1256.         DrawTextArea Handle
  1257.         _PUTIMAGE (x1, y1)-(x2, y2 - fh), tempimage
  1258.         y = y - 1
  1259.     LOOP
  1260.  
  1261.     IF blend THEN _BLEND
  1262.  
  1263.     COLOR TextHandles(Handle).TextColor, TextHandles(Handle).TextBackgroundColor
  1264.  
  1265.     SELECT CASE How
  1266.         CASE LeftJustify
  1267.             x = 0
  1268.             IF pw > w THEN
  1269.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  1270.                 text$ = RTRIM$(LEFT$(What, textallowed))
  1271.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  1272.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0
  1273.             ELSE
  1274.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1275.                 finished = -1
  1276.             END IF
  1277.         CASE CenterJustify
  1278.             IF pw > w THEN
  1279.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  1280.                 text$ = RTRIM$(LEFT$(What, textallowed))
  1281.                 x = (w - _PRINTWIDTH(text$)) \ 2
  1282.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  1283.                 PlaceText Handle, y + 1, CenterJustify, MID$(What, textallowed + 1), NoUpdate
  1284.             ELSE
  1285.                 x = (w - pw) \ 2
  1286.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1287.                 finished = -1
  1288.             END IF
  1289.         CASE RightJustify
  1290.             IF pw > w THEN
  1291.                 textallowed = WordBreak(LEFT$(What, w \ fw))
  1292.                 text$ = RTRIM$(LEFT$(What, textallowed))
  1293.                 x = w - _PRINTWIDTH(text$)
  1294.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  1295.                 PlaceText Handle, y + 1, RightJustify, MID$(What, textallowed + 1), 0
  1296.             ELSE
  1297.                 x = w - pw
  1298.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1299.                 finished = -1
  1300.             END IF
  1301.         CASE NoJustify
  1302.             x = TextHandles(Handle).Xpos
  1303.             firstlinelimit = (w - x) \ fw 'the limit of characters on the first line
  1304.             IF LEN(What) > firstlinelimit THEN
  1305.                 textallowed = WordBreak(LEFT$(What, firstlinelimit))
  1306.                 text$ = RTRIM$(LEFT$(What, textallowed))
  1307.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), text$
  1308.                 PlaceText Handle, y + 1, LeftJustify, MID$(What, textallowed + 1), 0 'After the first line we start printing over on the left, after a line break
  1309.             ELSE
  1310.                 _PRINTSTRING (x + 2 + TextHandles(Handle).x1, (y - 1) * fh + TextHandles(Handle).y1 + 2), What
  1311.                 finished = -1
  1312.             END IF
  1313.     END SELECT
  1314.     IF finished THEN
  1315.         SELECT CASE TextHandles(Handle).UpdateMethod
  1316.             CASE NoUpdate 'We don't update the position at all.
  1317.             CASE DoUpdate
  1318.                 TextHandles(Handle).Xpos = x + pw
  1319.                 TextHandles(Handle).Ypos = y
  1320.             CASE NewLine
  1321.                 TextHandles(Handle).Ypos = y + 1
  1322.                 TextHandles(Handle).Xpos = 1
  1323.         END SELECT
  1324.         _FONT OldFont
  1325.         _DEST D: _SOURCE S
  1326.         COLOR FG, BG
  1327.     END IF
  1328.  
  1329. SUB SetTextForeground (Handle AS INTEGER, Foreground AS _UNSIGNED LONG)
  1330.     u = UBOUND(TextHandles)
  1331.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1332.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1333.     TextHandles(Handle).TextColor = Foreground
  1334.  
  1335.  
  1336. SUB SetTextBackground (Handle AS INTEGER, Background AS _UNSIGNED LONG)
  1337.     u = UBOUND(TextHandles)
  1338.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1339.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1340.     TextHandles(Handle).TextBackgroundColor = Background
  1341.  
  1342. SUB SetTextFont (Handle AS INTEGER, FontName AS STRING, FontSize AS INTEGER)
  1343.     u = UBOUND(TextHandles)
  1344.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1345.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1346.     SELECT CASE TextHandles(Handle).Font
  1347.         CASE 8, 9, 14, 15, 16, 17 'In built QB64 fonts.  We don't need to free them.
  1348.         CASE IS > 1
  1349.             'we have the font already in use
  1350.             'REMOVE THIS CONDITION IF NECESSARY, AND MANUALLY FREE/RELEASE FONTS AS ABLE!!!
  1351.             _FREEFONT TextHandles(Handle).Font 'if it's in use elsewhere, this *WILL* toss an error.
  1352.     END SELECT
  1353.  
  1354.     temp = _LOADFONT(FontName, FontSize, "MONOSPACE")
  1355.     IF temp > 1 THEN
  1356.         TextHandles(Handle).Font = temp
  1357.     ELSE
  1358.         TextHandles(Handle).Font = 16 'default to font 16, in case
  1359.     END IF
  1360.  
  1361.  
  1362. SUB SetTextColor (Handle AS INTEGER, Foreground AS _UNSIGNED LONG, Background AS _UNSIGNED LONG)
  1363.     u = UBOUND(TextHandles)
  1364.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1365.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1366.     TextHandles(Handle).TextColor = Foreground
  1367.     TextHandles(Handle).TextBackgroundColor = Background
  1368.  
  1369.  
  1370. SUB SetPrintUpdate (Handle AS INTEGER, Method AS INTEGER)
  1371.     u = UBOUND(TextHandles)
  1372.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1373.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1374.     IF Method < 0 OR Method > 2 THEN ERROR 5: EXIT FUNCTION
  1375.     TextHandles(Handle).UpdateMethod = Method
  1376.  
  1377.  
  1378. SUB SetPrintPosition (Handle AS INTEGER, X AS INTEGER, Y AS INTEGER)
  1379.     u = UBOUND(TextHandles)
  1380.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1381.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1382.     SELECT CASE Y
  1383.         CASE BottomLine
  1384.             TextHandles(Handle).VerticalAlignment = -2
  1385.         CASE CenterLine
  1386.             TextHandles(Handle).VerticalAlignment = -1
  1387.         CASE ELSE
  1388.             TextHandles(Handle).VerticalAlignment = 0
  1389.     END SELECT
  1390.     IF X < 1 AND X > -4 THEN
  1391.         TextHandles(Handle).Justification = X
  1392.     ELSE
  1393.         TextHandles(Handle).Xpos = X
  1394.     END IF
  1395.     IF Y < 1 THEN EXIT SUB
  1396.     TextHandles(Handle).Ypos = Y
  1397.  
  1398. SUB SetPrintPositionX (Handle AS INTEGER, X AS INTEGER)
  1399.     u = UBOUND(TextHandles)
  1400.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1401.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1402.     IF X < 1 AND X > -4 THEN
  1403.         TextHandles(Handle).Justification = X
  1404.     ELSE
  1405.         TextHandles(Handle).Xpos = X
  1406.     END IF
  1407.  
  1408. SUB SetPrintPositionY (Handle AS INTEGER, Y AS INTEGER)
  1409.     u = UBOUND(TextHandles)
  1410.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1411.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1412.     SELECT CASE Y
  1413.         CASE BottomLine
  1414.             TextHandles(Handle).VerticalAlignment = -2
  1415.         CASE CenterLine
  1416.             TextHandles(Handle).VerticalAlignment = -1
  1417.         CASE ELSE
  1418.             TextHandles(Handle).VerticalAlignment = 0
  1419.     END SELECT
  1420.     IF Y < 1 THEN EXIT SUB
  1421.     TextHandles(Handle).Ypos = Y
  1422.  
  1423.  
  1424. FUNCTION GetPrintPositionY (Handle AS INTEGER)
  1425.     u = UBOUND(TextHandles)
  1426.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1427.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1428.     GetPrintPositionY = TextHandles(Handle).Ypos
  1429.  
  1430. FUNCTION GetPrintPositionX (Handle AS INTEGER)
  1431.     u = UBOUND(TextHandles)
  1432.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1433.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1434.     GetPrintPositionX = TextHandles(Handle).Xpos
  1435.  
  1436.  
  1437.  
  1438. FUNCTION WordBreak (text$)
  1439.     CONST Breaks = " ;,.?!-"
  1440.     FOR i = LEN(text$) TO 0 STEP -1
  1441.         IF INSTR(Breaks, MID$(text$, i, 1)) THEN EXIT FOR
  1442.         loopcount = loopcount + 1
  1443.     NEXT
  1444.     IF i = 0 THEN i = LEN(text$)
  1445.     WordBreak = i
  1446.  
  1447.  
  1448.  
  1449. SUB ClearTextArea (Handle AS INTEGER)
  1450.     u = UBOUND(TextHandles)
  1451.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1452.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1453.     IF TextHandles(Handle).SavedBackground THEN
  1454.         w = TextHandles(Handle).w
  1455.         h = TextHandles(Handle).h
  1456.         x1 = TextHandles(Handle).ScreenX
  1457.         y1 = TextHandles(Handle).ScreenY
  1458.         x2 = x1 + w - 1
  1459.         y2 = y1 + h - 1
  1460.         blend = _BLEND
  1461.         _DONTBLEND
  1462.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1463.         IF blend THEN _BLEND
  1464.     END IF
  1465.     DrawTextArea Handle
  1466.  
  1467.  
  1468.  
  1469. SUB DrawTextArea (Handle AS INTEGER)
  1470.     u = UBOUND(TextHandles)
  1471.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1472.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1473.     w = TextHandles(Handle).w
  1474.     h = TextHandles(Handle).h
  1475.     x1 = TextHandles(Handle).ScreenX
  1476.     y1 = TextHandles(Handle).ScreenY
  1477.     x2 = x1 + w - 1
  1478.     y2 = y1 + h - 1
  1479.  
  1480.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).BackColor, BF
  1481.     LINE (x1, y1)-(x2, y2), TextHandles(Handle).FrameColor, B
  1482.  
  1483.  
  1484.  
  1485. SUB ColorTextArea (Handle AS INTEGER, FrameColor AS _UNSIGNED LONG, BackColor AS _UNSIGNED LONG)
  1486.     u = UBOUND(TextHandles)
  1487.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1488.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1489.     TextHandles(Handle).FrameColor = FrameColor
  1490.     TextHandles(Handle).BackColor = BackColor
  1491.  
  1492.  
  1493.  
  1494. FUNCTION NewTextArea% (tx1 AS INTEGER, ty1 AS INTEGER, tx2 AS INTEGER, ty2 AS INTEGER, SaveBackground AS INTEGER)
  1495.     x1 = tx1: y1 = ty1 'We pass temp variables to the function so we can swap values if needed without altering user variables
  1496.     x2 = tx2: y2 = ty2
  1497.     IF x1 > x2 THEN SWAP x1, x2
  1498.     IF y1 > y2 THEN SWAP y1, y2
  1499.     w = x2 - x1 + 1
  1500.     h = y2 - y1 + 1
  1501.     IF w = 0 AND h = 0 THEN ERROR 5: EXIT FUNCTION 'Illegal Function Call if the user tries to define an area with no size
  1502.     'Error checking for if the user sends coordinates which are off the screen
  1503.     IF x1 < 0 OR x2 > _WIDTH - 1 THEN ERROR 5: EXIT FUNCTION
  1504.     IF y1 < 0 OR y2 > _HEIGHT - 1 THEN ERROR 5: EXIT FUNCTION
  1505.  
  1506.     u = UBOUND(TextHandles)
  1507.     FOR i = 1 TO u 'First let's check to see if we have an open handle from where one was freed earlier
  1508.         IF TextHandles(i).InUse = False THEN Handle = i: EXIT FOR
  1509.     NEXT
  1510.     IF Handle = 0 THEN 'We didn't have an open spot, so we need to add one to our list
  1511.         Handle = u + 1
  1512.         REDIM _PRESERVE TextHandles(Handle) AS TextArea
  1513.     END IF
  1514.     TextHandles(Handle).x1 = x1
  1515.     TextHandles(Handle).y1 = y1
  1516.     TextHandles(Handle).w = w: TextHandles(Handle).h = h
  1517.     TextHandles(Handle).InUse = True
  1518.     TextHandles(Handle).Xpos = 0
  1519.     TextHandles(Handle).Ypos = 1
  1520.     TextHandles(Handle).UpdateMethod = NewLine
  1521.     TextHandles(Handle).TextColor = _RGB32(255, 255, 255) 'White text
  1522.     TextHandles(Handle).TextBackgroundColor = _RGB32(0, 0, 0) 'Black background
  1523.  
  1524.     IF SaveBackground THEN
  1525.         imagehandle = _NEWIMAGE(w, h, 32)
  1526.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1527.         TextHandles(Handle).SavedBackground = imagehandle
  1528.     END IF
  1529.     TextHandles(Handle).ScreenX = x1
  1530.     TextHandles(Handle).ScreenY = y1
  1531.     TextHandles(Handle).Font = 16 'default to font 16
  1532.     NewTextArea% = Handle
  1533.  
  1534. SUB FreeTextArea (Handle AS INTEGER)
  1535.     IF Handle > 0 AND Handle <= UBOUND(TextHandles) THEN
  1536.         IF TextHandles(Handle).InUse THEN
  1537.             TextHandles(Handle).InUse = False
  1538.             IF TextHandles(Handle).SavedBackground THEN
  1539.                 IF TextHandles(Handle).HideFrame = 0 THEN 'If the frame isn't hidden, then restore what's supposed to be beneath it
  1540.                     w = TextHandles(Handle).w
  1541.                     h = TextHandles(Handle).h
  1542.                     x1 = TextHandles(Handle).ScreenX
  1543.                     y1 = TextHandles(Handle).ScreenY
  1544.                     x2 = x1 + w - 1
  1545.                     y2 = y1 + h - 1
  1546.                     blend = _BLEND
  1547.                     _DONTBLEND
  1548.                     _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1549.                     IF blend THEN _BLEND
  1550.                 END IF
  1551.                 'Even if it is hidden though, if we're going to free that frame, we need to free the stored image held with it to reduce memory usage.
  1552.                 _FREEIMAGE TextHandles(Handle).SavedBackground
  1553.             END IF
  1554.         ELSE
  1555.             ERROR 258 'Invalid handle if the user tries to free a handle which has already been freed.
  1556.         END IF
  1557.     ELSE
  1558.         ERROR 5 'Illegal function call if the user tries to free a handle that doesn't exist at all.
  1559.     END IF
  1560.  
  1561. SUB HideFrame (Handle AS INTEGER)
  1562.     IF TextHandles(Handle).HideFrame = 0 THEN 'only if the frame isn't hidden, can we hide it.
  1563.         TextHandles(Handle).HideFrame = -1
  1564.         w = TextHandles(Handle).w
  1565.         h = TextHandles(Handle).h
  1566.         x1 = TextHandles(Handle).ScreenX
  1567.         y1 = TextHandles(Handle).ScreenY
  1568.         x2 = x1 + w - 1
  1569.         y2 = y1 + h - 1
  1570.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1571.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1572.         IF TextHandles(Handle).SavedBackground THEN
  1573.             blend = _BLEND
  1574.             _DONTBLEND
  1575.             _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground
  1576.             _FREEIMAGE TextHandles(Handle).SavedBackground
  1577.             IF blend THEN _BLEND
  1578.         END IF
  1579.         TextHandles(Handle).SavedBackground = imagehandle
  1580.         TextHandles(Handle).x1 = 0 'When the frames are hidden, we calculate our print position based off the hidden image
  1581.         TextHandles(Handle).y1 = 0 'So we'd start at point (0,0) as being top left
  1582.     END IF
  1583.  
  1584. SUB RestoreFrame (Handle AS INTEGER)
  1585.     IF TextHandles(Handle).HideFrame THEN 'only if we have a hidden frame do we have to worry about restoring it
  1586.         TextHandles(Handle).HideFrame = 0
  1587.         w = TextHandles(Handle).w
  1588.         h = TextHandles(Handle).h
  1589.         x1 = TextHandles(Handle).ScreenX
  1590.         y1 = TextHandles(Handle).ScreenY
  1591.         x2 = x1 + w - 1
  1592.         y2 = y1 + h - 1
  1593.         imagehandle = _NEWIMAGE(TextHandles(Handle).w, TextHandles(Handle).h, 32)
  1594.         blend = _BLEND
  1595.         _DONTBLEND
  1596.         _PUTIMAGE , 0, imagehandle, (x1, y1)-(x2, y2)
  1597.         _PUTIMAGE (x1, y1)-(x2, y2), TextHandles(Handle).SavedBackground ', 0, (0, 0)-(w, h)
  1598.         _FREEIMAGE TextHandles(Handle).SavedBackground
  1599.         IF blend THEN _BLEND
  1600.         TextHandles(Handle).SavedBackground = imagehandle
  1601.         TextHandles(Handle).x1 = x1 'When the frames are restored, we need to recalculate our print position
  1602.         TextHandles(Handle).y1 = y1 'as we're no longer going over the image cooridinates, but the screen location of the top left corner instead.
  1603.     END IF
  1604.  
  1605. SUB MoveFrame (Handle AS INTEGER, x1 AS INTEGER, y1 AS INTEGER)
  1606.     'Only two coordinates here, so we'll be positioning our frames new movement by the top left corner.
  1607.     u = UBOUND(TextHandles)
  1608.     IF Handle < 1 OR Handle > u THEN ERROR 5: EXIT FUNCTION
  1609.     IF TextHandles(Handle).InUse = False THEN ERROR 5: EXIT FUNCTION
  1610.     HideFrame Handle
  1611.     TextHandles(Handle).ScreenX = x1
  1612.     TextHandles(Handle).ScreenY = y1
  1613.     RestoreFrame Handle

And why was picking up the food so hard?

I dunno!  It just was!!

Actually, it's just a complicated little set of game expansion at work.  We don't just pick up the food that creatures drop now -- the game also generates various degrees of food for us.  A rat doesn't just drop "rat meat" now; it can drop "Sickly Rat Meat", "Good Rat Meat", or "Tasty Rat Meat".  (Along with a few other varieties.) 

And, if we've never eaten the exact meat in question, the game only identifies it as "unknown rat meat" for us, so we're left wondering, "Should I eat this to appease my hunger, or will it be bad for me?"

Of course, once we've identified the food, it'll always be identified for us, so the longer our hero lives, the more correct information the game shares with us -- allowing us to experience "growing smarter" over time....



Now that we actually have hunger (which we've had, but ignored for ages), and we now have monsters dropping a source of food, the next step is to actually implement the ability to EAT some food!  And then.... Then I can actually make hunger do something and kill lazy players who just want to sit and heal to full health after each and every fight.  MuHaHaHaHa!

And after that, I suppose it'll be time to work on adding other type loots into the game for players to find and interact with.  ;)
Title: Re: Rogue-Like (work in progress)
Post by: madscijr on November 23, 2021, 02:14:10 pm
And we can now start picking up the food that the monsters drop!  (Which was actually one of the most complicated little additions to the game engine which I've worked on so far!)

I'd be intereseted in seeing this run, but it is throwing an error:

Unhandled Error #258
Line: 268 (in main module)
Invalid handle
Continue?
[Yes] [No]

I'm currently running QB64 2.0.0 - should I be using a different version?
Any other ideas?
Thanks...

Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on November 23, 2021, 02:20:23 pm
I'd be intereseted in seeing this run, but it is throwing an error:

Unhandled Error #258
Line: 268 (in main module)
Invalid handle
Continue?
[Yes] [No]

I'm currently running QB64 2.0.0 - should I be using a different version?
Any other ideas?
Thanks...

No idea.  Line 268 in the main module is a blank line.  I can't fathom why it'd toss an error.
Title: Re: Rogue-Like (work in progress)
Post by: Cobalt on November 23, 2021, 06:26:13 pm
I'd be intereseted in seeing this run, but it is throwing an error:

Unhandled Error #258
Line: 268 (in main module)
Invalid handle
Continue?
[Yes] [No]

I'm currently running QB64 2.0.0 - should I be using a different version?
Any other ideas?
Thanks...

be sure you get the 7z file in a previous post. you might be missing the PNG file.
Title: Re: Rogue-Like (work in progress)
Post by: xra7en on November 23, 2021, 07:45:54 pm
definitely following this:
rogue games have been my passion back in the day, thanks for reviving it


same unhandled error all over :-)
Title: Re: Rogue-Like (work in progress)
Post by: Cobalt on November 23, 2021, 09:08:33 pm
same unhandled error all over :-)

so the error is coming up for you as well?

That is odd, runs fine for me. You do have the TTF and PNG files right?
Title: Re: Rogue-Like (work in progress)
Post by: SMcNeill on November 23, 2021, 10:02:21 pm
There's only 2 files which need to be in the same folder as the BAS program -- Sprites.png and courbd.ttf (and the font file is only needed if you're not a Windows user as QB64 will find it automatically for you with windows). 

You might want to check to see if the OUTPUT EXE TO SOURCE FOLDER option is checked in the IDE for you, or not, as it can make a difference if you're compiling from the "Rogue Like" folder which the zip file extracted everything into.   
Title: Re: Rogue-Like (work in progress)
Post by: madscijr on November 23, 2021, 11:19:55 pm
There's only 2 files which need to be in the same folder as the BAS program -- Sprites.png and courbd.ttf (and the font file is only needed if you're not a Windows user as QB64 will find it automatically for you with windows). 

You might want to check to see if the OUTPUT EXE TO SOURCE FOLDER option is checked in the IDE for you, or not, as it can make a difference if you're compiling from the "Rogue Like" folder which the zip file extracted everything into.

Thanks for your reply - I'll check all that when I'm back at my PC & let you all know what works..,
Title: Re: Rogue-Like (work in progress)
Post by: madscijr on November 24, 2021, 12:52:06 pm
There's only 2 files which need to be in the same folder as the BAS program -- Sprites.png and courbd.ttf (and the font file is only needed if you're not a Windows user as QB64 will find it automatically for you with windows). 

You might want to check to see if the OUTPUT EXE TO SOURCE FOLDER option is checked in the IDE for you, or not, as it can make a difference if you're compiling from the "Rogue Like" folder which the zip file extracted everything into.

It turns out I didn't have the "courbd.ttf" and "Sprites.png" files,
and the "OUTPUT EXE TO SOURCE FOLDER" option was not selected.
I changed the option and downloaded 'Rogue Like.7z"
(instead of using the code from the forum post)
and it works like a charm. Thank you!

This is a very neat implementation of the game so far -
thanks for sharing it.

PS Are there any instructions?
Here's what I determined from reading the code:
Code: QB64: [Select]
  1. KEY         ACTION
  2. up          move north
  3. down        move south
  4. left        move west
  5. right       move east
  6. space       skip turn
  7. <           climb ladder
  8. +           turn lantern up
  9. =           turn lantern up
  10. -           turn lantern down
  11. _           turn lantern down
  12. SHIFT+up    increase scale
  13. SHIFT+down  decrease scale
  14. SHIFT+left  decrease font size
  15. SHIFT+right increase font size

FYI I tried playing it and got to a ladder, and when I pressed "<" the game seemed to lock up
(I waited about a minute and nothing happened so I killed it.)

PPS Some suggestions for future versions

PPS As a guide for anyone who wants to try adding features, here are the commands listed in the the original Rogue manual on myabandonware.com
https://dl2.myabandonware.com:8443/f/p1z/Rogue_Manual_DOS_EN.pdf (https://dl2.myabandonware.com:8443/f/p1z/Rogue_Manual_DOS_EN.pdf)

Code: QB64: [Select]
  1. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  2. ROGUE COMMANDS REFERENCE CARD
  3. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  4. The following is a complete list of all the commands used in the ROGUE program.
  5. Certain commands have several keys which perform the same function:
  6.  
  7. -------------------------------------------------------------------------------
  8. MOVEMENT/FIGHTING
  9. -------------------------------------------------------------------------------
  10. KEY(S)        COMMAND(S)
  11. , h           Moves you left one space.
  12. H             Moves you left until you run into a wall or door.
  13. f             Moves you left until you are near an object.
  14. , j           Moves you down one space.
  15. J             Moves you down until you run into a wall or door.
  16. f             Moves you down until you are near an object.
  17. , k           Moves you up one space.
  18. K             Moves you up until you run into a wall or door.
  19. f             Moves you up until you are near an object.
  20. , l           Moves you right one space.
  21. L             Moves you right until you run into a wall or door.
  22. f             Moves you right until you run into an object.
  23. Home, y       Moves you diagonally, up and to the left.
  24. Y             Moves you diagonally, up and to the left until you run into a wall or door.
  25. f Home        Moves you diagonally, up and to the left until you are near an object.
  26. End, b        Moves you diagonally, down and to the left.
  27. B             Moves you diagonally, down and to the left until you run into a wall or door.
  28. f End         Moves you diagonally, down and to the left until you are near an object.
  29. PgUp, u       Moves you diagonally, up and to the right.
  30. U             Moves you diagonally, up and to the right until you run into a wall or door.
  31. f PgUp        Moves you diagonally, up and to the right until you are near an object.
  32. PgDn, n       Moves you diagonally, down and to the right.
  33. N             Moves you diagonally, down and to the right until you run into a wall or door.
  34. f PgDn        Moves you diagonally, down and to the right until you are near an object.
  35.  
  36. NOTE: The above commands are also used for fighting monsters.
  37.  
  38. KEY(S)        COMMAND(S)
  39. s, Del        Search for hidden traps and secret doors in area adjacent to you.
  40. f             Find something. When followed by a direction, this will allow you to move continuously until you run into a wall or door.
  41. g             Go over. Allows you to step on an item without picking it up. Used as a prefix before a direction key.
  42. >, Ins        When standing on a staircase, this allows you to go down the staircase, deeper into the dungeon, to the next level.
  43. <             When standing on a staircase, this allows you to go up the staircase, out of the dungeon, to the next level.
  44. ^, F8         Identifies the trap type. (You will have to give the direction of the trap.)
  45. SCROLL LOCK   Fast Play Mode. When engaged, you move through the rooms and passageways continuously until you run into a door, monster, or other object. Also stops any count currently being performed.
  46.                          
  47. -------------------------------------------------------------------------------
  48. USING OBJECTS/FIGHTING
  49. -------------------------------------------------------------------------------
  50. KEY(S)        COMMAND(S)
  51. c, F5         Renames an object which you have not yet identified.
  52. d             Drop an object. To take something out of your pack and leave it on the dungeon floor.
  53. e             Eat food. Takes food out of your pack to eat.
  54. P             Put a ring on your hand.
  55. q             Quaff (drink) one of the potions you are carrying.
  56. r             Read one of the scrolls in your pack.
  57. R             Remove a ring you are wearing and put it back into your pack.
  58. t, +          Throw an object (i.e. arrow, dagger). (This command is followed by a direction.)
  59. T             Take off the armor you are currently wearing.
  60. w             Wield a new weapon.
  61. W             Wear a different piece of armor.
  62. z, –          Zap a monster with a wand or staff. (This command is followed by a direction.)
  63.  
  64. -------------------------------------------------------------------------------
  65. GENERAL (Function Keys)
  66. -------------------------------------------------------------------------------
  67. KEY(S)        COMMAND(S)
  68. F1, ?         Lists all the Commands (Help).
  69. F2, /         Lists all the symbols used in the game.
  70. F3, a         Repeat the last command given.
  71. F4, Ctrl r    Repeat message.
  72. F5, c         Rename object.
  73. F6, D         List items that have been discovered.
  74. F7, i         Inventory of all items in your pack.
  75. F8, ^         Identify type of trap. (This command is followed by a direction.)
  76. F9 Any key.   (Alt F9 defines this key to any command you wish to use.)
  77. F10, !        Clears the screen, and starts up a fake DOS. Hitting any function key or typing rogue will put you back in the game. (This can be used when playing Rogue at work when you shouldn't be!)
  78. Ctrl t        Terse message mode.
  79. Q             Quit and exit from ROGUE.
  80. S             Save the current game
  81. v             Prints the current version number.
  82. Esc           Cancels a command before you execute it.
  83. .             Allows you to rest and heal (regain hit points).
  84. 0-9           Number keys preceding a command allow you to repeat the command that number of times.
  85.