Game Development Community

MMORPG Tutorial Article 8 Forage, Bake and Eat a Cake!

by Dreamer · 05/20/2005 (9:56 am) · 22 comments

In all the MMORPGs I've ever played, my favorite parts were the tradeskill systems, so of course I wanted to include some in Dream.

The thing with tradeskills is that there are litterally a million ways to do them in TGE, I have choosen 3 basic types of tradeskills to use in Dream, they are... Player Item based, (This was introduced in the Fishing tutorial), Player Skill based (Needs no item, just the skill of the player), and World Item based (This is the usage of things in the world to create items, for instance place a few items in an Oven and cook them).

One real key to implementing successful Tradeskills is to make sure that the there are alot of new and interesting items for the player to create, being able to make useful items is key to this.

There is at present only one real way to create and utilize items in TGE and thats through the use of datablocks. I wanted to create a GUI based system so the DM (Dungeon Master) could create new items without having to write a stitch of code, to do that the first thing I had to do was create database, rather than datablock based system for all the in game items.

NOTE: Unless I state otherwise all the code block go in server/scripts

Add this to your commands.cs file
function LoadItems(){
	//This code creates a datablock dynamically from the database
	%query = "SELECT * FROM Items";
	%result = $SqLite.query(%query,0);
	$SQLite.dump();
	if(%result){
		while(!$SqLite.endOfResult(%result)){
			%BaseType = $SqLite.getColumn(%result,1);
			%Name = $SqLite.getColumn(%result,2);
			%dbstring ="datablock "@%BaseType@"("@%Name@"){";
			for(%x = 2; %x <= $SqLite.numColumns(%result) - 1; %x++){
				%varName = $SqLite.getColumnName(%result, %x);
				if(%varName !$= "Dynamics"){
					if($SqLite.getColumn(%result,%varName) !$="" ){
						%dbstring = %dbstring@ "\n"@ %varName@" = \""@$SqLite.getColumn(%result,%varName)@"\";";
					}
				}else{
					%Dynamics = $SqLite.getColumn(%result,%varName);
					//echo("To see this means that Dynamics is an invalid Column");
					%i = 0;
					while (getWord(%Dynamics, %i) !$= ""){
						%varName = getWord(%Dynamics, %i);
						%varValue = getWord(%Dynamics, %i++);
						%dbstring = %dbstring @ "\n"@ %varName @ " = \"" @ %varValue @ "\";";
						%i ++;
					}
				}
				
			}
		%dbstring = %dbstring@"\n};";
		echo("Pulling new datablock from db");
		echo(%dbstring);
		eval(%dbstring);
		$SqLite.nextRow(%result);
		}
		$SqLite.clearResult(%result);
	}else{
		
	}
}

I have made significant modifications to InitDB.cs to accomplish this.
So you need to replace that file with the following code

function InitializeDB(){
	//This function only ever needs initialized once
	//Create Tables
	if($Pref::Server::RunOnce != 1){
		%query[0] = "CREATE TABLE Items (BaseType VARCHAR(30),ItemName VARCHAR(50),Category VARCHAR(20),ClassName VARCHAR(20),ShapeFile VARCHAR(255),mass VARCHAR(10),elasticity VARCHAR(10),friction VARCHAR(10),emap VARCHAR(5),pickUpName VARCHAR(50),Image VARCHAR(50),Dynamics VARCHAR(255))";
		%query[1] = "CREATE TABLE Accounts (Name VARCHAR(20),password VARCHAR(20),Race VARCHAR(50),Level INT(11))";
		%query[2] = "CREATE TABLE Skills (SkillName VARCHAR(15),MinLevel INT(5))";
		%query[3] = "CREATE TABLE PlayerStats (Name VARCHAR(20), WIS INT(5),STR INT(5),STA INT(5),END INT(5),DEX INT(5),CHA INT(5))";
		%query[4] = "CREATE TABLE Inventory (Name VARCHAR(20),Item VARCHAR(20),Amount INT(11))";
		%query[5] = "CREATE TABLE NPC (Name VARCHAR(20),TextIndex int(11))";
		%query[6] = "CREATE TABLE NPCText (TextIndex int(11),NPCWords VarChar(255))";
		%query[7] = "CREATE TABLE PlayerSkills (Name VARCHAR(15),Skill VARCHAR(15),Level INT(5))";
		%query[8] = "CREATE TABLE Recipies (Skill VARCHAR(15),MinLevel INT(5),Reagents VARCHAR(255),Item VARCHAR(20))";
		for(%x = 0; %x <= 8; %x++){
			echo(%query[%x]);
			%result = $SqLite.query(%query[%x],0);
			$SqLite.clearResult(%result);
		}
		InitItems();
		InitSkills();
		InitRecipies();
		$Pref::Server::RunOnce = 1;
	}	
}

