WRITING A BASIC ACTOR v1-01-2003 Release 2 © RTCM
Reaper_Man
/!\ Attention: These FAQ's where written before the development of EDuke, many limits do not apply anymore. These CON FAQs was written because I believe that there is no one place where CON information can be found - from the
simplest ideas to the most complex effects. After the fall of Dukeworld and 3D Portal, a web-edition of the CON FAQ seems to have been lost, and that leaves Duke RTCM as the only other source of CON information. Though they do have information on CONs and how to edit them, I don't
believe that this information is easily understood by the newest of newbie's. One of the simplest and easiest actor to understand is the trash can found in levels like e1l1. Actor RUBBERCAN is such a simple creation, and is going to be the topic of our first installment of "Writing A Basic Actor". Since we have covered actions, movement, and a basic understanding of artificial intelligence, we're ready to look at out first basic actor - actor RUBBERCAN: action RUBCANDENT 1 1 1 1 1 actor RUBBERCAN WEAK ifaction RUBCANDENT { ifactioncount 16 { strength 0 action ANULLACTION break } } else ifhitweapon { ifwasweapon RADIUSEXPLOSION { state rats ifrnd 48 spawn BURNING debris SCRAP3 12 killit } else action RUBCANDENT } enda Ok, that looks a little difficult, so lets look at or friend actor RUBBERCAN one piece at a time. The first thing we have is the actor primitive. The actor primitive is what starts the whole process in writing an actor. actor RUBBERCAN WEAK Once the actor primitive has been started, the next item is an art tile definition. This means that RUBBERCAN stands for a number, and that number is the tile on which actor RUBBERCAN has it's first frame. The next thing on the line after actor is the amount of health the actor starts with. Here, actor RUBBERCAN starts out with WEAK health. In most cases, like this, the health will be pre-determined. Then, for our first lines of code: ifaction RUBCANDENT { ifactioncount 16 { strength 0 action ANULLACTION break } } Translation to pseudo-english: if i'm playing the action RUBCANDENT { if the action has been playing for 16 counts { set my strength to 0 start the action ANULLACTION break out of the current block of code } } If that didn't clear the code up any, then what this is doing here is asking the actor RUBBERCAN if it is playing the action RUBCANDENT. If it is, then it gives itself a strength of 0, starts playing the action ANULLACTION, and breaks from the current block of code. This sets the actor up to be ready to play the RUBCANDENT action again. After that bit of code, we come to the next part: else ifhitweapon { ifwasweapon RADIUSEXPLOSION { state rats ifrnd 48 spawn BURNING debris SCRAP3 12 killit } else action RUBCANDENT } Translation to pseudo-english: if i'm not playing the action RUBCANDENT if i've been hit by a weapon { if the weapon was a RADIUSEXPLOSION { cut to the statement 'rats' if a number between 0 and 48 is randomly picked spawn the actor BURNING make 12 pieces of debris of type SCRAP3 delete myself from the game } if the weapon wasn't a RADIUSEXPLOSION start playing the action RUBCANDENT } If the action RUBCANDENT isn't playing, what the code does here is doing is ask the actor RUBBERCAN if it was hit by a weapon. If it was and the weapon was a RADIUSEXPLOSION, then state the "rats" statement, about 20% of the time spawn the BURNING actor, make 12 pieces of SCRAP3, and kill itself. If the weapon wasn't a RADIUSEXPLOSION, then it plays the action RUBCANDENT instead. Lastly, the actor cuts to the enda part of the actor primitive and ends our friend actor RUBBERCAN's code. You can change the way actor RUBBERCAN behaves in the game by changing a few lines. First, lets change how it acts while it's playing: Change the lines... ifaction RUBCANDENT { ifactioncount 16 { strength 0 action ANULLACTION break } } To something like this... ifaction RUBCANDENT { ifactioncount 32 { strength 0 action ANULLACTION break } else { debris SCRAP1 3 debris SCRAP3 3 } } The first thing we do is change ifactioncount 16 to ifactioncount 32. This simply doubles the length of time it takes to cut from the RUBCANDENT action and get back to the normal action. The next thing we change is add in another else statement under ifactioncount 32. This will make the actor RUBBERCAN constantly make 3 pieces of SCRAP1 and 3 pieces of SCRAP3 until the action RUBCANDENT has been playing for 32 counts. For our next change, we'll change the way actor RUBBERCAN behaves when it has been hit by a weapon: Change the lines... ifhitweapon { ifwasweapon RADIUSEXPLOSION { state rats ifrnd 48 spawn BURNING debris SCRAP3 12 killit } else action RUBCANDENT } To something like this... ifhitweapon { ifwasweapon RADIUSEXPLOSION { state rats ifrnd 64 spawn BATTERYAMMO ifrnd 64 { ifrnd 128 spawn BURNING else spawn BURNING2 } debris SCRAP1 8 debris SCRAP3 8 killit } else action RUBCANDENT } The first thing we did was add in this line under ifwasweapon RADIUSEXPLOSION: ifrnd 64 spawn BATTERYAMMO What this does is spawn in the actor BATTERYAMMO about 25% of the time. The actor BATTERYAMMO is ammo for the Chaingun. The next lines: ifrnd 64 { ifrnd 128 spawn BURNING else spawn BURNING2 } This makes the actor RUBBERCAN want to spawn in some fire about 25% of the time, and there is a 50/50 chance of it picking BURNING or BURNING2. If you understand all of that, lets move on to our next section, where we write our own, brand-new actor - the VODKA actor.
Since we've looked at our friend RUBBERCAN, lets try writing our own actor. We'll make a bottle of vodka that explodes when the player shoots it, but gives him health if he comes close enough and presses spacebar. Our code would look something like this: define VODKA (tile number) ... actor VODKA 0 sizeat 32 32 cstat 257 ifphealthl MAXPLAYERHEALTH ifcansee ifp pfacing ifpdistl RETRIEVEDISTANCE ifhitspace { addphealth 15 soundonce DUKE_DRINKING killit } ifhitweapon { spawn EXPLOSION2 sound PIPEBOMB_EXPLODE lotsofglass 12 ifrnd 64 { ifrnd 128 spawn BURNING else spawn BURNING2 } killit } enda Though our actor VODKA looks like it, it's not as hard as it seems. Let's break him down one bit at time. First thing, we have a define primitive. The define primitive is really simple, it defines a number value to a keyword. In this case it is defining a tile number for VODKA, the name of our actor. Next we have the beginning of the actor primitive. The actor VODKA starts out with 0 health, since it only takes damage once before it explodes! After we start our actor, we get into the meat of user-written code: sizeat 32 32 cstat 257 The sizeat primitive is also simple, it instantly changes the X-size of the actor to (value 1) and the Y-size of the actor to (value 2). Then we have my favorite primitive, the cstat primitive. The cstat primitive is used to change physical information about the actor. In this case it makes the actor solid and able to block projectiles - bits 1 and 256 were added together. Next up, we have the part of code that gives the player health: ifphealthl MAXPLAYERHEALTH ifcansee ifp pfacing ifpdistl RETRIEVEDISTANCE ifhitspace { addphealth 15 soundonce DUKE_DRINKING killit } Translation to pseudo-english: if the player's health is lower than MAXPLAYERHEALTH if i can see the player if the player is facing me if the player is closer than RETRIEVEDISTANCE if the player presses spacebar { add 15 health to the player play the sound DUKE_DRINKING once delete myself from the game } What this code here is doing is making sure that a list of parameters are met - the player has less that 100 health, the bottle is visible, the player is looking at the bottle, the player is close enough to the bottle, and that the player presses space. When all of this is happening, then the code in the brackets is executed. Our next block of code is what controls the explosiveness of the VODKA actor: ifhitweapon { spawn EXPLOSION2 sound PIPEBOMB_EXPLODE lotsofglass 12 ifrnd 64 { ifrnd 128 spawn BURNING else spawn BURNING2 } killit } Translation to pseudo-english: if i've been hit by a weapon { spawn the actor EXPLOSION2 play the sound PIPEBOMB_EXPLODE make 12 bits of glass if a number between 0 and 64 is randomly picked { if a number between 0 and 128 is picked spawn the actor BURNING if another number is picked spawn the actor BURNING2 } delete myself from the game } The code here asks the VODKA actor if it has been hit by a weapon. If it has, then it spawns the actor EXPLOSION2, plays the sound PIPEBOMB_EXPLODE, makes some glass shards, and has a 25% chance of spawning either BURNING or BURNING2. After that block of code, the VODKA actor cuts to the enda part of the actor primitive, and closes the code for the VODKA actor. Now that you can write and understand your own simple actor, lets move onto the next step in writing basic actors - writing an actor with artificial intelligence.
Alright, we've looked at actor RUBBERCAN, a simple actor that either changes action or blows up when it has been hit. We've also made our own actor, actor VODKA, who gives the player health when he is close enough and presses space, or explodes when it is shot. Our next actor we'll be writing will be a follow-up to the AI Info section earlier in the FAQ. Writing artificial intelligence is a hard and tedious project, but the payout for writing good AI is worth the hassle. Here, we're going to write our own actor, actor ATTACKDOG. Lets take a look at what his code might look like: define ATTACKDOG (tile number) define DOGSTRENGTH (value) ... action ADOGWALK 0 4 5 1 12 action ADOGRUN 0 4 5 1 8 action ADOGATTACK 20 3 5 1 8 action ADOGDYING 35 4 1 1 16 action ADOGDEAD 39 1 action ADOGFROZEN 0 1 5 move DOGWALKVELS move DOGRUNVELS move DOGSTOPPED ai AIDOGSEEKENEMY ADOGRUN DOGRUNVELS seekplayer ai AIDOGFLEE ADOGRUN DOGRUNVELS fleeenemy ai AIDOGATTACK ADOGATTACK DOGSTOPPED faceplayer ai AIDOGWANDER ADOGWALK DOGWALKVELS randomangle state dogseekstate ifai AIDOGSEEKENEMY { ... } else ifai AIDOGFLEE { ... } else ifai AIDOGWANDER { ... } ends state dogattackstate ifactioncount 3 { ... } else { ... } ends state dogdying ifactioncount 4 { ... } ends state checkdoghit ifdead { ... } else { ... } ends state dogcode ifai 0 { cstat 257 ai AIDOGWANDER } else ifaction ADOGDYING state doydying else ifaction ADOGDEAD { ... } else { ifai AIDOGSEEKENEMY state dogseekstate ifai AIDOGFLEE state dogseekstate ifai AIDOGWANDER state dogseekstate ifai AIDOGATTACK state dogattackstate else state checksquished } ifhitweapon state checkdoghit ends useractor enemy ATTACKDOG DOGSTRENGTH ADOGWALK state dogcode enda Ok, thats _really_ fucking brief, but I don't feel like writing the AI for an actor that isn't really that useful. The guts of the code is missing, but the general idea is there, so that we can dissect the code and get what we need out of it. First thing, we have two define primitives, defining a tile number and health value for our dog. The health definition isn't necissary, but it is useful if you have the same health for a few different actors. Next, we have the first group of code - action, movement, and AI definitions. As you should know, this is simply defining animations, speeds, and AI routines that our actor will preform sometime during it's lifespan. Then, we have the meat of our code. We have several define primitives. The define primitive is used for a few reasons. For one, if you have some code that get calls often, such as gore spawning, it would be much easier to type state generic_jibs then all of the lines of code within that state. The define primitive is also used to keep similar code together, such as in our ATTACKDOG actor. This is very useful when you have several AI definitions for an actor, and the actor is supposed to preform different code depending on it's AI routine. Toward the bottom of the ATTACKDOG code is this: else { ifai AIDOGSEEKENEMY state dogseekstate ifai AIDOGFLEE state dogseekstate ifai AIDOGWANDER state dogseekstate ifai AIDOGATTACK state dogattackstate else state checksquished } Depending on the current AI being preformed, the actor will use different code statments. Lastly, we have the useractor primitive. The useractor primitive is best used when you want to create a new enemy. useractor is better than actor because when you use actor, the actor will be "alive" as soon as the map starts up. If you use useractor, the actor will only become "alive" when the player spots it. I know that was a brief overview, but the actor/useractor primitive isn't complex. It's the code that lies in it that makes the actors act certain ways.
|