Page 1 of 1

Adding items to POL - Part 01.

Posted: Sat Nov 18, 2017 10:33 pm
by Yukiko
During the course of this tutorial I will take you from a simple object definition to an object definition with a working script attached to it. I will also cover more advanced script types attached to objects in future segments as time permits. I apologize for the tab spacing in the cfg file examples. The items were aligned in my editor but the alignment doesn't transfer to the code elements in the PHPBB bulletin board.

For these tutorials you must use an editor that saves files as plain text. I use SciTE because it has syntax highlighting and some people prefer Notepad++ which also has syntax highlighting. Those are free editors you can download. There are some commercial editors and they can be very expensive. You can use Windows Notepad or the equivalent editor on Linux but those don't have syntax highlighting for your code.

If you need a lightweight code editor I have provided a ZIP file at the end of this tutorial of version 3.6.6 of SciTE. I have also written a small tutorial on configuring it for your POL installation.

Adding items Part 01.
Items are declared, or more properly defined, in an itemdesc.cfg file. We will look at some examples of item definitions. I will be using itemdesc.cfg files from the POL Distro. Unless otherwise specified the examples will be taken from the itemdesc.cfg file in \pol\config.

Go ahead and open the itemdesc.cfg file in \pol\config in your editor and let's look at the first two items defined in the file.

Code: Select all

Item    0x2
{
    Name                ank1a
    movable            0
}

Item    0x3
{
    Name                ank1b
    movable            0
}
The ank is a two part item and so it requires two items to be defined. The first thing to notice is the word Item followed by a 0x and a number. This is known as the Object Type or ObjType for short. Just to be clear that is a zero and an x not an uppercase 'O'. The 0x denotes that the number on the right side of the 0x is a base 16 or hexadecimal (hex) number. POL allows both decimal and hexadecimal numbers but you should use hexadecimal numbers for at least one important reason, which I will explain later. Back to the example. So the whole ank is made up of two parts, ank1a and ank1b. Each with hex numbers. Then on the next line you see the right brace. That tells POL that the next set of lines are elements associated with that item. In the case of the ank definitions you have two elements, Name and movable. These items, the ank definitions, are almost as simple of an item definition as you can make. The simplest would be without the movable element. Some items require a Graphic element but these do not because the Object Type number corresponds to the graphic number contained in the art tile file in the UO client. The Name element must be unique and have no spaces or non-printable characters. The movable element tells POL whether the item is movable, movable 1, or not movable, movable 0.

So let's create a custom ank. We will call the parts customank1a and customank1b. For these you will need the Graphic element.

Code: Select all

Item    0x20002
{
    Name                customank1a
	Graphic				0x2
    movable             0
}

Item    0x20003
{
    Name                customank1b
	Graphic				0x3
    movable             0
}
Notice that the ObjType number was also changed. That's because you should never define custom items in the UO graphic range. The graphic range is 0x0000 to 0xFFFF (0 to 65,535 decimal) for the latest UO expansion (Time of Legends when this tutorial was written). The POL developers have kindly given us a tremendous amount of custom item space to work with. The range for custom item numbers is 0x10000 to 0xFFFFFFFF hex 65,536 to 4,294,967,295 decimal). So your grand plans for the Ultimate Ultima shard have plenty of room to grow. You are probably wondering why should you use hexadecimal numbers rather than decimal numbers. The reason is that there is a file which POL creates every time it starts. You can find this file in the root directory of your POL installation. It's named objtypes.txt. It lists all items that are defined in the itemdesc.cfg files in your POL installation. Here is an example from the objtypes.txt file that is created when the POL Distro is run:

Code: Select all

# 0x9c18 - 0x10fff unused
0x11000 SmallBoat boat
# 0x11001 - 0x11003 unused
0x11004 SmallDragonBoat boat
# 0x11005 - 0x11007 unused
0x11008 MediumBoat boat
# 0x11009 - 0x1100b unused
0x1100c MediumDragonBoat boat
# 0x1100d - 0x1100f unused
0x11010 LargeBoat boat
# 0x11011 - 0x11013 unused
0x11014 LargeDragonBoat boat
# 0x11015 - 0x11017 unused
If you look at the example it will show the used and unused ObjType numbers. When you are defining new objects it is a good idea to check this file before assigning ObjType numbers to the new objects. POL will complain and shut down if you have an ObjType number collision, Two or more items withe the same number is a collision. You might be thinking "With so many numbers to pick from I could never pick the same number twice for an item". You would be surprised how easy it is to forget which numbers are already taken. Always consult the objtypes.txt file if you don't want to have POL complain. You will have to change ObjType numbers if it does so do it right the first time.