function InitItems(){

	//Insert Items
	//INSERT INTO Items VALUES (BaseType,ItemName,Category,ClassName,ShapeFile,mass,elasticity,friction,emap,pickUpName,Image, Dynamics)";
	%query[0] = "INSERT INTO Items VALUES ('ItemData','Sword','Weapon','Weapon','dream.game/data/shapes/sword/sword.dts','1','0.2','0.6','true','a sword','SwordImage','')";
	%query[1] = "INSERT INTO Items VALUES ('ItemData','HealthKit','Health','Health','dream.game/data/shapes/items/healthKit.dts','1','1','0.3','true','a health kit','','repairAmount 50')";
	%query[2] = "INSERT INTO Items VALUES ('ItemData','HealthPatch','Health','Health','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','a health patch','','repairAmount 20 maxInventory 0')";
	%query[3] = "INSERT INTO Items VALUES ('ItemData','Crossbow','Weapon','Weapon','dream.game/data/shapes/crossbow/weapon.dts','1','0.2','0.6','true','a crossbow','CrossbowImage','')";

	//Foragable Items
	%query[4] = "INSERT INTO Items VALUES ('ItemData','Berries','Forage','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','a small group of berries','','repairAmount 1')";
	%query[5] = "INSERT INTO Items VALUES ('ItemData','Flour','Forage','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','flour','','')";
	%query[6] = "INSERT INTO Items VALUES ('ItemData','Water','Forage','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','some water','','')";
	%query[7] = "INSERT INTO Items VALUES ('ItemData','Salt','Forage','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','a little salt','','')";
	%query[8] = "INSERT INTO Items VALUES ('ItemData','Sugar','Forage','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','a little sugar','','')";
	%query[20] = "INSERT INTO Items VALUES ('ItemData','Leaves_and_Twigs','Forage','','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','some leaves and twigs','','')";

	//Produced Cooking Items
	%query[9] = "INSERT INTO Items VALUES ('ItemData','Dough','','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','some dough','','')";
	%query[10] = "INSERT INTO Items VALUES ('ItemData','Salt_Water','','Drink','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','Salt Water','','')";
	%query[11] = "INSERT INTO Items VALUES ('ItemData','Frosting','','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','yummy frosting','','')";
	%query[12] = "INSERT INTO Items VALUES ('ItemData','Cake','','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','an unfrosted cake','','')";
	%query[13] = "INSERT INTO Items VALUES ('ItemData','Frosted_Cake','','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','a yummy frosted cake','','')";
	%query[14] = "INSERT INTO Items VALUES ('ItemData','Salt_Water_Taffy','','Food','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','salt water taffy','','')";
	%query[15] = "INSERT INTO Items VALUES ('ItemData','Scroll_Minor_Salt_Water_Taffy_Golem','Scroll','Spell','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','salt water taffy golem scroll','','')";
	%query[16] = "INSERT INTO Items VALUES ('ItemData','Mud','','','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','salt water taffy','','')";
	%query[17] = "INSERT INTO Items VALUES ('ItemData','Melted_Junk','','','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','melted junk','','')";

	//Items from fishing skill
	%query[18] = "INSERT INTO Items VALUES ('ItemData','Fish','Fishing','Food','dream.game/data/shapes/fish/fish.dts','1','1','0.3','true','a yummy fish','','repairAmount 50')";
	
	//Produced Tailoring Items
	%query[19] = "INSERT INTO Items VALUES ('ItemData','Clay','','','dream.game/data/shapes/items/healthPatch.dts','1','1','0.3','true','a piece of clay','','')";


	
	for(%x = 0; %x <= 20; %x++){
		echo(%query[%x]);
   		%result = $SqLite.query(%query[%x],0);
        	$SqLite.clearResult(%result);
   	}
}

function InitSkills(){
   //This function sets up the Skills table

   //Tradeskills
   %query[0] = "INSERT INTO Skills VALUES ('Beg','0')";
   %query[0] = "INSERT INTO Skills VALUES ('Cooking','0')";
   %query[1] = "INSERT INTO Skills VALUES ('Forage','0')";
   %query[2] = "INSERT INTO Skills VALUES ('WoodChop','0')";
   %query[3] = "INSERT INTO Skills VALUES ('Smithing','0')";
   %query[4] = "INSERT INTO Skills VALUES ('Fishing','0')";
   %query[5] = "INSERT INTO Skills VALUES ('JewelCraft','0')";
   %query[6] = "INSERT INTO Skills VALUES ('Mining','0')";
   %query[7] = "INSERT INTO Skills VALUES ('Refining','0')";
   %query[8] = "INSERT INTO Skills VALUES ('Tailoring','0')";
   %query[9] = "INSERT INTO Skills VALUES ('LeatherWork','0')";
   %query[10] = "INSERT INTO Skills VALUES ('Skinning','0')";
   
   //Combat Skills
   %query[10] = "INSERT INTO Skills VALUES ('1hs','0')";
   %query[11] = "INSERT INTO Skills VALUES ('2hs','0')";
   %query[12] = "INSERT INTO Skills VALUES ('Blocking','0')";
   %query[13] = "INSERT INTO Skills VALUES ('Kick','0')";
   %query[14] = "INSERT INTO Skills VALUES ('Bash','0')";
   %query[15] = "INSERT INTO Skills VALUES ('Throw','0')";
   %query[16] = "INSERT INTO Skills VALUES ('AtkSpeed','0')";

   //Magic and Misc
   %query[17] = "INSERT INTO Skills VALUES ('Focus','0')";
   %query[18] = "INSERT INTO Skills VALUES ('Energy','0')";
   %query[19] = "INSERT INTO Skills VALUES ('RunSpeed','0')";
   %query[20] = "INSERT INTO Skills VALUES ('CastSpeed','0')";
   
   for(%x = 0; %x <= 20; %x++){
	echo(%query[%x]);
   	%result = $SqLite.query(%query[%x],0);
        $SqLite.clearResult(%result);
   }	
}

Next we want to add some basic recipies to start the game with, but how do we want to do recipies?
I choose a vector or string based approach, all the items are loaded to and from a single string, which is treated much like a vector, this does of course limit us to 255 characters (including white space), in the recipies, but it was by far the easiest and most efficient way to cram it into the DB

