issue #4

Printing this issue? Use this version

REM 'Letter from the Editor
Welcome to Issue 5 of Qbasic: The Magazine
ON KEY 'Hot List
News from the QB world
INPUT 'Your Stuff
Survey #3 results plus yer letters!
! mov 'asm Intro: part 2
Petter continues his asm series with some fly code.
DRAW 'Art in Qbasic
Master artist Enigma tells ya' why art is important!
PRINT 'Rpg Scripting: part I
MA SNART starts his series on making script languages
CLS 'Dark Ages 2 Preview
The top-secret info on 1999's biggest sequel!
PRINT 'Memory
Types? Defs? Stack? What does it all mean? Thav tells ya'
END 'Next Issue
What's up in our next issue?

Letter from the Editor

By ZKman

How's it been goin' ev'ryone? I'm very happy to present issue 5 of Qbasic: the Magazine, and also to say it's our biggest issue yet. This month, Petter continues his groovy asm class, MA SNART starts his RPG script language series, Thav explains what types of memory there are and what they all mean, and Enigma (the creator of Eldim and one of the best artists in qb) presents an Art article on Why art IS an important part of qbasic games.

But wait...there's more. Michael Hoopman, the creator of Dark Ages and I have been busy preparing a blowout preview for ya'. What game is that, you ask? Why, none other than Dark Ages 2, one of 1999's biggest games! Tons of exclusive screenshots and info come your way in that article. Oh, and if you don't think DA2 will be huge, just check out the survey this month. ;)

I added a new section to the mag called "Index". There you'll find printable versions of all of our issues (including this one). By printable, I mean black text on white, cuz most of you have probably noticed that white on blue has a thing for not printing on most 'puters. But anyway, reading the back issues, I've noticed that I use a lot of big words. Wierd. Mebbe I should give these to my lit teacher for extra credit :). Like, last issue, I used the word "qualms". Wow. Impressive. :)

Whoa! I almost forgot! 'Tis the end of the year. It's been a great one, with releases in '98 such as Wetspot 2, Monospace Shooter Full, SFB2, Dark Ages, Dash, and dqb. I'm sure 1999 will be even better! Have a good Christmas/ Chanakauh / Kwanzaa / whatever and I'll...

See ya' next month!


Back to top

qbasic Hot List

Compiled by ZKman

Qlympics 1999!

   The entry period for qbasic's biggest award has started. Run 
   by SonicBlue, with Danny Gump of virtuaSoft as co-sponsor, the
   2nd Qlympics kicked off on 1 December, taking entries in 
   nearly every category. The entry period is schedueled to last
   until 31 December at press time, but we've heard rumours that
   the date might be moved up to 1 February. 

   I'm sure everyone remembers the sham that occured in last
   year's Qlympics in the "Best Game" category, with Wetspot 2
   being "beaten" by the mediocre-at-best Elysian Fields. 
   SonicBlue has implemented anti-cheating features this time.
   If you have a qb program released in the last year and would
   like to submit, go to the  official site for details. 

More on the QBt50 sale.

   Last month, we reported that the qbt50 was for sale. We've
   now learnt that it can be yours for the mere asking price
   of 1500 big ones (in American, that is). And what do you
   get for that price? How about 800hits/day!

   Steve Martin, the owner of qbt50, told us that the price
   includes the cgi script program that runs qbt50, plus
   all rights and licenses to the site name. He mentioned
   that he has had one advertiser that paid $100 for 10000
   impressions, as well as lower-paying banner ads. 

   If you're willing to do the work to find advertisers, this
   site could end up being a very valuable investment. If 
   interested, contact Steve and tell him qb:tm
   sent ya'!

Qb: the Greatest Hits

   Earlier this month, Danny Gump announced intentions to 
   create a CD compilation of the best qbasic programs.
   This will become a reality now, thanks to Qlympics 99.
   The winners in each category will be featured on the
   cd and also may recieve a share of the proceeds! If
   you're interested in acquiring one of these fab items,
   which should be out around mid-99, contact SonicBlue
   or Danny Gump

Darkdread = RavenNight?

   Most qb'ers have heard that a man calling himself 
   RavenNight has appeared with a new website, which is
   apparently the site of DarkDread. It features all
   of DD's proggies, as well as info on his "next
   project". Qb:tm doubts that RavenNight is DarkDread,
   just because "RavenNight" has not appeared on any
   of the boards/irc's that DD used to frequent. But,
   hey, we could be wrong. More on this in future issues.

"Nemesis" and "unnamed"

   Late last month, we picked up word that Pyrus was 
   intending to develop a dqb/dash style library called 
   "Nemesis". This lib will run in protected mode, giving
   it lots of fast access (compared to ems) memory for
   your use! More news as it comes on this one.
   Also, we've heard rumours that LordQB was developing
   a library set also. We currently have no other info
   on this one either, but we'll try for next issue.

Speaking about Libraries...

   Yup, Directqb and Dash have both been updated a few times
   since last issue. Here's the rundown of new features:
   dqb added a bunch of stuff like a neato-keen Tools program
   that included things like a Font Editor, as well as a
   blender map program, and 3 new versions of the base 
   library. In there, you'll find cool stuff like alpha-
   blending, texture-mapping, and more sound support. 
   On the Dash front, we've seen the biggest change in it's 
   addition of flat-poly filling and texture-mapping 
   routines. It seems that Dash fell a bit behind this 
   month, but qb:tm is sure that it'll have matched dqb's
   collection by next issue.

Qbasic: the Future Part II

   It seems that the biggest new innovation in the world
   of qb this month has been the interest in voxel, or
   volumetric pixel, engines, such as the one synnine's
   Vampire engine is made of.  Voxels are pretty much a
   cross between Pixel graphics and 3d, so this is 
   definitely a cool thing. Look for more "voxel engines"
   to start appearing next quarter.

Neozones hits the big 1-0-0-0-0-0

   On 7 December, Neozones hit 100,000 hits, becoming
   the 3rd qbasic site to do so ( and 
   were first and second). Congratulations! As far as
   qb:tm knows, the next site on track to hit the sixth
   digit is Qbasic Mania.

I like qmidi4.1

   Qmidi, the best current mid file player for qbasic
   released version 4.1 earlier this month and I like it
   a lot. Why? Not only does it use less memory than
   prior versions, it comes with 2 versions, one with
   less subs for smaller programs (sort of like dqbLite).
   But, also, it's the only qmidi version that seems to
   work with my computer. And since I'll probably be
   reviewing your game, you better use qmidi4.1 so you
   can get a high score, right? So go download it now!

