DialogueChecker (Latest version: V3.07, 18 December 2016) (1 Viewer)

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.14, 2 September 2014)

WeeWillie said:
FYI, I get the following error:

Variables - Major: Unused variable ("o3Name")

even though the variable is used as

[VA_SET_GLOBALVARIABLEBYNAME_gO3Name_o3Name]

Fully supporting the VariableArithmetic section is hard. I added what you asked though, but it only works if you don't build the variable name with insertions.
So something like [VA_SET_GLOBALVARIABLEBYNAME_gO3Name_o*counter*Name] will likely still complain.
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.15, 7 September 2014)

September 7, 2014
Introducing v2.15!
- Added checks for argument count mismatches for VA_SET functions.
- Fixed the issue where if you use a variable only for VA_SET functions, the checker would still tell that the variable was unused. Only works for static variables (not with variable insertions).
- Added checks for VA_SET functions that check if used variables are also declared. Only works for variables without insertions.
- Fixed a bug where using loadCharCode with a mood set would give you errors.
- Added checks for the use of punctuation in line-reference triggers.
- Added a check to see if a line's name is the same as a default or mod-defined trigger. This because it prevents triggering of the line in the most common way (as [LINE]).
- Fixed a crash where typing {"next":{"LINENAME"}} like an idiot programmer at 4 AM crashes the checker. Turned it into a check.
Download link in OP.
The archive download, containing the source of the DialogueChecker, as well as the current and all the previous versions has also been updated.

Woop 500 posts!

I've also updated the second post with a listing of URLs to each version of the DialogueChecker that I still have hosted online.
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.15, 7 September 2014)

BUG:
"%words%[trigger]" causes trigger to fail to register

see this dialogue for broken example.

Fixed in v2.16
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.16, 30 December 2014)

December 30, 2014
Introducing v2.16!
- Fixed a typo in the check for "dialogue-name"; it said you used "dialogue_name" and should use "dialogue_name" instead, which was unhelpful advice.
- Fixed a bug where %words%[trigger] would fail to cause a trigger to be recognized by the checker.
- Added checks for % that can't be converted to a percent encoded value. These are MAJOR or MINOR depending on whether it looks like it could be a percent encoded value.
- Added support for DA v3.01 to v3.05 for the simple variables they added. Triggers still unsupported.
- Fixed a bug where insertions would be checked wrongly; this caused the checker to think that [intro*introPlayed != 0*] contained raw spaces.
- Added partial support for the VA_SET replacements.
Download link in OP.
The archive download, containing the source of the DialogueChecker, as well as the current and all the previous versions has also been updated.

A new checker update? Really?

Special in this update, not listed in the changelog is the addition of #DialogueChecker-IgnoreObjects#. You can stick it anywhere in your dialogue and the dialogue checker will stop whining about things like "Player.Name is unused". It mostly shuts the checker up so you can read the more important stuff. It's also a way for me to allow WeeWillie to continue his work whilst making at least some use of the checker.

I've also fixed a few bugs that have been poking at me, and I've regained a bit of confidence in the checker when I ran it against various other dialogues; It works almost 100% for Dialogues using DA v3.00 or earlier; it's just the new features that are being a pain. A few bugs still exist:
- GrammarChecker won't shut up about [intro*introPlayed != 0*] having a space before a exclamation mark (Fixed in v2.17)
- LineUsageChecker thinks backgrounds aren't set properly when using VA_SET_VARIABLE or SETVAR to set da.background.load and [FADE_BACKGROUND] or [CHANGE_BACKGROUND] triggers

but these are minor.
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.17, 31 January 2015)

January 31, 2015
Introducing v2.17!
- Fixed a bug where the checker would see "variable":"" in initial_settings, set and check lineattributes as a mistake
- Enabled ErrorManager.showDevelopmentError which crashes the checker if something was programmed badly (if this causes a problem for you, switch back to v2.16 and let me know what dialogue you used to crash the checker)
- Fixed a bug where the checker fail to detect a unplayable line due to mood settings
- Added support for Animtools v14's new lines/triggers
- Fixed false "probable improper use of mood" messages when using charcodes
- Made the GrammarChecker shut its trap for DA arithmetic ([intro*introPlayed != 0*] no longer gives grammar errors)
- Added #DialogueChecker-DebugTime#
- Cleaned up some of the code (Main point of interest is that mod variables and linetypes have moved to Data.as, making adding new variables and linetypes pretty easy)
Download link in OP.
The archive download, containing the source of the DialogueChecker, as well as the current and all the previous versions has also been updated.

