SMFShop > SMFShop Announcements

Captain Basilbeard's Unoffical Item Creation Guide(Part 2 is out)

(1/5) > >>

Basil Beard:
After reading tazpot's post Here I decided to take him up on it.  :P. After all, item creation is pretty easy(I have coded several fine items with only a begginers knowlege of php and my_sql when I started) and there isn't any reason why Daniel needs to code all the items you have ideas for.  :D. So cause I had some free time, I was like "Lets write a guide"  O0. And so, without futhur ado...   :)

Item Creation 101
By Basilbeard

So we want to create an item for SMF shop. To do this, first figure what item you want to create. For this lesson, we will be creating a “Magic Die”. Upon using the die, it will target another player, and then either give them a random amount of points or destroy a random amount of points.

Note: This guide assumes a basic knowledge of php. Thought not much is required.

Start by opening the testitem.php file.


--- Code: --- <?php

/**********************************************\
| SMFSHOP (Shop MOD for Simple Machines Forum) |
|         (c) 2005 DanSoft Australia           |
|      http://www.dansoftaustralia.com/        |
\**********************************************/

//File: testitem.php
//      Test Item (gets some inputs, most likely you will base your items on this)

//VERSION: 1.1 (Build 4)
//DATE: 10th April 2005

//your class should always be called item_filename, eg. if your file is 
//myCoolItem.php then the class should be called 'item_myCoolItem'. This 
//class should always extend itemTemplate.

class item_testitem2 extends itemTemplate {
    
    //when this function is called, you should set all the item's
    //variables (see inside this example)
    function getItemDetails() {

        //VALUES CHANGABLE FROM WITHIN ADMIN PANEL:
          //the name of the item
          $this->name = "A Test Item 2";
          //the item's description
          $this->desc = "Just a test item! The second time!";
          //the item's price
          $this->price = 5;

        //UNCHANGABLE VALUES:
          //whether inputs are required by this item. In this case, we get some inputs,
          //so set this to 'true'.
          $this->require_input = true;
          //set this to 'false' if the item is unusable. This is good for display
          //items.
          $this->can_use_item = true;
    }

    //this function is called when the user tries to use the item.
    //if your item needs any further user input then you can get that 
    //input here (eg. if it's a "Change username" item then you have
    //to ask the user what they'd like to change their username to).
    //Any text you return will get shown to the user (DON'T ECHO STUFF).
    function getUseInput() {
        return "A Test: <input type='text' name='test123'>";
    }

    //the is where all the fun begins. This function is called when 
    //the user actually uses the item. Return stuff, DON'T ECHO!
    function onUse() {
        return "Hello, I am a test!<br>In the text box, you entered '{$_POST['test123']}'";
    }
}

?>
--- End code ---

Anything after a // is a comment and doesn’t actually appear in the code, so the real file looks like


--- Code: --- <?php
class item_testitem2 extends itemTemplate {
    function getItemDetails() {
          $this->name = "A Test Item 2";
          $this->desc = "Just a test item! The second time!";
          $this->price = 5;

          $this->require_input = true;
          $this->can_use_item = true;
    }

    function getUseInput() {
        return "A Test: <input type='text' name='test123'>";
    }

    function onUse() {
        return "Hello, I am a test!<br>In the text box, you entered '{$_POST['test123']}'";
    }
}

?>
--- End code ---
First lets try and see what this item does. The function getUseInput() prompts the user for input upon using the item. This is where they will often type in the member name of the target. The type=’text’ tells them to be a text box while the name=’test123’ defines its value for the onUse() function. In this case, name=’test123’ and so whatever the user inputs here can be called up using ($_POST[‘test123’]} in the onUse() function.

Note that if you wanted two inputs, say, an item that lets you pick a target and also an amount to steal, you would not have.

--- Code: ---    function getUseInput() {
        return "Target: <input type='text' name='target'>";
        return "Amount to steal: <input type='text' name='steal'>";
    }

--- End code ---

Because the return line causes that part of the function to end. Instead, you would need to have.


--- Code: ---    function getUseInput() {
        return "Target: <input type='text' name='target'> <br>
                Amount to steal: <input type='text' name='steal'>";
    }