function InitRecipies(){

     //Recipies (Skill VARCHAR(15),MinLevel INT(5),Reagents VARCHAR(255),Item VARCHAR(20))

   //Beg
   %query[1] = "INSERT INTO Recipies VALUES ('Beg','0','NULL','CopperCoin 1')";

   //Skinning
   %query[2] = "INSERT INTO Recipies VALUES ('Skinning','0','NULL','Raw_Leather 1')";

   //Cooking
   %query[3] = "INSERT INTO Recipies VALUES ('Cooking','1','Flour 1 water 1','Dough 1')";
   %query[4] = "INSERT INTO Recipies VALUES ('Cooking','2','Salt 1 water 1','Salt_Water 1')";
   %query[5] = "INSERT INTO Recipies VALUES ('Cooking','3','Berries 1 Sugar 1 water 1','Frosting 1')";
   %query[6] = "INSERT INTO Recipies VALUES ('Cooking','4','Dough 2 Salt 1 Sugar 1 water 1','Cake 1')";
   %query[7] = "INSERT INTO Recipies VALUES ('Cooking','8','Frosting 1 Cake 1','Frosted_Cake 1')";
   %query[8] = "INSERT INTO Recipies VALUES ('Cooking','2','Salt_Water 1 Sugar 4','Salt_Water_Taffy 1')";
   %query[9] = "INSERT INTO Recipies VALUES ('Cooking','10','Salt_Water_Taffy 4 Sand 1','Scroll_Minor_Salt_Water_Taffy_Golem 1')";
   %query[10] = "INSERT INTO Recipies VALUES ('Cooking','0','Sand 1 Water 1','Mud 1')";

   //Tailoring
   %query[11] = "INSERT INTO Recipies VALUES ('Tailoring','0','Sand 1 water 1','Clay 1')";
   %query[12] = "INSERT INTO Recipies VALUES ('Tailoring','1','Clay 1 Silk_Thread 1','Spool_Thread 1')";
   %query[13] = "INSERT INTO Recipies VALUES ('Tailoring','1','Clay 2 Silk_Thread 2','Large_Spool_Thread 1')";
   %query[14] = "INSERT INTO Recipies VALUES ('Tailoring','1','Cap_Pattern 1 Needle 1 Spool_Thread 4','Silk_Cap 1')";
   %query[15] = "INSERT INTO Recipies VALUES ('Tailoring','1','Frosted_Cake 2 Silk_Cap 1','Party_Hat 1')";

   //LeatherWorking
   %query[16] = "INSERT INTO Recipies VALUES ('LeatherWork','1','Cap_Pattern 1 Needle 1 Spool_Thread 4 Raw_Leather 1','Leather_Cap 1')";
   
   for(%x = 0; %x <= 20; %x++){
	echo(%query[%x]);
   	%result = $SqLite.query(%query[%x],0);
        $SqLite.clearResult(%result);
   }
   
}

Let's take a quick look at the first cooking recipie before moving on.
It says at skill level 1, 1 flour and 1 water will produce 1 dough.
This is nice, because we can redefine it at some other level, so the same ingredients create a whole new item.
Also in regards to our approach to recipies, if you integrate my code as-is 1 water and 1 flour will NOT yield 1 dough, I thought about adding some code later on that would sort the recipie coming from the player by alpha, but decided against it, that way if later on I decided that 1 water and 1 flour were to make Flour Water or some such thing, I wouldn't have to go back to square one. If you decide this is more of a burden than a feature I will discuss later on how to alpha sort the recipie before comparing it against the DB.

Next thing we want to do, is add the Forage tradeskill, I am making forage the first tradeskill since it was by far and away the easiest to implement.

In commands.cs add the following
//Handles the forage tradeskill
function serverCmdForage(%client){
	echo(%client.name@" is beginning to forage.");
	%Forage = getRandom(1,32);
	%Forage += (%client.Skill[Forage] * 0.1);
	%client.Player.setActionThread("sitting");
	if(%Forage < 20){
		echo(%client.name@" was unsuccessful at forage.");
		MessageClient(%client,'ForageFailure','You were unsuccessful at foraging');
		
	}else{
		
		%Item = PickRandomItem("Forage");
		echo(%client.name@" was successful at forage, now choosing item. Forage returned "@%Forage);
		%client.Player.incInventory(%Item,1);
		MessageClient(%client,'ForageSuccess','You have successfully foraged '@%Item);
		%incSkill = getRandom(1,100);
		if(%incSkill > 99){
			%client.Skill[Forage]++;
			CommandToClient(%client,'UpdateSkill','Forage',%client.Skill[Forage]);
		}
	}
}

Now in dbcommands.cs add the following.
function PickRandomItem(%Skill){
	//This Function picks a random item from the items table based on Skill
	//Skills may be any tradeskill and forage
	//Currently Only forage is implemented
	
	%ItemName = "";
	echo("We are picking a reward item for sucessful usage of Skill "@%Skill);
	%query = "SELECT * FROM Items WHERE Category = \'"@%Skill@"\'";
	echo(%query);
	%result = $SqLite.query(%query,0);
	echo("Result was "@%result);
	
	
	if(%result){
		%result.dump();
		echo("If we are seeing this then we have successfully queried for Items based on skill there are "@$SqLite.numrows(%result)@" possible choices");
		%RandomItemNum = getRandom(1,$SqLite.numrows(%result));
		//%RandomItemNum = %RandomItemNum--;
		if(%RandomItemNum < 1)
			%RandomItemNum++;
		echo("We are going with the item at row "@%RandomItemNum);
		echo("First thing we should move the result pointer");
		$SqLite.setRow(%result, %RandomItemNum);
		echo("Hey great we didn't segfault! Now we assign the Item column to the %Item variable");
		%ItemName = $Sqlite.getColumn(%result,"ItemName");
		echo("What? No segfault yet?  Kewl now lets clear the result!");
		if(%result){
			$SqLite.clearResult(%result);
			echo("Ok result set cleared");
		}else{
			echo("Error! No result to clear?  why is that?");
		}
		
	}else{
		echo("No item could be found to match the skill so we are giving the player a fish");
		echo("Result was "@%result);
		return("Fish");
	}
	echo("If we are seeing this then there is no problem in the process of item selection");
	if(%ItemName !$= ""){
		return(%ItemName);
	}else{
		return("Fish");

	}
}

Ok thats it for forage, just bind a key on the client that calls CommandToServer('Forage'); And your all set.