Some fixes for several annoying false positives. It's strange, I set out to make something which would absolutely guarantee you that anything it found would be an error, and then everything flips upside down and all of a sudden more than half of your issues are not really issues anymore.

Hmmh.

What I really wanna do some time in the future is overhaul the way mod dependancies work. Right now they're string based and they're also what threatens to invalidate the DialogueChecker. People make new linetypes and triggers and before you know it, half the errors the checker nags you about aren't really errors.

Three ways to tackle that problem of false positives:
- Work hard to make the checker up to date at all times
- Make it possible to select a set of mods that you use, so the DialogueChecker doesn't have to check for certain things if you are not using DA v3+
- Make it possible to individually disable checks

I think the third one is best, but it's gonna be a huge interface. That, or a gigantic list of directives.

I just need to figure out how to make something like this...

Code:
if (line.indexOf("[[") != -1) {
				issueSnippet = StringFunctions.getSnippet(line, "[[");
				issueMessage += "Double trigger opener on line " + l.getLineNumber() + ", col " + (line.indexOf("[[")) + " near \"" + issueSnippet + "\"" + linebreak;
				issueMessage += "Line " + l.getLineNumber() + ": " + l.getRaw() + linebreak + linebreak;
				issue = new Issue(Severity.getEnum("MAJOR"), l, issueSnippet, issueMessage, "Syntax");
				addIssue(issue);
				issueMessage = "";
			}
			if (line.indexOf("]]") != -1) {
				issueSnippet = StringFunctions.getSnippet(line, "]]");
				issueMessage += "Double trigger closer on line " + l.getLineNumber() + ", col " + (line.indexOf("]]")) + " near \"" + issueSnippet + "\"" + linebreak;
				issueMessage += "Line " + l.getLineNumber() + ": " + l.getRaw() + linebreak + linebreak;
				issue = new Issue(Severity.getEnum("MAJOR"), l, issueSnippet, issueMessage, "Syntax");
				addIssue(issue);
				issueMessage = "";
			}
			
			if (line.indexOf("::") != -1) {
				issueSnippet = StringFunctions.getSnippet(line, "::");
				issueMessage += "Double colon on line " + l.getLineNumber() + ", col " + (line.indexOf("::")) + " near \"" + issueSnippet + "\". Suggested fix - remove one of the colons." + linebreak;
				issueMessage += "Line " + l.getLineNumber() + ": " + l.getRaw() + linebreak + linebreak;
				issue = new Issue(Severity.getEnum("MINOR"), l, issueSnippet, issueMessage, "Syntax");
				addIssue(issue);
				issueMessage = "";
			}
			if (line.indexOf("{style") != -1) {
				issueSnippet = StringFunctions.getSnippet(line, "{style");
				issueMessage += "Probable improper use of style attribute on line " + l.getLineNumber() + ", col " + (line.indexOf("{style")) + " near \"" + issueSnippet + "\"" + linebreak;
				issueMessage += "Line " + l.getLineNumber() + ": " + l.getRaw() + linebreak + linebreak;
				issue = new Issue(Severity.getEnum("MAJOR"), l, issueSnippet, issueMessage, "Syntax");
				addIssue(issue);
				issueMessage = "";
			}

... uniquely identifyable per check and without too much duplication.

Hmmmh.
 

aztlan

Casual Client
Joined
Sep 14, 2013
Re: DialogueChecker (Latest version: V2.17, 31 January 2015)

Thanks much for this!

For new line types causing false positives - what if the dialogue checker had a another input space which allowed you to enter "new" line types?
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.17, 31 January 2015)

DialogueChecker v2.17 is broken - many false positives

