Menu

Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Messages - Snarky

#1
(I just deleted the post CW linked to and banned the poster, so the link doesn't work any longer.)

Quote from: Crimson Wizard on Mon 21/04/2025 11:07:50I also understand that it's not correct to blame without evidence, so won't.

You did, though.  :-D

As you know, Crimson, but others might not, the moderators keep an eye on certain posters we suspect may be spam bots, and as soon as we notice sufficient evidence (usually in the form of a spam link) they get banned. If any AGSers have suspicions about any member, new or old, please report one of their posts or send us a PM rather than speculating in public.

The moderators take slightly different attitudes to this, but personally I apply a policy of "innocent until proven guilty," and consider the act of publicly accusing someone of possibly being a spam bot as potentially a form of harassment. (Also, if you are right, they are going to be banned soon and all their posts deleted, so posting about it just adds clutter to the forums.)
#2
I went ahead with a rewrite of the module to fit my requirements and preferences. The implementation is still based on the same JsonParser (discarding MiniJsonParser), and it should produce essentially the same parse trees, but the user-facing API is rather different.

I still need to do some code cleanup, I haven't thoroughly tested it, and there are some minor features missing (there should be a way to dispose of the parsed data if you no longer need it, for example), but it seems to be working, so this could be considered a pre-release:

Json.ash
Json.asc


The API looks like:

Spoiler
Code: ags
/// A Json node/token, with parent/child/sibling links forming a Json tree
managed struct Json
{
  /// ID of this Json token (for internal/debugging use)
  import readonly attribute int Id;
  /// ID of this Json tree (for internal/debugging use)
  import readonly attribute int TreeId;
  
  /// Type of this Json node
  import readonly attribute JsonType Type;
  /// String representation of the Json Type
  import readonly attribute String TypeAsString;
  /// Key for this Json node (null if none, implies this node is the root)
  import readonly attribute String Key;
  /// Value of this Json node,  as a string (i.e. all its contents; if node is not a leaf, its full subtree)
  import readonly attribute String Value;
  /// Value as int
  import readonly attribute bool AsInt;
  /// Value as float
  import readonly attribute bool AsFloat;
  /// Value as bool
  import readonly attribute bool AsBool;
  
  /// Path from the root to this Json node
  import readonly attribute String Path;
  
  /// The root of the Json tree this node belongs to (if node is root, returns itself)
  import readonly attribute Json* Root;
  /// The parent of this Json node (null if none, i.e. this node is root)
  import readonly attribute Json* Parent;
  /// The direct children of this Json node
  import readonly attribute Json* Child[];
  /// The number of direct children of this Json node (size of .Child[])
  import readonly attribute int ChildCount;
  /// The next sibling of this Json node (so if this node is this.Parent.Child[i], returns this.Parent.Child[i+1]; null if none)
  import readonly attribute Json* NextSibling;
  
  /// Whether this Json node is a leaf node
  import readonly attribute bool IsLeaf;
  /// Whether this Json node is the root of the Json tree
  import readonly attribute bool IsRoot;
  
  /// Select a node in this node's subtree (using the subpath to navigate)
  import Json* Select(String path);
  
  /// Convert this Json node to a flat dictionary (making each leaf in its subtree a key/value pair)
  import Dictionary* ToDictionary(Dictionary* dictionary=0);
  
  /// Parse a String into a Json tree, returning the root node (null if error, see Json.ParserState)
  import static Json* Parse(String jsonString);
  /// Parse a text file into a Json tree, returning the root node (null if error, see Json.ParserState)
  import static Json* ParseFile(String fileName);
  /// Status of parser after parsing (if successful >0, count of tokens parsed; if error, JsonError value)
  import static readonly attribute int ParserResult;
  /// Number of Json trees parsed and stored
  import static readonly attribute int TreeCount;
  /// Retrieve a Json node directly (for internal/debugging use)
  import static Json* GetNode(int id);
  /// Retrieve a Json tree
  import static Json* GetTree(int treeId);
};
[close]

And you can use it like this, for example:

Code: ags
  Json* jsonData = Json.Parse("{ "ags-games": [ {"title": "Rosewater", "author": "Grundislav", "released": true} , {"title": "The Flayed Man", "author": "Snoring Dog Games", "released": true}, {"title": "Old Skies", "author": "Wadjet Eye Games", "released": false}] }");
  // Or to read from a file: Json* jsonData = Json.ParseFile("$INSTALLDIR$/ags-games.json");
  if(jsonData)
  {
    Json* agsGames = jsonData.Select("ags-games");
    for(int i=0; i<agsGames.ChildCount; i++) // Loop through "ags-games" array
    {
      Json* agsGame = agsGames.Child[i];
      AgsGameData* gd = AgsGameData.Create();  // Assume we have this managed struct defined

      // Set fields
      Json* title = agsGame.Select("title");
      if(title) gd.Title = title.Value;
      Json* author = agsGame.Select("author");
      if(author) gd.Author = author.Value;
      Json* released = agsGame.Select("released");
      if(released) gd.IsReleased = released.AsBool;
    }
  }

My plan is to use this to let developers and players import (and eventually export) style sheets for the TextField and SpeechBubble modules. I also think the approach of having styling specified in external data files rather than hardcoded in script is generally useful for other modules and templates, particularly in light of the accessibility considerations we discussed some time ago. (In many cases it will probably be more convenient to use INI files and Dictionaries, but JSON is better for more complex, structured data.)
#3
Damn, I must have made a mistake when I reduced it to the simplest case. The error had to do with an array of this struct, which isn't permitted, but it never got to that exception. Thanks!
#4
I'm getting a syntax error I can't figure out (AGS v3.6.1). As a minimal example:

Code: ags
struct Storage
{
  Object* oArray[];
}

Object*[] GetArray()
{
  Storage s;
  Object* oArray[] = s.oArray; // <-- Error "Type mismatch: cannot convert 'Object*' to 'Object*[]'"
  return oArray;
}

It works if I do:

Code: ags
Object*[] GetArray()
{
  Object* dummyArray[];
  Object* oArray[] = dummyArray;
  return oArray;
}

... So it must have something to do with accessing it from the struct. Is there a correct syntax, or have I hit an engine limitation?
#6
Quote from: Barn Owl on Tue 15/04/2025 23:49:53Edit: I may have figured out the way to do this, exporting and importing the GUIs from the BASS, after already having replaced the global script. I think that might do it.

Edit 2: No that didn't work. Why didn't I back up my game before trying that? Just kidding, everything is backed up.

Well, that is what I would recommend. You will then need to link all the event handlers for all the controls to the right functions.

How did it not work?
#7
OK, since people keep asking about it, I have updated the module to be compatible with AGS 3.6.2. The new version, SpeechBubble v0.9.0, only supports AGS 3.6.0 and later.

This is really just a compatibility update: there are essentially no other changes to the module. It therefore does not include any new functionality that people have requested over the years; that will have to wait until I finish my WIP rewrite of the module.
#8
Quote from: Crimson Wizard on Mon 14/04/2025 17:16:29In adventure games the logic is traditionally "inverted". In life we know the purpose first and look for solutions after. In adventure games it's often other way around, and I cannot say if that's a good thing.

I would disagree that "inverted logic" is traditional in adventure games. This sort of key-before-lock "backwards puzzles" have been widely considered bad game design at least since Ron Gilbert's classic 1989 article Why Adventure Games Suck, and most skilled designers make an effort to avoid them. (Though they can be hard to completely eliminate in very non-linear games.)
#9
Quote from: AGA on Thu 10/04/2025 23:37:07We've now arranged another summer holiday, on the assumption this wasn't going to happen.

Yeah, I figured. Maybe next year!
#10
... And Mittens planning, optimistically.
#11
Is anyone still interested in this, or have we given up? It does look like I will only be able to take vacation in July, so between me and AGA/Tampie that limits it to the week of 21. July.
#12
Quote from: Baguettator on Tue 08/04/2025 20:18:51Is it "I don't need" or "I must not" declare get_ and set_functions in the struct ?

You can, but you shouldn't. The effect of declaring them as imports is that they get exposed outside of the module, as part of the API of the struct. In other words, it means that other code can call them directly as functions, instead of just using the attribute (which calls them indirectly).

Typically you don't want that, and since you are annotating them with $AUTOCOMPLETESTATICONLY$ (which is wrong, since the functions are not static; if you simply want to prevent autocomplete from listing them, it should be $AUTOCOMPLETEIGNORE$), I assume you don't. So just take them out.
#13
You could also use the UI Scale module to automatically upscale all the UI graphics.
#14
I can see it load, but it becomes blank at some point during loading. Still get a scroll bar, and I can see content in source view. Firefox 137.0 (MacOS). I have a bunch of addons like NoScript, uBlock Origin, Privacy Badger and Disconnect that can interfere with scripts, but none of them appear to be blocking anything significant on the page.

The logs show this:

#15
I was pretty pissed at the April Fool Connections.

&X)
NOP
$(+
£R¥

Spoiler
The first group of currency symbols (€, $, £, ¥) is easy. A group of symbols that can represent "and" (&, +, N, X) is also fairly doable.

My objection is to the third group: symbols that mean "right" (⊾, →, ✔, R). That is not the symbol for a right angle! (Even though Unicode calls it "Right angle with arc.") I had all the other three, but I simply could not believe that Connections would use the wrong symbol instead of ⦜.

(The last group with the parentheses and O, P are "emoticon mouths." I don't think I would have figured out that link under any circumstances. Also, I dislike groups that also include some of the other entries, so that you need to eliminate them by making the other groups first. Feels unfair. In this case, X was a pretty common emoticon mouth, and $ was a less common one.)
[close]
#16
Please don't
#17
Advanced Technical Forum / Re: RAM limitations
Mon 31/03/2025 06:19:06
Are you scaling your characters? If so, using different-sized sprites is going to introduce positioning errors because of rounding.

Do you really need to have 17 different pieces of armor all loaded at once? Can't you load it as needed?

(But my advice would pretty much be to not try to make this game in AGS.)
#18
Quote from: Crimson Wizard on Sat 29/03/2025 13:19:35Not ready to give a full reply right now, just couple of quick notes.
I am not certain about getting rid of AudioChannel is a good thing or not. It may be a good thing to not have a channel as a return value from Play(), but at the same time I found audio channels to be an interesting logical concept in how multiple simultaneous playbacks are organized.

I think if there is something like an AudioPlayback instance for everything that's currently playing, that will basically take the place of AudioChannels. You might still want a way to iterate through all the playing sounds (for example if you need to adjust the overall volume), but I'm not sure a fixed list of AudioChannels (that exist regardless of whether anything is playing on the channel or not) is the right way to do that if the limit on simultaneous playback is removed.

AudioTypes seem more useful to me as a way to easily set up game logic for sound. MaxChannels would still help control whether a track replaces what is currently playing or not, for example. It's also important to consider how speech will work. It would be good if the system could support voiced background speech; some way to control the speech "channel(s)"/get speech playback instances would be needed.

Quote from: Crimson Wizard on Sat 29/03/2025 13:19:35In regards to a "null pointer" problem, there's something that hit me recently, and I wonder why did not I think about this earlier. What if Play() returned a dummy audio playback? There are 2 reasons for Play to fail: failure to decode and failure to play, the former happens when the file cannot be opened at all, and the latter when the sound device is not working, or game set to use "no driver".
We are mostly interested to prevent script errors in the second case, because the first case will be noticed immediately during the game development. In audio software that works with filter chains there's a concept of "null" output, where the processed sound is simply discarded. So what we could do is to process the sound internally (which will even update its Position property), but then discard it through such "null" output. I did not check yet, but it's possible that our sound library supports that already.

I think it's clear that if it must never return null but playback may fail for whatever reason, it must then return a "dummy." If I understand you correctly, though, what you're saying is that the dummy may still "pretend" to play, specifically in terms of reporting playback position. Yes, I think that could be useful because other game logic may depend on it (use it for timing, for example, like if a cut-scene has been designed to sync to a music track), but at the same time I think it's important that it should be possible to tell somehow that playback has failed.

I think there may be cases where the file fails to open/play but this isn't known at design time (e.g. if using AudioClip.GetByName() or Game.PlayVoiceClip(), or using an external AUDIO.VOX file; perhaps a way to load audio files from the file system or even streaming will also be reintroduced in the future), but there is already an AudioClip.IsAvailable property that can deal with that, if the AudioClip isn't simply null.
#19
As it seems like AGS 4.0 is moving closer to release, I would like to revive this thread, as I feel an improved audio API should be part of that revision.

I think the basic idea of getting rid of AudioChannel in favor of something tied to the playback instance, and guaranteed to be non-null, is probably the right approach. The API might not need to change all that much, but there are a few different things to consider:

- AudioClip
- Voice clips
- Frame-linked audio (esp. on looping animations)
- AudioType
- Playback with repeat

For example, with a looping or repeating sound, is it always the same playback instance, or a new one each time? And how would you configure frame-linked audio (volume, etc)? If there was a persistent playback instance, that might offer a good API.

I think @Crimson Wizard has also sometimes mentioned more advanced effects, like changing playback speed or adding echo. I would suggest that this shouldn't be a priority to implement at this time, but it might be worth keeping in mind how it might be added in future.
SMF spam blocked by CleanTalk