Now that forage is setup, we have a way to gather ingredients for our cooking recipies.
To cook I choose to go with a world container based approach.
In game.cs add the following code
function MakeOven(){
   new TSStatic(Oven) {
      position = "359.659 311.587 220.156";
      rotation = "1 0 0 0";
      scale = "2 2 1";
      shapeName = "~/data/shapes/items/oven.dts";
      isTrader = 1;
      isTargetable = 1;
      useSkill = "Cooking";
   };
   new ParticleEmitterNode(OvenFire) {
      position = "360.342 310.28 218.746";
      rotation = "1 0 0 0";
      scale = "1 1 1";
      dataBlock = "ChimneyFireEmitterNode";
      emitter = "ChimneyFireEmitter";
      velocity = "1";
   };
   new ParticleEmitterNode(OvenSmoke) {
      position = "360.342 310.28 218.746";
      rotation = "1 0 0 0";
      scale = "10 10 1";
      dataBlock = "ChimneySmokeEmitterNode";
      emitter = "ChimneySmokeEmitter";
      velocity = "1";
   };
    new TSStatic() {
         position = "360.173 309.965 218.314";
         rotation = "1 0 0 0.573347";
         scale = "1 1 1";
         shapeName = "~/data/shapes/campfires/campfire.dts";
      };
}

This has the effect of turning the campfire in the middle of the zone into an oven, for the oven.dts I just created a box with a side missing and exported it. If your too cheap and lazy to do this yourself I have included it in the zip :) Another option would be to just rename the campfire to Oven, and add the following lines
isTrader = 1;
 isTargetable = 1;
 useSkill = "Cooking";

Note for the OvenFire and OvenSmoke, I just made some minor modifications to some existing fire and smoke emitters in the particle editor.

Now you may be asking why the isTrader and isTargetable flags? Well the answer is simple.
I moidified targeting to use -1 as the TypeMask (this means everything) and then anything I actually wanted targetable, I just added the flag to, this way ONLY exactly what the player wanted to target is actually targetable.
isTrader is a new flag I will be using for anything or anyone who will be pushing a tradeGUI or MultiGUI (these are discussed in a minute or two).

Ok in commands.cs replace your serverCmdTarget function with the following code
//Sets a target for the player
function serverCmdTarget(%client, %mouseVec, %cameraPoint,%TargType){
	//Determine how far should the picking ray extend into the world?
	%selectRange = 200;
	%pi = 3.14159;
	// scale mouseVec to the range the player is able to select with mouse
	%mouseScaled = VectorScale(%mouseVec, %selectRange);
	
	// cameraPoint = the world position of the camera
	// rangeEnd = camera point + length of selectable range
	%rangeEnd = VectorAdd(%cameraPoint, %mouseScaled);
	%TargType = detag(%TargType);
	if(%TargType $= "Player"){
		%searchMasks = $TypeMasks::PlayerObjectType;
	}
	%searchMasks = -1;
	// Search for objects within the range that fit the masks above. If we are
	// in first person mode, we make sure player is not selectable by setting
	// fourth parameter (exempt  from collisions) when calling ContainerRayCast
	%player = %client.player;

	if ($firstPerson){
		%scanTarg = ContainerRayCast (%cameraPoint, %rangeEnd, %searchMasks, %player);
	}else{
	//3rd person - player is selectable in this case
		%scanTarg = ContainerRayCast (%cameraPoint, %rangeEnd, %searchMasks);
	}

	//Fail safe so we get SOME kind of target
	if(%scanTarg $= ""){
		InitContainerRadiusSearch(%cameraPoint, %selectRange / %pi, %searchMasks);
		%scanTarg = containerSearchNext();
	}
	
	// a target in range was found so select it
	if (%scanTarg){
		%targetObject = firstWord(%scanTarg);
		%client.setSelectedObject(%targetObject);
		if(%targetObject.isTargetable == 1){
			CommandToClient(%client,'UpdateTargetDialog','NewTarget',%targetObject.getshapeName());
			if(%targetObject.isTrader == 1){
				CommandToClient(%client,'MultiGUI',%targetObject.getName(),%targetObject.useSkill);
			}
		}
	echo("Client has selected" SPC %scanTarg);
	%targetObject.dump();
	}else{
		if(%client.getSelectedObject()){
			%client.clearSelectedObject();
			echo("Client has cleared selection");
			CommandToClient(%client,'UpdateTargetDialog','ClearTarget');
		}
	}
}

Notice that if isTrader and isTargetable are = 1 then we call a function called MultiGUI on the client.
When coming up with tradeskills, my first idea was to create a new gui for each and every tradeskill and also one for NPC interaction. After making the first gui I realized I could make a single generic GUI and just change a couple of variables, to essentially make an entirely new GUI.

