Enthalpy's Variable Guide

Find detailed help from the AAO community, or write your own tutorials.

Modérateur: EN - Forum Moderators

Enthalpy's Variable Guide 

Message par Enthalpy » Ven Sep 13, 2013 2:28 am

Have you wanted to get involved in trialmaking, but have been too nervous because of the editor's expression engine? Have you wanted to do an investigation, but don't know how to get the variables to work? Do you have an idea for a new feature, but don't know how to implement it? Do you not know what I'm talking about? If the answer to any of those questions is yes, you should keep reading. I'm going to show the sort of things you'll be working with on a regular basis. Because of the nature of this tutorial, it's a long one, so please ask questions or read this in multiple sittings if you find it necessary. You may also find it helpful to create a sandbox trial and experiment with variables as I explain them.

What is a variable, and how do I make one?

In the first place, we need to figure out what we're talking about. What are variables? A variable is simply something that has a name and a value. There are lots of possible names and lots of possible values. For instance, some variable has a name cat and a value dog. Weird, but valid. Some variable has a name s01 and a value 1. Valid. Some variable has a name variableAlpha. There's no value, so it isn't actually a variable. What about a variable with value variableAlpha? Without a name, it still isn't a variable.

How do these variables get their names and values, you ask? Just like in mathematics, we make them up. For instance, consider a simple word problem: Anna has 100 apples and gives Bailey some number. How many does she have left? We completely arbitrarily decide that some variable a now means the number of apples Anna gave Bailey. We could just as easily have named that variable y or made a mean the number of apples Anna has left. It's just that some names and meanings are more useful than others. (Note that where mathematics usually has its variables be pure letters, we can have variables be almost any combination of letters and numbers. Never start a variable with a number, though! That breaks the system!)

With this in mind, we're ready to make up our first variable. I'll give you a step-by-step procedure as well as a picture.

Spoiler : Image 1 :
Image


* First, click on the two yellow gears on the row where you want the variable to first show up.
* In the "Different available" action panel, select "Define a variable's value." It will be under the bold category "Manage variables."
* Check "Expression?" boxes where appropriate. A discussion of when this should be checked will occur later.
* Under "Parameters" input the desire variable name and variable value.

In this case, I set variable "contraB1" to mean "1". With this, you've set up a variable!

Can I change frame reading order based on a variable's value?

You may have noticed there are three other ways the editor allows us to manage variables. The first is through "Read a variable's value." What "Read a variable's value" does is look at what a variable's value is. If it matches the value you want, it goes to a special frame. Otherwise, it goes to a generic frame. For an example of how to put this in and what it does, please look at the next image.

Spoiler : Image 2 :
Image


* First, click on the two yellow gears on the row where you want the variable to first show up.
* In the "Different available" action panel, select "Read a variable's value." It will be under the bold category "Manage variables."
* Check "Expression?" boxes where appropriate. A discussion of when this should be checked will occur later.
* Add cases when appropriate. This will be discussed immediately after this procedure.
* Put in the name of the variable to be checked.
* Add each case of what value you want to go to which frame.
* Add the default case, for if a variable has none of the special values.

In this case, the variable contraB1 is checked to see if it has value 1. If so, it goes to frame 537. If it doesn't, it goes to frame 538. You may remember that on frame 538, contraB1 was set to 1. As you might imagine, this structure is put in to ensure that the player gets the dialogue starting on frame 538 (Edgeworth explaining a contradiction) exactly one time! Note from this that well-named variables can make it easy to understand what the variable is doing later. Without knowing anything else about the case, I can tell this being the variable concerned with if the player has found the first contradiction on testimony two is pretty likely. Also, where you put your actions can matter. Time for a thought experiment. What would happen if I defined contraB1 on 536 and moved the "read a variable's value" action to 538? (Do this once assuming frame 536 then goes directly to 538, skipping 537, and once without this assumption.)

Spoiler : Answer :
With the special assumption: The moment you got to frame 538, you would be directed to frame 537 and get sent back to the cross-examination without Egeworth getting to explain his contradiction!

Without the special assumption: Edgeworth never even gets to frame 538 before jumping back to the cross-examination start.


Now, you may have noticed the "Add" button. What the add button allows us to do is to create more special cases. For instance, instead of just having something fancy for contraB1 being 1, we could give a special redirect for 1 and another special one for 2. To see what it looks like, click the below spoiler.

Spoiler : Image 3 :
Image