Latest Game updates:

   "Project RT":
   From here on, qb:tm will be referring to the new
   top-secret project from Enhanced Creations as "Project
   RT". We're allowed to tell you a really, really small
   hint this month. I'm talking like, thing of something
   really small, and divide that by infinity. The hint
   is even smaller. Okay, here goes:
   Enhanced's top-secret really super cool game will have 
   "twitch" elements (i.e. it's not an rpg) and will not
   be an arcade-style game (i.e., it's not Wetspot 3 or
   anything like that). We'd tell you so much more if 
   Enhanced would let us! If you want some more news, go
   to and diluge them with email :). 
   Heh. Not really. But if they don't let us print something
   next month, we'll have to print Angelo's home phone 
   number and have you all call once an hour requesting 
   information :).

   BREAKING NEWS! Just as we were going to press, we
   picked up the specs on Dqb version 1.4! This new 
   version, which might be out as you read this, will
   feature flipped sprite drawing, blended gourard-shaded
   and blended texture-mapped triangles. But the most
   important feature will be that it's now split into
   15 object files! No more dqblite! Now you only link
   the .obj you need, making your proggie much smaller!
   An issue or two ago, we said that WoodLands, Pyrus's
   strategy game, had been cancelled. Well, Pyrus has
   changed his mind! WoodLands has been brought back from
   the land of the dead-qb-game. Yay! But, in less 
   fortunate news, we've heard that Marcade's strat game
   "Low Radiation" has been canned. More news as it comes.
   NutzBoy is working on a side-scroller project called "peanut
   patrol". This Mario Bros. style game casts you as a Peanut
   on a mission of rescue. All right! Anyways, the most current
   version allows you to walk and jump thru two levels. Only
   one of the many enemies has been implemented already, though.

   Enigma's next big project after Eldim will be a massive rpg
   by the name of "Realms". This project will be a 320x200 rpg
   with a long playtime, 500+ tiles, 20 characters and tons of
   rippin' gear! Not to mention asm special effects! Enigma's
   other project is called Bubble Fighters. It features 
   textured, scaled graphics, used fast asm and Dash, and 
   is, as Enigma put it, "Killer Instinct meets SFB2". This
   game will come out before "realms". BREAKING NEWS: Just 
   before publication, we've learnt that Eldim has been delayed,
   and that this might be a permanent delay! Enigma has noted
   that it may be resurrected at a later date, though.

   Pasco has 3! major projects in the works, all of which look
   super-cool. The first, Soccer 98, is being updated constantly,
   and qb:tm looks forward to it because Pasco is not American!
   Yeah! That means we'll have accurate football :). Us Yanks
   tend not to be good at that kinda thing, y'know? Also in 
   development is Groov Buggies 2. This project's pretty far
   off in the horizon, but it's going to be the first "real"
   3d (i.e., not wireframe) game in qbasic. His third project
   is codenamed "Vertical" and is a topdown shooter. The demo
   has great-lookin' animated tiles and translucent shadows,
   as well as it being already cool to shoot things :). Check
   out all of these at Pasco's Page
   Here come the shorts:
   Dunric is working on a strat/wargame called "Gradius". Set
   at the end of the the 18th century, it's a game of global
   conquest with Civ like elements....Hal_8900's and Necrolyte's
   game, Red Planet, has ditched svga in favour of fast GTA
   style scrolling...Seav has released a demo of his game called
   "labyrinth". It appears to be a faster version of DD's engine
   from "Legend of Lith 2" but looks pretty raycaster-ish. Not 
   too bad...BREAKING NEWS! Just as we went to press, we heard of
   a new project from a party wishing to remain unnamed. Based on
   what we've heard, it's gonna rock. Qb:tm will refer to this
   project as "Xeno" from now on, and try to break the veil of

That's all the latest news for this month. Remember, if you have any news or rumours, send them along to Qbasic: The Magazine

Back to Top

Input: Your Stuff

By ZKman

Here's what you had to say about last month. Be sure and send us more letters soon!

Quote :
"What the heck? There's not a singlue blurd about qbasic on this site! It's all asm and C++!

Ahhh...that's what I thought, too, when I first went to, but I was wrong. What this has is a gold mine of TECHNIQUES, which are the same whether done in C or qb."

I run Game Programming '98 and I want to say, thanks a lot for noticing. I haven't put things like DirectX up because it was too language specific. I'm glad you noticed the theme of my site, though, as it is easily missed. (i.e., "Why don't you have DirectX info, more source, etc.) =) I'd say you are one of the first [to notice]. When i first saw your review, I thought to myself, "Wow, someone has finally caught on". heheh.

Often, I see Game Programming '98 given names like...Game Programming in Pascal, Game Programming in C++, etc. I suppose I am accomplishing my coals with the site. =)

And I have to say, your page is great. It looks as if each issue is a monumental undertaking. Keep up the good work! Oh yes, thanks a lot for reviewing Game Programming '98. I appreciate it very much.

Take care!

Michael Tanczos
Game Programming '98

For any of you readers that didn't notice, was 
the winner of our Site of the Month last month. Be sure and
go there soon to get whatever you need in the way of 
programming techniques, from starfields to .mod file formats.

Hi there,

Can you release Issue 5 right now??? I really like to see more of Petter's ASM series. His first article is really great. I understand it all (it's not always that way with me), unlike some other tutorials. Doesn't know what we need, but what we want!!!

Seyuan Yang

Thanks for the letter! Sorry we can't release more often 
(due to the amount of time it takes to prep an issue). I love 
Petter's series, too! One of the first asm tutorials that I've
read where I understand every word.


My name is David Devenaud. While doing a history project, I had an inspiration, and wrote this:

Is it just me, or are Qbasic games getting better by the week? QB programmers are now creating games that people outsite the community might actually play! Gone are the crappy little text games that had no interface; now everyone is making an RPG, and that isn't a coincidence, I think.

RPG's are the hardest type of game to make, and the most fun to play if done right. Look at Zelda 64; it's supposed to be the game of the decade that will decide the fate of the N64. Because of all sorts of libraries, sound, graphics, and other tools, it is now possible to raise the standards and make games that are good quality.