Here is my code for the MultiGUI, this goes in the client/ui/MultiGUI.gui file
//--- OBJECT WRITE BEGIN ---
$ToGive = new array();
new GuiControl(MultiGUI) {
	profile = "GuiModelessDialogProfile";
	position = "0 0";
	extent = "640 480";
	new GuiBitmapBorderCtrl() {
		profile = "ChatHudBorderProfile";
		horizSizing = "right";
		vertSizing = "bottom";
		position = "0 0";
		extent = "640 480";
		minExtent = "8 8";
		visible = "1";
		new GuiWindowCtrl(InteractWindow) {
			profile = "GuiWindowProfile";
			horizSizing = "right";
			vertSizing = "bottom";
			position = "0 0";
			extent = "640 480";
			minExtent = "8 2";
			visible = "1";
			text = "";
			maxLength = "255";
			resizeWidth = "0";
			resizeHeight = "0";
			canMove = "1";
			canClose = "0";
			canMinimize = "0";
			canMaximize = "0";
			minSize = "50 50";
			closeCommand = "Canvas.PopDialog(MultiGUI);";
			new GuiTextCtrl() {
				profile = "GuiTextProfile";
				horizSizing = "right";
				vertSizing = "bottom";
				position = "79 42";
				extent = "50 18";
				minExtent = "8 2";
				visible = "1";
				text = "Back Pack";
				maxLength = "255";
			};
			new GuiScrollCtrl() {
				profile = "GuiScrollProfile";
				horizSizing = "right";
				vertSizing = "bottom";
				position = "17 70";
				extent = "200 400";
				minExtent = "8 2";
				visible = "1";
				willFirstRespond = "1";
				hScrollBar = "alwaysOn";
				vScrollBar = "alwaysOn";
				constantThumbHeight = "0";
				childMargin = "0 0";
			
				new GuiTextListCtrl(ClientListView) {
					profile = "GuiTextArrayProfile";
					horizSizing = "right";
					vertSizing = "bottom";
					position = "2 2";
					extent = "178 2";
					minExtent = "8 2";
					visible = "1";
					enumerate = "0";
					resizeCell = "1";
					columns = "0";
					fitParentWidth = "1";
					clipColumnText = "0";
				};
			};
	
			new GuiObjectView(MultiGUIItemView) {
				profile = "GuiDefaultProfile";
				horizSizing = "relative";
				vertSizing = "relative";
				position = "258 71";
				extent = "100 100";
				minExtent = "8 2";
				visible = "1";
				cameraZRot = "0";
				forceFOV = "0";
					helpTag = "0";
			};
	
			new GuiTextCtrl(TargetTextCtl) {
				profile = "GuiTextProfile";
				horizSizing = "right";
				vertSizing = "bottom";
				position = "484 43";
				extent = "26 18";
				minExtent = "8 2";
				visible = "1";
				text = "";
				maxLength = "255";
			};
			
			new GuiScrollCtrl() {
				profile = "GuiScrollProfile";
				horizSizing = "right";
				vertSizing = "bottom";
				position = "409 71";
				extent = "200 400";
				minExtent = "8 2";
				visible = "1";
				willFirstRespond = "1";
				hScrollBar = "alwaysOn";
				vScrollBar = "alwaysOn";
				constantThumbHeight = "0";
				childMargin = "0 0";
			
				new GuiTextListCtrl(GiveListView) {
					profile = "GuiTextArrayProfile";
					horizSizing = "right";
					vertSizing = "bottom";
					position = "2 2";
					extent = "178 2";
					minExtent = "8 2";
					visible = "1";
					enumerate = "0";
					resizeCell = "1";
					columns = "0";
					fitParentWidth = "1";
					clipColumnText = "0";
				};
			};
			new GuiButtonCtrl(ConfirmButton) {
				profile = "GuiButtonProfile";
				horizSizing = "left";
				vertSizing = "bottom";
				position = "278 437";
				extent = "65 30";
				minExtent = "8 2";
				visible = "1";
				command = "DoGive();";
				text = "Confirm";
				groupNum = "-1";
				buttonType = "PushButton";
			};
		};
	};
};
new GuiControl(GimmeGUI) {
	profile = "GuiDefaultProfile";
	horizSizing = "right";
	vertSizing = "bottom";
	position = "0 0";
	extent = "350 300";
	minExtent = "8 8";
	visible = "1";
	helpTag = "0";

	new GuiWindowCtrl(GimmeDialog) {
		profile = "GuiWindowProfile";
		horizSizing = "center";
		vertSizing = "center";
		position = "131 10";
		extent = "350 300";
		minExtent = "8 8";
		visible = "1";
		helpTag = "0";
		text ="Amount";
		maxLength = "255";
		resizeWidth = "0";
		resizeHeight = "0";
		canMove = "1";
		canClose = "1";
		canMinimize = "0";
		canMaximize = "0";
		minSize = "25 25";
		closeCommand = "Canvas.popDialog(GimmeGUI);";
		
		new GuiSliderCtrl(AmountSlider) {
			profile = "GuiSliderProfile";
			horizSizing = "center";
			vertSizing = "top";
			position = "8 41";
			extent = "300 35";
			minExtent = "8 2";
			visible = "1";
			ticks = "10";
			range = "1.0 10.0";
			autoSnap = "1";
			value = "0";
		};

		new GuiButtonCtrl(AcceptButton) {
			profile = "GuiButtonProfile";
			horizSizing = "center";
			vertSizing = "bottom";
			position = "150 150";
			extent = "67 30";
			minExtent = "8 2";
			visible = "1";
			text = "Accept";
			groupNum = "-1";
			buttonType = "PushButton";
		};
	};
};
//--- OBJECT WRITE END ---
function MultiGUI::OnWake(%this){
	$ToGive.empty();
	UpdateMultiGUI();
}

function UpdateMultiGUI(){
	ClientListView.clear();
	GiveListView.clear();
	echo("Done now looping through Inventory");
	for(%x = 0; %x < $Inventory.count(); %x++){
		if($Inventory.getValue(%x) >0){
			ClientListView.addRow(%x,$Inventory.getKey(%x) SPC $Inventory.getValue(%x));
			echo("Added Item at "@%x);
		}
	}
	for(%x = 0; %x < $ToGive.count(); %x++){
		if($ToGive.getValue(%x) >0){
			GiveListView.addRow(%x,$ToGive.getKey(%x) SPC $ToGive.getValue(%x));
			echo("Added Item "@$ToGive.getKey(%x) SPC $ToGive.getValue(%x));
		}
	}
	
	
	echo("Complete!, now sorting and cleaning");
	ClientListView.sort(0);
	//GiveListView.sort(0);
	
	ClientListView.scrollVisible(0);
	GiveListView.scrollVisible(0);

}

function DoGive(){
	//%Ingredients = "\'";
	for(%x = 0; %x <= GiveListView.rowCount(); %x++){
		%Ingredients= %Ingredients@getField(GiveListView.getRowTextById(%x),0)@" ";
	}
	%Ingredients = trim(%Ingredients);
	//%Ingredients = %Ingredients@"\'";
	CommandToServer('TradeSkill',%Ingredients);
	//GiveListView.dump();
	echo(%Ingredients);
	Canvas.PopDialog(MultiGUI);
}

