|
 |   |  |  |
Tutorial Submission Do you wish to submit a tutorial? README before posting! Submissions are accepted in this forum. |
 |
|
10-15-2008, 06:44 PM
|
#1 (permalink)
|
___ __ _____
Join Date: Dec 2007
Posts: 151
|
Learning JASS
Requires:
- Advanced GUI knowledge.
- Advanced WE knowledge.
- NewGen. (The easiest way, but you can also use JASSCraft.)
I hope you have learned something from this tutorial!
home - next
Last edited by Vestras; 11-01-2008 at 01:00 PM.
|
|
|
10-15-2008, 06:45 PM
|
#2 (permalink)
|
___ __ _____
Join Date: Dec 2007
Posts: 151
|
Introduction
First part - why JASS?
Why JASS and not GUI? Because JASS is:
- easy to make MUI.
- more efficient.
- you get more control over your code.
- easier to use. (yes, actually.)
And to you who doesn't know: you can't say that GUI is better and another thing than JASS. Why? Because GUI is JASS.
Second part - Goodies and badies?
Yes, there is goodies and badies - the goodies are stuff named "natives" and the badies are stuff named "BJs". BJs are functions (explained below) that are created by Blizzard (well, everything in JASS is.), which they used for GUI. The reason BJs is badies is:
- they use natives to work.
- they are slower than natives.
NOTICE - don't worry! I will explain what natives, BJs and functions is below this piece!
But, there is also good BJs! The BJ-variables. Now, in GUI, when you use "Get Last Created Unit", you actually use the function named "GetLastCreatedUnit()" in JASS. Now, all that GetLastCreatedUnit() does, is that it returns a BJ variable - this one:
This is a variable, also created by Blizzard, but because it doesn't call another function, it isn't slow. Therefore: goodie.
NOTICE - there is also good BJs! When debuging, many people uses this function:
this displays a message to all players, and can be used to check which triggers run and which doesn't. There is of course also much more good BJs, try looking around in the function list in TESH!
Third part - JASS basics.
JASS is basicly made up by functions and variables. Now, in functions, there is "subcategories", named natives and BJs.
Functions.
A function would be the following:
call UnitApplyTimedLife(myUnit, myBuff, myDuration) Now, this is a function - this would be adding a life timer in GUI. So, this function requires:
- myUnit - a unit variable. (doesn't have to be a variable though.)
- myBuff - a buff raw code. (in object editor, press ctrl + d to view raw codes)
- myDuration - this is a real. If I placed "2" in this one, the unit (myUnit) would die after 2 seconds. (just an example.)
NOTICE - a function calling another function that you have made, can't be over the function you call!
function A takes nothing returns nothing call B ()endfunctionfunction B takes nothing returns nothingendfunction// That will cause syntax errors.
Creating your own function.
Now, to create your own function for use, you'll have to do this:
function myFunction takes nothing returns nothingendfunction This will create a function for use. Now, this function can content other function calls, which will make this function do something.
Then there are something named arguments, like this:
function myFunction takes unit u returns nothingendfunction
There, you could do something with unit u, and when calling the function, you would do this:
Just like when calling the native function above!
If you about to get this, I suggest you to read that all over.
NOTICE - you can have more arguments by adding commas, like this:
function myFunc takes unit u, integer i returns nothingendfunction
Now, we do miss something, don't we? Returns..?
function myFunc takes unit u, integer i returns integer return 0 endfunction The "returns integer" part - the integer is the returned variable type. This could have been boolean, unit, group, everything as well though. Now, what is returning? A lot of functions, native and BJ, returns stuff, example:
constant native function GetTriggerUnit takes nothing returns unit This is a function you use for getting the triggering unit, right? Now, for making this function have an effect, it has to return something. Basicly, if a function doesn't take anything and doesn't return anything and doesn't set some variable, it pretty much does nothing. (Isn't always true though.) Now, set can actually set a variable to a return. Example:
set myUnit = GetTriggerUnit()
Now, actually, myUnit isn't set to the function GetTriggerUnit(), it is set to the variable the function returns. Get it? Good!
Calling a function.
Now, every single time you need a function, you'll have to call it - what does that mean? Basicly calling is the same as executing something, like if you wanted to take a step, your mind would "call" a function that made you walk.
When using a function, you'll always need to place call before the function name.
call UnitApplyTimedLife(...)
NOTICE - when calling functions, you cannot have
You'll have to use (), where there needs to be the variables you want to call the function with inside the ()s! If the function doesn't take any arguments, you'll just go straigth ()!
Variables.
Locals.
In JASS variables, there are two types of variables - locals and globals. (globals requires vJASS, which is found in NewGen.) Locals are variable types which only are available for use in the function they were declared in.
function myFunc takes nothing returns nothing local integer int = 0 local unit u = GetTriggerUnit ()endfunction
NOTICE - locals will cause syntax errors if another variable or function has the same name as the local!
If I used the variables int and u in other functions, I would have syntax errors.
Globals. (Requires vJASS!)
Globals, as the opposite of locals, can be accessed in every function in the map, and will, like globals, cause syntax errors if a variable or function is declared with the same name as the global.
Globals always needs to be in global "tags", like this:
globals integer i = 1 unit u = null real r = 2.00 // and so on... endglobals
These variables will be able to be accessed all over the map.
Arrays.
Now, an arrayed variable is a variable "with more of the same type in it". If you got an arrayed variable, you can set more of the same variable, example:
local integer array i set i[1] = 0 set i[2] = 1 set i[3] = 2 set i[4] = 3 // and so on...
NOTICE - arrayed variables can't be initialized!
Constants.
A constant variable can't be changed throughout the game, and will cause syntax errors if you try.
globals constant integer i = 0 // This will stay 0 throughout the whole game. unit u = null // This one can be changed, because it's non-constant constant real r = 2.00 // Again, can't be changed endglobals
Now, locals can't be constants.
Why is this useful? This can be useful for ability raw codes and condition functions.
NOTICE - functions can be constant too!
previous - home - next
|
|
|
10-15-2008, 06:45 PM
|
#3 (permalink)
|
___ __ _____
Join Date: Dec 2007
Posts: 151
|
Intermediate JASS
At this part, we are going to expand what we just learned. We are going to create a little more advanced functions, events, conditions and actions.
Creating Events.
When you convert your trigger into custom text, you will always have a function named InitTrig_TriggerName, and in there, the events, conditions and actions is.
function InitTrig_MyTrig takes nothing returns nothing set gg_trg_MyTrig = CreateTrigger ()// gg_trg_MyTrig is a trigger variable which is created for each trigger call TriggerRegisterAnyUnitEventBJ (gg_trg_MyTrig, EVENT_PLAYER_UNIT_SPELL_EFFECT )// here we register when any unit starts the effect of an ability// and so on...endfunction
This will register when a unit starts the effect of an ability, and then, to add conditions and actions, we would do this:
function InitTrig_MyTrig takes nothing returns nothing set gg_trg_MyTrig = CreateTrigger ()// gg_trg_MyTrig is a trigger variable which is created for each trigger call TriggerRegisterAnyUnitEventBJ (gg_trg_MyTrig, EVENT_PLAYER_UNIT_SPELL_EFFECT )// here we register when any unit starts the effect of an ability call TriggerAddCondition (gg_trg_MyTrig, Condition (function Conditions ))// adding conditions... Condition(...) is a function which causes syntax errors if not the function, here Conditions, doesn't return a boolean. Basicly, this function takes a trigger argument and a boolexpr (boolean expression) argument call TriggerAddAction (gg_trg_MyTrig, function Actions )// adds actions, very basic// and so on...endfunction
NOTICE - remember, that you can add more events, conditions and actions by having more calls of the same function names!
To find out new events, just convert some GUI events into JASS. There are some rather tricky ones out there, so watch it!
Remember, that in normal JASS, without NewGen, you will always have to have a function like:
function InitTrig_MyTriggerName takes nothing returns nothing
Else it won't compile! And unless you are calling the functions in the trigger, you will always need an InitTrig to add events and stuff!
Function Names and Indentation
This is a little bit unnecessary you might think, but no - if you want people to read your code easilier, you will have to have good function names and indentation!
NOTICE - I might not be the best example at indentation, but just create your own indentation methods if you want.
Indentation
Now, what is indentation? Basicly, it's the spaces in each line of code;
function no_indentation takes nothing returns nothinglocal unit u local real x local real y local real angle local unit t local location l endfunction
Pretty hard to read, right? This is a bit easier:
function with_indentation takes nothing returns nothing local unit u local real x local real y local real angle local unit t local location l call SetUnitAnimation (u, "spell") // and so on...
A bit easier to read, right?
Well, it can get too much sometimes though:
function toomuch_indentation takes nothing returns nothing local unit u local real x local real y local real angle local unit t local location l // and so on...endfunction
Got it? Great!
Function Names
Like indentation, these can be important too.
People probably won't read your code if your function names are like "MyFunctionName_lololo995830__75" (just an example of a pretty stupid name.), but more people will probably read it if it's like "Actions_Init" or "onSpellEffect" and stuff like that. This was very short, but very important if you would like to get better.
previous - home - next
|
|
|
10-15-2008, 06:46 PM
|
#4 (permalink)
|
___ __ _____
Join Date: Dec 2007
Posts: 151
|
Subchapter: Coding Conventions
By Eleandor.
Coding conventions are "rules" made to make code easier to be read. When you're making a jass script, at some point other people will be reading it as well. Don't let them have a hard time reading a chaotic script. Or even if the script is for personal use: avoid being confused because of bad writing habits.
A script that doesn't follow such conventions will work just as well as a script that does. However, it's like writing a letter: you start with introducing yourself and end with saying "greetings, name". It's just the way it is and makes a letter easier to read.
1) Identifiers
Identifiers are the "names" you give to variables, functions and constants. * Constants: constants are written in capital letters. Spaces are replaced by underscores. Example: UNIT_STATE_LIFE
* Variables: variables are written entirely in lower case. Spaces are either left out or replaced by underscores. Example: player1gold
* Functions: A functionname is written lowercase, and each word starts with a capital letter. Example: GroupAddUnit
Small variations on these naming conventions are possible. Some programmers start each function with a lower case character. For instance: groupAddUnit. This is a matter of habit. However, since the original jass functions start with capital letters, it's encouraged to do the same.
The name of a function or variable should be obvious. If you only have 1 unit variable, it's enough to say:
If you have 2 units, naming them u1 and u2 is confusing. Name them wisely. For instance, say I have 2 units, an attacker and an attacked unit, I will call them:
local unit attacker = GetAttacker() local unit target = GetTriggerUnit()
2) Indentation
Indentation makes "groups" of code more visible. For instance:
if booleanvar then Statements endif It is clear that the Statements are part of the "larger" if/then/else statement. For that reason, it's easier to see they are part of the statement by indenting them.
Following statements, among others, need to be indented: * function / endfunction and method / endmethod
* if / else / elseif / endif
* loop / endloop
* struct / endstruct
* globals / endglobals
* More of such examples
3) Interlinear Spacing
Interlinear spacing is leaving some space between 2 lines of code. Such space is left between: * 2 functions
* local variable declaration and the actual code
* ... 4) Spacing
Sometimes, leaving additional spaces where they're not required can make the code easier to read.
Example:
set a=b+1 set a = b + 1 call DisplayTextToPlayer(p,0,0,"lol") call DisplayTextToPlayer(p, 0, 0, "lol")
An example
globals constant boolean ELEANDOR_IS_COOL = true integer array score boolean score10isvictory = falseendglobalsfunction UpdateScore takes player p returns nothing // This function increases the score of a player. local integer playerid = GetPlayerId (p ) set score [playerid ] = score [playerid ] + 1 if score [playerid ] >= 10 then if score10isvictory then call DisplayTextToPlayer (p, 0, 0, "Good job, you've won the game!"). endif endifendfunction
previous - home - next
|
|
|
10-15-2008, 06:47 PM
|
#5 (permalink)
|
___ __ _____
Join Date: Dec 2007
Posts: 151
|
Advanced JASS (Game Cache Usage, Spell Making.)
Game Cache Usage - Warning
First of all, I would like to warn you - Game Cache usage is an outdated method of storing data, newer methods would be vJASS, struct using, stuff like that. These stuff will not be included in this tutorial however, if you want to learn about that, I suggest you to read Moving From JASS To vJASS by Art.
Game Cache Usage - Starting
Before we can start using our game cache, which will be named gc for the rest of this chapter, we need 2 things;
- A global game cache variable, created via the GUI Variable Editor. (CTRL + B)
- A function for initializing and returning the game cache variable, like this:
function gc takes nothing returns gamecache if udg_cache== null then call FlushGameCache (InitGameCache ("data.gc")) set udg_cache=InitGameCache ("data.gc") endif return udg_cache endfunction
The "data.gc" could be any name you would want.
This is the function we call every single time we use our gc.
Now, we will create our function for usage of the cache.
Basically, everything that you store in a cache you store with a name, so you will have to remember that name...
function gc takes nothing returns gamecache if udg_cache== null then call FlushGameCache (InitGameCache ("data.gc")) set udg_cache=InitGameCache ("data.gc") endif return udg_cache endfunctionfunction Get takes nothing returns integer return GetStoredInteger (gc (), "gc", "myInt")endfunctionfunction Set takes integer i returns nothing call StoreInteger (gc (), "gc", "myInt",i )endfunction
It's really simple. Now, storing and getting units is a pain in the ass, and everything else than boolean, string, integer and real is nearly impossible, or very annoying to do all the time, to do without a system.
Therefore, we got Local Handle Vars or Attachable Vars.
Those are both game cache systems, which can be very useful when using game caches. I myself have never used LHV, only AV.
Local Handle Vars.
Attachable Vars.
I have attached maps where I use LHV and AV.
That was basically it, ready to go on for some game cache spell making? Great, let's go!
Spell Making - The Beginning
We are going to make a simple damage over time spell, where will need a game cache.
It is suggested that you don't use this spell in your map.
So, we know that we need our gc function... Then we need an inittrig, condition function and action function.
function gc takes nothing returns gamecache if udg_cache== null then call FlushGameCache (InitGameCache ("data.gc")) set udg_cache=InitGameCache ("data.gc") endif return udg_cache endfunctionconstant function DoT_Conditions takes nothing returns boolean return GetSpellAbilityId ()==DoT_ID () // DoT_ID() is a configuration function that we haven't created yetendfunctionfunction DoT_Actions takes nothing returns nothingendfunctionfunction InitTrig_DoT takes nothing returns nothing local trigger t=CreateTrigger () call TriggerRegisterAnyUnitEventBJ (t,EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition (t,Condition (function DoT_Conditions )) call TriggerAddAction (t,DoT_Actions )endfunction
Now we need the configuring functions -
// !!!! CONFIGURATION !!!! \\constant function DoT_ID takes nothing returns integer // The raw code of spell return 'A000'endfunctionconstant function DoT_Damage takes integer lvl returns real // The damage dealt depending on level if lvl==1 then return 4. elseif lvl==2 then return 7. elseif lvl==3 then return 10. endif return 0. // This one just... has to be there (well, probably not, I just do it)endfunctionconstant function DoT_Effect takes nothing returns string // The special effect of the spell return "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"endfunctionconstant function DoT_Interval takes nothing returns real // The timer interval return 0.75 endfunction// !!!! SPELL CODE !!!! \\function gc takes nothing returns gamecache if udg_cache== null then call FlushGameCache (InitGameCache ("data.gc")) set udg_cache=InitGameCache ("data.gc") endif return udg_cache endfunctionconstant function DoT_Conditions takes nothing returns boolean return GetSpellAbilityId ()==DoT_ID ()endfunctionfunction DoT_Actions takes nothing returns nothingendfunctionfunction InitTrig_DoT takes nothing returns nothing set gg_trg_DoT=CreateTrigger () call TriggerRegisterAnyUnitEventBJ (gg_trg_DoT,EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition (gg_trg_DoT,Condition (function DoT_Conditions )) call TriggerAddAction (gg_trg_DoT, function DoT_Actions )endfunction
Got everything? Great!
Then we go on to creating the actual actions.
// WARNING!!!! THIS SPELL IS UNSAFE!!!!// !!!! CONFIGURATION !!!! \\constant function DoT_ID takes nothing returns integer // The raw code of spell return 'A000'endfunctionconstant function DoT_DummyID takes nothing returns integer // Notice this - we now need a dummy! // The raw code of the dummy unit return 'h000'endfunctionconstant function DoT_Damage takes integer lvl returns real // The damage dealt depending on level if lvl==1 then return 4. elseif lvl==2 then return 7. elseif lvl==3 then return 10. endif return 0. // This one just... has to be there (well, probably not, I just do it)endfunctionconstant function DoT_Effect takes nothing returns string // The special effect of the spell return "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"endfunctionconstant function DoT_Attach takes nothing returns string // The special effect attachment point return "chest"endfunctionconstant function DoT_Interval takes nothing returns real // The timer interval return 0.75 endfunction// !!!! SPELL CODE !!!! \\function gc takes nothing returns gamecache if udg_cache== null then call FlushGameCache (InitGameCache ("data.gc")) set udg_cache=InitGameCache ("data.gc") endif return udg_cache endfunctionfunction H2I takes handle h returns integer return h return 0 endfunctionfunction I2H takes integer i returns handle return i return nullendfunctionfunction H2U takes handle h returns unit return h return nullendfunctionconstant function DoT_Conditions takes nothing returns boolean return GetSpellAbilityId ()==DoT_ID ()endfunctionfunction DoT_Timer takes nothing returns nothing local unit t=H2U (I2H (GetStoredInteger (gc (), "gc", "t"))) local integer id=GetStoredInteger (gc (), "gc", "p") local integer lvl=GetStoredInteger (gc (), "gc", "lvl") local real x=GetUnitX (t ) local real y=GetUnitY (t ) local unit d=CreateUnit (Player (id ),DoT_DummyID (),x,y,0 ) call UnitDamageTarget (d,t,DoT_Damage (lvl ), false, false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL, null) call DestroyEffect (AddSpecialEffectTarget (DoT_Effect (),t,DoT_Attach ()))endfunctionfunction DoT_Actions takes nothing returns nothing local unit u=GetTriggerUnit () local unit t=GetSpellTargetUnit () local integer i=GetPlayerId (GetOwningPlayer (u )) local integer lvl=GetUnitAbilityLevel (u,DoT_ID ()) local timer tmr=CreateTimer () call StoreInteger (gc (), "gc", "p",i ) // Storing the player id, // so that we can create the dummy and the caster will still get credit call StoreInteger (gc (), "gc", "lvl",lvl ) // Storing the unit ability level, // so that we can deal damage depending on level call StoreInteger (gc (), "gc", "t",H2I (t )) // Storing the target unit via the unsafe // H2I to I2H functions. Do never use those in your map call TimerStart (tmr,DoT_Interval, true, function DoT_Timer ) set tmr= null set u= null set t= nullendfunctionfunction InitTrig_DoT takes nothing returns nothing set gg_trg_DoT=CreateTrigger () call TriggerRegisterAnyUnitEventBJ (gg_trg_DoT,EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition (gg_trg_DoT,Condition (function DoT_Conditions )) call TriggerAddAction (gg_trg_DoT, function DoT_Actions )endfunction
Now the spell actually does something... Now, we don't want the spell to last forever, do we? No, we don't, therefore we create the counting part.
// WARNING!!!! THIS SPELL IS UNSAFE!!!!// !!!! CONFIGURATION !!!! \\constant function DoT_ID takes nothing returns integer // The raw code of spell return 'A000'endfunctionconstant function DoT_DummyID takes nothing returns integer // Notice this - we now need a dummy! // The raw code of the dummy unit return 'h000'endfunctionconstant function DoT_Damage takes integer lvl returns real // The damage dealt depending on level if lvl==1 then return 4. elseif lvl==2 then return 7. elseif lvl==3 then return 10. endif return 0. // This one just... has to be there (well, probably not, I just do it)endfunctionconstant function DoT_Effect takes nothing returns string // The special effect of the spell return "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"endfunctionconstant function DoT_Attach takes nothing returns string // The special effect attachment point return "chest"endfunctionconstant function DoT_Interval takes nothing returns real // The timer interval return 0.40 endfunctionconstant function DoT_Duration takes nothing returns real // The duration of the spell return 5. endfunction// !!!! SPELL CODE !!!! \\function gc takes nothing returns gamecache if udg_cache== null then call FlushGameCache (InitGameCache ("data.gc")) set udg_cache=InitGameCache ("data.gc") endif return udg_cache endfunctionfunction H2I takes handle h returns integer return h return 0 endfunctionfunction I2H takes integer i returns handle return i return nullendfunctionfunction H2U takes handle h returns unit return h return nullendfunctionconstant function DoT_Conditions takes nothing returns boolean return GetSpellAbilityId ()==DoT_ID ()endfunctionfunction DoT_Timer takes nothing returns nothing local unit t=H2U (I2H (GetStoredInteger (gc (), "gc", "t"))) local integer id=GetStoredInteger (gc (), "gc", "p") local integer lvl=GetStoredInteger (gc (), "gc", "lvl") local real ti=GetStoredReal (gc (), "gc", "ti") local real x=GetUnitX (t ) local real y=GetUnitY (t ) local unit d=CreateUnit (Player (id ),DoT_DummyID (),x,y,0 ) call UnitDamageTarget (d,t,DoT_Damage (lvl ), false, false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL, null) call DestroyEffect (AddSpecialEffectTarget (DoT_Effect (),t,DoT_Attach ())) set ti=ti+DoT_Interval () call FlushStoredMission (gc (), "gc") // This clears our category "gc", so that we store the new values if ti>=DoT_Duration () then call PauseTimer (GetExpiredTimer ()) call DestroyTimer (GetExpiredTimer ()) return else call StoreInteger (gc (), "gc", "t",H2I (t )) call StoreInteger (gc (), "gc", "p",id ) call StoreInteger (gc (), "gc", "lvl",lvl ) call StoreReal (gc (), "gc", "ti",ti ) endifendfunctionfunction DoT_Actions takes nothing returns nothing local unit u=GetTriggerUnit () local unit t=GetSpellTargetUnit () local integer i=GetPlayerId (GetOwningPlayer (u )) local integer lvl=GetUnitAbilityLevel (u,DoT_ID ()) local timer tmr=CreateTimer () local real ti=0 call StoreInteger (gc (), "gc", "p",i ) // Storing the player id, // so that we can create the dummy and the caster will still get credit call StoreInteger (gc (), "gc", "lvl",lvl ) // Storing the unit ability level, // so that we can deal damage depending on level call StoreInteger (gc (), "gc", "t",H2I (t )) // Storing the target unit via the unsafe // H2I to I2H functions. Do never use those in your map call StoreReal (gc (), "gc", "ti",ti ) // Storing our counter call TimerStart (tmr,DoT_Interval, true, function DoT_Timer ) set tmr= null set u= null set t= nullendfunctionfunction InitTrig_DoT takes nothing returns nothing set gg_trg_DoT=CreateTrigger () call TriggerRegisterAnyUnitEventBJ (gg_trg_DoT,EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition (gg_trg_DoT,Condition (function DoT_Conditions )) call TriggerAddAction (gg_trg_DoT, function DoT_Actions )endfunction
That was it. It works, nothing is really wrong with it, except that it's unsafe. Because it's unsafe, I recommend you not to use this spell.
I have attached the map where it is implemented.
Now, you pretty much learned everything. Go get yourself some exercises, and yeah... enjoy what you've learned!
previous - home - next
|
|
|
10-15-2008, 06:47 PM
|
#6 (permalink)
|
___ __ _____
Join Date: Dec 2007
Posts: 151
|
|
|
|
10-15-2008, 06:48 PM
|
#7 (permalink)
|
___ __ _____
Join Date: Dec 2007
Posts: 151
|
Goodbye
That was it, wasn't as hard as it seemed was it? Nah, really, it's getting easier after some time. Just keep on learning, and the step right now would probably be to learn vJASS.
This can be done via the following links;
I hope that you learned something from this tutorial!
previous - home
|
|
|
10-16-2008, 09:47 AM
|
#8 (permalink)
|
You are never right
Join Date: Sep 2007
Posts: 1,275
|
Really ownage! You deserve +rep :D Even when I know jass :)
|
|
|
10-16-2008, 10:18 AM
|
#9 (permalink)
|
Mi eez ztill Cute~~
Join Date: Jun 2008
Posts: 1,443
|
Way better than your Blood models
But who is that Eleandor person mentioned?
__________________
If i have helped you in any way, don't be shy, and click the  button.
|
|
|
|
|
|