PDA

View Full Version : The MVC model!


Novo
01-11-2008, 05:11 PM
To make a good NPC system (not merely a stand-alone project) requires a lot of practice. I myself tend to make several versions of the system: I personally have made more than a dozen MUD systems -- each time I was finished, I'd scratch it and started anew. The reasons were simple: As time went on, the complexity of the script becomes too high to actually progress any further.

There is a way to limit complexity, however! The MVC model is based on modularizing your compartments in your scripts. This approach dictates how you should go about.

As a way to show this, I'll be displaying my Item system that I've honed.

There are three compartments to any system. The (M)odel, the (V)isual, and the (C)ontrol. The Model is the system logic, in this example, the core item system... In simple terms, the primitive functions that work on a system. The Visuals, such as the inventory, communicates with the Control to get answers from the Model or other Visuals ( if need be ). In this example, we'll focus on the Model <=> Control <=> Visual. In no instance does the visual and model communicate directly! The Model and Visual are meant to be portable: You can change the system which they work with and still maintain them without problems. The Control, however, usually needs some reworking ( To fix the links between the model and visuals ).

We'll start by the foundations of the system -- The Model. In the Item system, it becomes pretty straightforward. To design the system, you would first ask what methods that the system would have. Suggestively, I would go by this:


addItem( itemID, quant );
removeItem( itemID, quant );
hasItem( itemID );


It's a start, but you might want to add other functions, such as getItemQuantity, setItemQuantity, setItemState, getItemState, hasItem... But for this presentation, we'll keep it simple.

To decide the primitives, I personally like the system where you have your dynamic variables and static variables separated. In this system, the items themselves don't change ( this minimizes the memory required to run the system ).

What I prefer, in this instance, is using player.clientr.items to define the item and its quantity, and player.clientr.item_[ITEMID] to define the item information.

An easy way to get this to work would be having a multi-dimensional array for the item information, thus, {itemID, QUANT}. Alternatively, you could have more information: {itemID, QUANT, STATE, POSITION, FORSALE }...

Something that I've been starting to do with these systems, especially for the item information, is using a mapped array. I define mapped array as an array of arrays whose structure is {key, value}, in this case... {{"itemID", itemID}, {"Quantity", QUANT}, {"State", STATE}, ...}

How this information is stored, however, is up to you. For sakes of minimizing the memory footprint, I'll use {itemID, quant}.

Once you have decided on the structure of the data, it is now time to do the nitty-gritty. This 'tutorial' isn't based on how you should proceed, so I won't go through why it is made as such:


// Serverside
public function addItem( itemID, quant )
{
if ( quant < 1 )
return;

for ( i = 0; i < clientr.items.size(); i ++)
{
if ( clientr.items[ temp.i ][ 0 ] == itemID )
{
clientr.items[ temp.i ][ 1 ] += quant;
return true;
}
}

clientr.items.add( { itemID, quant } );
clientr.(@"item_"@ itemID ) = ITEMINFORMATION( itemID );
return true;
}

public function removeItem( itemID, quant )
{
if ( !hasItem( itemID ) )
return false;

for ( temp.i = 0; temp.i < clientr.items.size(); temp.i ++ )
{
if ( clientr.items[ temp.i ][ 0 ] != itemID )
continue;
if ( clientr.items[ temp.i ][ 1 ] > quant )
{
clientr.items[ temp.i ][ 1 ] -= quant;
} else clientr.items.delete( temp.i );

return true;
}

return false;
}

public function hasItem( itemID )
{
for ( temp.i = 0; temp.i < clientr.items.size(); temp.i ++ )
{
if ( clientr.items[ temp.i ][ 0 ] == itemID )
return true;
}
return false;
}


Simple enough. So the next step... Because I hate doing visuals, I tend to do them last. Whether the Model or Visual is done first isn't important, but by the time you do Control, you need to know what you're working with.

So... The Control... The control in this scenario is based on the clientside, but this could work just as well on the serverside. In my Item control, I don't want the items to be manipulated. First, I would decide what the control would allow us to get... For simplicity sakes, I've decided on this control:


getAllItems();
getItemQuantity( itemID );
getItemProp( itemID, propName );
hasItem( itemID );