We have added the Graphic element to these custom anks so that POL knows to use the ank graphic for these objects. If you create a custom object you must assign it a valid graphic number, something from the art tile graphics that are defined in your client files. If you do not do that POL will complain and shut down. It will tell you that ObjType <some number> doesn't have a graphic assigned to it. That may not be the exact phrase it uses but it will be similar. A good tool for viewing the art tiles is UO Fiddler.

If you have not opened itemdesc.cfg in \pol\config then open that file now in your editor and go to the end of the file and copy the items from this tutorial and paste them at the end of the file and save it then start POL. Login to your server. You can create the anks using .create customank1a and .create customank1b. That would create them in your backpack but unless you have your moveany privilege set then you would have to use .unlock to set them to movable. Otherwise they are locked down in your backpack and because they have the movable element set to 0 are not movable. There is another item creation command, .createat, which gives you a target cursor that allows you to target the location where you want to place the item. So at this point go ahead and add these items to the itemdesc.cfg file in \pol\config and start POL and use .createat 0x20002 and place the customank1a in the world. Oh. You noticed I used the ObjType number in that .createat command? You can use either the Name of the item or the ObjType number to create an item.

Once you have created the item, depending on your settings in Options in your client, either hover your mouse over the customank1a or single click on it. Notice it displays the name "customank1a". I like to play shards that are more role play oriented and that is not very role play-ish. POL insists on unique names for items so you could change the name to something unique but you have the other half of the ank and it requires a unique name. Since the two halves make one ank you don't actually want one half of the ank to appear as one name and the other half to appear named something else. Thankfully there is a solution, the Desc element which means "description". Let's add that to our customank items.

Code: Select all

Item    0x20002
{
    Name                customank1a
	Desc				Healig Ank
	Graphic				0x2
    movable             0
}

Item    0x20003
{
       Name                customank1b
	Desc				Healig Ank
	Graphic				0x3
        movable             0
}
Notice that both halves of the ank use the same description. Desc elements do not have to be unique. So a multipart item, such as the ank, can have the same value for the Desc element. Notice also that Desc values can contain spaces unlike the Name element. Be sure to save the file after adding the new element.

Alright, we've created a custom item called Healing Ank. It heal's players correct? Well, not yet. It needs to have a script attached to it to do that. There are a number of script types that can be assigned to items that make those items do things. The first script we are going to look at is a UseScript. You can also use the element name Script instead of UseScript but I will use the traditional name here. When the item has a use script attached to it that script is executed when the item is "used" by double-clicking on it.

But before I discuss UseScripts I want to discuss creating a package for our healing ank. A package is basically what the name implies, a special place where you put things. That helps keep them organized and can make it easier for sharing your creations with others if you want to. Packages are typically kept in the \pol\pkg directory and have their own directory there. Packages can be inside directories in the pkg directory. If you open \pol\pkg\items you will see some subdirectories there that are packages. A package directory contains a special file named pkg.cfg. Here is what the contents of a typical pkg.cfg file looks like:

Code: Select all

Enabled 1
Name things
Maintainer Yukiko
Email hopelives@outlook.com
Version 1.0
The first line, Enabled 1, tells POL that this package is enabled and that items and compiled scripts in this package are able to be accessed and run. To disable the package you would change Enabled 1 to Enabled 0. There might be reasons to disable a package. For example if you are creating some package and some of the scripts are not finished and they would generate eCompile errors you would want to disable it. That's because eCompile will not try to compile a package that is disabled.

The second line is of course the name of the package. Curiously this does not have to be the same as the directory in which the package is located. I could create a directory named "stuff" that contains the "things" package but it's customary to give the directory the same name as the package to avoid confusion.

The next line is obvious. The maintainer of the package. This usually is the creator but not always. Some older packages that have been released and abandoned but then have been used and undergone major modifications and upgrades would have a new maintainer. You should always give attribution to the original creator in the comments section of the scripts. It's not necessarily a requirement but it is good programming etiquette.

The next line is obvious. It's the eMail address of the maintainer and the last line is the version number of the package.