Not that RPG's are teh only games that can be good. Every type of game can. But because of all these semi-finished projects, good QB games are like diamonds in the rough. We need to make it more like there's a few bad apples out of all the QB games.

The old, good QB games are not going to go away. We will have as many games and possibly could make commercial-quality games.

Here's an idea: maybe all Qb programmers could come together; sort of in one big software company, creating excellent games very quickly. We could be the next Microsoft (but less evil) of games. One idea would be passed along, improved upon, written, compiled, artwork drawn, in like a month. A new good product every month, with Silicon Valley headhunters beating a path to our door (Maybe). What would it's name be? With this idea, we could make some money, maybe. Anyway, that's just an idea. Keep on programming.

Even though, as I've said on almost every webboard, I am totally against the idea of more than 4 or 5 person qb groups, if you're interested in this idea, contactDavid. Dontcha think a product a month seems a little fast though? Square has 100 ppl working round the clock on FFVIII and it's taking years!

Wow! Issue 4 of QBASIC: the Magazine was undoubtedly teh best collection of qbasic related rants, raves, articles, etc. that I have ever read! I knew that you were going to release Issue 4 today, so I stayed up to get it! Now it's 12:30 on the 15th of November. (Yes...only 39 minutes since you released Issue 4 to the world)! Good job!


Why thank ya! Why can't more of you be like good ol' Pete here? :)

qbasic: the magazine reserves the right to edit any letter recieved for purposes of clarity and space. But you already knew that.

Here's the results of the third survey from Qbasic: The Magazine! We need more people to Vote! So do it!

    Favourite Game     |  Last Month  | Change    
    1. Wetspot 2              1           <> 
    2. Dark Ages              2(t)        <> 
    3. SFB2 (t)               2(t)        D1  
    3. Groov Buggies (t)      -           --
    5. Monospace S. (t)       -           -- 
    Comments: Wetspot 2 did well again. Dark Ages pulls ahead  
       of SFB2, and Groov and Monospace Shooter join the list.

    Favourite Utility  |  Last Month  | Change
    1. DirectQB               1           <>
    2. DashX (t)              2           <>
    2. N!Media (t)            -           --

    Comments: Dqb and Dash were very close this month, with
       N!Media's new version helping it garner votes. QMIDI
       didn't get enough votes to make the list.

    Best Upcoming      |  Last Month  | Change
    1. Dark Ages 2            3(t)        U2
    2. "Project RT" (t)       -           --
    2. Sypharage (t)          -           --
    4. Mario Clone (t)        1(t)        D3
    4. Last of Legends (t)    -           --
    4. Pasco Soccer (t)       -           --
    4. Groov 2 (t)            -           --
    Comments: Dark Ages 2 beat everything by a TON, easily
       pulling back into first. Mario Clone fell from the 
       top spot, and Wrath of Sona didn't get any votes this
       month. Enhanced's new project, "Project RT", got a
       lot of votes despite being top-secret! Wow!

Make your vote count! Vote today by emailing your votes for:
favourite game
favourite utility
Game/utility that you're most looking forward to.
Send your vote here

Must Downloads returns for another showing. This month, we've added Absolute Assembly , by our good friend Petter. Why use DEBUG when this nifty tool exists? We've also changed the recommended qmidi from 2.0 to 4.1. Everyone knows that there are a god-awful amount of qb games out there, but what's worth downloading? These, my friend. Here, you'll find a list of the best of the best in QB progs. See something that should be here? Tell us and we'll check it out. You HAVE to have this stuff!

Absolute Assembly
Petter Holmberg of Enhanced Creations's assembly converter. By typing in normal asm, this proggy will convert the asm into goods that qb will understand. Super-spiffy!

Dark Ages
One of the most engaging QB games ever, as well as one of the only complete rpg's. This was featured in PC Gamer! Check it out!

Groov Buggies
The best QB racer ever. Although it has some control problems and some clipping glitches, this wireframer set a new standard. the Dark Crown
Darkdread best and most complete game. Many hours of gameplay, and featuring a battle system that's been imitated in countless qb projects since. Not to mention you get cat food from the enemies!

Monospace Shooter
Gradius' 2 color side-scrolling space shooter. Featuring very detailed enemies, flickerless animation and a devious AI, this game is a classic

Wetspot & Wetspot 2
Wetspot, the bubble-bobble like block-pushing action game was one of the best QB games when it came out, but W2 is just incredible. Super midi sound, great fast graphics, tons of variety, an insane number of levels...everything you could want. GET this game. Now.

One of only 2 fighters in QB (the other being Sphere Fighter) this newly released game is Super-fab. Even though it's wireframe, it has cool particles and smooth animation as well as rippin' gameplay.

Called the best tile editor in QB ever, PP256 has loads of tile-editing options at your disposal. If you use tiles in your game, you can't live without this.

These 3 sets of libraries take QB graphics to the extreme. They all have strenghts and weaknesses, but you should check them all out before you start a big project. You'll save a lot of coding plus get a big speed increase (and in DQB's case, save memory). Try these now.

This is the best version of Qmidi. Play .mid's in your game! The new qmidi4.1 rules! It has tons of features and a "light" version. Get it now!

Marko Dokic's 3d routines. Phong shading, gourard shading, environment mapping, you name it- it's here. If you want to see how to do good 3d, come here!

Alternate Logic

Alternate Logic, a site run by HAL_8900, is the site to go to if you're lookin' for qb specific tutorials. From beginners to advanced, you'll be able to get whatever you need to do at this resource.

Need to know what bits are, or how to write a tsr in qbasic? You'll find it here. When I say this is the place for qb tutorials, I mean, this is THE place. That's why Alternate logic is the third winner of qb:tm's Site of the Month award.

Back to Top

assembly tutorial

By Petter Holmberg

Assembly language programming tutorial part 2: The basics

Hello again!
The first part of this tutorial was written in a hurry, but this one wasn't, so I hope you will find this one better. Last time I discussed the history of assembler and told you where to use it and not. I also gave you some information about the binary and hexadecimal system, and briefly explained how the base memory is addressed. This background information was needed to give you a good start in the learning process. This time I will teach you the basics of the assembly language and show you how to use it in QuickBASIC. Let's get to it!

How the heck can I execute assembly code in QuickBASIC? This might be the first question you're asking yourselves. How can you make QuickBASIC understand assembly code? Well, you can't. QuickBASIC can only understand regular BASIC expressions. However, it is possible to make QuickBASIC execute snippets of machine language code in a program. Machine language is the only language the processor really understands, and the BASIC code you usually see is translated into machine language instructions when you run the program. But as assembly code basically is machine language represented in a more humane way, all you need is a program that translates your assembly code into machine code, and the knowledge on how to get QuickBASIC to run that machine code.

