Difference between revisions of "Coding-tutorial"

From The Powder Toy
Jump to: navigation, search
m (1 revision)
m (Reverted edits by JohnLolFun (talk) to last revision by LBPHacker)
 
(52 intermediate revisions by 20 users not shown)
Line 1: Line 1:
For a video tutorial, check out:
+
{{Languages|Coding-tutorial}}
''<ins>'''[[ http:''www.youtube.com/watch?v=SYl5eEQ6Mus| gamerboy8864's Tutorial on how to mod The Powder Toy]]'''</ins>//
 
 
 
 
 
 
 
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, if you have any problems please contact me, if there are any obvious errors, feel free to correct.
 
  
 +
'''ATTENTION: This guide is severely out of date as of 2021. Disregard everything you see below this notice. This should be fixed soon. Until then, the quick and informal one-sentence tutorial is as follows: successfully compile TPT first [[Building TPT with Meson|following this guide]], then create a new element file in src/simulation/elements based on other element files in the same directory, add it to the list in src/simulation/elements/meson.build, then recompile.'''
  
 +
This tutorial will give you guidelines on creating an element in The Powder Toy. We will use the heater (HETR) element in this example, which you can find [https://drive.google.com/uc?id=0B1XWtCTn2YPAUTl2WTFMRG9lYWs&export=download here]. 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. If you have any problems please post a thread on the [https://powdertoy.co.uk/Discussions/Categories/Topics.html?Category=5 forums]
  
 
It's not as simple as typing the name, color, and features, but it's almost that easy.  
 
It's not as simple as typing the name, color, and features, but it's almost that easy.  
  
 +
== Part One: Defining the Element's Properties ==
  
 +
=== Step One: Defining the Element ===
  
Note: Line numbers given here will probably change as more code is added. Look in the general area of the line number given. Use your editors "Find" tool to get the precise line number.
+
Create a new element file, (HETR.cpp for this example, or whatever name your element will be) inside of src/simulation/elements.
  
 +
After you put this into the /elements folder, you have to add it to the solution. 
  
 +
1.  Open up your solution file, then go to the Solution Explorer pane. 
  
'''Part One: Defining the Element's Properties'''
+
2.  Go to src/simulation/elements/
  
 +
4.  Right click the 'elements' folder
  
 +
5.  Go to Add -> Existing file
  
'''Step One: Defining the Element'''
+
6. Navigate to src/simulation/elements and double click your element file HETR.cpp
  
Open powder.h in the editor of your choice (Visual Studio for windows users) and go to ~line 199 (Find: #define PT_NUM)
+
Your new element should now be included in the solution!  If you don't do this, you will get errors when you try compiling your new mod.
<code c>
 
#define PT_NONE 0
 
#define PT_DUST 1
 
  
...
+
Open ElementClasses.h in the editor of your choice (Visual Studio for windows users). ElementClasses.h is located under Source Files -> generated in 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. Currently, the largest element ID is LDTC, 186. Your element should have an ID of 187.
  
#define PT_FROG 145
+
You should not edit ElementClasses.h yourself, this file is generated automatically when generator.py is ran. It uses the "//#TPT-Directive" comments to do this, so make sure you have the element fully written before you run it. For Visual Studio you have to run generator.py manually every time you finish making a new element. The Visual Studio compiling guide should have given a good explanation. If you didn't read that, then here is a short version:
#define PT_BRAN 146
 
#define PT_NUM 147
 
</code>
 
  
Notice each element has an incrementing ID and the last number in the list is equal to the number of elements (it is one higher then the highest because the elements start on 0)? This is necessary to note because it is necessary to change when adding new elements.
+
1.  Locate the folder where your source code is located.
  
lets add a new value.
+
2.  In this directory there should be a file named "generator.py"
  
Each element's number is incremented by one. PT_NUM is the number of elements in the game (PT_NONE starts at zero).
+
3. Double click it to run it.  A black window should appear for a second or so, then disappear. This means that it worked. You can check to see if ElementClasses.h was updated to confirm.
  
<code c>
+
=== Step Two: Defining the Element's Primary Properties ===
#define PT_BRAN 146
 
#define PT_HETR 147
 
#define PT_NUM  148
 
</code>
 
  
When adding an element, place it before PT_NUM. Give it the number PT_NUM had previously and add one to PT_NUM's number. The elements should still increment by one.
+
Now, open up your new element file. It is empty right now, it is recommended you copy from another element, or this template here: [https://drive.google.com/uc?id=0B1XWtCTn2YPAUTl2WTFMRG9lYWs&export=download HETR.cpp] . The file is entirely commented, you can just change the properties to how you want.
  
 +
Please look at the full list of Properties is here: [[Element_Properties]]
  
 +
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 copy the values from those.
  
'''Step Two: Defining the Element's Primary Properties'''
+
=== Step Three: Defining the Element's State Changes ===
  
Still in powder.h, go to ~line 519 (Find: {"BRAN", PIXPACK(0xCCCC00) )
+
In the linked property list page you will notice some special ones named LowPressure, LowPressureTransition, etc. These control all state changes for high/low pressure and temperature. It is very easy to edit these and add in transitions. Lets take GAS as an example. In GAS.cpp, you will find this:
<code c>
+
<syntaxhighlight lang="c">
{"", PIXPACK(0x000000), 0.0f, 0.00f * CFDS, 1.00f, 0.00f, 0.0f, 0.0f, 0.00f, 0.000f * CFDS, 0, 0, 0, 0, 1, 1, 100, SC_SPECIAL, R_TEMP+0.0f +273.15f, 251, "Erases particles.", ST_NONE, 0, NULL},
+
LowPressure = IPL;
{"DUST", PIXPACK(0xFFE0A0), 0.7f, 0.02f * CFDS, 0.96f, 0.80f, 0.0f, 0.1f, 0.00f, 0.000f * CFDS, 1, 10, 0, 0, 30, 1, 85, SC_POWDERS, R_TEMP+0.0f +273.15f, 70, "Very light dust. Flammable.", ST_SOLID, TYPE_PART, NULL},
+
LowPressureTransition = NT;
.....
+
HighPressure = 6.0f;
{"FROG", PIXPACK(0x00AA00), 0.0f, 0.00f * CFDS, 0.90f, 0.00f, 0.0f, 0.0f, 0.00f, 0.000f * CFDS, 0, 0, 0, 0, 0, 1, 100, SC_LIFE2, 9000.0f, 40, "Frogs S12/B34/3", ST_NONE, TYPE_SOLID|PROP_LIFE, NULL},
+
HighPressureTransition = PT_OIL;
{"BRAN", PIXPACK(0xCCCC00), 0.0f, 0.00f * CFDS, 0.90f, 0.00f, 0.0f, 0.0f, 0.00f, 0.000f * CFDS, 0, 0, 0, 0, 0, 1, 100, SC_LIFE2, 9000.0f, 40, "Brian 6 S6/B246/3", ST_NONE, TYPE_SOLID|PROP_LIFE, NULL},
+
LowTemperature = ITL;
//Name Colour Advec Airdrag Airloss Loss Collid Grav Diffus Hotair Fal Burn Exp Mel Hrd M  Weights Section H Ins Description State Properties Function
+
LowTemperatureTransition = NT;
 +
HighTemperature = 573.0f;
 +
HighTemperatureTransition = PT_FIRE;
 +
</syntaxhighlight>
  
</code>
+
This makes it change into OIL at higher than 6.0 pressure, and change into FIRE at higher than 573.0K. Note that all temperatures are in Kelvin, so you have to subtract 273.15 to get the temperature in Celcius. In this case it transitions at 299.85C.
 
  
These are the primary definitions for the elements. You can see what each variable means at the top of the code as well as just below.
+
For some transitions, there is one more step. If you want it to transition back, you need to add similar code into the other element. Lets say you wanted OIL to change back into GAS once it goes under 6.0 pressure again. You would need to go into OIL.cpp and change the pressure transitions to this:
 +
<syntaxhighlight lang="c">
 +
LowPressure = PT_GAS;
 +
LowPressureTransition = 6.0f;
 +
</syntaxhighlight>
  
'''Name''': The name of the element. Always use four letters, even if the element has a three letter name.
+
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.
  
 +
HETR is very simple and has no transitions, so everything should be IPL,IPH,ITL,ITH and NT.
  
'''Colour''': Color in hexadecimal code. Go to http://www.colorpicker.com/ to find the hexadecimal code (at the top) for your color. The hexadecimal code goes AFTER the "0x" prefix, always.
+
=== Step Four: Defining initial values ===
  
 +
Some elements start off with certain properties by default, for example PHOT gets a .life value of 680.
  
'''Advec''': How much the particle is accelerated by moving air.
+
'''CURRENT VERSION OF TPT`
  
 +
In the current version of TPT, you can directly modify a DefaultProperties variable. For example, if we wanted .tmp to start out as "1" for HETR, we can write anywhere in the properties definition (for instance, below the line that says LowPressureTransition = 6.0f;)
  
'''Airdrag''': How much air the particle generates in the direction of travel.
+
<syntaxhighlight lang="c">
 +
DefaultProperties.tmp = 1; // This defines default properties
 +
</syntaxhighlight>
  
  
'''Airloss''': How much the particle slows down moving air (although this won't have as big an effect as a wall). 1 = no effect, 0 = maximum effect.
 
  
 +
Note that this doesn't allow random properties, for example, PHOT starts with a random vx and vy. To set dynamic properties per particle, you need to add a create function. Let's suppose we want to randomize the HETR's life between 0 and 100 for no reason. In the properties add this line.
  
'''Loss''': How much velocity the particle loses each frame. 1 = no loss, .5 = half loss.
+
<syntaxhighlight lang="c">
 +
Create = &Element_HETR::create;
 +
</syntaxhighlight>
  
 +
Outside of the properties definition add the following lines:
  
'''Collid''': Velocity is multiplied by this when the particle collides with something.  
+
<syntaxhighlight lang="c">
 +
//#TPT-Directive ElementHeader Element_HETR static void create(ELEMENT_CREATE_FUNC_ARGS)
 +
void Element_HETR::create(ELEMENT_CREATE_FUNC_ARGS) {
 +
// Randomize life
 +
sim->parts[i].life = RNG::Ref().between(0, 100);
 +
}
 +
</syntaxhighlight>
  
 +
We use TPT's builtin random number generator to create a random number.
  
'''Grav''': How fast the particle falls. A negative number means it floats.
 
  
  
'''Diffus''': How much the particle "wiggles" around (think GAS).
+
'''BELOW IS FOR OLDER VERSIONS OF TPT ONLY!'''
  
 +
This is done inside of Simulation.cpp. Navigate to src/simulation/Simulation.cpp and find the function create_part. If you scroll down in this function, you will eventually find case statements that look like this:
  
'''Hotair''': How much the particle increases the pressure by.
+
<syntaxhighlight lang="c">
 +
case PT_SOAP:
 +
parts[i].tmp = -1;
 +
parts[i].tmp2 = -1;
 +
break;
 +
case PT_ACID: case PT_CAUS:
 +
parts[i].life = 75;
 +
break;
 +
</syntaxhighlight>
  
 +
Adding default values is as simple as making a new case statement. HETR doesn't require any default values, but lets pretend we wanted .tmp to start out as "1". We would add a new statement (anywhere in the list, but near the end might be better) that looks like this:
  
'''Fal''': How does the particle move? 0 = solid, 1 = powder, 2 = liquid
+
<syntaxhighlight lang="c">
 +
case PT_HETR:
 +
parts[i].tmp = 1;
 +
break;
 +
</syntaxhighlight>
  
 +
Don't forget the "break;", without it wouldn't error, but your element might get some properties you didn't want it to have.
  
'''Burn''': Does it burn? 0 = no, higher numbers = higher "burnage".
+
=== Step Five: Defining the Element's Special Properties ===
  
 +
This is the part where we actually code what the element does. The HETR.cpp linked above already has an element function, which looks like this:
  
'''Exp''': Does it explode? 0 = no, 1 = when touching fire, 2 = when touching fire or when pressure > 2.5
+
<syntaxhighlight lang="c">
 
+
//#TPT-Directive ElementHeader Element_HETR static int update(UPDATE_FUNC_ARGS)
 
+
int Element_HETR::update(UPDATE_FUNC_ARGS)
'''Mel''': Does it melt? 1 = yes, 0 = no.
+
{
 
+
int r, rx, ry;
 
+
for (rx = -1; rx < 2; rx++)
'''Hrd''': How much does acid affect it? 0 = no effect, higher numbers = higher effect.
+
for (ry = -1; ry < 2; ry++)
 
+
if (BOUNDS_CHECK)
 
+
{
'''M''': Does it show up on the menu? 1 = yes, 0 = no.
+
r = pmap[y+ry][x+rx];
 
+
if (!r || TYP(r) == PT_HETR)
 
+
r = sim->photons[y+ry][x+rx];
'''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.
+
if (!r)
 
+
continue;
 
+
if (parts[ID(r)].temp + (parts[ID(r)].temp*0.2f) <= MAX_TEMP)
'''Section''': The section of the menu it is in. Prefix everything with 'SC_'.
+
{
 
+
parts[ID(r)].temp += parts[ID(r)].temp*0.2f;
 
+
}
'''H''': What temperature does it have when created? Temperature is in Kelvin (Kelvin = degrees C + 273.15). R_TEMP+273.15f gives room temperature.
+
else
 
+
{
 
+
parts[ID(r)].temp = MAX_TEMP;
'''Ins''': specific heat value (how fast it transfers heat to particles touching it), can be found by using the real life heat value in J/G K (or KJ/KG K) by 96.635/RealLifeValue. 0 - no heat transfer, 250 - maximum heat transfer speed.
+
}
 
+
}
 
+
     return 0;
'''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.
 
 
 
'''Properties''': Does this element have special properties? The properties can be found at ~214.  Separate each property with | inside the property variable.
 
 
 
'''Function''':  This is new, and adds a huge performance increase, for now please put NULL here, we will come back to this later.
 
 
 
 
 
 
 
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.
 
<code c>
 
...
 
{"BRAN", PIXPACK(0xCCCC00), 0.0f, 0.00f * CFDS, 0.90f, 0.00f, 0.0f, 0.0f, 0.00f, 0.000f * CFDS, 0, 0, 0, 0, 0, 1, 100, SC_LIFE2, 9000.0f, 40, "Brian 6 S6/B246/3", ST_NONE, TYPE_SOLID|PROP_LIFE, NULL},
 
{"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,    1,    1,    100,    SC_SPECIAL,        22.0f+273.15f,            251,    "Heats objects it touches", ST_SOLID, TYPE_SOLID, NULL},
 
};
 
</code>
 
 
 
'''Step Three: Defining the Element's State Changes'''
 
 
 
 
 
 
 
Still in powder.h, ~line 681 (Find: /* GOL */ {IPL ... )
 
 
 
<code c>
 
{ //     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},
 
</code>
 
 
 
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:
 
<code c>
 
/* WATR */ {IPL, NT, IPH, NT, 273.15f,PT_ICEI, 373.0f, PT_WTRV},
 
</code>
 
 
 
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:
 
<code c>
 
/* ICE  */ {IPL, NT, 0.8f, PT_SNOW, ITL, NT, 233.0f, ST},
 
</code>
 
 
 
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.
 
<code c>
 
...
 
    /* GOL  */ {IPL, NT, IPH, NT, ITL, NT, ITH, NT},
 
     /* HETR  */ {IPL, NT, IPH, NT, ITL, NT, ITH, NT},
 
};
 
</code>
 
 
 
 
 
'''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.
 
<code c>
 
#include <element.h>
 
 
 
int update_HETR(UPDATE_FUNC_ARGS) {
 
 
 
return 0;
 
 
}
 
}
</code>
+
</syntaxhighlight>
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.
 
<code c>
 
int update_GOO(UPDATE_FUNC_ARGS);
 
int update_HETR(UPDATE_FUNC_ARGS);
 
int update_HSWC(UPDATE_FUNC_ARGS);
 
</code>
 
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.
 
<code c>
 
{"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,    1,    1,    100,    SC_SPECIAL,        22.0f+273.15f,            251,    "Heats objects it touches", ST_SOLID, TYPE_SOLID, &update_HETR},
 
</code>
 
 
 
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.
 
<code c>
 
for(rx=-1; rx<2; rx++)
 
    for(ry=-1; ry<2; ry++)
 
</code>
 
 
 
This code simply means "any particle touching a heater particle".
 
 
 
It basically sets the grid size in which particles are affected by the effect of the particle. 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.
 
<code c>
 
for(rx=-2; rx<3; rx++)
 
    for(ry=-1; ry<2; ry++)
 
</code>
 
This would make the grid affected 5x3
 
<code c>
 
for(rx=-1; rx<2; rx++)
 
    for(ry=-2; ry<3; ry++)
 
</code>
 
And this would flip the dimensions.
 
 
 
 
 
 
 
Add this:
 
<code c>
 
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)
 
        {
 
</code>
 
 
 
 
 
  
Your entire code should look like this so far:
+
This is a model for what most elements should look like. We can break it down line by line.
<code c>
 
        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      {
 
</code>
 
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)
+
<syntaxhighlight lang="c">
 +
//#TPT-Directive ElementHeader Element_HETR static int update(UPDATE_FUNC_ARGS)
 +
int Element_HETR::update(UPDATE_FUNC_ARGS)
 +
{
 +
</syntaxhighlight>
  
4th line: that particle is not a wall,
+
The first is the line that is read by generator.py and makes it work. Make sure this line is 100% correct or else your element won't compile. If it is not named HETR, you should change Element_HETR to match your element name.
  
5th line: THEN, do some code
 
  
 +
<syntaxhighlight lang="c">
 +
int r, rx, ry;
 +
for (rx = -1; rx < 2; rx++)
 +
for (ry = -1; ry < 2; ry++)
 +
if (BOUNDS_CHECK)
 +
{
 +
</syntaxhighlight>
  
Now add this:
+
These lines start a search around the particle of HETR for things to heat up. It searches the 8 spaces directly surrounding HETR. It also searches the location the HETR itself is in, which is almost always unnecessary. You can fix this by changing the BOUNDS_CHECK line to <tt>if (BOUNDS_CHECK && (rx || ry))</tt>.
<code c>
 
r = pmap[y+ry][x+rx];
 
</code>
 
  
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.
 
<code c>
 
int r, rx, ry;
 
</code>
 
  
Now add
+
<syntaxhighlight lang="c">
<code c>
+
r = pmap[y+ry][x+rx];
if(parts[r>>8].temp + (parts[r>>8].temp*0.2f)<=MAX_TEMP)
+
if (!r || TYP(r) == PT_HETR)
</code>
+
r = sim->photons[y+ry][x+rx];
Let's analyze.
+
if (!r)
 +
continue;
 +
</syntaxhighlight>
  
1) if ()
+
These lines get the particle at location (x+rx, y+ry). It first checks if it exists, and then checks whether it isn't another particle of HETR (we don't want HETR heating up other HETR). If there is no particle in that location, it then checks whether any energy particles are in that location. If there isn't an energy particle either, it stops, continue; in C++ skips the entire for loop above and goes onto the next location.
  
IF a is true, THEN b happens
+
For more information on pmap, photons, r, and i; go to [[Variables]]
  
2) parts[r>>8]
 
  
The currently selected particle (the one that's not HETR or a wall)
+
<syntaxhighlight lang="c">
 +
if (parts[ID(r)].temp + (parts[ID(r)].temp*0.2f) <= MAX_TEMP)
 +
{
 +
parts[ID(r)].temp += parts[ID(r)].temp*0.2f;
 +
}
 +
else
 +
{
 +
parts[ID(r)].temp = MAX_TEMP;
 +
}
 +
</syntaxhighlight>
  
3) .temp
+
This is the code unique to HETR, the rest was just a base used by almost every element. The temperature of the particle we found is stored in parts[ID(r)].temp. We want to multiply this by 1.2. If it's too close to MAX_TEMP, we just want to increase it to MAX_TEMP instead of doing the multiplication though. This code achieves just that.
  
means it's temperature.
+
<syntaxhighlight lang="c">
 
+
}
 
 
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 it won't go over the maximum temperature. Now add this:
 
<code c>
 
parts[r>>8].temp += parts[r>>8].temp*0.2f;
 
</code>
 
 
 
In English,
 
 
 
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.
 
 
 
 
 
Now add:
 
<code c>
 
else {
 
    parts[r>>8].temp = MAX_TEMP;
 
}
 
</code>
 
 
 
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 the temperature is 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:
 
<code c>
 
#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;
 
     return 0;
 
}
 
}
</code>
+
</syntaxhighlight>
Congrats, your HETR element should now work.
 
  
+
Not much interesting here, except for the return 0; The function returns an int, so you should always have a return 0; at the end. If your particle gets killed during the update function, you should immediately return 1;. This will let the game know it died, and skip the movement code for it since it's already dead and doesn't need to move.
  
  
 +
Fore more complicated element functions, check out the list of useful [[Functions]].
  
 
+
== Part Two: Uploading Your Work to GitHub ==
 
+
'''(NOTE: GitHub is NOT necessary to just add elements, it is for getting code into the official)'''
 
 
 
 
 
 
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).
 
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".
+
2) Simulation.cpp 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".
 
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.
 
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) 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!
 
 
  
  
Line 376: Line 236:
  
 
Welcome to coding the powder toy!
 
Welcome to coding the powder toy!
 +
 +
[[Category:Development]]

Latest revision as of 11:33, 14 March 2022

Language: English  • русский

ATTENTION: This guide is severely out of date as of 2021. Disregard everything you see below this notice. This should be fixed soon. Until then, the quick and informal one-sentence tutorial is as follows: successfully compile TPT first following this guide, then create a new element file in src/simulation/elements based on other element files in the same directory, add it to the list in src/simulation/elements/meson.build, then recompile.

This tutorial will give you guidelines on creating an element in The Powder Toy. We will use the heater (HETR) element in this example, which you can find here. 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. 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.

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.

After you put this into the /elements folder, you have to add it to the solution.

1. Open up your solution file, then go to the Solution Explorer pane.

2. Go to src/simulation/elements/

4. Right click the 'elements' folder

5. Go to Add -> Existing file

6. Navigate to src/simulation/elements and double click your element file HETR.cpp

Your new element should now be included in the solution! If you don't do this, you will get errors when you try compiling your new mod.

Open ElementClasses.h in the editor of your choice (Visual Studio for windows users). ElementClasses.h is located under Source Files -> generated in 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. Currently, the largest element ID is LDTC, 186. Your element should have an ID of 187.

You should not edit ElementClasses.h yourself, this file is generated automatically when generator.py is ran. It uses the "//#TPT-Directive" comments to do this, so make sure you have the element fully written before you run it. For Visual Studio you have to run generator.py manually every time you finish making a new element. The Visual Studio compiling guide should have given a good explanation. If you didn't read that, then here is a short version:

1. Locate the folder where your source code is located.

2. In this directory there should be a file named "generator.py"

3. Double click it to run it. A black window should appear for a second or so, then disappear. This means that it worked. You can check to see if ElementClasses.h was updated to confirm.

Step Two: Defining the Element's Primary Properties

Now, open up your new element file. It is empty right now, it is recommended you copy from another element, or this template here: HETR.cpp . The file is entirely commented, you can just change the properties to how you want.

Please look at the full list of Properties is here: Element_Properties

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 copy the values from those.

Step Three: Defining the Element's State Changes

In the linked property list page you will notice some special ones named LowPressure, LowPressureTransition, etc. These control all state changes for high/low pressure and temperature. It is very easy to edit these and add in transitions. Lets take GAS as an example. In GAS.cpp, you will find this:

	LowPressure = IPL;
	LowPressureTransition = NT;
	HighPressure = 6.0f;
	HighPressureTransition = PT_OIL;
	LowTemperature = ITL;
	LowTemperatureTransition = NT;
	HighTemperature = 573.0f;
	HighTemperatureTransition = PT_FIRE;

This makes it change into OIL at higher than 6.0 pressure, and change into FIRE at higher than 573.0K. Note that all temperatures are in Kelvin, so you have to subtract 273.15 to get the temperature in Celcius. In this case it transitions at 299.85C.

For some transitions, there is one more step. If you want it to transition back, you need to add similar code into the other element. Lets say you wanted OIL to change back into GAS once it goes under 6.0 pressure again. You would need to go into OIL.cpp and change the pressure transitions to this:

	LowPressure = PT_GAS;
	LowPressureTransition = 6.0f;

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.

HETR is very simple and has no transitions, so everything should be IPL,IPH,ITL,ITH and NT.

Step Four: Defining initial values

Some elements start off with certain properties by default, for example PHOT gets a .life value of 680.

CURRENT VERSION OF TPT`

In the current version of TPT, you can directly modify a DefaultProperties variable. For example, if we wanted .tmp to start out as "1" for HETR, we can write anywhere in the properties definition (for instance, below the line that says LowPressureTransition = 6.0f;)

DefaultProperties.tmp = 1; // This defines default properties


Note that this doesn't allow random properties, for example, PHOT starts with a random vx and vy. To set dynamic properties per particle, you need to add a create function. Let's suppose we want to randomize the HETR's life between 0 and 100 for no reason. In the properties add this line.

Create = &Element_HETR::create;

Outside of the properties definition add the following lines:

//#TPT-Directive ElementHeader Element_HETR static void create(ELEMENT_CREATE_FUNC_ARGS)
void Element_HETR::create(ELEMENT_CREATE_FUNC_ARGS) {
	// Randomize life
	sim->parts[i].life = RNG::Ref().between(0, 100);
}

We use TPT's builtin random number generator to create a random number.


BELOW IS FOR OLDER VERSIONS OF TPT ONLY!

This is done inside of Simulation.cpp. Navigate to src/simulation/Simulation.cpp and find the function create_part. If you scroll down in this function, you will eventually find case statements that look like this:

			case PT_SOAP:
				parts[i].tmp = -1;
				parts[i].tmp2 = -1;
				break;
			case PT_ACID: case PT_CAUS:
				parts[i].life = 75;
				break;

Adding default values is as simple as making a new case statement. HETR doesn't require any default values, but lets pretend we wanted .tmp to start out as "1". We would add a new statement (anywhere in the list, but near the end might be better) that looks like this:

			case PT_HETR:
				parts[i].tmp = 1;
				break;

Don't forget the "break;", without it wouldn't error, but your element might get some properties you didn't want it to have.

Step Five: Defining the Element's Special Properties

This is the part where we actually code what the element does. The HETR.cpp linked above already has an element function, which looks like this:

//#TPT-Directive ElementHeader Element_HETR static int update(UPDATE_FUNC_ARGS)
int Element_HETR::update(UPDATE_FUNC_ARGS)
{
	int r, rx, ry;
	for (rx = -1; rx < 2; rx++)
		for (ry = -1; ry < 2; ry++)
			if (BOUNDS_CHECK)
			{
				r = pmap[y+ry][x+rx];
				if (!r || TYP(r) == PT_HETR)
					r = sim->photons[y+ry][x+rx];
				if (!r)
					continue;
				if (parts[ID(r)].temp + (parts[ID(r)].temp*0.2f) <= MAX_TEMP)
				{
					parts[ID(r)].temp += parts[ID(r)].temp*0.2f;
				}
				else 
				{
					parts[ID(r)].temp = MAX_TEMP;
				}
			}
    return 0;
}

This is a model for what most elements should look like. We can break it down line by line.


//#TPT-Directive ElementHeader Element_HETR static int update(UPDATE_FUNC_ARGS)
int Element_HETR::update(UPDATE_FUNC_ARGS)
{

The first is the line that is read by generator.py and makes it work. Make sure this line is 100% correct or else your element won't compile. If it is not named HETR, you should change Element_HETR to match your element name.


	int r, rx, ry;
	for (rx = -1; rx < 2; rx++)
		for (ry = -1; ry < 2; ry++)
			if (BOUNDS_CHECK)
			{

These lines start a search around the particle of HETR for things to heat up. It searches the 8 spaces directly surrounding HETR. It also searches the location the HETR itself is in, which is almost always unnecessary. You can fix this by changing the BOUNDS_CHECK line to if (BOUNDS_CHECK && (rx || ry)).


				r = pmap[y+ry][x+rx];
				if (!r || TYP(r) == PT_HETR)
					r = sim->photons[y+ry][x+rx];
				if (!r)
					continue;

These lines get the particle at location (x+rx, y+ry). It first checks if it exists, and then checks whether it isn't another particle of HETR (we don't want HETR heating up other HETR). If there is no particle in that location, it then checks whether any energy particles are in that location. If there isn't an energy particle either, it stops, continue; in C++ skips the entire for loop above and goes onto the next location.

For more information on pmap, photons, r, and i; go to Variables


				if (parts[ID(r)].temp + (parts[ID(r)].temp*0.2f) <= MAX_TEMP)
				{
					parts[ID(r)].temp += parts[ID(r)].temp*0.2f;
				}
				else 
				{
					parts[ID(r)].temp = MAX_TEMP;
				}

This is the code unique to HETR, the rest was just a base used by almost every element. The temperature of the particle we found is stored in parts[ID(r)].temp. We want to multiply this by 1.2. If it's too close to MAX_TEMP, we just want to increase it to MAX_TEMP instead of doing the multiplication though. This code achieves just that.

			}
    return 0;
}

Not much interesting here, except for the return 0; The function returns an int, so you should always have a return 0; at the end. If your particle gets killed during the update function, you should immediately return 1;. This will let the game know it died, and skip the movement code for it since it's already dead and doesn't need to move.


Fore more complicated element functions, check out the list of useful Functions.

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) Simulation.cpp 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.


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!