TheGreatCthulhu
Member
Posts: 213
Threads: 10
Joined: Oct 2010
Reputation:
32
|
Script Language Reference and Guide
This is for the people who want to learn how to script, and want to learn the rules of the scripting language itself.
Those of you who just want to get things done and don't care much about scripting as long as your maps don't crash, won't find this too interesting, but, then again, you never know...
OK, what's been mostly missing from the wiki are articles on the features and the rules of the scripting language used by Amnesia (known as Angel Script) - people have mostly gathered information from various sources, and through trial and error.
I thought it would be nice to have all of this information in one place, so I've been writing the Script Language Reference and Guide for quite some time now. It's not finished yet, but it already covers everything most people will ever need - so I decided to announce it here.
I also intend to add one (or, more likely) several downloadable sandbox maps, along with some exercises to help you learn, but don't expect those too soon.
So, here's a proper announcement:
Also, there's a section of this guide that should be interesting to anyone who ever wondered where to put some peace of code they wrote themselves, or some code snippet someone provided them with:
I'd appreciate feedback, and comments on how the guide could be improved. If something is unclear, by all means point it out. Also report any errors you might spot.
Enjoy.
|
|
01-14-2013, 10:01 PM |
|
The chaser
Posting Freak
Posts: 2,486
Threads: 76
Joined: Jun 2012
Reputation:
113
|
RE: Script Language Reference and Guide
This is probabily something that A LOT OF PEOPLE will love. I already love et
Just one thing:
Here,
http://wiki.frictionalgames.com/hpl2/amn...statements
there are missing statements: An user called ZoDiaC (more or less) said that he could use "while" and "do" statements, as well as the "for" is commonly used in the modding. Just some tips
THE OTHERWORLD (WIP)
Aculy iz dolan.
|
|
01-14-2013, 10:07 PM |
|
TheGreatCthulhu
Member
Posts: 213
Threads: 10
Joined: Oct 2010
Reputation:
32
|
RE: Script Language Reference and Guide
|
|
01-14-2013, 10:22 PM |
|
The chaser
Posting Freak
Posts: 2,486
Threads: 76
Joined: Jun 2012
Reputation:
113
|
RE: Script Language Reference and Guide
oops, didn't see it :p
THE OTHERWORLD (WIP)
Aculy iz dolan.
|
|
01-14-2013, 10:25 PM |
|
Adrianis
Senior Member
Posts: 620
Threads: 6
Joined: Feb 2012
Reputation:
27
|
RE: Script Language Reference and Guide
That's really good work man, nice one! Very well structured, well laid out.
It'd be great to get some examples of why you might actually use some of the more complicated parts, specifically I was thinking of enumerations (cause I didn't know what they were before, but now I'm not sure why I would want them (in a script, at least)), and reference parameters (cause I've only found 1 excuse to use them so far, and they kick ass). If that's intended for your test maps then I apologise. The AddDebugMessage example for function overloading did this very well, an example that is directly in context of Amnesia modding goes a long way for people who are focused on that
Aside from that, there's a couple of very, very slight knit-picky suggestions
1 - Theres a bit of a problem in the enum section, where you've provided an example of a couple of items that they player may define stats for, you have this...
HealingPotion vial = HealingPotion::Small;
HealingPotion leatherArmor = BodyArmor::Small;
Forgive me if I am mistaken, but it should be BodyArmor leatherArmor = BodyArmor::Weak;
2 - Do you think that the input-output reference parameter, for the sake of readability would be better when explicitly written as &inout ? I was surprised when you said it would be preferable to just use &, I figured given that you'd pointed out that its better to explicitly write things like HealingPotion::Small; rather than just Small; for the sake of clarity, that similar rules should apply elsewhere, particularly as accidentally returning modified variables could be a real pain in the A to debug
Hope that's useful anyway, like I said, very small and particular suggestions.
Looking forward to the section on classes, keep it up!
P.S. Armour, not Armor, and while we're at it, colour and favourite too
|
|
01-15-2013, 12:49 AM |
|
TheGreatCthulhu
Member
Posts: 213
Threads: 10
Joined: Oct 2010
Reputation:
32
|
RE: Script Language Reference and Guide
(01-15-2013, 12:49 AM)Adrianis Wrote: It'd be great to get some examples of why you might actually use some of the more complicated parts, specifically I was thinking of enumerations (cause I didn't know what they were before, but now I'm not sure why I would want them (in a script, at least)) You're right: enumerations aren't all that common in scripting languages; but they provide a neat way to group related constants (for example, the meaning of lever positions). They are more useful in some languages than others; generally, on an object-oriented language, you'd use enumerations for the "magic number"-type constants, and for flags, both of which are intended to be passed as function arguments, and you'd use constants for things like the value of pi, or max and min values of something, etc.
Now, here's the thing with enums. When you have a lot of constants defined, you might confuse some of them: you can use enums to let the compiler catch any errors for you. This is because the compiler won't let you pass in constants from one enum when another is expected, and it won't let you pass "raw" integers either (that is, it won't allow for an implicit conversion - you may chose to force a conversion, though).
It's always better to set up things so that the computer can spot errors, since it's much better then a human for that job.
In Amnesia specifically, you might never use them and do just fine; however, I wanted to explain them anyway, so that people know enums are at their disposal.
(01-15-2013, 12:49 AM)Adrianis Wrote: and reference parameters (cause I've only found 1 excuse to use them so far, and they kick ass). Please do share the code! Anyway, reference parameters may be of use in certain situations (like multiple returns, or using an output parameter, while returning an error code from the function), especially when reference types like strings, arrays, and ultimately, classes are involved. Note that all predefined functions take strings as (inout) references - I wanted people to understand what that means.
Since HPL3 will use a lot of classes and objects (which I intend to cover as well), and since objects are often passed around by reference, knowing about references (and something similar - object handles) will pay off in the long run.
(01-15-2013, 12:49 AM)Adrianis Wrote: Aside from that, there's a couple of very, very slight knit-picky suggestions
1 - Theres a bit of a problem in the enum section, where you've provided an example of a couple of items that they player may define stats for, you have this...
HealingPotion vial = HealingPotion::Small;
HealingPotion leatherArmor = BodyArmor::Small;
Forgive me if I am mistaken, but it should be BodyArmor leatherArmor = BodyArmor::Weak; Will correct soon.
(01-15-2013, 12:49 AM)Adrianis Wrote: 2 - Do you think that the input-output reference parameter, for the sake of readability would be better when explicitly written as &inout ? I was surprised when you said it would be preferable to just use &, I figured given that you'd pointed out that its better to explicitly write things like HealingPotion::Small; rather than just Small; for the sake of clarity, that similar rules should apply elsewhere, particularly as accidentally returning modified variables could be a real pain in the A to debug There are several reasons I prefer using just &:
- When writing, say, string&, it can only be an inout reference, and nothing else
- The in- and out- references are rather specific to AngelScript; in most other languages, there's just "reference", and it's equivalent to the inout reference of AS. This is what is generally meant when someone talks about passing by reference, and this is the reason why the creator(s?) of AS decided to provide an alternate notation solely for the concept of inout reference. Besides, references in C++ use the notation typename& as well, and people might be familiar with that.
- The Engine Scripts page on the wiki lists all inout string parameters as string&, not strint &inout, so I wanted to be consistent with that.
(01-15-2013, 12:49 AM)Adrianis Wrote: P.S. Armour, not Armor, and while we're at it, colour and too American English vs British English - a matter of preference.
|
|
01-15-2013, 05:37 AM |
|
Adrianis
Senior Member
Posts: 620
Threads: 6
Joined: Feb 2012
Reputation:
27
|
RE: Script Language Reference and Guide
Thanks for the response, that all seems very reasonable. For the references, I've only ever used pointers in C, and that uses a different notation altogether (type*) so that makes sense from your context.
I'm be happy to share my code if it'll be useful, it's pretty specific to a particular mechanic for Spacies but maybe theres some transferable stuff in there
Wrote this back in November, we needed a function that would return an integer that could be appended onto a string used for picking entities and script areas, and we also needed somewhere to store the locations that had been used already. So, I'm using a global string to store the numbers (array's don't get saved when the player quits the game, so had to be one of the function defined variables, string made the most sense), the function would need to return the modified global string as well as take it in to be read through, in order to get the next location
int GetSetNextSpawnSequential(string &inout strInput)
{
int inNextPlayerPos; // return val
int inAscii; // int holder for ascii nums
bool blZeroFound = false; // defensive
blZeroFound = ValidateStringForInput(strInput);
if (blZeroFound)
{
for (int c = 0; c < strInput.length(); c++)
{
inAscii = strInput[c] - 48;
if (inAscii == 0)
{
inNextPlayerPos = (strInput[c-1] - 48) + 1;
strInput = GetNewSpawnString(strInput, inNextPlayerPos);
return(inNextPlayerPos);
}
}
}
return(0);
}
There are a couple of other custom functions in there which carry out specific tasks, ValidateStringForInput is pretty self explanatory - checks whether there is any space left to use or whether all the locations had been used already, and GetNewSpawnString does the actual modifying of the string
If you have any suggestions I'd love to here them, I've yet to have anyone more experienced than myself check over it
"American English vs British English - a matter of preference"
Of course you mean American English vs English, one of which clearly came first, and the other of which is clearly an unnecessary attempt by colonial powers to seperate themselves from their Glorious Leaders
/imperialism
Ok I'll stop...
|
|
01-15-2013, 11:29 AM |
|
TheGreatCthulhu
Member
Posts: 213
Threads: 10
Joined: Oct 2010
Reputation:
32
|
RE: Script Language Reference and Guide
Using a string as an array - nice! If I got it right it essentially searches for a "0" character, and then takes the value stored in the one before it, and than (I'm guessing now) replaces it with a "0" when it's done, while filling the string with something non-"0" from the right, right?
But, what happens when there's a "0" as the first character? Are some checks made, or does it just go: (strInput[ 0-1] - 48)?
Out of bounds access like that can stop the entire script from executing, maybe even crash the game.
Anyway, I do have 2 suggestions:
- If GetNewSpawnString() is made to take a string& (inout), then it doesn't have to return the string (although it can, for convenience), you can just set it via the reference param, just like GetSetNextSpawnSequential() does. Along the lines of
void A(string& s) { B(s); } // corresponds to GetSetNextSpawnSequential() void B(string& s) { s = NewString(); } // corr. to GetNewSpawnString()
- You could also avoid all the string-gymnastics if you simply store the index of the current character as a (game-)global variable. Then you just get the index global var, test against the string's length, and get the char, incrementing the index afterwards. (Or, if you're going backwards, start at length() - 1, test if its >0, decrement). This would eliminate the need for having special tokens like "0" in your string.
|
|
01-15-2013, 03:11 PM |
|
Adrianis
Senior Member
Posts: 620
Threads: 6
Joined: Feb 2012
Reputation:
27
|
RE: Script Language Reference and Guide
Yep, that's about right! Theres a string defined using the SetGlobalVarString in global.hps, thats set up to be, say, "10000". It's a bit of a bodge-job, because it essentially requires that I set up the global strings exactly right - not that it is complicated to set up (make sure it starts with '1', and make sure there are the right number of '0's), but it does require a certain amount of 'compatibility' between the number of positions available, the naming of SA's and entities to be used, and in the case of the GetSetNextSpawnRandom, the arguments given to the RandInt function
So in answer to "what happens when there's a "0" as the first character?", there isn't a 0 as the first character These aren't random or dynamically allocated strings, I had no intention of them being so, they are static and I set them up so the chances of that going wrong are low, added to that the fact that I've plastered warning comments all over the original code file, as well as the global.hps file
Your probably right though, currently the ValidateStringForInput function doesn't check that the first character is not 0 so I will add something in there (seems like the appropriate place for it)
Perhaps I should have qualified with some extra info, but in answer to your suggestions
1 - Thats perfectly valid, but I prefer not to use pointers unless necessary (I know I said they were awesome, and they are, but still..). In your example, both A and B will modify the original variable when they return - in my mind, there should only be 1 point where the modified value should be returned if possible.
Since A would be passed the reference, it then passes the reference to the original to B, when B returns it will modify the original, and when A returns it will also modify the original... For ease of debugging, B returns its own string, which A will use to overwrite the value of the original and return it.
B is written to have no other purpose than modifying the string (moved to a seperate function to prevent A from being too messy), so it doesn't need to have multiple return values.
Your method would certainly be more efficient, not having to pass around variables unnecessarily, but I'm not massively concerned about memory efficiency in these scripts, not unless theres going to be a real problem with it
2 - The problem is that the number of positions is deliberatly limited, e.g. level A has 4 positions, level B has 6, there would need to be an arbitrary limit set somewhere, so I thought it best to simply build it into the 'array' from the start to avoid having too many different required variables in different places if things need changing
An example of its usage would be
void ChangeTheMap(string &in strTimer)
{
int inNextPos = GetSetNextSpawnSequential(GetGlobalVarString("globalstringname"));
if (inNextPos == 0) return; // check for returned 0, if so, no places left so exit function
ChangeMap("mapname.hps", "PlayerStartArea_" + inNextPos, etc...)
}
I was wary at first about using 'GetGlobalVarString()' as an argument, as I'm not sure exactly what it returns, but I tested it (extensively, and hopefully thoroughly) and it does work as intended. If you happen to know of a problem with doing this I would love to know it
|
|
01-15-2013, 05:14 PM |
|
TheGreatCthulhu
Member
Posts: 213
Threads: 10
Joined: Oct 2010
Reputation:
32
|
RE: Script Language Reference and Guide
(01-15-2013, 05:14 PM)Adrianis Wrote: In your example, both A and B will modify the original variable when they return - in my mind, there should only be 1 point where the modified value should be returned if possible.
Since A would be passed the reference, it then passes the reference to the original to B, when B returns it will modify the original, and when A returns it will also modify the original... No, since string is a reference type (not the & kind of references, something else - didn't cover that yet in the guide, will do together with classes). AngelScript has two kinds of types - value types (like bool, int and float), which store their value directly, and reference types , which are more like pointers, except you don't have to deference them or worry about memory deallocation. This type system is different than those of C and C++, but is very similar to that of C# (which I understand pretty well). So, when strings are passed by reference like that, all of those parameters are, internally, essentially pointers to the same thing. So, in my example, when the string is changed in B(), the original is affected instantly (as soon as that line executes, before the func ends) - nothing else happens when each of the function returns.
This is the reason why (I suspect, though I could be wrong) callbacks accept sting &in and not string& - if it was the other way around, you'd potentially be able to affect some internal strings keys of the game, as the engine passes in names of various entities, or timer IDs, etc. Since &in references are basically the same as passing by value (except if the param is const), this can't happen.
(01-15-2013, 05:14 PM)Adrianis Wrote: I was wary at first about using 'GetGlobalVarString()' as an argument, as I'm not sure exactly what it returns, but I tested it (extensively, and hopefully thoroughly) and it does work as intended. If you happen to know of a problem with doing this I would love to know it
The wiki says it returns a string& (string by reference), but that's a bit tricky to figure out since you are not allowed to have non-parameter string& variables. When you assign the result to a string, a copy is made (you can tell by passing it by reference to a function that changes it, and then call GetGlobalVarString() to see if it was affected). But, you can assign it to a string@ variable, which is AngelScript's equivalent to those "smart" pointers I mentioned before (works only on reference types). A string@ variable is used as a normal string, but it points to the original memory.
You were right to be wary though; I did some tests, and in some cases, unclear to me at this point (in my case it was when I set the string to be a small sequence of 1-3 chars), strange things happen (nothing get's printed out, etc.). Not sure why.
But both this is pretty much equivalent and should work without problems:
string@ s = GetGlobalVarString("globalstringname"); int inNextPos = GetSetNextSpawnSequential(s);
|
|
01-15-2013, 06:02 PM |
|
|