When converting a program written in a high-level language such as QuickBASIC to machine code, you say that you compile a program. When you do the same with an assembly program, you say that you assemble the program. A program that does this is called an assembler. Do not mix up the expressions here! Now you may be thinking that you don't have an assembler on your hard drive, but that's where you're wrong. All Microsoft operating systems, from MS-DOS to Win98 have a program called DEBUG somewhere. This program was included in Microsoft OS:s as a tool for advanced users, and it has the possibility to convert raw assembly code to machine code and the reverse. This program can be very useful, but it's also very hard to use. Luckily, you won't need to worry about that. I will explain this later.

There are two ways to run machine code in QuickBASIC. I will start by only explaining the first one. QBASIC and QuickBASIC both have a built-in function called CALL ABSOLUTE. With CALL ABSOLUTE you can execute a machine language routine and then return to QuickBASIC. If you use the standard QBASIC, you can use CALL ABSOLUTE directly, but in QuickBASIC it's included in the external library QB.QLB/QB.LIB. So if you use QuickBASIC, you must start it with the syntax QB /L, which includes this library.

To begin with, we will work with DEBUG and CALL ABSOLUTE as our tools to learn assembler. But as I told you, DEBUG is hard to use, so I have created a program that makes everything a whole lot easier. It is called Absolute Assembly, and it can be downloaded at the Enhanced Creations website at this address. The last version was written many months ago, but it works fine. This program releaves you from the pain of using DEBUG manually to create a program. It takes a raw text file with assembly code as input, and through DEBUG generates a snippet of QuickBASIC code in a file of your choice. I'll give you the details as we continue.

The basics of assembler:
Now we are ready to begin discussing some serious stuff: The first steps into the asm world!
First of all: When working with assembler, you mainly process a lot of numbers. These numbers needs to be stored somewhere. You can of course use the memory to store your data, but there's another way to do it: Through registers. Registers are a bit like variables, but they're not stored in the memory as variables are. They are stored in the microprocessor, where they can be accessed instantly and effectively. However, there are only a few of them, so you'll have to use them carefully and keep track of what you're doing with them. Many registers also have special uses, so you can, and must, use them only in certain places. This may seem a little confusing to you right now, but you will soon understand how it works.

There are four basic registers that can be used for almost anything. They are called AX, BX, CX and DX. You can think of these registers as INTEGER variables in QuickBASIC. They are small memory cells that can store a 16-bit large number. If you want to use only one of the two bytes in these registers, you can do so by calling them AH/AL, BH/BL, CH/CL, and DH/DL. The H and L stands for "high" and "low". So you can use only the upper 8 bits of the AX register by calling it AH, and the lower 8 bits by calling it AL. On 386 computers and later, you can also call these registers EAX, EBX, ECX and EDX, and use them to store 32-bit large numbers. I'll better draw this to make you understand it:

Writing data into AL doesn't affect AH, but it affects AX and EAX. The same rules goes for BX, CX and DX. Remember that this is all just different names to access different parts in the same register. As DEBUG cannot handle any 386 or above processor instuctions, we won't be using 32 bits registers as long as we're working with it.

Although these four general purpose registers can be used for almost everthing, they also have special purposes. The A, B, C and D in the registers actually stand for Accumulator, Base, Counter and Data. I will tell you when they should be used when we come to such situations. There are many other registers with more special uses that you will need to learn, but I will return to them later when we need them. Now we're going to learn our first assembly instruction!

MOV, your key to data transfer:
The most common and important assembly instruction is called MOV. Assemblers in most platforms have this instruction. It's purpose is to move or copy values between memory and registers. As you probably guessed, MOV is short for move. Most assembly instructions are three letters long. The name is a little misleading, because moving a value would mean taking it away from the source, but MOV actually copies the value. The general syntax for MOV is:

MOV destination, source

Beginners always tend to mix up the positions of the source and destination with MOV. It may seem more natural to put the source first, but if you think about it you'll see that you do the same in BASIC. (destination = source) The source can be a direct number, a register or a value in the memory. The destination can be a register or a memory position. Here are some exaples: If you want to put the value 8 in the AX register, you type:


If you would like to copy the contents of the CH register into BL, you type:


A thing that you cannot do is to move a 16-bit value into an 8-bit register. An instruction like MOV AL, BX is therefore not possible.

But what about values in the memory? Well, then you must learn to use three new registers: DS, SI and DI.

Accessing the memory:
In order to be able to read and write in the memory, you need the special memory addressing registers. If you want to read data from the memory, you must put the memory address into the two registers DS and SI. Their full names are the Data Segment register and the Source Index register. In DS, you should put the segment address of the memory position you want to read from, and in SI you should put the offset address. How this works was discussed in part 1 of this tutorial.

Suppose you want to copy byte number 18 in the memory into AL. Then you need the following assembly code:


This requires some explaining. First of all: It's impossible to move direct values into the DS register. Don't ask me why, but it has to do with the Intel PC processor architecture. So what you need to do is to put a value in one of the general purpose registers, here I used BX, often used in this situation, and then copy the contents of that register into DS. That explains the first two lines. Next we put a value into the SI registers. Luckilly this can be done directly. We wanted memory position 18, and now the DS register is 1 and the SI register is 2. Remember that the actual memory position is the segment times 16 plus the offset. In our case this gives us 1*16+2=18. The final line copies the byte located at this memory position into AL. The brackets [] around SI means that the computer should fetch the byte at the memory position pointed out by SI, (DS is automatically assumed to hold the correct segment address) instead of the value in SI itself. Without the brackets, the value of SI, 1, would get into AL. Well, actually that wouldn't work because AL is only 8 bits and SI is 16 bits. If it had been the whole AX register it would work though. But with the brackets, the value from memory position DS * 16 + SI is read.

If you want to do the opposite, write to memory, you use DI instead of SI. DI is short for Destination Index. Writing data is done in almost the same way as reading data. If you would like to write the value 5 into the same memory position as we were reading from in the previous example, you type this:


Easy, huh? Here the DS register is also used for the segment address. In this example, AL, an 8 bit register part, is used, and so 8 bits will be written to memory. If you had changed AL to AX, 16 bits would have been written.

