Show Posts

You can view here all posts made by this member. Note that you can only see posts made in areas to which you currently have access.

Topics - Snarky

Pages: [1] 2 3 ... 9
Beginners' Technical Questions / Max Wait...() length
« on: Yesterday at 22:48 »
I want to block indefinitely until a mouse or key is pressed. AGS only offers timed blocks in WaitKey() and WaitMouseKey(), but if the timing is long enough that's probably fine. So I want to put in as big a number as I can when calling these functions.

The int maxvalue is 2,147,483,647 (according to the manual). But trying to Wait() or WaitKey() for that length of time (or 1 less, in case it has to be able to go above it) doesn't work: it just gets ignored. What's the biggest number you can provide as an argument to the Wait...() functions?

(Though since there's no WaitMouse(), I'll probably have to build my own solution anyway, with Wait(1); in a loop, and flag that gets set on the keyboard/mouse events.)

Is there some way to find out from within AGS how long a certain speech clip is, without playing it?

Here's why I'm asking:

When you call Character.Say() with a voice clip (e.g. player.Say("&14 This line has voice-over");), the speech lasts as long as the clip, logically enough. (Depending on your Speech.SkipStyle setting the text may stay on screen longer, but the character won't do the Talk animation.)

However, if you set your Speech.VoiceMode to eSpeechTextOnly, the voice clip is ignored, and it seems the game falls back to using the standard AGS formula for how long to display the text, based on the string length and  Game.TextReadingSpeed.

I want to be able to maintain the same timing whether or not speech is turned on. If I could get the length of the speech clip I could do that (writing my own little function to manually do the Talk animation and time it correctly).

I have a question for the engine experts. In 32-bit, AGS allows you to set the transparency of various things and drawing operations as a percentage (0-100). However, 32-bit color actually uses an 8-bit alpha channel internally (0-255). Do you know how this is mapped?

For example, if I set the transparency of a GUI to 3%, am I really setting it to the closest alpha value, 8? (8/255 = 0.03137..., or about 3.14%). Is this calculated as (in AGS syntax) int alpha = FloatToInt( IntToFloat(percentage)*255.0/100.0, eRoundNearest), or some other way?

I've noticed that a few functions in the editor don't work for enums, and one other thing for user-defined structs (I'm testing in version

  • Auto-complete doesn't work for enum types. For example, if you want to use the built-in enum CharacterDirection, the auto-complete list pops up once you type "Cha", but CharacterDirection does not appear in the list. This is also true of user-defined enum types.
  • The "Go to definition" function (available by right-clicking on an instance of the enum name in the editor) doesn't work for user-defined enum types (and obviously not for built-in enum types). Nothing happens. However, "Find all usages" does work, with the definition included in the list of results.
  • A smaller matter, but if you declare an enum variable and try to set it on the same line, the auto-complete dropdown with the valid values doesn't appear right away when you type "=". Once you start typing a name, the regular auto-complete (with all other matching tokens as well) shows up after three characters as usual. Again, this applies both to built-in and user-defined enums. So, for example:
Code: Adventure Game Studio
  1.   CharacterDirection exitDirection = eDirectionLeft;

You get no dropdown after "=". After "eDi" you get the full list with "eDirectionDown" (the first matching token) highlighted. But:

Code: Adventure Game Studio
  1.   CharacterDirection exitDirection;
  2.   exitDirection = eDirectionLeft;

On the second line you get a dropdown after "=" with just the CharacterDirection values.

Ideally you would get a list with all the CharacterDirection values AND all the CharacterDirection variables only. Alternatively the list of values could show up after the =, but if you type something else it could disappear and after three characters you'd get the the full list. In any case, it should probably behave the same way whether you define and set the variable on one line or multiple.

  • For user-defined structs, the right-click situation is reversed. "Go to definition" works, but "Show all instances" doesn't. Or rather, it only finds instances in the current script. This applies both to the data type and to variables of that type. So if you export a user-defined struct variable from your script and use it in some other script, "Show all instances" will incorrectly claim that it's not being used anywhere else. This is perhaps the most serious, since it's actively misleading. I've had a lot of failures to compile because of this.

Another thing that seems to break the AGS scripting language: chaining together attributes that represent dynamically generated managed structs.

I'll explain. I have a managed struct that just holds a bunch of related data (I use a managed struct so I can put it inside various other structs and arrays):

Code: Adventure Game Studio
  1. managed struct Data
  2. {
  3.   int a;
  4.   int b;
  5. };

However, I also need to include this in some other managed structs. Since you can't store a managed struct in a managed struct, I've added some functions to convert Data to array form:

Code: Adventure Game Studio
  1. // I've used a static extender function, available with AGS 3.4, but it's the same with a regular static function
  3. Data* fromArray(static Data, int array[])
  4. {
  5.   Data* d = new Data;
  6.   d.a = array[0];
  7.   d.b = array[1];
  9.   return d;
  10. }
  12. int[] toArray(this Data*)
  13. {
  14.   int array[] = new int[2];
  15.   array[0] = this.a;
  16.   array[1] = this.b;
  17.   return array;
  18. }

Now I can store it in an array inside my other managed structs, and use an attribute with getters and setters calling the conversion methods to make the whole thing transparent:

Code: Adventure Game Studio
  1. // Header
  2. managed struct Primary
  3. {
  4.   // Underlying array
  5.   protected int _data[2];
  7.   // Attribute, accessors
  8.   import attribute Data* data;
  9.   import Data* get_data();        // $AUTOCOMPLETEIGNORE$
  10.   import void set_data(Data* d);  // $AUTOCOMPLETEIGNORE$};
  12. // Script
  14. Data* Primary::get_data()
  15. {
  16.   // Has to be a dynamic array, so make a copy
  17.   int array[] = new int[2];
  18.   for(int i=0; i<2; i++)
  19.     array[i]=this._data[i];
  21.   return Data.fromArray(array);
  22. }
  24. // Note this works as pass-by-value, not reference
  25. void Primary::set_data(Data* d)
  26. {
  27.   if(d != null)
  28.   {
  29.     this._data[0] = d.a;
  30.     this._data[1] = d.b;
  31.   }
  32. }

This works fine so far:

Code: Adventure Game Studio
  1.   Data* raw = new Data;
  2.   raw.a = 2; raw.b = 3;
  4.   Primary* p = new Primary;
  5. = raw;
  7.   // These should display the same
  8.   Display("Raw data a:%d b:%d", raw.a, raw.b);            // Displays: "Raw data a:2 b:3"
  9.   Display("Primary data a:%d b:%d",,;  // Displays: "Raw data a:2 b:3"

However, I also have another managed struct that uses Data. The actual link is a bit complex, but basically it's supposed to use the Data values associated with a particular Primary struct that is looked up in an array based on other fields; the Data* attribute is therefore read-only. So I tried to do something like this:

Code: Adventure Game Studio
  1. // Header
  2. import Primary* primaries[10];
  4. managed struct Secondary
  5. {
  6.   int primaryIndex;    // index into primaries[]
  8.   // Attribute, accessor
  9.   import readonly attribute Data* data;
  10.   import Data* get_data();        // $AUTOCOMPLETEIGNORE$
  11. };
  13. // Script
  14. Data* Secondary::get_data()
  15. {
  16.   if(this.primaryIndex != -1)
  17.   {
  18.     Data* d = primaries[this.primaryIndex].data;
  19.     return d;
  20.   }
  21.   return null;
  22. }

So, putting it all together, I try this:

Code: Adventure Game Studio
  1.   Data* raw = new Data;
  2.   raw.a = 2; raw.b = 3;
  4.   primaries[0] = new Primary;
  5.   primaries[0].data = raw;
  7.   Secondary* s = new Secondary;
  8.   s.primaryIndex = 0;
  10.   Display("Raw data a:%d b:%d", raw.a, raw.b);
  11.   Display("Primary data a:%d b:%d", primaries[0].data.a, primaries[0].data.b);
  12.   Display("Secondary data a:%d b:%d",,; // CRASH!

The last line crashes with a null exception. After some testing, it seems that is always null. Doing exactly the same calculation outside of the attribute accessor works:

Code: Adventure Game Studio
  1.   Data* test1 =;                            // This will be null...
  2.   Data* test2 = primaries[s.primaryIndex].data;    // ... while this will be a valid object

So is it a problem with trying to "chain" attribute accessors together like this? If I rewrite Secondary::get_data() as a direct copy of Primary::get_data() (making the _data[] array not protected and accessing it directly), it works, but it's an ugly workaround.

I've also got an inkling that it could have something to do with the readonly tag on the attribute. Oddly, when I take it away and try to provide a dummy setter, I get a hard crash (i.e. not to debugger) at an earlier line instead:

Illegal exception
An exception 0xC0000005 occurred in ACWIN.EXE at EIP = 0x00401633 ; program pointer is +1101, ACI version, gtags (0,0)

AGS cannot continue, this exception was fatal. Please note down the numbers above, remember what you were doing at the time and post the details on the AGS Technical Forum.

in "AttributeTest.asc", line 49
from "room1.asc", line 54
from "room1.asc", line 63

Most versions of Windows allow you to press Ctrl+C now to copy this entire message to the clipboard for easy reporting.

An error file CrashInfo.dmp has been created. You may be asked to upload this file when reporting this problem on the AGS Forums. (code 0)

The line reference is to Data* d = primaries[this.primaryIndex].data; in Secondary::get_data().

I don't really have a good understanding of what's going on.

I'm experiencing... weirdness, and sometimes crashes, in methods of managed structs that use the this-pointer, if I delete/overwrite all external handles to the object partway through the method.

The setup is something like this:

Code: Adventure Game Studio
  1. // Simplified struct
  2. managed struct Job
  3. {
  4.   int value;
  5. };
  7. // A different script:
  9. // I need to keep track of a bunch of jobs
  10. Job* jobPool[100];
  12. // This function manipulates the job pool
  13. void Process(this Job*, int index)
  14. {
  15.   Display("This Job rating: %d", this.value); // I access the value
  18.   // Then delete a particular entry in the jobPool
  19.   jobPool[index] = null;
  20.   // ... and create a new Job
  21.   Job* job = new Job;
  22.   job.value = 20;
  24.   Display("This Job rating recheck: %d", this.value); // I access the value FOR THIS JOB again
  25. }
  27. // And I call that function:
  28. void someOtherFunction()
  29. {
  30.   // Fill the job pool
  31.   for(int i=0; i<100; i++)
  32.   {
  33.     jobPool[i] = new Job;
  34.     jobPool[i].value = i*2;
  35.   }
  37.   // ... and later on
  38.   jobPool[5].Process(5); // The SAME NUMBER
  39. }

So I'm calling Job.Process() on jobPool[5], telling it to remove the entry in jobPool[5] from the array (in my real program, Job.Process() handles the indexes and always removes the job you call it on from the array, but let's keep it simple here). Since this is done with pointers, the object itself should not be affected (though we won't have any way to reference it after the function completes).

However, what actually happens is that it Displays:

This Job rating: 10 (the original value, correctly)
This Job rating recheck: 20 (the value of the new Job we made, job)

In other words, the this pointer is now pointing to the wrong Job!

If I remove the jobPool[index] = null; on line 19, this doesn't happen. I'm guessing that since the only external reference to this object is the entry in jobPool[5], setting that to null reduces its reference count to 0 and marks the object as deleted, even though we're still inside a function that has a local pointer to it, and that the new object is therefore slotted into the newly "free" memory, so that the this-pointer ends up pointing to it by "accident" (supporting this theory, if I add the line Job* j = jobPool[5]; on line 36 right before I call jobPool[5].Process(5); it also doesn't happen).

Under other circumstances, this can cause AGS to crash with the error "Pointer cast failure: the object being pointed to is not in the managed object pool", but I'm not able to recreate that crash in a simple sample program.

Beginners' Technical Questions / AGS games run slow in VM
« on: 15 Oct 2017, 08:08 »
I'm on a MacBook, and running Windows in a Virtual Machine to work on AGS games. I've used both Parallels and VirtualBox for my VM.

Windows runs pretty smoothly inside the VM, and I can use most apps no problem. However, when I try to run an AGS game with the debugger and sound, it is very sluggish (appearing to run at somewhere between 10 and 20 loops per second). Running it outside the debugger, or without sound, it runs much better, close to the standard 40 loops/second (the mouse cursor still lags a bit).

This isn't a huge problem, since I can usually just set "No Digital Sound" in winsetup. But is there anything else I can do to speed things up?

I have some Character* variables in a module script that I need to expose, but that I would like to stay read-only outside of the script. To that end, instead of just exporting the variables globally, I'm trying to put them as static attributes inside of a struct, with getters that return the script-local values:

Code: Adventure Game Studio
  1. struct MyModule
  2. {
  3.   import static readonly attribute Character* ActiveCharacter;  // $AUTOCOMPLETESTATICONLY$
  4.   import static Character* get_ActiveCharacter();               // $AUTOCOMPLETEIGNORE$
  5. };

Implemented in the script as:

Code: Adventure Game Studio
  1. Character* _activeCharacter;
  2. static Character* MyModule::get_ActiveCharacter()
  3. {
  4.   return _activeCharacter;
  5. }

However, when I try to actually refer to this character, I get a compilation error:

Code: Adventure Game Studio
  1.   MyModule.ActiveCharacter.Say("Blah blah"); // Fails to compile

"must have an instance of the struct to access a non-static member"

It works (or at least, it doesn't throw a compilation error: I haven't been able to test it live) if I instead write:

Code: Adventure Game Studio
  1.   Character* c = MyModule.ActiveCharacter
  2.   c.Say("Blah blah");

However, that is massively less convenient, and makes the whole approach pretty much useless for me.

It seems to be related to this bug:

Is there any way around this or some way to fix it?

There's one thing about AGS coding that I can never seem to get right: capitalization.

I like to be consistent, but I'm not sure what convention to follow. AFAIK, all AGS engine functions are capitalized, whether plain functions (Random(), QuitGame(), IsKeyPressed(), etc.) or methods inside structs (Dialog.Start(), Maths.Sin(), Character.Say(), etc.). However, the built-in function handlers (repeatedly_execute(), on_mouse_click(), game_start(), etc.) are lowercase, and object event handlers (stuff like btnOk_OnClick()or cBob_Look()) will be initial-lowercase when following the AGS object naming convention. Also, the function examples in the manual are lowercase.

Similarly, the member properties of the built-in structs (Character.Speaking, GUI.Height, Hotspot.Name) are all capitalized, with a few exceptions (Character.x/y/z, mouse.x/y, Character.scrname). In contrast, plain variables (player, guis[], etc.) and the members of the game struct (game.score,  game.text_align, game.show_single_dialog_option, etc.) are lowercase. The struct member property examples in the manual are all in lowercase.

So, there's not a whole lot of consistency on the AGS end.

I usually write regular functions in lowercase (e.g. updateCategoryTree(), getScreenGraphic()) and methods inside structs in uppercase (e.g. Avatar.ReadProfile(), TotalLipSync.AutoMapPhonemes()), but this can lead to some weird inconsistencies. For variable properties I've never been able to decide between uppercase or lowercase.

Any tips? How do you do it?

Modules & Plugins / MODULE: TotalLipSync v0.5
« on: 18 Apr 2017, 19:22 »

(Character head/animation by Preston Blair. King Graham sprite ripped from King's Quest II VGA by AGDI. Background from AGS Awards Ceremony by Ali.)

TotalLipSync is a module for voice-based lip sync. It allows you to play back speech animations that have been synchronized with voice clips. TotalLipSync has all the same capabilities as the voice-based lip sync that is built into AGS, and offers the following additional advantages:
  • It works with LucasArts-style speech (as well as with Sierra-style and full-screen speech modes).
  • In addition to the Pamela (.pam) and Papagayo/Moho Switch (.dat) formats supported by AGS, it also reads Annosoft SAPI 5.1 (.anno) and Rhubarb (.tsv) lip sync files.
  • In particular, Rhubarb support means that lip syncing can be 100% automated (with decent results): no manual tracking of the speech clips is required.
  • It is more flexible: You can switch speech-styles mid-game, change the phoneme mapping, use files with different data formats, etc.
  • You don't have to do the phonemes-to-frames mapping manually: The module comes with a default auto-mapping.

How to use
  • Create the lip sync data files for the speech clips. You can use one of these tools (personally I would recommend Papagayo for manual tracking, and Rhubarb for automatic lip syncing, but the Lip Sync Manager plugin is good too):
    The filename of each sync file should be the same as the speech clip except for the extension, and you need to place them in your compiled game folder (by default, in a folder names "sync/" inside the game folder).
  • Create the speech animation for your character(s), with different animation frames for the different phonemes (see below), and set it as their speech view.
  • Download and import the TotalLipSync module into your AGS project.
  • Make sure your game settings are correct: the AGS built-in lip sync (in the project tree under "Lip sync") should be set to "disabled".
  • If you are going to use Sierra-style (or full-screen) speech for your lip sync animations, you must create a dummy view. Make sure to give it exactly one loop and one frame. If you name the view TLS_DUMMY it will automatically be used by the module. Otherwise you can set the view to use with TotalLipSync.SetSierraDummyView().
You are now ready to use the module. Add the code to initialize TotalLipSync on startup:

Code: Adventure Game Studio
  1. function game_start()
  2. {
  3.   TotalLipSync.Init(eLipSyncRhubarb);    // Or whatever lip sync format you're using
  4.   TotalLipSync.AutoMapPhonemes();
  5. }

Or if you want a custom phonemes-to-frames mapping:

Add spoiler tag for Example code:
Code: Adventure Game Studio
  1. function game_start()
  2. {
  3.   TotalLipSync.Init(eLipSyncPamelaIgnoreStress);
  5.   TotalLipSync.AddPhonemeMappings("None",0);
  6.   TotalLipSync.AddPhonemeMappings("B/M/P",1);
  7.   TotalLipSync.AddPhonemeMappings("S/Z/IH/IY/SH/T/TH/D/DH/JH/N/NG/ZH",2);
  8.   TotalLipSync.AddPhonemeMappings("EH/CH/ER/EY/G/K/R/Y/HH",3);
  9.   TotalLipSync.AddPhonemeMappings("AY/AA/AH/AE",4);
  10.   TotalLipSync.AddPhonemeMappings("AO/AW/UH",5);
  11.   TotalLipSync.AddPhonemeMappings("W/OW/OY/UW",6);
  12.   // Frame 7 unassigned to match default Moho mapping
  13.   TotalLipSync.AddPhonemeMappings("F/V",8);
  14.   TotalLipSync.AddPhonemeMappings("L",9);
  15. }
To speak a line with lip syncing, you simply call the extender functions Character.SaySync() or Character.SayAtSync(), using a speech clip prefix:

Code: Adventure Game Studio
  1.   cGraham.SaySync("&1 This line will be animated with lip sync");
  2.   cGraham.SayAtSync(320, 100, 240, "&2 ... and so will this");    // x_left, y_top, width, message

And that's all there is to it! (If you don't use a speech clip prefix, or if there is no matching sync file, the speech animation won't play at all.)

Phoneme-to-frame mappings
The principle of lip syncing is that different sounds (phonemes) correspond to different mouth shapes. If we display an animation frame with the right mouth shape at the same time as that sound appears in the audio being played, the animation will seem to match the speech. The first step, then, is to identify the phonemes and timing of them in the speech (that's what the tools listed above are for), and the second step is to choose an appropriate animation frame for each phoneme. We usually don't use different animation frames for all the different phonemes, so we combine phonemes into groups that are all mapped to a single frame. The different tools have different sets of phonemes (or phoneme groups), so we have to define different mappings from phonemes to frames.

So here is the default mapping for each data format used by TotalLipSync. It has been set up for a speech animation with ten different frames, each representing a different mouth position. (This is a fairly standard setup.) If you stick to these frames and these mappings, you can use the same speech view no matter what lip sync tool or data format you use:

Add spoiler tag for Animation frames:
phoneme ID
0Mouth closed
(or slack)
[slack or same as 1]XrestNone
2Various consonants,
(Rhubarb: Ee-type
3Eh-type sounds,
Ee-type sounds)
4Ah-type and
I-type sounds
5Aww-type sounds,
Ow-type sounds
(can also go in 6)
6U-type and
Oo-type sounds
(Non-Moho: W)
7Moho: W[same as 6][same as 6][same as 6]WQW
(Th-type sounds
can also go here,
rather than in 2)
Where to get it
TotalLipSync is hosted on Github (mainly just as a way for me to learn about how Github works):

You can download the current release from there:

Known bugs

Change log
-Added APIs to get the currently lip syncing character, the current phoneme and current frame.

-Fixed support for Sierra-style speech
-Minor bug fixes for edge-cases

0.2 (pre-release)
-Added support for Papagayo/Moho Switch (.dat), Annosoft SAPI 5.1 LipSync (.anno) and Rhubarb (.tsv)

0.1 (pre-release)
-Pamela support for LucasArts-style speech

Originally based on code by Calin Leafshade (though very little of it remains in the current version).
Thanks to Grundislav for providing a speech view used in development and testing of the module!

Advanced Technical Forum / Unicode support via plugin?
« on: 15 Apr 2017, 14:46 »
It occurred to me today: Given that implementing Unicode support in the engine is apparently a daunting task, would it be possible to provide it via a plugin for now?

What I'm thinking is that you'd export a translation file, which you would then edit and save in Unicode format. The plugin would read the file (parsing the Unicode) and render the text (using a font library), passing it back as an overlay to AGS – basically the same thing (I assume) the Sprite Font plugin does. Since the bitmap font formats used by AGS can't support an extensive set of Unicode glyphs, the plugin would only need to work with TTF fonts.

Am I overlooking some major roadblock, or is it worth a shot?


The final version of the client is now available!

UPDATE! New version that fixes random laughter bug:

Some instructions on how to use it:
Add spoiler tag for Hidden:

You can also use a couple of special commands by typing them into the chat line:
/cinema - hide the UI (and bring it back: you'll have to type it blind since the input box is invisible)
/help - IRC tips

Hi gang!

The 2016 Awards are almost upon us, and once again it falls upon me to update the client. One of my main tasks has been to update my old version to the higher resolution introduced last year. So far it's looking pretty good (the UI could use some polishing), but better safe than sorry. So if you have the time, please join me for a beta test of the client tomorrow, Thursday 9. March, at 20:00 Central European Time. (That's 19:00 GMT, and other times around the world – you're smart people, you know how time zones work!)

I've been working on the client all day, so here's a fresh-off-the-compiler build (link removed to avoid confusion):

AGS Awards 2016 Client (03-09 beta test build)

Things I want to test:

  • Whether the speed is OK (there's a lot going on!)
  • Running in Direct3D
  • Glitches, bugs
Fair warning: I've only had a couple of weeks to work on it, so it's not going to be as slick and elaborate as I would like. But hey, the games and the people are the important thing, right?

Pages: [1] 2 3 ... 9