Coding-tutorial

From The Powder Toy
Revision as of 04:31, 26 January 2014 by Argentum (talk | contribs) (Step One: Defining the Element)
Jump to: navigation, search

This tutorial is in progress (not finished)


This tutorial will give you guidelines on creating an element in The Powder Toy. We will use triclops200's heater element in this example. The color will be the same as the HEAT element, and it will be an indestructible solid in the special menu, that transfers heat quickly. Updated to latest source by cracker64 (and now again by jacob1), if you have any problems please post a thread on the forums


It's not as simple as typing the name, color, and features, but it's almost that easy.


With tpt++, it is now much easier to create new elements, without needing to add extra lines to powder.h. Everything is done in the one new element file now.


Part One: Defining the Element's Properties

Step One: Defining the Element

Create a new element file, (HETR.cpp for this example, or whatever name your element will be) inside of src/simulation/elements. (or, i'll probably have people download mine when this guide is finished)

Open ElementClasses.h in the editor of your choice (Visual Studio for windows users). ElementClasses.h is located in the "Header Files" folder of the Solution Explorer. Double click it to open it up. You will see a file that should have a big list of #define PT_XXXX Y. Scroll down until you reach the end of this list of statements that follow this format. Remember this number. You will need to enter a number greater than this one into the .cpp file you create for your new element.

You do not need to edit ElementClasses.h yourself anymore, this file is generated automatically when generator.py is ran. If you use visual studio, you have to run this file manually every time you make a new element. The Visual Studio compiling guide should have told how to do this.

Step Two: Defining the Element's Primary Properties

Now, open up your new element file. It already has all the values filled in, but if you don't want to make HETR (you probably don't), you should edit them. There are descriptions for all of them inside the file, and some include examples of the ranges for the values. The descriptions are repeated here:

Property name Function
Identifier Identifier string, used for lua and internal stuff. Formatted like DEFAULT_PT_XXXX
Name Name, it is recommended to use 4 letters, but less works. 5 or more will probably not fit on the buttons
Colour Default color, in hexadecimal (RRGGBB). In the deco editor, it will tell you the color in hex, ignore the initial FF, and use the 6 digits after for this
MenuVisible If it's visible in the menu, if 0 then it is hidden and can only be produced with reactions or lua
MenuSection The menu section it's in, see src/simulation/SimulationData.h for a list, but you can probably guess their names on your own
Enabled If 0, it cannot be created or seen in any way, not even lua. It will just disappear if somehow created. Used for removed elements, you should leave this 1
Advection How much the particle is accelerated by moving air. Normally 0 for solids, and up to 1 for other elements. It can be negative, ANAR and DEST do this so it goes towards pressure
AirDrag How much air the particle generates in the direction of travel. Generally is very small, 0.04f creates a lot of positive air (- creates negative pressure).
AirLoss How much the particle slows down moving air (although not as big an effect as a wall). 1 = no effect, 0 = maximum effect. Solids are generally above 0.90f, along with most other elements too
Loss How much velocity the particle loses each frame. 1 = no loss, .5 = half loss. Solids have it at 0. Only a few have it at 1, like energy particles, and old moving sponge.
Collision Velocity is multiplied by this when the particle collides with something. Energy particles have it at -0.99f, everything else is -0.01f or 0.0f. This property does not do much at all.
Gravity How fast the particle falls. A negative number means it floats. Generally very small, most gasses are negative, everything else is usually less than 0.04f
Diffusion How much the particle "wiggles" around (think GAS or HYGN). Set at 0, except for gasses, which is a positive number. Up to 3 (or higher) for a large amount of wiggle, GAS is 0.75f, HYGN is 3.00f
HotAir How much the particle increases the pressure by. Another property only for gasses, but VENT/VACU have theirs at (-)0.010f. An extremely small number, sometimes as small as 0.000001f
Falldown How does the particle move? 0 = solid, gas, or energy particle, 1 = powder, 2 = liquid.
Flammable Does it burn? 0 = no, higher numbers = higher "burnage". Something like 20 is WOOD, while C-4 is 1000. Some are a few thousand for almost instant burning.
Explosive Does it explode? 0 = no, 1 = when touching fire, 2 = when touching fire or when pressure > 2.5. Yes, those are the only options, see FIRE.cpp or somewhere in Simulation.cpp to modify how they work
Meltable Does it melt? 1 or higher = yes, 0 = no. This is actually only used when heat sim is off, to make it actually melt use element transitions
Hardness How much does acid affect it? 0 = no effect, higher numbers = higher effect. Generally goes up to about 50
Weight Heavier elements sink beneath lighter ones. 1 = Gas. 2 = Light, 98 = Heavy (liquids 0-49, powder 50-99). 100 = Solid. -1 is Neutrons and Photons
Temperature What temperature does it have when created? Temperature is in Kelvin (Kelvin = degrees C + 273.15). R_TEMP+273.15f gives room temperature
HeatConduct 0 - no heat transfer, 255 - maximum heat transfer speed
Description A short one sentence description of the element, shown when you mouse over it in-game
State What state is this element? Options are ST_NONE, ST_SOLID, ST_LIQUID, ST_GAS. Powders use solid, energy particles and some other random things use none
Properties Does this element have special properties? Properties are listed in src/simulation/Element.h, you at least need to have the correct state property. If you want it to conduct electricity, be sure to use both PROP_CONDUCTS and PROP_LIFE_DEC
Update The update function, todo: description
Graphics The graphics function, todo: description, + description of transitions too