Fix soon (~1 hour)
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.18, 3 February 2015)

February 3, 2015
Introducing v2.18!
- Fixed a bug where I accidentally the variables.
- Added a check for check lineattributes with missing quotes.
Download link in OP.
The archive download, containing the source of the DialogueChecker, as well as the current and all the previous versions has also been updated.

That's a major bug right there. Everyone who has used v2.17 should recheck with v2.18 - it was basically giving you a lot of nonsense.

Why I didn't get a PM about this, I don't know. I guess people are getting used to false positives?

Remember, if you see a false positive, just PM me the dialogue and the false positive. I'll try to fix it as soon as I can.

The bug was caused due to some of my cleanup. Sorry about that.
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.19, 12 February 2015)

February 12, 2015
Introducing v2.19!
- Massive performance improvements.
Download link in OP.
The archive download, containing the source of the DialogueChecker, as well as the current and all the previous versions has also been updated.

Huh, just performance improvements?

Yep.

I was testing with Fleack's dialogues and found that waiting 13-14 seconds was really annoying.
Plus, I have a fast PC. Sure, I use a debug player, but I have a fast PC. I don't want other users to have a bad experience with the checker.

So I set about improving it.

I've thought and read about regexes, vectors instead of arrays, proper string concatenation...
When I realized that maybe, I just went about things the wrong way.

You see, there's this loop for checking the lines.

Code:
var line:Line = dialogue.getNextLine(0);
while (line != null) {
    checkLine(line, dialogue, options);
    line = dialogue.getNextLine(line.getLineNumber());
}

And it retrieves the next line like this:

Code:
public function getNextLine(previousLineNumber:uint):Line {
    for (var i:uint = 0, isize:uint = lines.length; i < isize; i++) {
        var line:Line = lines[i];
        if (line.getLineNumber() > previousLineNumber) {
            return line;
        }
    }
    return null;
}

FYI: That's the same as doing this:

- 1. Start at the top of the dialogue.
- 2. Check the first line you haven't checked yet.
- 3. If there were no lines to check left, you're done.
- 4. If there was a line to check, go to step 1.

... So instead of 1, 2, 3, 4, 5, ... it went 1, skip 1, 2, skip 1, skip 2, 3, skip 1, skip 2, skip 3, 4...

That's ((lines+1) * lines) / 2 iterations. For 100 lines, that's 5050 iterations. For 4000 lines... 8002000 iterations. Or basically 2000 times more iterations than necessary.

This adds a stupid overhead.

I took it out.

What happened?

Runtime (OLD):
SyntaxChecker: 2530ms.
VariableChecker: 3700ms.
DialogueChecker: 1543ms.
LineUsageChecker: 3833ms.
GrammarChecker: 1990ms.

Runtime (New):
SyntaxChecker: 670ms.
VariableChecker: 343ms.
DialogueChecker: 30ms.
LineUsageChecker: 430ms.
GrammarChecker: 250ms.

Yep.

It's insane.

If you're interested in the time distribution in your dialogue, simply add #DialogueChecker-DebugTime# to your dialogue.
 

TheLowKing

Potential Patron
Joined
Nov 22, 2014
Re: DialogueChecker (Latest version: V2.19, 12 February 2015)

IANAFlashProgrammer, but if reading lines from an array/list/whatever is so expensive, it might further help to change the architecture from:
Code:
for line in lines {
  do check 1(line)
}
for line in lines {
  do check 2(line)
}
...
for line in lines {
  do check n(line)
}

To:
Code:
for line in lines {
   do check 1(line)
   do check 2(line)
   ...
   do check n(line)
}
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.19, 12 February 2015)

The problem was not iterating over the lines, it was starting over at the start of the list everytime.
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.20, 14 February 2015)

February 14, 2015
Introducing v2.20!
- Fixed a bug where the VariableChecker didn't complain about undeclared variables in set lineattributes, and DA variable triggers.
Download link in OP.
The archive download, containing the source of the DialogueChecker, as well as the current and all the previous versions has also been updated.

Not a very big update today, just a small fix. Well, when I say small, it actually affects ALL variables checks. So yeaaaah.
It's a pretty important fix, but it's a small change.