Now you should know how to read and write values from/to registers, and how to access specific memory positions. But you can't do much with it yet. Now it's time to learn how to call asm routines from QB!

The interface:
Now you need to have both DEBUG and Absolute Assembly, the two programs I talked about earlier. You won't have to know how to use DEBUG, because Absolute Assembly will do all the dirty work for you. You can use your favorite text editor when creating your assembly routines. Just make sure the code is saved in a file of the standard ASCII .TXT format. When you want the code to be fed into QB, you just start Absolute Assembly. You will be asked to type in the name of the text file containing the asm source, the QB program file to put the code in, and the name of a code string. Make sure you have saved the BAS file in standard ASCII format (this only applies to QuickBASIC users). The code string is a string variable name that is going to be used in the QB program to access the code. If you're making an asm routine to draw pixels on the screen, you should call it drawpixel$ or something like that. You don't have to type in the $ sign in Absolute Aseembly. Next, you will be asked to answer yes or no to two questions. The first asks you if you want to append the code to the basic source file. If you answer no, your BAS file will be cleared before the code is written to it. If you answer yes, the ASM code will end up in the bottom of the file, without erasing its old contents. The other question is if you want to add CALL ABSOLUTE lines to the program. This is a little hard to explain right now. I'll get back to it soon.

Our first assembly routine:
It's time to try writing an asm routine for QB. The first test routine won't do anything, it's just a test. First, start your favorite text editor and type in the following lines:


Now you wonder what this means, but don't worry, I will explain it to you. The first line is a new assembly instruction for you: PUSH. This introduces a new part of assembly programming. PUSH is an instruction used to put values on the stack. What is the stack then? Well, it's a part of the memory that can be used to store temporary values. You will often have the need to keep track of more numbers than there are registers, and the most convenient way to go then is to use the stack. The stack is a place where you can shuffle away values until you want to use them. PUSH is the instruction you use to copy a value to the stack. When you want to get the value back, you use the opposite of PUSH, an instruction called POP. You can see that POP is used on the third line of the program. There's a special register that keeps track of where in the memory the stack is located. It is called the Stack Pointer, or SP. When you use PUSH, the value goes to the memory address in SP. Then SP is changed, so that it points at a new memory position in the stack. This works a little strange. You can think of the stack as a stack of plates. When you use PUSH, it's like you were putting a new plate on the stack. When you use POP, you remove it, revealing the plate underneath. So you must keep order of the values you put on the stack. Consider this example:


First, AX is copied to the stack, and then BX. When the first POP instruction is called, the value that was last put on the stack, the BX value, is returned to AX. When the second POP is called, the first value of AX gets into BX. So this example will actually swap the values in AX and BX, using the stack. If you want the values to get back in the right order, you must POP them in the opposite order:


This would correctly return the values to their original registers. This is a technique called LIFO, and it stands for Last In, First Out. Get it? Of course, there's no reason to PUSH and POP values like in the example above, but if you needed AX and BX for other things between the PUSH and POP calls, you would find it very useful.

There's a strange thing about the stack that you need to know also. The image of a stack of plates is not entirely correct. The value in the SP register is not increased after each PUSH, it's actually decreased! So it would be more like a stack of plates turned upside down, even though earthly physics obviously woldn't accept stacks of plates hanging upside down on the roof. :-) This is not something you need to care about now though.

Now let's return to the test program. As you can see, the PUSH instruction uses a register called BP. This is another new thing for you. BP is short for Base Pointer, and it is a value that points to the base of the stack. So in the plate example, it would point at the plate in the bottom of the stack, (if you ignore the upside down-thing for a while). It's essential that this value stays the same before and after the call to the asm routine, because QB uses it too. So therefore we always PUSH it in the beginning of the routine, and then POP:s it back in the end.

Now we have come to the second line. By now you should understand what it does: It puts the value of the SP register in BP. Now the computer will think that the bottom of the stack is at what's actually the top of the stack. In the next part of this tutorial series I will explain why we do this. After the first two lines, we would be free to write anything, but as we don't want the routine to do anything yet, we'll just POP the old value of BP back and return to QB. The last line with an instruction called RETF, which stands for Return Far, will make sure we get back to the BASIC program.

Let's try this out:

' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '

test$ = ""
test$ = test$ + CHR$(&H55)              ' PUSH BP
test$ = test$ + CHR$(&H89) + CHR$(&HE5) ' MOV BP,SP
test$ = test$ + CHR$(&H5D)              ' POP BP
test$ = test$ + CHR$(&HCB)              ' RETF

offset% = SADD(test$)

' ------ Created with Absolute Assembly 2.1 by Petter Holmberg, -97. ------- '

As you see, there's now a string variable called test$. For each line, numbers (converted to ASCII codes) are added to the string. On the right, you can see the assembly instructions you typed in earlier as comments. For each line, one assembly instruction, or more correctly, one machine language equvivalent to an assembly instruction, is added to the string.

Because we answered yes to the question if we wanted to add CALL ABSOLUTE lines to the BAS file, there are also four other lines under the test$ declaration. the offset% variable gets the offset address of the string test$, and the DEF SEG instruction makes sure the default segment is the segment of the test$ string. (DEF SEG in QB is almost the same as typing MOV DS, BX in assembler)

And now comes the CALL ABSOLUTE call. This line will execute the code located at the start of the test$ string. As this test program doesn't really do anything, you won't see anything happening. Finally, the second DEF SEG resets the default QB segment. Run the program and make sure it actually works! It won't do anything, but just the fact that it didn't crash is enough to make an assembly programmer happy!

This is the end of the second part of my assembly tutorial. I've tried to go slowly in the beginning so that you would understand everything, but now the vital basics of assembler should be crystal clear for you. The next time we can start the fun! I will teach you more assembler instructions, and we will start writing programs that can do something useful, such as manipulating BASIC variables and returning the answer. As the last time, make sure you understand everything I explained in this part, and I'll see you in January!

Have a Merry Christmas and a Happy new Year!

Petter Holmberg

Back to Top

By Enigma

Before diving into what will eventually become a frayed state of confusion on your part, I'd like to point out that these are only my opinions. So, if anything in this article makes you mad or sets you off on a killing spree, please refrain from taking out your personal anger on me, just because my article on QBRPG art made you tiffy.