Properties: There are 5 properties for the different states:

TYPE_PART(powders), TYPE_LIQUID, TYPE_SOLID, TYPE_GAS, and TYPE_ENERGY. You should pick one of these to use for your element.

If your element conducts electricity, use PROP_CONDUCTS and PROP_LIFE_DEC.

PROP_DEADLY makes your element kill stickmen.

PROP_HOT_GLOW makes your element glow when hot, like metl does.

PROP_RADIOACTIVE makes your element radioactive.

There are a few more properties, you can find out about them in src/simulation/Elements.h

This is a lot to handle, and if you feel overwhelmed by some of the choices, try looking at elements similar to what you are creating and base the value off of that. The following values are an example of what your code is supposed to look like. The color of heater will be the same as the HEAT element, and it will be an indestructible solid in the special menu that transfers heat quickly.

END OF EDITING, THE REST BELOW IS OLD

 {"FIGH",	PIXPACK(0x000000),     0.5f,	0.00f * CFDS,     0.2f,      1.0f,	0.0f,    0.0f,    0.0f, ...
 {"HETR",    PIXPACK(0xFFBB00),    0.0f,    0.00f * CFDS,    0.90f,    0.00f,    0.0f,    0.0f,    0.00f,
 0.000f  * CFDS,    0,    0,      0,  0,    0,    1,    1,    100,    SC_SPECIAL,        22.0f+273.15f,   251,
 "Heats objects it touches", ST_SOLID, TYPE_SOLID, NULL, NULL},
 };

OLD: Step Three: Defining the Element's State Changes

In powder.h, ~line 828 (or at the end of elementdata.c) Find:

 
//	    if low pressure		if high pressure		if low temperature	if high temperature
 // Name	    plv		plt	 	phv	pht			tlv	tlt	  	thv	tht
 /* NONE */ {IPL,	NT,		IPH,	NT,			ITL,	NT,		ITH,	NT},
 /* DUST */ {IPL,	NT,		IPH,	NT,			ITL,	NT,		ITH,	NT},

This part of the code is new as well, it replaces the old states table and replaces it with a transition table. This means you will define when the element changes into another. For example WATR, it will freeze at 273.15K, so in this table it has a transition at a LOW temp of 273.15, and will turn into ICE. Similarly, water boils at 373, so it will have a transition at a HIGH temp of 373. Here is the line for WATR:

	/* WATR */ {IPL,	NT,			IPH,	NT,			273.15f,PT_ICEI,	373.0f,	PT_WTRV},