Thanks to SDT-FAN in this thread for providing a dialogue for easy validation and debugging.
 

TheLowKing

Potential Patron
Joined
Nov 22, 2014
Re: DialogueChecker (Latest version: V2.19, 12 February 2015)

Pim_gd said:
The problem was not iterating over the lines, it was starting over at the start of the list everytime.
But that would have negligible performance impact in faster languages like, C or C++. Since it clearly has a much larger effect in AS, I figure looping over lines itself must also be slow. In which case looping once might be noticably faster than looping 6(?) times. Obviously the impact won't be anywhere as large as changing from O(n^2) to O(n), but still.
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.19, 12 February 2015)

TheLowKing said:
Pim_gd said:
The problem was not iterating over the lines, it was starting over at the start of the list everytime.
But that would have negligible performance impact in faster languages like, C or C++. Since it clearly has a much larger effect in AS, I figure looping over lines itself must also be slow. In which case looping once might be noticably faster than looping 6(?) times. Obviously the impact won't be anywhere as large as changing from O(n^2) to O(n), but still.

Iterating over the lines can't be what's costing me performance. Performance test:

Code:
			var lines:Array = dialogue.getLines();
			for each (var line:Line in lines) {
				checkLine(line, dialogue, options);
			}
This takes me 270-278 ms for fleacks 4000 line dialogue (GrammerChecker).

Remove the checkline function and do something simple with each line instead...

Code:
			var lines:Array = dialogue.getLines();
			var num:int = 0;
			for each (var line:Line in lines) {
				//checkLine(line, dialogue, options);
				num += line.getLineNumber();
			}
			trace(num);
0-2 ms. I think we're having trouble with the granularity of my stopwatch here - it can't be more precise than 2 milliseconds. So likely this is taking around ~1 ms.

Probably be even faster if I used a uint like I should. And a vector.

Point is, it's the actual checking that's eating most of the time.

However, replace the line iteration with a loop that goes from 0 to 8000000...

Code:
			var lines:Array = dialogue.getLines();
			var num:uint = 0;
			for (var i:int = 0; i < 8000000; i++) {
				num += i;
			}
			trace(num);
And the GrammarChecker takes 560-580 ms to run.

Add a function call per iteration and 1.5 seconds doesn't seem that strange anymore.

It's entirely the algorithm that was hurting me here, and not the language.

Besides, if I didn't have this problem at 4000 lines it would have popped up at 10000 lines or at 20000 lines. Or perhaps in the future, when the DialogueChecker would check multiple dialogues at the same time (for multi-dialogue projects).



As for the architectural change, that's something that's not worth it to do. Right now I have modularity - I can take out a Checker easily and slide new ones in. You could even write your OWN checker using my framework - you get a Dialogue object that you can ask for lines, and from there you can iterate over the lines and perform your checks. That is far more important than the performance benefits of changing O(5n) to O(n). ... And even that's not the case; If N is a checked line, 4000 lines are still checked. It's only the iteration count that has gone up. So what you're saying is not changing O(5n) to O(n) (80% performance increase), it's O(5m + n) to O(m + n). And m turns out to be... well, given that 8 million iterations take 560-580 milliseconds, 4000 iterations would be 0.29 milliseconds. Meanwhile, checking all the lines normally takes 2200 milliseconds ... with overheads everywhere, granted, but even if we reserve half of this as "overhead"... then if checking 4000 lines takes 1200 milliseconds and 4000 iterations takes 0.29 milliseconds, it looks like m = n*0,0002417.

Math aside, it boils down to the iterations having less than a 1% impact. Adding more lines to the dialogue would impact runtime far more.

Last point;

C++ is a language I'm not that good at. It would likely take me longer to develop the DialogueChecker in C++. For this reason alone, it is not smart to develop the DialogueChecker in C++. You see, I only develop this if I feel like it. And if I don't feel like working on it, no work gets done. So if I wrote the DialogueChecker in C++, I wonder whether we'd even have the wide range of functionality and checks like we have today. Perhaps we'd still be stuck with something from v1 - I'd never have the courage to rewrite the whole thing.