Let's face the facts, here. Final Fantasy 3 would not have been Final Fantasy 3 if it starred Terra the bolemic stick character. Zelda; Ocarina of Time would not have been Zelda; Ocarina of Time if Link had a Cartman head and neon pink clothes. Art is the first thing that strikes an RPG player's mind, since it's the first thing that shows up onscreen. And the art is one of the factors that keeps players coming back for more. Now it's time for cheesy comparisons. Note: if you've programmed an RPG with crap art, and you know it sucks, then stop reading, because if I don't like you, you're in the next section.

Now, let's set up a scenario here. You go to your favorite QB source. Neozones, QuickBasic RPGs, (god forbid you stoop so low), whatever. For now, I'll stick with the Neozones format. The news page declares a new game in the RPG section. Hey, let's have a little fun with this. The RPG is the FULL VERSION. Not a demo, the full version. Doubtless if it's complete, it MUST be good! It might even be better than DarkAges. So you click on over to the RPG section and find the new entry:

"Mystic World: Sword of the Undead (, 1500k), ?/10, Terranigma Software

I haven't finished playing this RPG yet, so I can't give it a full statement yet. I'll put up a review when I get done with it."

Okay, so Tek hasn't finished the game yet. It must be really good, then! But wait, it's 1500k! You have been living in a cave and have not yet received your fiberoptic new-fangled 56.6k modem. So you spend about an hour d/ling the game. Let's have more fun with this scenario. You spend two hours d/ling the first half, and the download crashes because your server sucks, so you spend another four hours downloading the whole game. Tired, you open up Winzip and open the archive. Wow! Over 600 files! This RPG will be worth the frustration! So you frantically open up the MS DOS prompt, zip over to the directory, scan the EXEs, find and then run the setup. Wow! This screen 0 setup program is actually really cool! It has a windowing function! So you enter your statistics. Yes, I'd like some music. Sound would be nice. Of course I want high detail! Sure, save my settings. So you exit the setup program and run the main EXE, drooling in anticipation.

The company logo, Terranigma, is displayed in rendered glory with an animated fiery background. Not too damn shabby. Spectacular even. The title screen is loaded up. Mystic World, typed up in Tekton 12 font, with the subtext underneath in white Times New Roman, all superimposed over an exploding crater, fire billowing everywhere. And you don't see a trace of the GIF-BSV conversion. You're stunned. New Game, Options, About and Quit, eh? I think I'll go with New Game for now, mess with the little tweaks later. So you press enter, hear assorted clicks and whirrs as the game is loaded up, and the game slowly fades onscreen.

The shock on your face would send a grown adult into hysterics.

It was bad enough they made you sit there for six, count em, SIX hours downloading the game, but then they had the nerve to cover up such crappy art with such a gorgeous intro! Betrayed by the promise of a past-decent QBRPG, you stare in awe at what appears on your screen.

The main character stands in a field of grass. The blue textbox with LINE B borders states, "Where am I?" in basic QB font. Which means, the textbox background is black, therefore making it easier on the programmers so they didn't have to go to the trouble of a decent font engine. Now, if it were only the textbox, you could get over it. But the main character is a stick figure wearing a blue tunic and holding a 5 pixel color 15 sword. If your computer weren't new, you'd punch a hole through the monitor. So, you grin and bear it and continue playing. After getting accustomed to the graphics and the horrible textboxes, you soon come to realize you're playing an epic RPG! The plot elements are really intriguing to you! Wow! This game is DEEP!

Do you see what I'm getting at? A potentially epic QBRPG, ruined by 2 minute graphics. So what's my point? Basically, if you're doing a QBRPG and you suck royally at art, then practice. ALOT. And if you're one of those lazy types who likes other people doing their work, then hire or convince an artist. But for god sakes, people, DON'T release a QBRPG with crappy art. There's enough of those already.

Enigma, horrible ranter and tangent-exploring extraordinaire, signing off...

Back to Top

Rpg Script



Without a doubt nearly every Role-Playing Game you have ever played has used Scripts. In fact most games made today use scripts in some form or other.

So what do Scripts do? And how do you get QBasic to use them? I hope to answer those questions, and even more, in this series of articles. I'll even get into some more creative and advanced things later on [like "compiling" a Script to run faster, and even using a Script to program your program].

What are Scripts?

Well, for example, when you are programming a game such as a RPG you become faced with several problems trying to integrate all the things you want done into one program. Let's say in your RPG you have 17 'worlds' each with several towns and in each town you have several shops for items, weapons and whatnot. Because a single .EXE program that contains every shop within every town in every 'world' would be too large to manage; you can break it apart into small .EXEs for each shop, town and 'world'. This of course can be just as unmanageable, because you end up repeating a lot of the core code you really don't need to. The other way to go is to break that big program apart and only leave in what you would have repeated in all the small programs [graphics engine, sound engine, etc.] and add a new piece I'll call a INTERPRETER. You then take all the details of each room/shop/town/'world' and edit them into a script. This nice smaller .EXE only has to OPEN a Script and INTERPRET it, done correctly it will do the same as both the other larger EXE and the many smaller EXEs.

Scripts have been used for just about everything from the Artificial Intelligence of the monsters in QUAKE to unit abilities in CIVILIZATION II. But the easiest to understand and program for are RPG cut-scenes and NPC dialog, so this is where we will begin.

How to get QBasic to use scripts?

Just imagine for a moment that you created a programming language...Would it be like QBasic?...Or would you make something along the lines of Assembler?...Would the syntax be quite easy to learn?...Or would you need a 1,000 page manual to understand it?

These are some pretty important questions as a SCRIPT can be it's very own programming language. Your main program can basically become an Operating System designed just to run your script files. So before you read on take some time to decide just what you want your SCRIPTS to do...And...Compare this to what you want your game to do.

Okay, we'll begin with a simple SCRIPT format, that can easily be expanded to meet future needs...[some may find what follows very basic...I'm doing it this way just to get everybody up to speed]

To start off you'll need to know what you want your script to do. In this beginning article we only need it to tell the game what the NPCs say.

*******What the script should tell the game: [version 1]
A] What a NPC says when 'talked' too by the player.

That's it for now, it is a simple and good start, as we can build on it later. Okay first off we will need a way to show the player what the NPC says. So in the main EXE we will code a SUB I'll call MESSAGEBOX.

*******MESSAGEBOX will be passed a STRING of text that it will write on the screen, then wait for the player to press a key, before RETURNing.

Okay now that we have a backbone for our Script to operate on, we will now need a brain to read the script. Introducing the INTERPRETER.