So let's create a package for the healing ank. Open the \pol\pkg\items directory and create a folder there and name it "healingank" (without the quotes). Open that folder and create another folder inside that one named "config" (no quotes). Open that folder and create a file named itemdesc.cfg. Load that file in your editor. Go back to the itemdesc.cfg file you opened in \pol\config and select the two custom ank items you placed in there and cut them from that file and paste them in the new itemdesc.cfg file. Save and close the itemdesc.cfg that you had opened in \pol\config.

Switch back to the new itemdesc.cfg file in the healingank folder that you just created and let's change their Name property to something that is relevant to what they are and do. What could be more relevant than "healingank1a" and "healingank1b" (again without the quotes). Save that file but keep it open in your editor.

Now go bank to the main healingank folder and create a file named pkg.cfg there. Open it in your editor and paste the following lines in the file:

Code: Select all

Enabled 1
Name healingank
Maintainer <your name goes here>
Email <your email address goes here>
Version 1.0
Replace <your name goes here> with your name and <your email address goes here> with your eMail address.
You can leave the version at 1.0 because this will be version 1.0 of the healingank package.
Save the file.

Now we have a neat little package where we can add the UseScript for the ank parts.

Ok. Let's make them do what they are supposed to do. For that we need a script. Here is a simple little script that will do what we want it to do:

Code: Select all

use uo;

include ":attributes:attributes";

program heal(who, healingank)
	// Get the character's hit points and compare their current HP to their maximum HP.
	// If they are greater than or equal to their maximum hit points then don't do any healing
	// and tell them privately they don't need to be healed using font 1 and colour 88 (blue) text.
	// Then exit the script via the return statement.
	if(GetHP(who) >= GetMaxHP(who))
		PrintTextAbovePrivate( healingank, "You hear: You do not need healing.", who, 1, 88, 0x00 );
		return;
	endif
	// We will be nice and grant the player up to 100 hit points of healing.
	var amount := 100;
	// HealDamage will only heal to their maximum hit points. 
	HealDamage(who, amount);
	// Play a sparkly effect around the player.
	PlayObjectCenteredEffect(who, FX_SPARK_EFFECT, 7, 0x10);
	// Play the "Ahhh..." healing sound we have all come to know and love.
	PlaySoundEffect(who, SFX_SPELL_HEAL);
	// Have the ank tell them privately they have been healed this time with colour 66 (green) text.
	PrintTextAbovePrivate( healingank, "You hear: You have been healed.", who, 1, 66, 0x00 );
endprogram
Open a blank file in your editor and copy the script to it and save the file in the healingank folder as healAnk.src
Go back to the itemdesc.cfg file you created for this package and enter the line Script healAnk after the Graphic line. Your itemdesc.cfg file should look like this:

Code: Select all

Item    0x20002
{
	Name                healingank1a
	Desc				Healig Ank
	Graphic				0x2
	UseScript				healAnk
	movable             0
}

Item    0x20003
{
	Name                healingank1b
	Desc				Healig Ank
	Graphic				0x3
	UseScript				healAnk
	movable             0
}
Save the file and start POL. Log in with your client and .createat healingank1a and one square to the North .createat healingank1b
Double click on either part of the ank and see what happens. You should "hear" that you do not need healing. To test the
*speaks with a loud booming voice"
HEALING POWER
of your ank type the command .info and target yourself. Click on the button by "Stats and Vitals". Now click on the button next to "Life". A gump will open up. Backspace over the number there and type 5 and click the "Okay" button. Right-click on the main info gump to close it. Now double-click on the ank and see what happens. Check you Hit Points in your Status gump and you should be at full health.

Congratulations! You have created your first working object and you also have a new item you can add to the world.

I hope this helped you understand how items are defined in POL. There is more to come in this series.