This looks like a rant and it probably is a rant, but you shouldn't feel attacked. Your suggestion has merit, but I've ran the tests, and it's not worth losing functionality for.

Feel free to download the code from the archive in the second post and look for optimizations - 2.2 seconds is fast, but I'd like it to be yet another 50% faster. This because if I do ever start supporting multiple dialogue checking, I want to be able to run WeeWillie's slave bazaar in under 10 seconds. And right now it takes...

69ms
90ms
1433ms
1359ms
212ms
90ms

Huh. It takes 3253ms to run the whole lot.

Well. I assume he'll be expanding the dialogues a lot - I want to be able to check any dialogue under 10 seconds, because if it is too much effort to run the tool repeatedly, people will stop using it.


EDIT:

So after this long winded post I couldn't really stop thinking about the performance and I went to do some more about it. I got myself a profiler, and then spent a lot of time getting the profiler to give me some usable data.

So turns out the SyntaxChecker spends a lot of time toying with TriggerTypes. And identifying a TriggerType is a lot of work. You see, the previous implementation of a TriggerType.identifyType went through multiple arrays and called indexOf(trigger) on them. Basically it makes a list and then goes "nope, nope, nope, nope, nope..." until it finds one it likes.

I changed it to use a lookup table. I'm not quite sure what the internal implementation is of that, but it's a lot faster because it doesn't check them one-by-one. I think it's kinda like looking for a word in a dictionary; you start with the first letter, then the next, and search alphabetically like that until you've found what you need. This is, of course, a lot faster than checking ALL the words in a dictionary for if it's the word you're looking for.

This all in v2.21, which I will release when I'm done dicking about.
 

sby

Content Creator
Coder
Joined
Sep 11, 2012
Re: DialogueChecker (Latest version: V2.20, 14 February 2015)

i can see how a bunch of increasingly redundant checks per additional line could slow things down ::)

i think that if the total usage of it throughout a dialog's creation is less than 1% of the time it takes to just write it, you are probably fine.
~write a dialog in 50 minutes. if i check it 5 times during creation and it takes 6 seconds each time that seems very reasonable.
 

TheLowKing

Potential Patron
Joined
Nov 22, 2014
Re: DialogueChecker (Latest version: V2.19, 12 February 2015)

Pim_gd said:
0-2 ms. I think we're having trouble with the granularity of my stopwatch here - it can't be more precise than 2 milliseconds. So likely this is taking around ~1 ms.
Many operating systems (ie, Windows) don't provide timers with better than 5ms accuracy, so a proper benchmark would need to run at least half a second, preferably longer. That nitpick aside, if these are the kind of times we're dealing with, then array looping is indeed utterly insignificant compared to checker time, and my optimization would yield neglible performance benefits.

Pim_gd said:
As for the architectural change, that's something that's not worth it to do. Right now I have modularity - I can take out a Checker easily and slide new ones in. You could even write your OWN checker using my framework - you get a Dialogue object that you can ask for lines, and from there you can iterate over the lines and perform your checks.
Totally fair. Maybe you could work around that, but the amount of architecture code you'd have to write for that is just not worth the effort (see above).

Pim_gd said:
C++ is a language I'm not that good at. It would likely take me longer to develop the DialogueChecker in C++.
Oh, I wasn't suggesting that you should. I just needed a reference point, and C++ happens to be the language I myself am most familiar with.

Pim_gd said:
This looks like a rant and it probably is a rant, but you shouldn't feel attacked.
No worries. :) Considering the data you provided, I would make the exact same choice.

Pim_gd said:
I changed it to use a lookup table. I'm not quite sure what the internal implementation is of that, but it's a lot faster because it doesn't check them one-by-one. I think it's kinda like looking for a word in a dictionary; you start with the first letter, then the next, and search alphabetically like that until you've found what you need. This is, of course, a lot faster than checking ALL the words in a dictionary for if it's the word you're looking for.
I can find very little information on the Dictionary class, Adobe's documentation is woefully inadequate. Generally speaking, though, associative containers (that is, with keys and values) tend to work in one of two ways: You have your hash tables and search trees. The former uses some math magic (a hashing function) to turn any kind of object into a number, then looking up that number in a simple array. You pick your hashing function to be constant time, making lookups constant time too. If your hash function generates the same value for two different objects, you have to do some more magic to make sure they don't overwrite each other.