This table now also has pressure transitions, such as ICE breaking into SNOW under pressure, this is done the same way, there is a LOW pressure change, and a HIGH pressure, here is the line for ICE:

	/* ICE  */ {IPL,	NT,			0.8f,	PT_SNOW,	ITL,	NT,			233.0f,	ST},

As you can see, there is a HIGH pressure transition of 0.8, which means if the pressure goes above 0.8, then it will turn into SNOW.

NOTE: For an element that does NOT have a transition at high/low pressure/temp, please follow the same format as the others and use IPL,IPH,ITL,ITH and NT.

lets add heater, this is simple because for our simple heater, it has no transitions, so everything should be IPL,IPH,ITL,ITH and NT.

    /* FIGH   */ {IPL,	NT,	IPH,	NT,	ITL,	NT,	ITH,	NT},
    /* HETR  */ {IPL,	NT,	IPH,	NT,	ITL,	NT,	ITH,	NT},


Step Four: Defining the Element's Special Properties

At this point, you would be able to compile, and the HETR would show up in the menu and you can place it, BUT it doesn't do anything! Now for the part where we actually code what the element does. Make sure to save powder.h.

This is also where new element creation is different from before, if you look inside the src folder, you will now see an elements folder. Inside here is a *.c file for each major element.

Now we need to make a hetr.c file for our new heater element, if using visual studio, you should be able to right click on the source folder inside the project, and create a new file, and name it hetr.c Once you have a blank hetr.c created and it is included in the project, we need to add a few things to this file.

 #include <element.h>
 
 int update_HETR(UPDATE_FUNC_ARGS) {
 
 	return 0;
 }

Before we go on with the actual code, we need to finish up a few things first so that the code actually knows there is a new update_HETR function. Go back to powder.h at line ~250. You will see lots of int update_(UPDATE_FUNC_ARGS); This list is sorted alphabetically so lets put in our new HETR function.

 int update_GOO(UPDATE_FUNC_ARGS);
 int update_HETR(UPDATE_FUNC_ARGS);
 int update_HSWC(UPDATE_FUNC_ARGS);

Remember that function variable in the ptypes array? we need to let it know that HETR has a special function to use, instead of NULL. Replace NULL with &update_HETR.

 ... "Heats objects it touches", ST_SOLID, TYPE_SOLID, &update_HETR, NULL},

Now that our new HETR function will be called properly, we can go back into hetr.c and finish it up. NOTE: Put all code BEFORE the return 0; line so when it finishes running, it will go back to the main code. If you kill the particle from inside the function, please return 1;. Our HETR element will not die, so you don't have to worry about that.

Now we need to go over some useful ways of detecting particles, so that we can heat them.

 
for(rx=-1; rx<2; rx++)
     for(ry=-1; ry<2; ry++)

This code simply loops, generating coordinates in rx and ry to find neighbours, in this case, it is a 3x3 grid around the center one. If you are having trouble getting this, try thinking about rx and ry, as a radius around the current particle.

 for(rx=-2; rx<3; rx++)
     for(ry=-1; ry<2; ry++)

This would make the grid affected 5x3

 for(rx=-1; rx<2; rx++)
     for(ry=-2; ry<3; ry++)

And this would flip the dimensions.

