Always check here first. Always. This page is your handbook, your top reference, your easy-access bookmark, your holy bible of Amnesia scripting. I'm never without it open on a tab.
INTRODUCTION:
Spoiler below!
Hey guys, this is a first effort in providing a reference thread with information and tutorials that is easily accessible for all users. I'm creating this because there are LOTS of repeat threads about things such as monsters, script syntax and more. As a result, I'm making this thread to address these problems, especially the most common ones. This way, when someone posts a repeat thread, you can just copy and paste this link and be done with it!
It sounds a little rude, but users need to realize that the internet is a place where almost anything you want to know is accessible, and that the best way to get it is searching for yourself. So let's stop constantly repeating ourselves and get an in-depth tutorial going! With any luck this thing will be a big hit and hopefully get sticky'd.
Now you might ask why I'm making this a thread as opposed to making multiple pages on the wiki. Well, ask your question again and you've answered it. I don't want to spend time figuring out where to put each wiki page, not to mention I have no idea how to create a new wiki page and no desire to create one either! A thread reference is simple, and this forum gets more attention than the wiki probably does. Oh, and my favorite chemistry teacher always told me to K.I.S.S. (Keep It Simple, Stupid)
Now, it is also very important that you completely read each section and do your best to fully understand. If you're still having trouble, seek other references and ask on the forums.
If you would like to contribute a tutorial, please PM me or post here. Try to avoid posting random stuff here, I don't want this bumped up unless it proves to be actually useful for people
If you come across a thread which this tutorial addresses, direct the person here.
Also if you have a suggestion for a reference to link here, or a better one than I've provided, feel free to make me aware of it. I want this to be the most easily accessible reference for everyone.
HOW TO USE THIS REFERENCE:
Use the search function in your web browser and search for key-words like 'monster', 'diary', etc. Easy as that! If that doesn't find you what you need, scroll around and check. This thread will probably end up getting pretty long, so if you still can't find it, post here or PM me and I'll either add it or point it out.
AMNESIA 101
Let's begin...
- Information regarding the script, functions, names, etc [1]
Spoiler below!
Amnesia uses the AngelScript library to run its in-game shenanigans. AngelScript is a C-style language whose main features make it a perfect language for Amnesia. More info here.
In the script functions I show you below, I will simply post a function. You can NOT copy and paste it into your scripts and expect it to work. You will need to do a lot of renaming. For instance, If I tell you to use this function: AddEntityCollideCallback("Player", "YourDoor", "YourDoorFXN", true, 1); you will need to change YourDoor and YourDoorFXN to whatever your door's name is, and the name of the function you would like it to call, respectively.
Link [1] is a thread by Kyle regarding certain aspects of scripting.
- I'm having textures flickering/overlapping!
Spoiler below!
If they are from two planes, move the planes so they don't overlap. Using grid snapping or locking is great for placing core level objects like walls, ceilings, welders and planes.
If it's from two walls intersecting at a corner, with a small bit of them flickering (happens with some dungeonbase statics) simply add 0.001 to the vertical position of one of them. The flickering will stop and you won't notice the change in position.
- I can't see the entire Level Editor; part of it extends past my screen
Spoiler below!
Two options:
1) Set to full-screen mode by going to Documents > HPL2 > LevelEditor.cfg and changing FullScreen="" to FullScreen="1". The location of the text file will most likely be somewhere else for users running an OS other than Windows.
2) In LevelEditor, expand the Edit dropdown menu and click on Options. Set your Y resolution to a smaller value and close and re-open the editor. Tweak it until you can see everything.
- How do I make water?
Spoiler below!
Go to the areas tool (press 8) and select the 'Liquid' type from the drop down menu on the right. Draw your liquid area.
Next, go to your primitives tool (press 9) and select your file under textures > water. Draw your plane. Select the plane you just drew and go to the 'Primitive' tab. Uncheck the 'Collides' box. Next go to the 'Plane' tab and set the first (X) and third (Z) Tile amount values to a lower number. This increases the size of the tile (by decreasing the ratio of tile to grid meter), making the waves look longer and more realistic. It depends on which water texture you choose, but there is a sweet spot where the water will move realistically.
The last step is optional: Creating water reflections on the walls. Go to the lights tool (press 2) and select SpotLight from the drop down menu on the left. Draw your spotlight. Rotate it so that it is pointing upwards. Place the spotlight below your ground and water, and make the FOV a value somewhere between 120 and 150. It depends on the size and shape of the room. Fiddle with it until you can see the light shining on all surfaces you want water reflections to appear. Next, lower the diffuse color of the light. Make it low, but still high enough so you can see it. Now, under the Gobo map field, select first frame of the last gobo in the list. It's called gobo_water_001.dds. Right under the gobo map field, you'll see Anim mode and Frame time options. Set the Anim mode to loop, and the frame time to 0.05. This is the highest time you can use without the animation looking choppy, and the lowest you can go without the animation looking way too fast. Check and make sure the water reflections aren't appearing anywhere they shouldn't be, like the room next to your flooded room. If they do, either adjust the spotlight's position/rotation/FOV, or make it a shadowcaster and put a barrier (either of walls or planes) that extends below the ground with the texture-side facing the room with water.
Use the above link. Scroll down to the AddUseItemCallback stuff. Use those functions to control item interactions. Simply using a key on a door won't unlock it though, you need to unlock it with the script function that is called on interaction. Click here for functions involving doors.
This is also used for puzzles, too. Once you get the hang of coding, you can work on more complicated interactions, like the popular crowbar vs door puzzle.
There is no one function to make a player faint or wake up. All the cinematic stuff going on in Amnesia is a tightly-controlled set of script functions which work together to make the event come to life.
Switch functions (see below) are generally used for cinematic-type events, but there are, as always, many options.
Link to an old thread listing some cool script widgets I made. Use them well.
- My game crashes when I launch my map!
Spoiler below!
It is either A) Your script file has syntax errors, or B) You have a custom entity which is corrupted, or C) Something else that is no-good :(
First, figure out which it is. It is most likely A. To avoid crashing on compile-time, you need to open your map in-game BEFORE you start editing and writing code. This way, when you re-compile the script or reload the map, the game will tell you if there are any errors without crashing! Hooray! This makes it easier to tell what is causing your errors and even tells you where they are!
Now, make sure the functions in your script all follow this same structure:
Make sure the signatures are correct. If this function were called on a Player-entity interaction (player clicks on an entity such as a lamp or inventory item), the tags would need to be (string &in entity, string &in type). For a list of functions and their tags, go here.
Now, if my little pseudo-code confused you, ignore it and just make sure:
1) You've closed your braces & parenthesis.
2) You've ended each line of code in your call stack with ;
3) Your function actually exists.
4) Your strings are in double quotation marks and your integers, floats, and boolean values AREN'T
5) There are no spelling/capitalization errors
Now AFTER you've done all this and checked twice for errors in syntax & spelling, you can start asking on the forums for help. People will probably be reluctant to repeatedly spoon-feed you your scripts. I personally love to help on issues which look worth my effort. I rarely reply to threads requesting help for syntax errors. You can do that yourself. And once you learn to spot them for yourself, you'll have grown as a programmer <3
PointLights and BoxLights don't cast rays of light, so to speak. The way I'd describe how they work is that they raise the light level of the area they occupy, with no regard for objects that may block actual light-rays, such as walls and props.
Use point- and boxlights to illuminate rooms and large areas. Use spotlights for tighter-controlled lighting (windows, fireplace) and as shadowcasters.
Don't place your point light right on top of the light source, especially if it is on or near a wall. Put it a little farther away so the light won't bleed through the walls.
You can also avoid this by spacing out the rooms and hallways in your map. If it isn't absolutely necessary to have two rooms RIGHT next to each other, you can space them out 2 or 3 meters to avoid light bleeding through to the next room.
Monsters in Amnesia have a very simple AI. The have a priority list.
1) Kill you
2) Hunt you
3) Check out that random sound
4) Patrol that path
5) Wait
Their AI was designed with the goal of being "stupid", but that doesn't mean they're SUPPOSED to walk into a corner and derp there until you poke it. It means they are supposed to be tightly controlled with path nodes and user-end scripting.
To understand how to control a monster, you'll need to understand how its AI will work. Path nodes are extremely important. If you put a monster in a flat map with a small barrier between it and the player's start position, you can use ShowEnemyPlayerPosition("nastygrunt"); and sit there all day while the monster tries to be that cute X-Men girl Kat and run through the wall. That, my friends, is "stupid" AI.
Now, the first step in making a successful enemy encounter is pathing. Place path nodes in your map, orienting them along a path you want the monster to patrol, as well as along additional paths you want the monster to be able to take. Put paths around barriers, static props, around corners, through doorways, into rooms, etc. Scatter them across rooms.
If you don't add path nodes in a certain area, the monster will not be able to go there unless it is hunting the player. Even then, it will still need the help of path nodes to maneuver around objects. And that brings me to my next point: How path nodes actually work.
Path nodes are waypoints for enemies, used to navigate the level. Without them, a monster has no idea how to traverse obstacles to get a good swing at the player. Remember our situation with Wanna-be X-Men Cute Girl Kat Grunty? Well, if you place path nodes surrounding the barrier, Grunty will run around it and start chasing you.
This is why proper pathing is CRUCIAL. Another aspect of pathing that is important is the space between the nodes. Path nodes create a path for the monster by drawing lines to edge nodes (which are just another name for path nodes that are near your original node). Edge nodes are linked to each path node by the .nodes file. So PathNode_1 can be an edge node of PathNode_2, but the reverse is also true! If there is an object blocking PathNode_1 from a straight line to PathNode_3, the monster will use the edge node PathNode_2 to get around the obstacle and proceed to PathNode_3.
Now, once you've properly pathed your map, activate the Entity Info option in your debug menu. It will show you the path nodes and their linked edge nodes. Use this to determine if the paths' spacing is correct. If there are two nodes that are too far apart, they will not be linked, and the monster may take a path you don't want it to take when moving between them.
Use those functions in your script file. Easy. If you don't want the monster to despawn after it completes its path, clear its nodes and add a new path before it reaches the end of its first one. If the monster is too far away and doing nothing after it completes its path, it will despawn.
- How do I make a monster break down a door?
Spoiler below!
Monsters break down doors when their patrol paths are obstructed by them. Give it a patrol path (see above) through a doorway with a closed door and watch the magic.
Diaries must have narrations. Notes do not require them. That is the distinction. Diaries without narration will open and close at the same time when picked up. To counter this, you need to add a narration, or use an empty sound file with a long enough length to let the player read the diary. The diary closes once the sound ends.
Syntax error in your lang file. Check and double check. If it still won't work, close Amnesia and re-open it. Launch your custom story from the main menu. Lang file won't load if you use the load map functionality in the debug menu without launching the actual story.
IMPORTANT: Diaries must have an index. The index is the number after _Name or _Text in the entry name.
Code:
/////////////////////////
//this diary's index is 1
<Entry Name="Diary_MyDiary_Name1">My Diary (1/2)</Entry>
<Entry Name="Diary_MyDiary_Text1">[voice mydiarynarration1.ogg][br]My diary text here! Stop reading my diary :(</Entry>
/////////////////////////
//this diary's index is 2
<Entry Name="Diary_MyDiary_Name2">My Diary (2/2)</Entry>
<Entry Name="Diary_MyDiary_Text2">[voice mydiarynarration2.ogg][br]My second diary text here! SERIOUSLY, Stop reading my diary :(</Entry>
- How do I add narration to my diary?
Spoiler below!
Add [voice soundfilename.ogg][br] before your entry's text. Your lang file should look like this:
Code:
<Entry Name="Diary_MyDiary_Name1">My Diary (1/1)</Entry>
<Entry Name="Diary_MyDiary_Text1">[voice mydiarynarration1.ogg][br]My diary text here! Stop reading my diary :(</Entry>
It's as simple as that!
To add separate narrated pages of the diary (page changes to follow the voice) just add
Variables are very important in making dynamic gameplay. Without them, gameplay is static, unchanging, boring, and not fun :( There are two types of variables used in Amnesia's script. Local variables and global variables.
Local variables are used within the map, and only within the map. They can only be called within the script they are declared in. Hence they are local.
Global variables on the other hand, can be called or checked across the entire story. You can't check Story A's global variables in Story B, though. Global variables are declared in the global.hps file, which is placed in the custom story's root.
Practical uses for variables: switch statements: See below
if statements: See below
- Switch statements: How do they work?
Spoiler below!
I would describe switch statements as clean and tidy forms of if statements. You can use a switch statement which looks at a variable and performs a certain set of actions depending on what value the variable holds, instead of doing multiple if-else-if statements, or having a plethora of void if statements. This variable can be in any form (double, integer, float). The switch statement follows this basic structure:
Code:
void MySwitchFunction(sig 1, sig 2, sig 3)
{
switch(<get your int variable here>) {
case 1:
//cool stuff 1
break;
case 2:
//cool stuff 2
break;
case 3:
//cool stuff 3
break;
}
}
You can get your int variable by using RandInt(), GetLocalVarInt(), GetGlobalVarInt() or calling another function which returns an integer value.
- If statements: How do they work?
Spoiler below!
If statements take many forms. The structure is simple and a little flexible, so you can use it many ways. NOTE: All the code below is not tabulated, but you can do so if it pleases your eyeballs
The simplest construct follows this theme: if(condition) { <execute code> }
You can follow this with else statements which are similar to if statements, but only executed when the condition is false. You can also string together multiple if statements when there are multiple conditions to be met.
For loops are handy tools. The things I find myself using them for most are scripting soundscape and doing multiple actions with a single statement. There is probably way more utility to them than what I use them for, so if you're interested, go out and search for more info on them.
They follow this basic structure:
for(statement;condition;operation) { <execute code> }
The simplest explanation for how they work that I can give is: The operation you state will be performed on your statement, and for every case that the condition is true, the code will execute. If that doesn't make sense, I'd suggest consulting with someone with more experience.
So what can those three objects be? The statement can be any string, integer, float, etc. Make sure it is stated correctly. The condition can be anything you want too. The operator can be anything you see from this list.
The execute-code can and should involve the statement, except in situations where you want to execute a single unrelated line of code multiple times. (See below)
Here's a simple script that will activate 3 indexed entities in a single line of code, to help you get an idea of how for loops can be used.
Now, the breakdown: int x=1; is my statement. x<=3; is my condition. Only when x is 1, 2 or 3 will the code execute. Using x as a part of the entity name puts the integer which matched the condition in its place. As a result, the entities named entity_1, entity_2, and entity_3 will be activated. To break it down even further:
- x is 1.
- 1 satisfies the condition x<=3.
- SetEntityActive("entity_"+x, true); is executed, using 1 as the value for x.
- Performing x++ yields 2, which satisfies the condition x<=3.
- SetEntityActive("entity_"+x, true); is executed, this time with 2 as the value of x.
- This repeats until the operation no longer yields a value which satisfies the condition x<=3.
There is no requirement for using the stated variable in the code to execute. For example, you can use a for loop to give the player a certain amount of tinderboxes as opposed to calling GiveItemFromFile("tinderbox", "tinderbox.ent"); repeatedly.
You can also use different variables, as long as they are declared. For example, if you want a dynamic condition, you can state a separate variable, y for example, and use that in your for loop to set the condition.
- Infinite loop: for(;;) SetPlayerSanity(30); //never lets player sanity rise or fall from 30
- Sequential timers: for(int t=1;t<=5;t++) AddTimer("timer_"+t, t, "TimerFunc"); //creates timers which end in sequence every 1 second
- What does the asterisk do?
Spoiler below!
You can use asterisks in a single line of code to apply that code to all objects that are indexed. Meaning, if you call SetEntityActive("entity_*", true); all objects named entity followed by an index will be activated.
A subroutine is, to be concise, a portion of code which performs its own task when called by the program. You use them all the time. When you add a collision callback, you're telling the game to execute a subroutine when the parent entity collides with the child entity.
Having a bunch of subroutines for multiple events and functions in your map is great, but the script becomes very cluttered when you create separate subroutines for very insignificant and minute tasks.
Instead, you can create a general subroutine which executes a lower tier of code based on what objects are involved.
For instance, say you had an event in which the player sees three doors slam in front of them one at a time. Instead of having three subroutines with a single line of code adding a prop impulse to push it shut, you can merge them into a single function. Makes things neat, organized, and easy for others to follow.
That last script is just plain messy and hard to read. It's much better for you to organize and make your script look neat, especially if you plan on coming back to do more work on it. By using if statements which check the timer's name, you can do multiple things in a single subroutine. Your new script will look like this:
You can even take things a step further by using a combination of if statements and for loops, further compressing your script, allowing you to combine subroutines into further generalized functions.
You can call this subroutine with any timer you want, but you must declare what each timer will do by using if statements to check what the name of the timer is. This can be used if you want to script very small or simple events but don't want to give it its own function. Be warned, it may be a hassle to tweak or modify these events later on, cuz they'll be tricky to locate and can look kind of cluttered.
- So what does return; actually do??
Spoiler below!
I myself was unclear on this until recently. Amazing what the internet can teach you.
The return; statement, to use a proper definition, outputs a value from a subroutine to the parent function which called it. An elementary description of a function is that it follows this process: input > function > output. By this token, return values are the outputs.
Here's a scenario: There are three barrels, named barrel_1, barrel_2, and barrel_3 respectively. There is a script area nearby for which all three barrels have scripted callbacks. The player can pick up and place any one of the barrels into the area. Upon doing so, a subroutine is called:
The first function is the function called when the collision happens. The second function is the subroutine which cranks out the output value, which (hopefully) matches the barrel which was placed in the area. You'll notice the BarrelReturnValue function is not void BarrelReturnValue(), but instead int BarrelReturnValue(). This is because void functions do not return values. They follow this process flow: trigger > execute. They are run once and then are never seen nor heard from again, unless called again by another script.
Now, what about simply return;? The line return; simply tells the function to break out of its loop and return back to the parent function and resume from where the parent function broke off into the subroutine.
This is great for stopping a subroutine according to certain conditions. For example, you can have the player take sanity damage only if their sanity is above a certain amount. (There are other ways to do this, too)
The above function will stop if the player's sanity is below 30. Otherwise, the return; command will be ignored, and the player will receive 10 sanity damage.
What's a full conversion and how do I make one? [1][2]
Spoiler below!
A full conversion is a step up from a custom story -- it gives you the ability to customize almost every element of Amnesia's gameplay. Some examples:
- change the lantern to a flashlight (or something else, if you have the modelling mojo)
- change the inventory/main menu UI
- change the player's body physics, sounds
- change virtually all in-game text you see
- streamlines the gameplay for the player. There is no "custom story" intermediate step.
- much, much more
Now, there are some downsides as well.
- FC mods are sometimes confusing to install, and even more confusing to uninstall
- Can be VERY frustrating to get one to work successfully. Thankfully there are tutorials out there to help us
- If incorrectly or sloppily built, you risk overwriting Amnesia: The Dark Descent's configuration and "corrupting" peoples' files.
Now, how do you create one? There are some links next to the header above. Many thanks to their creators. My only advice is that you be VERY meticulous and EXTREMELY careful when building your own. Do not overwrite ANYTHING when asked to. Double check when you save/delete things to make sure you don't delete the wrong things. Otherwise, you can corrupt Amnesia's files.
Dammit, that's a freakin neat idea! This should be added to the 'Important Threads'.
This will really help those, who are new to Amnesia, but too lazy to google
Also, there are some new things for me, like the asterix. Thanks buddy
Very interesting and well built I'll say, but I did notice that there was already some information like this on the wiki. I'm not sure if I should put this on the wiki, but please tell me if you do want it on there, for the wiki will be where people should be visiting first before asking questions about how to do everything unless in a strange, new issue they have with something.
Although I appreciate the effort and find it very useful, I still find Wiki as a more convenient way to have how to's and all kind of tutorials. And of course, direct people to there before asking any questions.
I'm sure even the most basic how to's will get their questions as new topics either way.
(07-23-2011, 01:12 PM)MrCookieh Wrote: This will really help those, who are new to Amnesia, but too lazy to google
I still can't understand how a person too lazy to search can build a good mod.
Hello, I've been trying to find some information (but to no avail and yes i've checked the wiki) about recording new sound files for diaries and enemies. All I'm equipped with is a microphone and Microsoft Sound Recorder, unless I should use my macbook but I feel like switching between computers is a hassle. Perhaps you could help? What do I need to do to make these custom sounds?
(07-23-2011, 08:22 PM)convolution223 Wrote: Hello, I've been trying to find some information (but to no avail and yes i've checked the wiki) about recording new sound files for diaries and enemies. All I'm equipped with is a microphone and Microsoft Sound Recorder, unless I should use my macbook but I feel like switching between computers is a hassle. Perhaps you could help? What do I need to do to make these custom sounds?
What I do to record sounds for diaries is record my microphone on a program called audacity, which is a free program. This program allows you to export sounds in .ogg files which Is the file formats used in diaries. I'm not sure about the file formats used for enemies though.
Regarding asterisks: some functions don't support asterisks in some/all of their fields. Check the script functions wiki page (top of the original post); each function should have a note mentioning if it doesn't support asterisks. I don't have an answer for why one function versus another does or does not support their use, but I'm sure there's a reason
About variables: they're just a way to store information/data. There's plenty of resources available for learning about them on the web