Here is the SciTE editor I mentioned. When you download it, unzip it to your hard drive. It is a Windows program. There should be a Linux version available from SciTE website but you will want to copy the escript.properties and the SciTEGlobal.properties files from the ZIP file to your SciTE directory in Linux because those files contain syntax highlighting and eScript configuration data that allows SciTE to compile a script from within the editor.
Scite.zip
SciTE version 3.6.6
(1.3 MiB) Downloaded 328 times
Once you have SciTE on your machine open the Scite folder and double-click on the SciTE executable. Then choose Options from the menu. Look in the list of files for Open escript.properties and click on that. Look for the POL Root Folder section near the top of the file. In that section you will see POLROOT=C:\pol. Change that to the location of your POL installation, save and close the file. That's all you need to do to configure the version of SciTE you downloaded. If you get SciTE rom the SciTE website you need to copy the escript.properties and SciTEGlobal.properties files from the ZIP file because SciTE has loading of eScript *.src files disabled in the SciTEGlobal.properties file and the syntax highlighting in the escript.properties file provided by the website is very outdated. You will probably want to change the association of *.src, *.ini, and *.cfg files to be loaded by SciTE when you double-click on them. To do this, for each file type, right click on a file of one of the types and choose Properties from the context menu. In the properties window you will see Opens with:. Click the Change button and in the file selection window that opens locate the SciTE program and select it. Click on Apply and close that properties window. Do this for the other two file types and you are all set.

Note: When changing the Opens with setting you need to be aware that some programs may already be assigned to open that type of file. Visual Studio for example is assigned to open *.inc files. So be very certain you want to reassign SciTE to open these files when you double-click them. If you are unsure then DON'T change the Open as setting. You can always open the files from within SciTE using the File menu.

One last thing. To compile a script you have open in SciTE you can choose Tools --> Compile in the menu or simply press <control> F7. Oh and keep in mind that *.inc files do not compile. I can't count the number of times I have been editing an include (inc) file and forgot it was an include file and become frustrated when SciTE refused to compile it. I think it happened again just last week in fact. :)

When compiling a script (*.src) SciTE saves the file before attempting to compile it. If you have any include files that a script you are editing needs to access don't forget to save any changes you have made to the include files before compiling your script. I've forgotten to do that too.

I hope SciTE serves you well. I have been using it a long time and, though its settings are a bit quirky, I like it.

Re: Adding items to POL Objects explained - Part 02.

Posted: Wed Dec 06, 2017 2:03 am
by Yukiko
Adding items to POL Objects explained - Part 02.

Hello and welcome to part 02 of the adding items to POL tutorial.

As I started to write part 2 of my adding items to POL series I realized that a discussion of of what items are in the POL world is a good idea. This probably should have been part 01 but due to some new POL users coming from the ServUO emulator there were questions about adding items to a POL server and I wanted to give a simple example of how that is done to help orient them to POL's way of defining an item and attaching a script to it. It is considerably different than the method employed with ServUO. Under ServUO items are defined in C# code and there is no equivalent to the itemdesc.cfg file system used by POL, at least as far as I can tell anyway.

Because we have people coming to POL from all experience levels, from new to programming to experienced, college educated programmers I will attempt to aim this "tutorial" with the new programmer in mind. Hopefully I have accomplished this goal. So forgive me if my approach is too simplistic for the more experienced programmer. Also my explanations are in my own words and may not be the way you are taught in a programming course. See my disclaimer at the end of this post.

POL uses the object metaphor to define items. All of my discussion will pertain to how objects are represented in POL and may not necessarily apply to other programming environments but basically the general descriptions of objects is universally applicable.

Let me take a moment to explain some terminology used when we talk about objects in POL. First, what is an object? An object is anything that can exist and/or be manipulated in eScript or POL. Objects aren't always visible or accessible or usable in game. For example an Account is an object class, so also is a String or a Packet or an Error. Yes, Error is an object class. None of those examples are things that you as a player have the direct ability to manipulate. Objects are hierarchical, meaning that they are arranged in order of rank. An object can exist by itself or it can have descendants and even branches like a tree. An Account object exists by itself with no ancestors or descendants. However the Item object, such as our ank, does have ancestors and the Item object has descendants. Objects can, and usually do, have Members and Methods. Members are properties that define certain aspects of an object. The Graphic member that we used in our definition of the healing ank is a member of our custom item (object). Methods are funtions that can be used to retrieve information about an object, manipulate an object or modify an object or its Custom Properties (CProps). I will cover CProps later. All objects that can be manipulated in game are descendants of their progenitor or root object UObject. Just like human descendants, object descendants inherit the members and methods of their parent objects.

Let's take a look at the UObject's list of members:

Code: Select all

Members
Name 			Type 		Access