function Cheat(){
	for(%x = 0; %x <= 1000; %x++){
		CommandToServer('Forage');
	}
}

function ItemRemove(){
	Canvas.PopDialog(GimmeGUI);
	%AmountToGive = AmountSlider.getValue();
	if(%AmountToGive == 0)
		return;
	echo("AmountToGive is "@%AmountToGive);
	%Item = getWord($SelectedItem,0);
	echo("Item = "@%Item);
	if(!$Inventory.getValue($Inventory.getIndexFromKey(%Item))){
		echo("Evidently a new item");
		$Inventory.add(%Item, %AmountToGive);
		$Invetory.echo();
	}else{
		echo("Player already gave one of these items");
		%OldAmount = $Inventory.getValue($Inventory.getIndexFromKey(%Item));
		%Total = %AmountToGive + %OldAmount;
		$Inventory.erase($Inventory.getIndexFromKey(%Item));
		if(%Total >0){
			$Inventory.add(%Item, %Total);
		}
		$Inventory.echo();
	}
	%OnHand = $ToGive.getValue($ToGive.getIndexFromKey(%Item)) - %AmountToGive;
	echo("OnHand is now "@%OnHand);
	$ToGive.erase($ToGive.getIndexFromKey(%Item));
	if(%OnHand >0){
		$ToGive.add(%Item,%OnHand);
	}
	echo("Changed Inventory at "@%Item@" to "@%OnHand);
	$ToGive.echo();
	UpdateMultiGUI();
}

function ItemAdd(){
	Canvas.PopDialog(GimmeGUI);
	%AmountToGive = AmountSlider.getValue();
	if(%AmountToGive == 0)
		return;
	echo("AmountToGive is "@%AmountToGive);
	%Item = getWord($SelectedItem,0);
	echo("Item = "@%Item);
	if(!$ToGive.getValue($ToGive.getIndexFromKey(%Item))){
		echo("Evidently a new item");
		$ToGive.add(%Item, %AmountToGive);
		$ToGive.echo();
	}else{
		echo("Player already gave one of these items");
		%OldAmount = $ToGive.getValue($ToGive.getIndexFromKey(%Item));
		%Total = %AmountToGive + %OldAmount;
		$ToGive.erase($ToGive.getIndexFromKey(%Item));
		if(%Total > 0){
			$ToGive.add(%Item, %Total);
		}
		$ToGive.echo();
	}
	%OnHand = $Inventory.getValue($Inventory.getIndexFromKey(%Item)) - %AmountToGive;
	echo("OnHand is now "@%OnHand);
	$Inventory.erase($Inventory.getIndexFromKey(%Item));
	if(%OnHand >0){
		$Inventory.add(%Item,%OnHand);
	}
	echo("Changed Inventory at "@%Item@" to "@%OnHand);
	$Inventory.echo();
	UpdateMultiGUI();
}

function ClientListView::OnSelect( %this, %id, %text ){
	MultiGUIItemView.setEmpty();
	//$SelectedItem = ClientListView.getRowTextById(%id);
	$SelectedItem = getField(ClientListView.getRowTextById(%id),0);
	%Item = getWord($SelectedItem,0);
	echo("Selected Item is "@%Item);
	MultiGUIItemView.setObject(%Item,$InventoryImage.getValue($InventoryImage.getIndexFromKey(%Item)),"", 0);
	AmountSlider.setMin(0);
	%Amount = $Inventory.getValue($Inventory.getIndexFromKey(%Item));
	echo("InventoryAmount is "@%Amount);
	AmountSlider.setMax(%Amount);
	AmountSlider.ticks = %Amount - 1;
	GimmeDialog.setText(%Item);
	AcceptButton.Command = "ItemAdd();";
	Canvas.PushDialog(GimmeGUI);
}

function GiveListView::OnSelect( %this, %id, %text ){
	MultiGUIItemView.setEmpty();
	//$SelectedItem = ClientListView.getRowTextById(%id);
	$SelectedItem = getField(GiveListView.getRowTextById(%id),0);
	%Item = getWord($SelectedItem,0);
	echo("Selected Item is "@%Item);
	MultiGUIItemView.setObject(%Item,$InventoryImage.getValue($InventoryImage.getIndexFromKey(%Item)),"", 0);
	AmountSlider.setMin(0);
	%Amount = $ToGive.getValue($ToGive.getIndexFromKey(%Item));
	echo("GiveListAmount is "@%Amount);
	AmountSlider.setMax(%Amount);
	AmountSlider.ticks = %Amount - 1;
	GimmeDialog.setText(%Item);
	AcceptButton.Command = "ItemRemove();";
	Canvas.PushDialog(GimmeGUI);
}

Make sure to exec this since it's a new file.

Now in client/scripts/client_commands.cs add the following
//This function pushes the MultiGUI used for tradeskills
function ClientCmdMultiGUI(%target,%skill){
	%target = detag(%target);
	InteractWindow.setText(%target);
	ConfirmButton.setText(%skill);
	Canvas.PushDialog(MultiGUI);
}

Ok one final file and we are done...
In server/scripts create a new file named WorldItemTrades.cs
//Tradeskills work like this, when a targeting collision occurs between the player and a WorldItem a TradeGUI is created on the client
//The player then selects the items he wants to use.
//The server then looks up any recipies matching said ingredients.
//If a match is found the players inventory is reduced by the amount of each ingredient and a new item is placed in his inventory
//If no match is found then melted junk is added to the players inventory

//CREATE TABLE Recipies (Skill VARCHAR(15),MinLevel INT(5),Reagents VARCHAR(255),Item VARCHAR(20))