Search trees work by putting your data in a sorted tree structure, which is pretty much what you described. In this kind of tree, each parent node's key is in between that of its left child and its right child. When you're looking for a specific key, you start by comparing it to the root node's key. If your key is smaller than the root node's key, you go down to the root node's left child (where all the keys that are smaller than the root node's are located), if your key is bigger, you go to the right. You keep going until you find a key that matches (which means you found it) or you reach the bottom of the tree (which means the key is not in the tree). Unlike hash tables, this kind of lookup is not constant time, because generally you will have to compare your value to a number of nodes proportional to the height of the tree. However, while the hash table is constant time, hash functions tend to be resource intensive operations, so it's a fairly high constant. When you don't have a lot of data to store, a search tree may be faster.

Both approaches provide much faster lookups than arrays do, but again, only for large collections. If you're dealing with a list of 10 values, the overhead of hash tables or search trees ruins any performance benefits they might have in theory. In addition, there are other operations that may need to be taken into account, such as adding or deleting values. Which data structure is 'best' depends entirely on how you use your data.
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.20, 14 February 2015)

Well, we're looking for simple triggers, provided by either SDT or a mod. That's a good 50+ triggers, I think.
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.21, 17 February 2015)

February 17, 2015
Introducing v2.21!
- Improved the performance for SyntaxChecker by about 20% via a lookup table for Trigger.identifyType().
- Some refactoring for Dialogue parsing (potential source of bugs).
- Fixed false positive warnings for lack of mood triggers (bug introduced in v2.17)
Download link in OP.
The archive download, containing the source of the DialogueChecker, as well as the current and all the previous versions has also been updated.

More small patches. I actually did some more than what I described in the patch notes here, but I forgot what it was. Oh well. If you really want to know I guess you could run a diff tool of some sort.

This release was mostly triggered because of VincentL's Loving Wife dialogue. It showed 255 false positives (which turned out to be a single character typo, != instead of ==).

I'm thinking about adding more checks for line attributes; things like "don't refer to a variable twice" and "don't have empty line attributes" (a la intro:"Hello" {}). More checks is always good, I guess.
 

EnigmaTSvirus

Potential Patron
Joined
Jan 29, 2012
Re: DialogueChecker (Latest version: V2.21, 17 February 2015)

Howdy Pim_gd, I was wondering if you could assist me with a dialogue problem. Anyways, I'm trying to make a dialogue and every time I run it through Loader to test out the "intro" it will stop around midway through the intro(it stops in different places every time), and then the intro will start from the beginning. I tried running it through the "Checker," but it keeps giving me this error code:

Error: Error #1502
at Dialogue/parseLine()
at Dialogue/parseDialogueFromString()
at Dialogue()
at Main/parseDialogue()
1502

If you're able to, please let me know what I'm doing wrong.
Please and thanks.
 

Pim_gd

Content Creator
Joined
Jan 25, 2013
Re: DialogueChecker (Latest version: V2.21, 17 February 2015)

Vergil said:
Howdy Pim_gd, I was wondering if you could assist me with a dialogue problem. Anyways, I'm trying to make a dialogue and every time I run it through Loader to test out the "intro" it will stop around midway through the intro(it stops in different places every time), and then the intro will start from the beginning. I tried running it through the "Checker," but it keeps giving me this error code:

Error: Error #1502
at Dialogue/parseLine()
at Dialogue/parseDialogueFromString()
at Dialogue()
at Main/parseDialogue()
1502

If you're able to, please let me know what I'm doing wrong.
Please and thanks.

No dialogue provided, I can't help you.

EDIT: I seem to have a bug with percent encoding... Will fix later tonight.
 

Users who are viewing this thread

Top


Are you 18 or older?

This website requires you to be 18 years of age or older. Please verify your age to view the content, or click Exit to leave.