color 			Integer 	r/w
dirty 			Boolean 	r/o
facing 			Integer 	r/w
graphic 		Integer 	r/w
height 			Integer 	r/o
multi 			Multi 		r/o
name 			String 		r/w
objtype 		Integer 	r/o
realm 			String 		r/o
serial 			Integer 	r/o
specific_name	 	Boolean 	r/o
weight 			Integer 	r/o
x 			Integer 	r/o
y 			Integer 	r/o
z 			Integer 	r/o
I drew upon the Object Class Reference documentation as the source for this information. I deliberately removed the descriptions of these members because formatting them in this post would have been problematic. You can view the documentation for the descriptions later. A link to the POL Object Class Reference is provided later in this article.

You can see that UObject has quite a few members. Some are self explanatory such as an object's 'name' or 'color'. Some are a little more cryptic such as 'multi'. If you don't know what a multi is don't worry about it for now. Notice that these members can have various types as their values. If you don't know what a type is, very briefly, it is, quite literally, the type of value that can be stored in the member on the object. Here we have Integers or whole numbers, Strings, usually printable text, Booleans, a 1 (true) or a 0 (false), and an Object Reference as in the MultiRef. This last type is a reference to the multi object which in turn has its own set of members and methods. The last thing to take note of is the Access that scripts have to these members. Some are read/write (r/w) which means you can both get the value of the member but you can also change the value. Some are Read Only (r/o) which means you can get the value but it is not changable by a script. These types of members are set either in the itemdesc.cfg definition of the object or by the Core. If the item is not defined in an itemdesc.cfg file then some members are assigned a default value based on information contained in a file in your UO client installation called tiledata.mul. This file is used by uoconvert.exe to create tiles.cfg in the \pol\config directory. It is this file which POL uses to obtain default values for items you can create in game, which are not defined in itemdesc.cfg files, by using their UO graphic number. Later you can take a look at descendant objects of the UObject. If you think UObject has a lot of members just wait until you look at the Item object.

You can retrieve a member's value with the statement: var membervalue := object.membername, where 'membername' is the name of the member of which you need to find the value.
If the member is a read/write member you can change the value of the member with the following statement: object.membername := somevalue. Remember to make sure the value that you are setting the member to is the proper type for that member.

Now it's time to look at UObject's methods:

Code: Select all

Methods
Prototype 					Returns

eraseprop(string propname) 			true/error

get_member(string membername)			object, value of member or error

getprop(string propname) 			script object/error
isa(POLCLASS_*) 				boolean

propnames() 					Array

set_member(string membername, obj value) 	object, new value of member or error

setprop(string propname, object propval) 	true/error
Again I have removed some of the information due to formatting issues.

Remember that methods are functions that you can call "to retrieve information about an object, manipulate an object or modify an object or its Custom Properties". This is accomplished in a similar fashion as accessing member values. For example, assume you want to determine if an object is an NPC you could use the following code snipped:

Code: Select all

if(someobject.isa(POLCLASS_NPC))
    do some stuff...
endif
Some methods duplicate things that can be done more simply in scripts such as the var objname := obj.get_member("name") example above. var objname := obj.name will return the same value.

Most of the methods listed for a UObject relate to Custom Properties or CProps.

Warning: Disclaimer ahead.
So technically speaking what is a CProp? I don't know the technical definition of a CProp. I searched core-changes.txt all the way through to 2/1/2000 POL 086 and found no technical definition of CProps. In Racalac's eScript Reference and Guide v096 in Chapter 7 Much Ado about CProps and Stupid Chapter Names he describes CProps from a functional perspective.

"CProps (Custom Properties) are what makes scripting with eScript so flexible. In addition to all the built-in properties in Appendix A, CProps let you store arbitrary amounts of arbitrary data to any object. This data can be stored by any script and recalled by any script that has a reference to that object. You can store any type of data: string, integer, real, arrays. You should be able to see that this ability to store and retrieve arbitrary data is a powerful one."

I should note that his reference to "Appendix A" is erroneous. Appendix A is now a GUMP tag reference. It used to be a simpler version of the POL Object Class Reference.

I also want to thank Racalac for creating the wonderful documentation page that we have and especially the developers who came after him who have made it more functional, especially the downloadable version, and for updating its contents as POL has evolved.

For the purposes of my explanation I am going to use my own phrase to define a CProp on an object. I am going to call them "pseudo members". The reason I am calling them "pseudo members" is, unlike normal members, you can have many of these pseudo members named "CProp". The official nomenclature of a CProp's definition may not be the same as mine. I apologize to the developers if I am not accurately describing the internal definitions of what a CProp is. So my definition will have to stand until, and if, I get a better one.
End disclaimer.