You can see here that if hoserName is edgeworth, it goes to 2310. If it's phoenix, it goes to frame 2319. You can make the rest of the connections yourself.

The add button may be used to add special cases, and the delete button to delete them. Note that after a certain point, it becomes hard to see all the special cases! Also, note that I made the "if none of these work" option be the start of the case. NEVER do this unless every option has a special case, and consider making the "generic redirect" leading to a frame telling the player to report a bug!

A few side notes. First, if a variable has not yet been set, it takes a value of 0 for purposes of actions that care about what the variable is set to. Secondly in AAOv5, frame numbers go down sequentially, and frames are re-numbered upon saves, which can cause the numbers of some frames to change. For instance, if you delete Frame 5, the old Frame 6 is the new Frame 5. Luckily, all frame redirect pure numbers automatically get changed accordingly, so if there was a "go to frame 6" command, it now is a "go to frame 5 command." Handy. Unfortunately, this doesn't work with variables.

Ferdielance a écrit :The exception to this rule, which probably won't exist in AAOv6, is if you try to redirect to a frame number that is an expression or a variable's value. If you set a redirect to go to the frame defined by the expression "1 + 1", it will always go to frame 2. I strongly recommend that you never, ever do this. It's very tempting to set up tricks where you make a variable store the frame to redirect to, but it's not worth it. Believe me. This is the voice of someone who tried to create "functions" in AAOv5 that way. Don't.


What is the expression engine, and how do I use it?

Here we get to the fun part, expressions. If you want to do something with variables that's more complicated than "if X has value Y, go to frame Z," you're going to be using it.


Spoiler : Henke's explanation of the expression engine :
The expression engine explained
As a few posts have shown already, we got an expression engine. I felt that someone should give an userfriendly explanation on how it works.

Quick word list:
Operator Thing that does stuff to one or more things. The plus sign is an operator.
Operand A value that an operator operates on.
Function A named chunk of code that does something. It may return a value.
Argument or Parameter A value passed to a function for use by the function.
Prefix Place some text before some other text or the text being placed before.
Evaluate Fancy word for "Do" or "Check"
Expression A string of values and operators that has a value.

As you might already know, it is a custom engine built by unas since he didn't feel like exposing the site to insecure usage of eval.
The engine is fairly easy to use, it has a these operators:
  • + Add. Add two numbers
  • - Subtract. Subtract one number from another number
  • - Sign flip. Flip the sign of a number
  • / Division. Do an integer only division. Any reminders is thrown away.
  • % Modulus, aka reminder. Gives the reminder of a division.
  • ^ Power. Gives the first number to the power of the second number.
  • & And. Returns a boolean that is true only if both operands is true.
  • | Or. Returns a boolean that is true if one or both of the operands is true.
  • ! Not. Inverts a boolean. True is false and false is true.
  • = Equals. Returns true if both operands are equal.
  • < Less than. Returns true if the left operand is less than the right operand.
  • > Greater than. Returns true if the left operand is greater than the right operand.
  • . Concanation. Joins two strings together.

The operators have the following priority order:
  1. !
  2. ^
  3. *, / ,%
  4. +, -
  5. >, =, <, .
  6. & and |
Lower value means that it is done earlier. Operators on the same row is evaluated in a left to right order.
You can use parentheses to override the operator order by wrapping the sub expression with them.

If you want to do less than or equal, you got plenty of ways of doing it, you can use <, & and = to do the check, you can use < after adding 1 to the right side and you can use !(x>y). Do the same for greater than or equal.

There are several functions that you can use:
  • str_begins_with. Checks if one string begins with another.
  • str_contains. Checks if one string is in another.
  • str_ends_with. Checks if one string ends with another.
  • str_first_word. Returns the string up to the first space, the space not being included.
  • str_distance. Returns the percentage of difference between two strings. Returns 100 if they have nothing in common, 0 if they are identical.
  • evidence_is_revealed. Checks if a given evidence ID is revealed. You must pass the type (In French! 'preuve'='evidence' and 'profil'='profile') as the first argument.
  • random_int. Generates a random number. If you call f:random_int(), the number will be chosen between 0 and 100. You can also call f:random_int(5,10) to generate a number between 5 and 10, and so on.
  • get_date. f:get_date() returns the current date. The difference between two dates will be the number of milliseconds between them.

You can (and should) use variables in the expressions. Do this by just typing their names where you want their value.