function ServerCmdTradeSkill(%client,%ingredients){
	%ingredients = detag(%ingredients);
	echo("\n\nIngredients = "@%ingredients);
	%obj = %client.getSelectedObject();
	%Skill = %obj.getDataBlock(useSkill);
	%query = "SELECT Item from Recipies WHERE Reagents = \'"@%ingredients@"\'";
	echo(%query);
	%result = $SQLite.query(%query,0);
	if($SQLite.numRows(%result) > 0){
		%item = $SQLite.GetColumn(%result,1);
		echo ("Item is "@%item);
		$SqLite.clearResult(%result);
	}else{
		echo("Item was nothing, junk in, junk out!");
		%item = "melted_junk 1";
		//%Item  = CreateNewRecipie(%ingredients);
	}
	%i = 0;
	while (getWord(%ingredients, %i) !$= ""){
		%key = getWord(%ingredients, %i);
		%value = getWord(%ingredients, %i++);
		%client.Player.decInventory(%key,%value);
		%i ++;
	}
	%i= 0;
	while (getWord(%item, %i) !$= ""){
		%key = getWord(%item, %i);
		%value = getWord(%item, %i++);
		%client.Player.IncInventory(%key,%value);
		echo("Gave Player "@%value@" of "@%key);
		%i ++;
	}
	
}

Well there ya go! Thats 2 Tradeskills in the same tutorial, I hope you've enjoyed them, you can modify this a little tiny bit and come up with a whole plethora of tradeskills, the key is to make sure you create WorldItems with new useSkill values, I have given you the roadmap, now it's up to you.
Page «Previous 1 2
#1
05/20/2005 (10:05 am)
Dang there sure are alot of line breaks to fix this time :(
#2
05/20/2005 (10:10 am)
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7514

This is the continuing part of the great series Dreamer is providing us, starting with the above URL. Keep up the great work Dreamer. You're really helping a lot of people.
#3
05/20/2005 (10:40 am)
@Chip, thanks man!
#4
05/20/2005 (3:20 pm)
Sure, these tutorials are very very very helpful !
They are the best tutorials for a MMORPG i've never seen.

Great job Dreamer !
#5
05/20/2005 (3:21 pm)
*shameless self promotion* If you really like my tutorials please rate them a 5 or better :) *end shameless self promotion*
#6
05/20/2005 (9:17 pm)
Thanks again for yet another great tutorial.

Well Done Dreamer.
#7
05/21/2005 (11:28 pm)
My favorite part of these tutorials, where I have to change 90% of it (whenever they use SQlite, I need to change most of it).

These tutorials have been useful, although I have changed them extensively for my uses. Too bad this code can't make the game for us, right beginners? (That kinda sounds like an insult, :| )

Robert
#8
05/21/2005 (11:52 pm)
I've always felt that tutorials are supposed to lay down the general concept using practical examples with the implicit expectation of the reader to "apply" the concept as opposed to implement the resource verbatim.
#9
05/22/2005 (12:09 am)
@Robert, if your not using SQLite for a DB what are you using? I'm considering taking the new MySQL ODBC tutorial thats up and creating a DAL (Database Abstraction Layer) for TGE, that way whether your DB engine is SQLIte, MySQL, MSSQL or other, as long as there is an ODBC driver for it, it should all function the same.

As far as making the game, no my tutorials aren't intended to be anything other than a framework you can add content to, to create a game.

I have to agree wholehearetedly with OneST8, in this regard.
#10
05/22/2005 (10:23 pm)
When's 9 gonna be posted? :)
#11
05/23/2005 (9:04 am)
oooohhhhh now to work on a xylobot script to exploit the tradeskill system ;-)
#12
05/23/2005 (1:24 pm)
Why xylobot? You could easily exploit the tradeksill system with just with this function

function Cheat(){
	for(%x = 0; %x <= 1000; %x++){
		CommandToServer('Forage');
	}
}

Bind that to a key:)

I really should get around to fixing the exploits some time.
#13
05/23/2005 (2:14 pm)
Quote:I really should get around to fixing the exploits some time.
I'd say leave those for your advanced series and even then keep in mind that tutorials are still intended as a "proof-of-concept" rather than being a defacto implementation.

If we all spent the time and effort to write infallible tutorials, we'd have no time to spend on writing the real code that the tutorials are based upon.

Of course if this had been an offical RPGKit I'd expect lots of attention to security as it's meant to be "grounds to build on" whereas a tutorial is meant to be "concepts to build with".

Again, just my opinion. Disregard at your leisure.
#14
05/24/2005 (12:52 pm)
RPG's are may favorite games.

I've been reading along and trying to pound this code from all the tutorials into TGE and make it work. I'm finding this stuff more useful than the All-In-One book.
#15
05/26/2005 (12:53 pm)
In your commands.cs at bottom of serverCmdForage

I have a problem i get a successful forage and then it tries to run

CommandToClient(%client,'UpdateSkill','Forage',%client.Skill[Forage]);

and it cant find it... something missing? for updateskill.

also looking over the database you are missing sand so i added

INSERT INTO Items VALUES ('ItemData','Sand','Forage','Food','starter.fps/data/shapes/items/healthPatch.dts','1','1','0.3','true','Some Sand','','');

if you can help with updateskill would be very helpful to see this thru completed.



Gotta say Dreamer this database with objects is nice i do get every once inawhile i notice when i forage sometimes i will have multiple say berries instead of stacking not to sure where this exists from but will figure it out. again great job
#16
05/27/2005 (4:35 pm)
@Deja, the problem with the berries doing thier thing is due to a bug in the inventory system, it's an easy fix.
UpdateSkill is intentionally left out at present, because I needed to make everything as generic as possible, and updateskill was really specific to my implementation, you basically were able to guess the exact values I had for sand, sorry I left them out.

