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.

The information here is for those newbie's, moderate programmers', and the experts alike. Know one knows everything there is to know about CONs, or any computer programming language for that matter. Everyone has their own style and their own ideas, and that leads to the limitless possibilities.

These FAQs are not a solid piece of work - it is an ever changing, ever evolving compilation of the DukeC programming language - the unofficial name of the CON language. If any new information is brought to my attention, or something that needs to be fixed, I will post updates. You can reach me at http://www.msleeper.com/ as that is my personal site/blog.

Enjoy!

Part 1 | Part 2 | Part 3

Actor Part 1

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.

 


Actor Part 2


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.

 


Actor Part 3


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.