Add this:

 if(x+rx>=0 && y+ry>0 &&    x+rx<XRES && y+ry<YRES &&
                pmap[y+ry][x+rx] &&
                (pmap[y+ry][x+rx]&0xFF)!=PT_HETR&&
                (pmap[y+ry][x+rx]&0xFF)!=0xFF)
        {

Your entire code should look like this so far:

        for(rx=-1; rx<2; rx++)
            for(ry=-1; ry<2; ry++)
 1               if(x+rx>=0 && y+ry>0 && x+rx<XRES && y+ry<YRES &&
 2               pmap[y+ry][x+rx] &&
 3               (pmap[y+ry][x+rx]&0xFF)!=PT_HETR&&
 4               (pmap[y+ry][x+rx]&0xFF)!=0xFF)
 5       {

1st line: If the current grid particle around the HETR pixel is within the screen, AND

2nd line: there is a particle in that point, AND

3rd line: that particle is not a HETR, AND (note: != means not equal)

4th line: that particle is not a wall,

5th line: THEN, do some code


Now add this:

r = pmap[y+ry][x+rx];

It means you can just type 'r' instead of 'pmap[y+ry][x+rx]'. This will simplify code later on. NEW: because our hetr.c file is separate, we need to initialize these variables we are using inside hetr.c, add this as the first part of the update_HETR function.

 int r, rx, ry;

Now add

 if(parts[r>>8].temp + (parts[r>>8].temp*0.2f)<=MAX_TEMP)
{

Let's analyze.

1) if (a) { b }

IF a is true, THEN b happens

2) parts[r>>8]

The currently selected particle (the one that's not HETR or a wall)

3) .temp

means it's temperature.

In English, the statement reads:

IF the particle's temperature + 20% of it's temperature is less then then the maximum temperature possible, THEN...


This ensures that when increasing the temperature, it won't go over the maximum temperature. Now add this:

 parts[r>>8].temp += parts[r>>8].temp*0.2f;

In English,

Add 20% of the particle's temperature to itself.


Now add:

} else {
     parts[r>>8].temp = MAX_TEMP;
 }

IF the particle's temperature + 20% of it's temperature is less then then the maximum temperature possible, THEN add 20% of the particle's temperature to itself, ELSE Set temperature to the maximum temperature.


Now close the two brackets we used for the if statements to complete the section. The entire hetr.c should now look like this:

#include <element.h>
 
 int update_HETR(UPDATE_FUNC_ARGS) {
    int r, rx, ry;
    for(rx=-1; rx<2; rx++)
        for(ry=-1; ry<2; ry++)
            if(x+rx>=0 && y+ry>0 && x+rx<XRES && y+ry<YRES &&
                pmap[y+ry][x+rx] &&
                (pmap[y+ry][x+rx]&0xFF)!=PT_HETR&&
                (pmap[y+ry][x+rx]&0xFF)!=0xFF)
    {
        r = pmap[y+ry][x+rx];
        if(parts[r>>8].temp+ (parts[r>>8].temp*0.2f)<=MAX_TEMP)
        {
            parts[r>>8].temp += parts[r>>8].temp*0.2f;
        }
        else 
        {
            parts[r>>8].temp = MAX_TEMP;
        }
    }
    return 0;
 }

Congrats, your HETR element should now work :).

What this code means in English: FOR all neighbours in a 3 pixel diameter, IF the particle's temperature + 20% of it's temperature is less then then the maximum temperature possible, THEN add 20% of the particle's temperature to itself, ELSE Set temperature to the maximum temperature.


Part Two: Uploading Your Work to GitHub

(NOTE: GitHub is NOT necessary to just add elements, it is for getting code into the official)

1) Open SmartGit (make sure you've saved your changes in Visual Studio).

2) powder.c and any other files you may have changed should be listed as "Modified".

3) Press "Commit" at the top, list the things you have changed in the text box, and press "Commit".

4) Press "Push" at the top, and press "Push" again.

5) Go to your Powder Toy repository page on GitHub and press "Pull Request" at the top.

6) Do not send the request to Simon if it is the element created in this tutorial. He will not accept it. If it's a very good and very useful element, send the request to facialturd (Simon's username) and you should be done. Verify that the code has been changed if you like.

7) If Simon decides to accept your request, your code will be in the official Powder Toy source code. Congratulations!


Now you are done, if you have any more questions, type them at the discussion part of this page.

Welcome to coding the powder toy!