The idea behind it is to READ in a Script file and tell the EXE what the Script file wants done. To do this we need to come up with Script language, by creating KEYWORDS and a SYNTAX. It doesn't have to be fancy [yet!] so I'll obey the K.I.S.S. principal [Keep It Simple Stupid]. We will use a KEYWORD followed by perimeters system. So as to allow our Script language to expand we will allow our KEYWORDS to have different STRING lengths as long as a colon follows them [:]. So our first KEYWORD is 'MESSAGE BOX:'. Anything after it will be sent as a text STRING to the MESSAGEBOX SUB. Simple enough.

Our simple version 1 INTERPRETER looks something like this:
[This is untested pseudo code...]



    IF MID$(SCRIPTCODE$,1,1) <> "*" THEN
        IF MID$(SCRIPTCODE$,I,1) = ":" THEN
           KEYWORD$ = MID$(SCRIPTCODE$,1,I-1)
          EXIT FOR
        END IF
      NEXT I
           CASE "MESSAGE BOX"

    END IF


  CLOSE #1


Okay now to explain it...First off we open the Script file, then create a DO-UNTIL loop that will continue until we have no more Script lines to read...We get our first Scriptcode line, and see if the first Character is a astrik ["*"...this will allow you to enter comments]. If it isn't an astrik then the line of code needs to be INTERPRETED. The next FOR-NEXT loop checks the code letter by letter until it finds the KEYWORD separator [a colon ":"]. All of the characters before the colon are entered into the KEYWORD$ variable, everything after is then placed in the PERAMITER$ variable...Then the REAL meat of the INTERPRETER begins, we do a CASE select of KEYWORD$ to find a match. If it matches then the correct subroutine is CALLed and sent the data in PERAMITER$.... This can easily be modified by adding more CASE "KEYWORD" statements to the SELECT-CASE portion of the Subroutine [If you want you can even allow different KEYWORDS to mean the same thing by adding them to the CASE CASE "MESSAGE BOX","MB","M BOX" and so long as it doesn't contain a colon ":"].

To write the Scripts; you can use NOTEPAD, EDIT.COM even QBASIC as long as it can be read as a TXT file your all set. Although you can give it any extension you wish, for these articles, I'll give the Scripts a .TXT extension. Example Script [this should work with our current INTERPRETER]:

*This is the first script!
*The line below will allow us to do a MESSAGE BOX
MESSAGE BOX: What do you think?
*Okay now this maybe simple so far but check back for the 
*next article installment

Next time I'll add a bit more functions to the Script and show you how to do some real Cut-scenes.

Back to Top

Welcome to Qbasic

By Zkman/DarkAges

500 years have passed and civilisation has flourished. The nightmare of Greyor and the hero who defeated him faded from reality to legend, legend to myth. Such a thing could never have really happened, not in a Renaissance such as this where knowledge and goodwill erased all fear and anxiety...

...or so it seemed. Energy is everywhere, it makes possible the universe, the light, the suns, planets, and matter of all sorts. Every living thing possess this energy - it is itself alive. Free to roam the universe, but not anymore... Matter, physical substance, energy trapped within, confined to a prison until the matter consumed back into energy. Such is Tatkraftengel, an immense entity, trapped for an eternity inside the small planet. It is alive, the plants, animals, and weather were more than proof, yet it is confined and forever searching for an escape, back to energy, free to roam the universe again.

It was a myth, few even remembered it. Greyor, greatest mind to have ever existed. Superior to all other humans, only needing to dominate over them as proof. He knew of Tatkraftengel; could feel the lifeforce flowing through the matter, pulsating, angry, seeking escape. Tempted to use this power to become immortal, physically greater than all mankind to complement his intellectual superiority, he tried to harness the power. Tatkraftengel would have escaped and all life would convert back to energy, but one hero defeated Greyor and sent him back to Tatkraftengel.

So the years passed, and lifeforce was again discovered as the arcane arts were perfected. Experiments were made in manipulation, each more dangerous than the next. It was only a matter of time before one went wrong, one that would give Tatkrafengel its release.

It was in the Floating City of mages that it occurred - more drastic than ever imaged. So many mages sacrificed their lives to close the gap, but it was too late, something had escaped. It wasn't Tatkraftengel and in its anger he destroyed the land. Mountains collapsed, other rose high from the depths of the ocean. Continents split to islands, jungles turned to deserts, cryptic pits opened and spewed forth diseased and disfigured creatures who ravaged the countryside. Yet Tatkraftengel remained entombed, but something did escape. Greyor, driven mad and sent again to perform his suicidal task, once again roamed free to search for a way to release the energy that is Tatkraftengel.

It is here where the adventure begins...

Dark Ages II, one of the biggest games of 1999 (currently release is schedueled for march, but I'd estimate sometime in Late Spring) and the sequel to qb's top rpg, Dark Ages, is almost here, and we've got all the top secret info you could want! As you've just read, a wonderful story is in place for this next installment, in addition to high-quality svga graphics, a new asm pixel by pixel scrolling engine, more hours of gameplay, a totally refined battle engine and more! Read on!

The plot
The plot of the Dark Ages II is definitely NOT a standard "visit a dungeon and kill a dragon" setup, but rather, as DA1 creator and DAII lead programmer put it, "a plot that can really make you think and a mystery to solve. It is in DAII that I can really put my talents to use". Many of the places from the first story have remained intact, but you may not recognise them at first. For instance, the floating City of Mages is still there, but it is predicted that there will be great peril because of the "accident" that has let Greyor free.

Feeling excited already? Did I mention that Michael is predicting an average playtime for intermediates of 15-20 hours?! That's twice as long as the original! Life is good. Also, the story for Dark Ages 2 will be less linear that the original. Expect the NPC's to befriend or hate you based on your prior actions, some gambling, and lots of subquests to choose. This will be one of those games that you play a second and third time, just to see what else there is to do.

The fight
One of the biggest complaints about the original Dark Ages was the battle engine. It was, admittantly, very antiquated, being pretty much like the engine behind Legend of Lith and Lianne in the Dark Crown. The new engine is TOTALLY revised. In what ways? How about 3 character parties? Yup, in DA2, there'll be three characters in your party, a warrior, mage and thief, to partake in your fights.

The fights will be, as Michael put it, "battlefield style, but without the tediousness of an oversized battle area." Instead, you'll be doing a lot of melee fighting. And not just swinging one of the many swords and shields either; there will be "fighting abilities" that can be honed with training and also 25-50 spells for the magician in all of us. Strategy will be key. In DA2, you'll have to think to win.

Your enemies will not all be "generic fantasy monsters" either. Human foes will be very common, and depending on who you fight, the odds could be up to 6-1 against you! Tread carefully. But here's one of the coolest ideas of DA2 (and rpg's in general!) - when your characters spot an enemy, you will have the option of attacking it or going around! Doesn't it make sense that if you were weak and saw a huge dragon, you'd try to sneak past it? This won't always work, however. Surprise attacks will occur, mostly in dark forests and mountain passes, which also makes perfect sense. Why don't games like FFVII use this technique?