It's relatively simple, but this could be extended further to getItemState( itemID ), setItemState( itemID, state ) (something I want edited ), placeItem( itemID, position ), getSelectedItem( itemType ), and anything else that you might find important.

So... To start, I'm going to work on what I know from my Model. The values that I have access to SHOULD ONLY BE ACCESSED HERE. In no instance should my Visual interact DIRECTLY with the values. For instance, instead of returning {itemID, QUANT, STATE, POSITION, etc }, I'll have functions that are designed to return each of the properties INDEPENDANTLY. This is vital to diminish the complexity for when you change the system ( add a new property, or convert the system to one that requires more information ).


//#CLIENTSIDE
public function getAllItems()
{
temp.itemsID = null;
for ( temp.itemData: clientr.items )
temp.itemsID.add( temp.itemData[0] );

return temp.itemsID;
}

public function getItemQuantity( itemID )
{
for ( temp.itemData: clientr.items )
{
if ( temp.itemData[0] == itemID )
return temp.itemData[1];
}

return 0;
}

public function getItemProp( itemID, propName )
{
if ( !hasItem( itemID ) )
return false;

temp.keyMap = {"itemId", "stuff", "OtherStuff"};
temp.keyIndex = temp.keyMap.index( propName );
return clientr.(@"item_"@ itemID )[ temp.keyIndex ];
}

public function hasItem( itemID )
{
for ( temp.itemData: clientr.items )
{
if ( temp.itemData[ 0 ] == itemID )
return true;
}

return false;
}


So far, we got the Model and Control down pretty nice. These CAN change... When you do a change in the system, all you have to do is keep the function names the same. Like that the Visual doesn't change even if you change some rather deep system components. The Control would have to remap to the new primitive data types, but in general, everything should stay the same visually.


//#CLIENTSIDE
function drawInv()
{
temp.items = ItemControl.getAllItems();
temp.count = 0;
for ( temp.item: temp.items )
{
temp.itemName = ItemControl.getItemProp( temp.item, "Name" );
temp.itemIcon = ItemControl.getItemProp( temp.item, "Icon" );
// temp.itemState = ItemControl.getItemState( temp.item );
temp.itemQuant = ItemControl.getItemQuantity( temp.item );

temp.posX = temp.count [MOD] displayWidth;
temp.posY = int( temp.count / displayWidth );

temp.img = showimg( ... );
temp.img.layer = 5;
// ...

temp.count ++;
}
}


Once that is done... The system is over! The model and visual can change, but apart from the minor changes inside the Control, the other components don't change. This allows you to focus on what makes your server unique instead of always trying to find how something is connected to something else.

I hope this helps! If you want other non-technical help on scripting, I have posted guidelines on how to increase productivity here:

http://forums.graalonline.com/forums/showthread.php?t=77413

Dan
01-11-2008, 10:22 PM
I dislike your styling, but I can tell these scripts might be useful for someone in need of a simple, yet effective, basic item system.

Novo
01-12-2008, 12:08 AM
I dislike your styling, but I can tell these scripts might be useful for someone in need of a simple, yet effective, basic item system.

Have you even read what I wrote? ( In case you did: It doesn't mention styling nor is this a working item system. Stay on topic, please. Hint: It talks about the MVC model. )

Twinny
01-12-2008, 01:38 AM
Although I never made it official, I guess i've always coded like this: easy to upgrade the system the base-system without screwing over everything else ^^

Kristi
01-13-2008, 05:27 PM
I dislike your styling, but I can tell these scripts might be useful for someone in need of a simple, yet effective, basic item system.
I think most the world dislikes yours!

That point being made though, neither of our comments had a place in this thread :)

Codein
01-13-2008, 07:10 PM
I just read in my Computing handbook about the importance of modularisation. It's great to see how it'd be done in GScript :)

Inverness
01-13-2008, 08:49 PM
Modularization is good, but its made a bit harder by not being able to use catchevent() for dynamic events.

Dan
01-16-2008, 09:47 AM
My bad xP

MrAnonymous_P2P
01-16-2008, 03:48 PM
Looks good to me ;)