--- End code ---

Here, the target name could later be called up using {$_POST[‘target’]} and the amount to steal could later be called up using {$_POST[‘steal’]}.

Now lets examine the second half of the code.


--- Code: ---    function onUse() {
        return "Hello, I am a test!<br>In the text box, you entered '{$_POST['test123']}'";
    }

--- End code ---

This is very simple. ‘return’ tells it print the follow text in the quotes. Thus, upon using the item it will display

Hello, I am a test!
In the text box, you entered ‘Arrrrrrrr!’

(this assumes that I did infact enter Arrrrrrr! In the textbox.

So now that we understand the basics behind how this item works, lets start coding out item.

We start by copying this to a new file, which we call MagicDie.php


--- Code: ---  <?php
class item_testitem2 extends itemTemplate {
    function getItemDetails() {
          $this->name = "A Test Item 2";
          $this->desc = "Just a test item! The second time!";
          $this->price = 5;

          $this->require_input = true;
          $this->can_use_item = true;
    }

    function getUseInput() {
        return "A Test: <input type='text' name='test123'>";
    }

    function onUse() {
        return "Hello, I am a test!<br>In the text box, you entered '{$_POST['test123']}'";
    }
}

?>
--- End code ---


The most important change is in the second line. We need to change class item_testitem2 extends itemTemplate { to class item_MagicDie extends itemTemplate {
We then need to change the name and desc and price. This item requires input and it can be used, so the other two values stay as true. Lets make this item cost 20 points.


--- Code: ---  <?php
class item_MagicDie extends itemTemplate {
    function getItemDetails() {
          $this->name = "Magic Die";
          $this->desc = "Why risk your own points when you can gamble with someone elses?";
          $this->price = 20;

          $this->require_input = true;
          $this->can_use_item = true;
    }

--- End code ---

For this item, we also need to include the getAddInput() function. This will allow us, upon the creation of the item, to send bounds on how rewarding or damaging this item can be.

--- Code: ---    function getAddInput() {
        return "Max amount to give: <input type='text' name='info1' value='100'><br>
               Max amount to take: <input type='text' name='info2' value='-100'>";
    }

--- End code ---

Unlike with the getUseInput() function, you cannot change the value of the ‘name’ in these two lines. If you were to add a third, it would have to be called ‘info3’ and a fourth would be ‘info4’. You can’t add anymore after that due to database constraints.

Now that we have that part coded, lets again code our getUseInput() function. This is best served by copying the same function from another item, say the steal item we already have.


--- Code: ---    function getUseInput() {
global $context, $scripturl, $settings, $txt;
        return "Steal From: <input type='text' name='stealfrom' size='50'>
         <a href='{$scripturl}?action=findmember;input=stealfrom;quote=0;sesc={$context['session_id']}' onclick='return reqWin(this.href, 350, 400);'><img src='{$settings['images_url']}/icons/assist.gif' border='0' alt='{$txt['find_members']}' /> Find Member</a>";
    }
--- End code ---

We of course need to make some changes. Namely, the “Steal From” line needs to be changed and the name should be more like ‘dicetarget’ rather than ‘stealfrom’. So we make out changes.


--- Code: ---    function getUseInput() {
global $context, $scripturl, $settings, $txt;
        return “Choose a target for your die: <input type='text' name='dicetarget' size='50'>
         <a href='{$scripturl}?action=findmember;input=dicetarget;quote=0;sesc={$context['session_id']}' onclick='return reqWin(this.href, 350, 400);'><img src='{$settings['images_url']}/icons/assist.gif' border='0' alt='{$txt['find_members']}' /> Find Member</a>";
    }
--- End code ---

Don’t worry about all that funky code. That adds the button that lets you search for member’s names. Now we just need to code the fun part. The onUse() function. The part that actually tells the item what to do.

--- Code: ---    function onUse() {
        global $db_prefix, $ID_MEMBER, $item_info;

--- End code ---

The first task is to make sure they are not targeting themselves with the item. They should use random money if they want to do that. So we do a database query.

--- Code: ---$result = db_query("SELECT memberName
                                FROM {$db_prefix}members
                                WHERE ID_MEMBER = {$ID_MEMBER}", __FILE__, __LINE__);

--- End code ---

I personally hate these queries. The syntax needs to be perfect or else the item will not work, and I often find I need keep fixing the syntax on my items several times after I add them before they actually work. But they are needed for any good item. They are what you use for almost everything, from changing someone’s money count to modifying their title or post count.

We then need to add a few more lines of code. I am not a PHP know-it-all, so can’t really explain why it works I just know it does.


--- Code: ---$row = mysql_fetch_array($result, MYSQL_ASSOC);
$user = $row[‘memberName’]

--- End code ---

This creates an array called row(I think) which contains the previously selected memberName. We can now call that value using $row[‘memberName’]; our next part needs to be an if statement which gets to see if $row[‘memberName’] is the same name as the name the user inputted. If this is true, we need to return an error message instead of executing the random part.


--- Code: ---if ($user == $_POST[‘dicetarget’]) {
return “Use some random money instead. Use the die on someone else”;
die();
}

--- End code ---

According to Daniel, the die() function should stop the item from deleting itself. I have worked around this differently and thus cannot test this, but if it gives you any errors, just remove it from the code.

Now we need to code the other part of the code, what happens when the target is someone different. To do this, we just use an else.

--- Code: ---else {
$amount = mt_rand($item_info[2], $item_info[1]);

--- End code ---

The mt_rand function creates a random integer(inclusive) between the two given bounds. Exactly what we want. We now need to update the other guys point count. Guess what? We need another database query to do this.

--- Code: ---$result = db_query("UPDATE {$db_prefix}members
                                SET money = money + {$ammount}
                                WHERE memberName = '{$_POST[‘dicetarget’]}'", __FILE__, __LINE__);

--- End code ---

So we have updated the database with the random amount. There are two things left to do. The first is to send a friendly PM to the unlucky or lucky target. They should know their points have been changed. To do this, we actually need to add a couple of lines into the top of the code. There again might be a better way to do this, but this is how I’ve figured out how to do it. So until Daniel tells me a better way to do it, add


--- Code: --- global $sourcedir;
require_once($sourcedir . '/Subs-Post.php');
--- End code ---

below the <?php line that starts your code.

Now let’s send the PM. We first need to select the targets ID_MEMBER number so that the PM can be sent.


--- Code: ---$result = db_query("SELECT ID_MEMBER
       FROM {$db_prefix}members
                           WHERE memberName = '{$_POST[‘dicetarget’]}'", __FILE__, __LINE__);
$row = mysql_fetch_array($result, MYSQL_ASSOC);
$pmfrom = array(
'id' => 1,
'name' => 'Basilbeard',
'username' => 'Basilbeard'
);
$pmto = array(
to' => array($row['ID_MEMBER']),
'bcc' => array()
);
sendpm($pmto, 'Care for a game of chance?', “{$user} uses a Magic Die on you. You check your inventory, and find that your point total has been changed by ".formatMoney($amount).”! [i] You do not need to reply to this automated message”, 0, $pmfrom);                                           
--- End code ---

Lastly, we need to return a statement to the user of the item and then make sure we end all our brackets and such.


--- Code: ---Return “You use your Magic Die on {$_POST[‘dicetarget’}. It spins around for a few minutes, before landing on {$amount} and thus changing their point count by “.formatMoney{$amount}.”!”;
} // This ends the else bracket.
} // This ends the onUse function
} // This items the item function itself.
?> // this ends the PHP code, and thus out file.

--- End code ---

So in the end, our whole file looks like:


--- Code: ---<?php
/**********************************************\
| SMFSHOP (Shop MOD for Simple Machines Forum) |
|         (c) 2005 DanSoft Australia           |
|      http://www.dansoftaustralia.com/        |
\**********************************************/

//File: MagicDie.php
//      Magic Die

//VERSION: 1.0
//DATE: 16th April 2006
//AUTHOR: Basilbeard
global $sourcedir;
require_once($sourcedir . '/Subs-Post.php');
class item_MagicDie extends itemTemplate {
    function getItemDetails() {
          $this->name = "Magic Die";
          $this->desc = "Why risk your own points when you can gamble with someone elses?";
          $this->price = 20;

          $this->require_input = true;
          $this->can_use_item = true;
    }

    function getAddInput() {
     return "Max amount to give: <input type='text' name='info1' value='100'><br>
               Max amount to take: <input type='text' name='info2' value='-100'>";
    }

    function getUseInput() {
global $context, $scripturl, $settings, $txt;
        return "Choose a target for your die: <input type='text' name='dicetarget' size='50'>
         <a href='{$scripturl}?action=findmember;input=dicetarget;quote=0;sesc={$context['session_id']}' onclick='return reqWin(this.href, 350, 400);'><img src='{$settings['images_url']}/icons/assist.gif' border='0' alt='{$txt['find_members']}' /> Find Member</a>";
    }

    function onUse() {
    global $db_prefix, $ID_MEMBER, $item_info;
$result = db_query("SELECT memberName
                                FROM {$db_prefix}members
                                WHERE ID_MEMBER = {$ID_MEMBER}", __FILE__, __LINE__);
$row = mysql_fetch_array($result, MYSQL_ASSOC);
$user = $row['memberName'];
if ($user == $_POST['dicetarget']) {
return "Use some random money instead. Use the die on someone else"; 
die();
}
else {
$amount = mt_rand($item_info[2], $item_info[1]);
$result = db_query("UPDATE {$db_prefix}members
                                SET money = money + {$ammount}
                                WHERE memberName = '{$_POST['dicetarget']}'", __FILE__, __LINE__);
$result = db_query("SELECT ID_MEMBER
        FROM {$db_prefix}members
                         WHERE memberName = '{$_POST['dicetarget']}'", __FILE__, __LINE__);
$row = mysql_fetch_array($result, MYSQL_ASSOC);
$pmfrom = array(
'id' => 1,
'name' => 'Basilbeard',
'username' => 'Basilbeard'
);
$pmto = array(
to' => array($row['ID_MEMBER']),
'bcc' => array()
);
sendpm($pmto, 'Care for a game of chance?', "{$user} uses a Magic Die on you. You check your inventory, and find that your point total has been changed by ".formatMoney($amount)."! [i] You do not need to reply to this automated message[/i]", 0, $pmfrom);                                           
return "You use your Magic Die on {$_POST['dicetarget']}. It spins around for a few minutes, before landing on {$amount} and thus changing their point count by {$amount} points!";;
}
}
}
?>

--- End code ---

Now of course, if I am coding my own items I don’t worry too much about commenting. But if I am doing some coding for others and that, its nice to add some recognition both to myself and to Daniel. So that’s why I put that block of code up at the very top.

The only thing remaining is to upload the item, install it and test it out. For me this never seems to work the first time, or the second time, or even the nth time. I personally prefer to just code the item in something like Crimson Editor, which shows any really dumb mistakes, and then to just upload and let it tell me what I did wrong instead of spending time looking for it myself. For example, the first time I coded this item I forgot the ; on the $user = $row['memberName']; line, causing the whole function not to work.

Once all the errors in the code are sorted out. We need to see if the item works. I start by using it on myself.


--- Quote --- Use some random money instead. Use the die on someone else
--- End quote ---

Yay! It worked!.

I then try on someone else. And get a mysql error. As I said, I hate those errors. Luckily, this one wasn’t bad. I had {$ammount} instead of {$amount}.

I use it again and get

--- Quote --- You use your Magic Die on Bunny. It spins around for a few minutes, before landing on 52 and thus changing their point count by 52 points!
--- End quote ---

Yay! The last thing to do is log into Bunny and see the PM. It works! So I assume the item is working just fine and release it on my forum. Or, in this place, remove it from my forum and post this guide.


I'm open to any questions you might have about the guide. Questions about SMF_SHOP itself would probably be better suited for Daniel cause he was the one that acutally wrote it  :P

Update: 4/23: Scroll down for part 2! Learn how to code items which use custom database fields!

tazpot:
Aye captain that be a fine tutorial for the young noobs like myself. This shipmate is gonna be trying to learn to code in php and what a great place to start.

By the way i have been to your site and had a look around, very impressive shop i must say. O0

Daniel15:
Nice tutorial :) The only thing I would change would be how you handle the user functions. You use the member name in your queries (eg. UPDATE ......... WHERE memberName = .......). What I'd do is instead get the user's ID, and use that instead. The user's ID is a "database key", so database lookups performed using the ID are faster.

This would mean that the section at the top (the section that checks if the user is using the item on themselves) would be changed. My way would be:


--- Code: --- global $db_prefix, $ID_MEMBER, $item_info;
$result = db_query("SELECT ID_MEMBER
                                FROM {$db_prefix}members
                                WHERE memberName = {$_POST['dicetarget']}", __FILE__, __LINE__);
$row = mysql_fetch_array($result, MYSQL_ASSOC);
$user = $row['ID_MEMBER']
if ($user = $ID_MEMBER) {
return "Use some random money instead. Use the die on someone else";
die();
}

--- End code ---


--- Quote --- I am not a PHP know-it-all, so can’t really explain why it works I just know it does.


--- Code: ---$row = mysql_fetch_array($result, MYSQL_ASSOC);
$user = $row[‘memberName’]

--- End code ---

This creates an array called row(I think) which contains the previously selected memberName. We can now call that value using $row[‘memberName’];

--- End quote ---
Yes, you're correct. mysql_fetch_array() fetches the current row into an array. Since you did 'SELECT memberName' in the MySQL query, the only array element will be 'memberName'.

Oh, by the way, I stickyed this post :D

Basil Beard:
Oh cool  :D.

Actually, while in a normal situation that would be best; we still need to do the select memberName query in order to tell the target who used magic die them. So we would have to make the same two querys in the end. Your way would be slightly more efficant because the faster query would be used to run the if statment. Thus if you are targeting yourself, only the faster query would be done.

tazpot:
What do you think of this ? it is my first attempt at knocking a new item together and thought that rather than test it on the site i would get you guy's to give it the once over first and point out any obvious errors.


--- Code: ---<?php
/**********************************************\
| SMFSHOP (Shop MOD for Simple Machines Forum) |
|         (c) 2005 DanSoft Australia           |
|      http://www.dansoftaustralia.com/        |
\**********************************************/

//File: KarmaBash.php
//      Item

//VERSION: 1.1 (Build 4)
//DATE: 10th April 2005

class item_KarmaBash extends itemTemplate {
    function getItemDetails() {
        $this->name = "Karma Bash";
        $this->desc = "Bash 10 points of another members karma";
        $this->price = 50;

        $this->require_input = true;
        $this->can_use_item = true;
    }

    function getUseInput() {
global $context, $scripturl, $settings, $txt;
        return "Karma Bash: <input type='text' name='bashwho' size='50'>
         <a href='{$scripturl}?action=findmember;input=bashwho;quote=0;sesc={$context['session_id']}' onclick='return reqWin(this.href, 350, 400);'><img src='{$settings['images_url']}/icons/assist.gif' border='0' alt='{$txt['find_members']}' /> Find Member</a>";
    }
         function onUse() {
        global $db_prefix, $ID_MEMBER, $item_info;

$result = db_query("UPDATE {$db_prefix}members
                               SET karmaBad = karmaBad + 10
                               WHERE membername = {$_POST['bashwho']}",
                               __FILE__, __LINE__);
   return "Successfully bashed 10 karma points from {$_POST['bashwho']}!";
$result = db_query("SELECT ID_MEMBER
       FROM {$db_prefix}members
                           WHERE memberName = '{$_POST[‘bashwho’]}'", __FILE__, __LINE__);
$row = mysql_fetch_array($result, MYSQL_ASSOC);
$pmfrom = array(
'id' => 1,
'name' => 'Tazpot',
'username' => 'Tazpot'
);
$pmto = array(
to' => array($row['ID_MEMBER']),
'bcc' => array()
);
sendpm($pmto, 'You have been bashed', “{$user} bashed 10 points off your karma."”! [i] You do not need to reply to this automated message”, 0, $pmfrom);
}
}

?>
--- End code ---

I have used bits of code from increasekarma.php and fiddled with it a bit. Any tips or advice would be great.

Navigation

[0] Message Index

[#] Next page

Go to full version