The specs
Dark Ages 2 will not only play better than the original, but look and move better than the old too. Elemental is doing assembly routines for the game, one of which is a pixel by pixel scroller capable of going 1200 frames per second on a P200! Insane! Another team member, Gavan, is handling the art for DA2, which, as you can see from the screens in this article, definitely looks more professional than DA1. Expect to see 1000+ tiles in the final game, all drawn in lovely 640 by 480 by 256 colors. Qb:tm estimates that DA2 will be the first qb game to be released using svga.

Enigma, artist extraordinaire and writer of the Art article in this issue, will be doing anime-style cutscenes for the game, which will have a look much like what was seen in the Dark Ages 2 preview movie (address is below). Sylvain Mercure of Quebec will be creating a Windows installation program and also be doing a French Translation! Next spring, I'm gonna be the first one lined up to download DA2, and as this month's Survey shows, there're lotsa people who agree. If you need more DA2 action, haul your browser to the official "Dark Ages 2: Tatkraftengel" web site.

Back to Top

By Thav

A lot of people have had problems with memory in qbasic. Everyone has at least once in their programming career. Memory problems can be fixed if you know what could possibly be causing the problem. A lot of people have asked me questions about "Out of stack space" and "Out of memory" errors. This short article will clarify some things about these two errors.

Your program will use stack space, there's no way of getting around this. Every program that you execute has a runtime stack. This is where all of the program information is stored. No variables or data is stored in this stack, but there is a bit of information that is pushed onto it every time you run your program.

An entry on the runtime stack is called an Activation Record. An activation record is pushed onto the stack every time your program is run or calls a function or sub. Activation records contain information used by the computer to pass variables and to return to the right place. An activation record contains information about the function you call: a return address, any paramaters, and any return variables. A return address is something used by the computer to control program flow. Think of this as a line number, say you call your sub Compute on line 156, an activation record is pushed onto the stack, with a return address of 157 in it. So when Compute is done running, your program will return to line 157. Let's say Compute has some paramaters: Compute (first%, last%). First% and Last% will also be stored in the activation record. Return variables are also stored in the activation record. When Compute is called, space is allocated for a return variable. When Compute is done, it's activation record is popped off the runtime stack and that variable is returned. This is basically how a runtime stack works. It may not be exactly, because it's been a while since i've looked at the runtime stack. When you run your program and get an "Out of stack space" error, you have blown the stack. You blow the stack when you try to push another record on the stack and there's no more room. The way you can do this is to have a recursive function, a function that keeps calling itself, that doesn't have an exit condition.

SUB BlowStack()
CALL BlowStack()

That little code will effectively blow the stack. It keeps calling itself and doesn't have an exit condition, so an activation record will be pushed on the stack until the program runs out of space. The easiest way to keep a recursive function from using up all the stack, is to not have them. So unless you really know how to work recursive functions, I'd suggest staying away from them.

There are a few rules to follow to avoid out of stack space errors:

I've never needed more than the default stack space. You can always use CLEAR to make more room for the stack. Just remember, no variables go on the stack, so if you get Out Of Memory errors, using CLEAR won't solve that.

Now for a stint about variables. Aaah variables, the best invention since fire. But how much do we really know about variables. Look out, because I'm about to spill all the info I know about variables.

There's the basic definition of variables, but how can you use them more effectively? Depends on what you want to do. Here's some fairly advanced data structures:

These guys are fun. They're basically a collection of variables under a certain name. For example: DIM scores(0 to 100) AS INTEGER will give you 100 integers. The way you access each of these variables is by subscripting them. scores(57) will give you the 57th score in your array. You can use another integer variable inside the subscript. scores(topscore) would give you the entry that corresponds with topscore. The variable inside the subscript has to be an integer by convention.

Commonly refered to as records, typedefs (in C) or classes(in C++). They are a collection of different types of variables under one name. Here's an example:

TYPE PersonalInfoType
  theName as string * 10
  age as integer
  address1 as string * 20
  address2 as string * 20

This is a collection of data all rolled into one name. They are especially nice for having a collection of global data. Here's a snippet on how you use a type:

DIM info AS PersonalInfoType

info.theName = "John"
info.age = 23

Pretty easy huh? Well, you can add those into arrays and have all kinds of fun.

DIM info(3000) as PersonalInfoType

Now we have 3000 personal info type thingies. How can we access each part? info(55).theName = "Jane". Just like accessing an array and a type at the same time.

Now that I've told you a bunch of neat stuff that you probably knew, how can you use this to your advantage? There are a few techniques that I use to expand the use of my variables:

String * 1. Ahh the byte. Such a use for it. I've found these to be useful for ASCII flags. using an ascii character set, you can have roughly 255 different values for it. You can also use them if you only have a number from 0 to 255. Let's say you have some file format that uses numbers in this range. Instead of using a 2-byte integer, you can use a String *1, and just use ASC(byte) to get it's value. Using this technique is good for bsaving arrays and such.

Types. I use these alot when I have a lot of data I want to use globally. For instance, in Medallion, I have a global data type called GameInfo, which contains the current map, the old map, and other values pertaining to the world. I also have one for the character, and other parts of the game. It makes it easier than typing DIM SHARED variable AS type...

Well, I guess that's about it for this article. There are a lot of other topics I could cover, but my time is pressed. I hope you enjoyed and got some information out of this. Good Luck.

Raul Carolus

Back to Top

next issue

By ZKman

Thanks for reading Issue 5! Next issue is coming on 16 January, so mark your calenders! We should have the next asm article, another part of the RPG script series, possibly an art article and more! Yeah, baby! Also, don't forget to register your QB company with the Visionaries Exchange. Till next time... END

Back to Top