Ok here's the code fix for the inventory system
In client_commands.cs
//This function handles Inventory Update messages from server
function clientCmdUpdateInventory(%Item,%Amount,%Image){
	echo("Recieved Inventory Item Update from server");
	if(%Item !$=""){
		echo("Item is not NULL");
		if(!$Inventory.getValue($Inventory.getIndexFromKey(%Item))){
			echo("Evidently a new item");
			$Inventory.add(%Item, %Amount);
			$InventoryImage.add(%Item,%Image);
			echo("Added new item successfully");
		}else{
			$Inventory.erase($Inventory.getIndexFromKey(%Item));
			$Inventory.add(%Item, %Amount);
		}
	}else{
		echo("Whoops!  There was no Item in that call!");
	}
	echo("Recieved "@$Inventory.getValue(%Item)@" of "@%Item);
	echo("If we are seeing this there were no segfaults in UpdateInventory");
	//UpdateInventoryListView();
}

BTW, it might be a fun side project for someone to document a way to utilize King Tuts TorqueDB journal system, with this and keep a list of recipies the player knows... I have done this with the advanced tutorial series, but I would like to see others specific implementations to see if they came up with the same or similar solution as I did. :)
#17
05/27/2005 (8:27 pm)
ill post tomorow but i have a generic update skill useable by any of the tradeskills. just by adding 1 thing to any. works great in terms of inc the skill value for the tradeskills. i had a little problem with the oven (static shape) getting the useskill of cooking so i did a npc with the flags and worked great. so i got recipes working completely, also couldnt get the skill of recipes which we got a fix for. but reworked just a couple lines and have it all working great together. cant cook something without the required skill. and when you forage enough using the >99 we have skill going up and adding the new skill to database. so everytime you log in you have your skill saved. I am going to work on npc sale, and buy. using database and level range. i was looking into the journal today seems easy enuff to mix together. til tomorrow happy torquing. and ill get add my updates see if anyone can use.
#18
05/28/2005 (5:19 pm)
Here we go on what i did to get things going and also my update skill, very minor changes

this is forage section in commands.cs

//Handles the forage tradeskill
function serverCmdForage(%client){
	 %foragecall = "Forage";
   echo(%client.name@" is beginning to forage.");
   %Forage = getRandom(1,32);
   %Forage += (%client.Skill[Forage] * 0.1);
   %client.Player.setActionThread("sitting");
   if(%Forage < 20){
      echo(%client.name@" was unsuccessful at forage.");
      MessageClient(%client,'ForageFailure','You were unsuccessful at foraging');
      
   }else{
      
      %Item = PickRandomItem("Forage");
      echo(%client.name@" was successful at forage, now choosing item. Forage returned "@%Item);
      %client.Player.incInventory(%Item,1);
      MessageClient(%client,'ForageSuccess','You have successfully foraged %1 ', %Item);
      %incSkill = getRandom(1,100);
      echo("------------the number was, "@%incSkill);
      if(%incSkill > 99){
      	 %client.Skill[Forage]++;
      	 UpdateSkill(%client,%foragecall);
      }
   }
}

this is the skillupdate section just put into server/scripts/client_commands.cs

function UpdateSkill(%client,%foragecall)
{
	 %query = "SELECT MinLevel FROM SKILLS WHERE SkillName = \'"@%foragecall@"\'";
	 %result = $SQLite.query(%query,0);
   if($SQLite.numRows(%result) > 0){
      %MinLevelResult = $SQLite.GetColumn(%result,1);
      %incValue = %MinLevelResult + 1;
	    %query = "UPDATE Skills SET MinLevel = "@%incValue@" WHERE Skillname = \'"@%foragecall@"\'";
      %result = $SQLite.query(%query,0);
      MessageClient(%client,'ForageSuccess','\c1You Gained Skill in %1 it is now %2', %foragecall, %incvalue);
   }
}

if you are going to use this for other tradeskills just add in
function UpdateSkill(%client,%foragecall) %tailoring and such here and add %tailoring and such to your skill function very easy to use for every tradeskill. will inc your skill as you gain.


this worked for me to actually get the skill from database to use the recipes. as i was having trouble getting the call to get my useskill from my oven (converted campfire) so i created a npc and used him. would rather get the tsstatic to work but couldnt get it to give me back the info.

function ServerCmdTradeSkill(%client,%ingredients){
   %ingredients = detag(%ingredients);
   echo("\n\nIngredients = "@%ingredients);
   %obj = %client.getSelectedObject();
   %Skill = %obj.useSkill;
   echo("-----------Skill is: "@%Skill);
   %query = "SELECT Item FROM Recipies, Skills WHERE Recipies.Reagents = \'"@%ingredients@"\' AND Recipies.Skill = \'"@%Skill@"\' AND Recipies.Skill = Skills.SkillName AND Recipies.MinLevel <= Skills.MinLevel";
   %result = $SQLite.query(%query,0);
   if($SQLite.numRows(%result) > 0){
      %item = $SQLite.GetColumn(%result,1);
      echo ("Item is "@%item);
      $SqLite.clearResult(%result);
   }else{
      echo("Item was nothing, junk in, junk out!");
      %item = "melted_junk 1";
      //%Item  = CreateNewRecipie(%ingredients);
   }
   %i = 0;
   while (getWord(%ingredients, %i) !$= ""){
      %key = getWord(%ingredients, %i);
      %value = getWord(%ingredients, %i++);
      %client.Player.decInventory(%key,%value);
      %i ++;
   }
   %i= 0;
   while (getWord(%item, %i) !$= ""){
      %key = getWord(%item, %i);
      %value = getWord(%item, %i++);
      %client.Player.IncInventory(%key,%value);
      echo("Gave Player "@%value@" of "@%key);
      %i ++;
   }
   
}



hope this can help atleast on the skillup for tradeskills.

@Dreamer
what exactly is the call to retrieve the tsstatic useskill? i tried everything and couldnt get it to work.


BTW looks like wordwrapping is weird on here so might wanna fix the on going lines. sorry bout that, but easy to slap back together.
#19
05/30/2005 (9:41 am)
@Deja %objname.useSkill for instance Oven.useSkill, the code I have above should work as is. It's working here... The problem however might be that you didn't remove the previous campfire from the mission file.
Page «Previous 1 2