So I will take this opportunity to explain the function of a CProp and how they can be used.
So what are they? CProps are, as the name says, custom properties that you can assign to objects that give you the ability to set values on an object beyond the built-in members. In reality CProps in and of themselves are not, in the strictest meaning of the word "properties". By using the method setprop you do not in any way change the way an item behaves in the world. For example if I use the following code to set the CProp "Cursed" to 1 or true on an "evildagger" item, that in and of itself doesn't make the item cursed:

Adding a CProp to an item defined as "evildagger":

Code: Select all

evildagger.setprop("Cursed", 1);
In order for this CProp to be useful a script must be looking for it on the object and take appropriate action if it is there. In our case the EquipScript might check for the CProp "Cursed" and do some damage to the player when he equips it.

Code: Select all

use uo;
use util;

include ":attributes:attributes";
include ":damage:damage";

program equip(who, item)

	if(item.getprop("Cursed") == 1)
		SendSysMessage(who, "The " + item.desc + " is cursed!", 1, 33);
		ApplyRawDamageEX(who, RandomDiceRoll("2d8"), DMG_FORCED)
	endif
    
	// Do more stuff...
	
endprogram
This is what happens when POL executes the code to add a CProp to the "evildagger". POL adds a pseudo member, "CProp", to the object. This pseudo member has a string value attached to it with the name of the CProp its type and the value of the CProp. In this case "Cursed i1". "Cursed" being the name "i" being the type, integer, and "1" being the value of the CProp "Cursed". If you were to view the item in items.txt it would look something like this:

Code: Select all

Item
{
	Name	evil dagger
	Serial	0x4003dff9
	ObjType	0xf51
	Graphic	0xf51
	X	5843
	Y	1165
	Z	0
	Facing	1
	Revision	2
	Realm	britannia
	CProp	Cursed i1
	HitScript	mainHitScript
}
As I mentioned you can have multiple CProps on an item. You could have a second CProp that defines several types of curses for equipping the cursed dagger and select a random curse to perform on the player or start a script that continually repeats the curse as long as the dagger is equipped. The possibilities are, as they say, endless.

There are a couple of things that you need to know about CProps. The name of the CProp is case sensitive. In our evil dagger example item.getprop("cursed") will attempt to retrieve a different CProp than item.getprop("Cursed"). So keep that in mind when creating CProp names. Another thing you need to know is that a CProp can be set to prevent it from being saved in the world data during a world save or upon server shutdown. The way this is accomplished is by prefixing the CProp with the hash symbol "#". So if we didn't want our evil dagger's "Cursed" CProp to be saved on the item in the world data file we could name it "#Cursed". I have had a short discussion with Core Developer Turley about this feature and I owe him a "thank you" for explaining the function of # when appended to the CProp name. This was implemented some time in the past before he came on board as a developer so what the actual use case for this is unclear.

I think I have covered objects and CProps pretty thoroughly. At least this "tutorial" is enough to give you an idea of how they are implemented in POL and a basic overview of their members and methods.

A last word from me about CProps: I believe CProps make POL one of the most versatile UO emulators in existence.

For a complete description of all of the objects defined within POL including their hierarchy, members and methods check out the POL Object Class Reference. You need to familiarize yourself with this eventually so you might as well go there and get overwhelmed now so that later you will be over the shock of how many options are available with the various object definitions. :)

Warning: Another disclaimer: I am an amateur programmer. I have had no formal college training in computer programming. As such I may, and frequently do, use terminology, definitions, and descriptions that may not be proper "by the book" terminology, definitions or descriptions. I believe they are accurate to the best of my understanding. If you find I have made an error please PM me with the correction and I will make changes to my "tutorials" when necessary.

Re: Adding items to POL - Part 01.

Posted: Wed Dec 06, 2017 7:05 am
by boberski
Great tutorial!

Re: Adding items to POL - Part 01.

Posted: Wed Sep 15, 2021 1:33 am
by strobelartesao
That was excellent!

Thanks! I'll try it as soon as I can and bring you the results

Re: Adding items to POL - Part 01.

Posted: Sun Oct 03, 2021 5:04 pm
by strobelartesao
This tutorial was excellent! I'm understanding POL better and better with every tutorial available! Thanks