The following actions can use expressions:
  • DefinirVar. Set a variable. You need to check the "expression" checkbox.
  • TesterVar. Test a variable. You need to check the "expression" checkbox.
  • EvaluerCondition. Test a condition. You do not need to prefix the expression.


That's certainly a lot of information, but when are we ever going to use it? Or can we even understand it? Well, this provides the first step in the incredibly powerful tool that is the "evaluate condition" action. If you give it an expression, it will see if it's true or false and then take you to one of two frames, depending on what you get out of it. It's a lot easier to understand the expression engine if you see it in action, so I'll also take this opportunity to acquaint you with "evaluate condition."

Spoiler : Image 4 :
Image


* First, click on the two yellow gears on the row where you want the variable to first show up.
* In the "Different available" action panel, select "Evaluate condition." It will be under the bold category "Manage variables."
* Write the condition to be evaluated.
* Add each case of what value you want to go to which frame.

Let's look at this piece by piece. We start with "pressA1=1". We know that if pressA1 is 1, this will work out. (In checking when an event has happened, 0 often means it has not, when 1 means it has.) Now let's look at the & symbol. In order for this to give us the true case, we need all the other expressions to be true. Using this, we can pretty quickly say that all six statements in the cross-examination need to be pressed in order to go to frame 314. Otherwise, we're stuck at 207. If this seems familiar, there's a good reason. This is the procedure for a press-all-to-continue cross-examination. (Also, note that the best way to understand what henke wrote was practicing it. Don't hesitate to make a sandbox trial and experiment in the editor!)

There would be other ways to get the same result. For one, I could have instead typed "pressA1*pressA2*pressA3*pressA4*pressA5*pressA6=1" If I only have 0 and 1, that would also need all values to be 1 for the true case to occur. This sort of trick is going to be the primary way investigations work. Set a variable for each trigger, and write expressions for what happens when the write triggers are done. Note that when working with investigations in particular, keeping track of variables is extremely important. "step5=1 & step6=1 & step7=1 & step8=1 & step13=1 & step14=1 & step25=1 & step17=1 & step18=1 & step19=1" would mean nothing to me if I didn't have a written list of what each variable corresponded to.

There are some other interesting cases. For instance, sometimes you may need to use one of the functions. Let's say that after reading Ferdielance's fairness guide, you've found inspiration for your sadism and wanted to make a press conversation that relies on luck to decide if you get the new statement or not. You could define a variable as f:random_int(0,1) to make it a coin flip whether the poor player gets the hint or not. You would need a frame to read the variable's value and send the player either into the new statement or the trick end. Note that any time you use expressions outside of the Evaluate Condition feature, the expression box must be checked! This is the general rule for checking the box. Also note that in functions, variable names must be in double quotes. Any time you use a string, put it in single quotes.

Are there any other special tricks I should know about?

There are three.

The first one is a bit more advanced. Credit to Ferdielance for publicizing this.

Spoiler : Using & and | to set variable values :
Suppose you want to do the following:

* If some condition is true, set variable Y to the value 'Well done!'
* But if that condition is false, set variable Y to 'Oops.'

Normally, you might evaluate a condition, then jump to a frame that sets variable Y's value depending on the result, then proceed from the next frame out. But there's a faster way!

1. Define Y as the expression:
Code : Tout sélectionner
((condition) & 'Well done!') | 'Oops.'


See, X & Y follows this rule:

If X is false, give false; If X is true, give Y.

...and X | Y follows this rule:

If X is false, give Y; If X is true, give true.

To see why this is REALLY useful, consider the following case:

1. Define victoryMessage as:

Code : Tout sélectionner
((score < 2) & 'That was... not good.') | ((score < 4) & 'Not bad.') | ((score = 4) & 'Well done!') | 'Perfect score!'


Consider how many frames that would have taken by any other means! Uses of this include, but are not limited to:

* Making more efficient examine-based engines.

* Giving the player a different message depending on how many penalties they've received (requires you to increment a penalty counter.)

* Making type-and-response engines that DON'T take up an insane number of frames.

Warnings:

* I don't know how much longer Unas will support this. It works in the v5 and v6 player, but the source of the v6 player says that Unas will probably rewrite the expression engine soon...

I don't think it's a security issue, seeing as the expression engine sanitizes everything quite carefully, but it's conceivable that Unas might decide that this behavior of the Javascript & and | is potentially risky in some way and switch to truly Boolean operators. :(

* Don't assume that evaluation always occurs in the obvious order. If, for example, you do:

Code : Tout sélectionner
(!(couldBeUndefined) & 'Variable is undefined!') | (f:str_first_word(couldBeUndefined))


...on the theory that this will prevent str_first_word from ever being fed a variable that might not be defined, you're in for a surprise!

This:

Code : Tout sélectionner
(!(couldBeUndefined) & 'Variable is undefined!')


returns 'Variable is undefined!' if the variable truly is undefined. However, the longer expression just doesn't return anything. This is probably because the engine processes the entire expression and evaluates functions even if they're on the wrong side of an OR. But I'm not really a programmer, so take my explanations with a grain of salt.

* Make sure you can read your code. Use parentheses and spacing consistently, and consider downloading Notepad++ or another text editor that automatically tells you whether your parentheses match. Don't make something that it will be a nightmare to follow later...

Whew!


The second one is the "ask player for a variable's value" feature, often used as a password. (Feel free to play around with the expression options here, but there really isn't any reason why you would use these unless you want to make the variable name be dependent on other variables, in which case you would check the expression box for the name.) This is pretty straightforward and just requires you to input the variable name and the data type. "Strings" is any alphanumeric character with punctuation. "Word" is pure alphabet characters. "Integer number" is pure digits. To use this as a password, have another frame read the value of this password to see if it agrees with the values you want.

The third trick isn't a trick so much as the ability to think with variables. If you have a feature, how are you going to get it to work using variables? The rule of thumb is to think of what is important to what you want and creating variables to reflect this, making sure the variables reflect the conditions you want to control. Understanding this is critical for doing anything reasonably complex with variables.

To demonstrate, I'll give an example. Your mission is to figure out how you're going to implement a super-objection. The rules for a super-objection are as follows: a player is asked to present evidence. At any point, they may either choose to present more evidence, or they may object. When they object, if they have presented every "right" piece of evidence and have not ever presented a "wrong" piece of evidence, they move on to explaining the contradiction. If either condition fails, the player goes to the penalty conversation and may try again. Please think for a few minutes about how to do this before looking at the spoiler tag. Even play around with the editor to test it out! For simplicity, you may assume there are two pieces of right evidence, although the solution should generalize for any number of pieces of evidence.

Spoiler : Enthalpy's Solution :
We already know there's going to be an "ask for evidence" frame. We'll call this Frame 1. We also know that depending on the evidence we get, we can manipulate what frame the players goes to. So, what frame should the player go to?

Thinking in terms of variables, we need to make sure Good Evidence A presented, Good Evidence B presented, and nothing else has been presented. Those all sound like reasonable variables, so we now know that the evidence one presents should somehow go to a variable-setting frame.

Connecting our ideas, we have a general plan. Presenting Good Evidence A takes you to Frame 2, which sets some variable like superEv1 to be 1. Frame 3 will redirect to a later frame to avoid triggering the other variables. (We'll later find that 7 works out nicely.) Good Evidence B goes to Frame 4 and sets superEv2 to be 1. Five redirects to seven, and presenting anything else takes you to frame six, which sets superEv0 to be 1. Frame 7 will ask the player to either go back to Frame 1 and present more evidence, or object. If the player selects object, we take them to Frame 8. Frame 8 has a command to evaluate condition "superEv1=1 & superEv2=1 & superEv0=0" This accounts for all the variable behavior. (Remember, we want no bad evidence, and any variables that aren't messed with are automatically set to 0.) Success goes to the success conversation. Failure goes to the failure conversation, where the values of the three variables are set to 0.

Viola! That's the core of a super-objection!


And... That's it. Tutorial complete! It may feel a bit confusing, but it should start making more sense as you work with the editor. If you have any questions, please feel free to ask here!
Dernière édition par Enthalpy le Ven Nov 01, 2013 9:11 pm, édité 7 fois.
[D]isordered speech is not so much injury to the lips that give it forth, as to the disproportion and incoherence of things in themselves, so negligently expressed. ~ Ben Jonson

Current AAO Development Priority: Issue #94: Grayscale Mode
Avatar de l’utilisateur
Enthalpy
Community Manager
 
Message(s) : 4380
Inscription : Mer Jan 04, 2012 4:40 am
Genre: Masculin
Langues parlées: English, limited Spanish

Re: Enthalpy's Variable Guide 

Message par Ferdielance » Jeu Sep 19, 2013 11:42 pm

This excellent. One quick note on something that confused me when I started out:

If you put a frame number in the "Frame to go if failure" box, or any other redirection box, then save your trial, AAO will "link" those frames. You may notice, however, that if you add and delete frames in your trial before saving, all of the frame numbers will change when you reload it! Don't panic! AAO just puts the frames in order for you, and automatically updates all of the redirect boxes.

The exception to this rule, which probably won't exist in AAOv6, is if you try to redirect to a frame number that is an expression or a variable's value. If you set a redirect to go to the frame defined by the expression "1 + 1", it will always go to frame 2. I strongly recommend that you never, ever do this. It's very tempting to set up tricks where you make a variable store the frame to redirect to, but it's not worth it. Believe me. This is the voice of someone who tried to create "functions" in AAOv5 that way. Don't.
"A slow sort of country!" said the Queen. "Now, here, you see, it takes all the running you can do, to keep in the same place. If you want to get somewhere else, you must run at least twice as fast as that!"
Avatar de l’utilisateur
Ferdielance
 
Message(s) : 762
Inscription : Dim Mars 09, 2008 12:46 am
Genre: Masculin
Langues parlées: English

Re: Enthalpy's Variable Guide 

Message par Enthalpy » Sam Sep 21, 2013 1:53 am

Thanks for the feedback! I've added your comments into the post.

Looking at what people have been saying over Xat, I'd like to emphasis that you probably are not going to understand all of expressions right away. Start off with knowing the equals sign, plus, minus, and experiment in the editor to build up from there. Expressions are too abstract for anything less than working with them yourself to help you understand them.
[D]isordered speech is not so much injury to the lips that give it forth, as to the disproportion and incoherence of things in themselves, so negligently expressed. ~ Ben Jonson

Current AAO Development Priority: Issue #94: Grayscale Mode
Avatar de l’utilisateur
Enthalpy
Community Manager
 
Message(s) : 4380
Inscription : Mer Jan 04, 2012 4:40 am
Genre: Masculin
Langues parlées: English, limited Spanish

Re: Enthalpy's Variable Guide 

Message par CardiaX » Sam Nov 16, 2013 12:20 am

Thanks, this was really helpful in a trial I'm making.
Anything I don't like will be given the Ammy treatment.
Image
Avatar de l’utilisateur
CardiaX
 
Message(s) : 378
Inscription : Jeu Juil 21, 2011 2:19 am
Localisation : The Internet
Genre: Masculin
Langues parlées: English, Al Bhed

Re: Enthalpy's Variable Guide 

Message par The Ash Raichu » Mar Déc 03, 2013 5:24 am

Don't you have to use variables in "press-all-to-continue" cross-examinations? I don't know how I would do that. Could you please help me out?
Image
Avatar de l’utilisateur
The Ash Raichu
 
Message(s) : 34
Inscription : Dim Oct 27, 2013 5:29 pm
Localisation : The ever-vague deserted city street
Genre: Masculin
Langues parlées: English

Re: Enthalpy's Variable Guide 

Message par Enthalpy » Mar Déc 03, 2013 5:49 am

Variables do have to be used in "press-all-to-continue" cross-examinations. I talk specifically about this in the "What is the expression engine, and how do I use it?" section. The tools you'll need for coding something like that can be found by applying the techniques taught here.

This post provides exactly how you can code press-all-to-continue cross-examinations, although I would recommend reading this guide and thinking of how you can use the "define a variable's value" and "evaluate condition" commands to do such a thing first. The learning experience would be very useful if you ever want to do anything more complicated.
[D]isordered speech is not so much injury to the lips that give it forth, as to the disproportion and incoherence of things in themselves, so negligently expressed. ~ Ben Jonson

Current AAO Development Priority: Issue #94: Grayscale Mode
Avatar de l’utilisateur
Enthalpy
Community Manager
 
Message(s) : 4380
Inscription : Mer Jan 04, 2012 4:40 am
Genre: Masculin
Langues parlées: English, limited Spanish

Re: Enthalpy's Variable Guide 

Message par E.D.Revolution » Mer Déc 04, 2013 12:52 am

Hmm, Enthalpy. I wonder if you could introduce the concept of "flags". For game designers like us, flags are an important concept to learn, especially for investigations. Where you make these variables to check that something has happened before a new action is to be done. I know in PyWright and PWLib, they do have a "flag" function with a syntax like
Code : Tout sélectionner
flag CheckA AND CheckB AND CheckC AND CheckD moveon

Where in this syntax "flag" is the command that checks the flags have, well, been flagged. "CheckA AND CheckB" and such are all the flags that have to be checked before a new action at place "moveon".

The point is that certain actions in games need flags, and that concept needs to be introduced.

So maybe a new section as "Expressions as Flags: How to use the Expression Engine As a Flag Engine" or something like that. Maybe talk about a hypothetical example like "You need to get all the evidence on the scene before moving on." Or "You exhausted all the talk options but presenting evidence will reveal new talk options."
Image
Avatar de l’utilisateur
E.D.Revolution
 
Message(s) : 5721
Inscription : Lun Juil 26, 2010 9:00 pm
Localisation : Across dimensions, transcending universes
Genre: Masculin
Langues parlées: English and decent Spanish

Re: Enthalpy's Variable Guide 

Message par Enthalpy » Mer Déc 04, 2013 1:49 am

I agree that the concept is helpful, but I don't believe that talking about it in that way would be helpful. Because the flag function doesn't exist in AAO, I would only talk about it as a certain specific use of the "evaluate an expression" command, not something that warrants its own section. Since I use a "flag function" as the example for "evaluate an expression" and spend two paragraphs on that specific use of the command, I don't believe there is anything further to say on the subject, and the concept is sufficiently introduced. If part of the explanation is unclear, or something is missing, I would be open to changing that, but I believe that what is currently written covers the subject well.
[D]isordered speech is not so much injury to the lips that give it forth, as to the disproportion and incoherence of things in themselves, so negligently expressed. ~ Ben Jonson

Current AAO Development Priority: Issue #94: Grayscale Mode
Avatar de l’utilisateur
Enthalpy
Community Manager
 
Message(s) : 4380
Inscription : Mer Jan 04, 2012 4:40 am
Genre: Masculin
Langues parlées: English, limited Spanish

Re: Enthalpy's Variable Guide 

Message par fanfreak247 » Sam Juin 25, 2016 4:14 am

This guide is extremely confusing.... :(
I wanted to learn how to just make sure the player selects both talk conversations and then goes to a different frame.
You use a lot of the terms and I don't know what anything does and I get really lost.
Then again, I read it pretty fast to try and find what I was looking for, your tutorial is probably good. Please help me specifically.
Thanks
-fanfreak247
fanfreak247
 
Message(s) : 43
Inscription : Dim Juin 12, 2016 1:45 am
Localisation : Anywhere you are
Genre: Masculin
Langues parlées: English, Limited Spanish, Patois/Patwa

Re: Enthalpy's Variable Guide 

Message par Enthalpy » Sam Juin 25, 2016 5:24 am

Read it more slowly. Without more specifics about where you are confused, I can't help you. And if you don't understand what I've presented in this tutorial, you won't understand what you're doing with this.
[D]isordered speech is not so much injury to the lips that give it forth, as to the disproportion and incoherence of things in themselves, so negligently expressed. ~ Ben Jonson

Current AAO Development Priority: Issue #94: Grayscale Mode
Avatar de l’utilisateur
Enthalpy
Community Manager
 
Message(s) : 4380
Inscription : Mer Jan 04, 2012 4:40 am
Genre: Masculin
Langues parlées: English, limited Spanish

Re: Enthalpy's Variable Guide 

Message par Kroki » Jeu Août 24, 2017 5:18 am

Hey, the images are down, do you still have them?
ImageImage Image Image Image
Avatar de l’utilisateur
Kroki
Admin
 
Message(s) : 7423
Inscription : Ven Nov 23, 2007 10:05 pm
Langues parlées: Français, English, Español, 日本語

Re: Enthalpy's Variable Guide 

Message par Enthalpy » Jeu Août 24, 2017 5:21 am

I blame Photobucket.

Thanks for the reminder! I'll get these up again over the weekend at the latest.
[D]isordered speech is not so much injury to the lips that give it forth, as to the disproportion and incoherence of things in themselves, so negligently expressed. ~ Ben Jonson

Current AAO Development Priority: Issue #94: Grayscale Mode
Avatar de l’utilisateur
Enthalpy
Community Manager
 
Message(s) : 4380
Inscription : Mer Jan 04, 2012 4:40 am
Genre: Masculin
Langues parlées: English, limited Spanish


Retour vers Tutorials

Qui est en ligne ?

Utilisateur(s) parcourant ce forum : Aucun utilisateur inscrit et 1 invité