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

Topics - eri0o

#61

Mana Match is a match3 game with pixel aesthetic, with old-school and newer elements.




This is not an adventure game

Music by Juhani Junkala

This game is a side project I plan to develop and push updates casually on the game's itch.io page. For now, the game is very barebones and I consider the art placeholdery. I want to have a good "Endless" game going, and once I get this, proceed to work in the other play modes, like Puzzle, VS, and Story Mode. If you want to run the game on your computer, I recommend using the itch client to get newer updates. A web player is also available there for testing, but the desktop game should run smoother.

I initially was developing this game with a friend, we worked on it from 2018 to 2019 in JavaScript, and while we advanced a lot at some point, the development just stopped. I still talk a lot with my friend, so recently, I asked about it, and with his consent, I ported the game to AGS because I think some pixel aesthetics I want may be easier to achieve with it.

  • graphics 5%
  • puzzles 0%
  • scripting 65%
  • music/sound 5%

If you download and play, for now, the only supported input method is swiping pieces using a mouse, you click hold the piece you want to swap with another adjacent piece, drag towards it and release. The web player actually works on my phone but for really smooth gameplay for now a mouse in the desktop version is recommended.
#62
Completed Game Announcements / I rented a boat
Thu 01/04/2021 15:08:31


A journey sometimes is a short game and sometimes is a lifetime.

This game was made for MAGS March “Journey�.

Note: This is a first person game made in AGS, it plays best in Fullscreen, use ESC in game to reach the menu, there you can look into Controls if you need help figuring the game keyboard+mouse movement scheme.








Attribution:

Sound Effects, Ambience and Music:
  • World Map - Scrabbit
  • rpg_sound_pack - by artisticdude
  • Grass Foot Step Sounds (Yo Frankie!) - by Blender Foundation
  • 40+ FREE Footstep SFX - by PremiumBeat.com
  • "Windy Ambience" - by MrAuralization
  • Radio Static - by digifishmusic
  • cave_amb.wav - Zixem
  • Cavernous Drone - glueIsobar
  • crowd int medium movie theater lobby Paramount2 Montreal, Canada.flac - kyles
  • brazilian-beach-party.wav - audiactiva
  • The Refrigerator - karinalarasart
  • Ambience, Seaside Waves, Close, A.wav - InspectorJ (Jonathan Shaw)
  • "Flying above the ocean" - Tad Miller

Graphics:
  • Walkie talkie - by Noah
  • sunset - Abdullah Ghatasheh
  • Balloon - by Michael Stöter

Script Modules:
  • Timer - Crimson Wizard
  • Tween - Edmundito

Edit: holy crap someone played my game in Japanese! I have no idea if the person liked or not but I was absolutely in love with their work!
#63
I have a big matrix I would like to read in-game, and to make it easier to manipulate, I would like to store it as a grayscale bitmap. I need 8 bits, values going from 0 to 255.

Is it possible to do this in a 32 bit game? If I save it as a RGB BMP (24-bit) and import, I only get 5 bit of resolution per channel using GetPixel.

I never imported 8-Bit images and I am not sure if this is possible in a 32-bit color game - there's an option about remapping palettes, I don't understand it.
#64
tap version 0.1.0


Get Latest Release tap.scm | GitHub Repo | Demo Windows | Demo Linux | Download project .zip

Test Anything Protocol - AGS Module

This Module implements Test Anything Protocol for Adventure Game Studio. It's useful for unit testing! You can read more about it here: testanything.org

An example test file output by this libary may look like:
Code: bash
1..4
ok 1 - Input file opened
not ok 2 - First line of the input valid
ok 3 - Read the rest of the file
not ok 4 - Summarized correctly # TODO Not written yet


The demo game will execute and exit, leaving a agstest.log file, with the test results, under this game savegame directory (ex: C:\Users\%USERNAME%\SAVEDG~1\tap_demo). This module is more useful to other people building modules and plugins, who wish to unit test in AGS Engine itself.

Motivation
A long time ago, Morgan talked to me about tests and unit testing, and showed me this magical thing that is TAP. Time passed and after experimenting with a bunch of different continuous integration systems I discovered they were able to magically pickup the specific output from tap and understand them when they are output to the CI shell... A bit of fiddling around later I got it working with AGS! This has been laying around in my repository for quite sometime and I noticed I never released it as a module. So here it is!

The plan
The plan tells how many tests will be run, or how many tests have run.  It’s a check that the test file hasn’t stopped prematurely. It must appear  once, whether at the beginning or end of the output. The plan is usually the  first line of TAP output (although in future there may be a version line  before it) and it specifies how many test points are to follow.  For example,
Code: bash
1..10

means you plan on running 10 tests. This is a safeguard in case your test  file dies silently in the middle of its run. The plan is optional but if  there is a plan before the test points it must be the first non-diagnostic  line output by the test file. In certain instances a test file may not know  how many test points it will ultimately be running. In this case the plan can  be the last non-diagnostic line in the output. The plan cannot appear in the  middle of the output, nor can it appear more than once.

The test line
The core of TAP is the test line. A test file prints one test line per test  point executed. There must be at least one test line in TAP output. Each test  line comprises the following elements:
Code: bash
ok or not ok

This tells whether the test point passed or failed. It must be at the  beginning of the line. /^not ok/ indicates a failed test point. /^ok/ is a  successful test point. This is the only mandatory part of the line. Note that unlike the Directives below, ok and not ok are case-sensitive.

Test number
TAP expects the ok or not ok to be followed by a test point number.

Description
Any text after the test number but before a # is the description of the test  point.
Code: bash
ok 42 this is the description of the test


Directive
The test point may include a directive, after a hash on the test line.  There are currently two directives allowed: TODO and SKIP. Hashes at the  beginning of a line are considered comments.

Executing tap with AGS

Before running any test, remove any leftover files and from a previous run, by running a clean test.
Code: ags
tap.clean_test()

By default, test is printed to a file named agstest.log on the saved games directory for the game under test.

If you have a plan, state it before starting out.
Code: ags
tap.plan(3);


Then run the needed tests.
Code: ags
String null_string;
tap.ok(String.IsNullOrEmpty(null_string), "String.IsNullOrEmpty test null string");
tap.ok(String.IsNullOrEmpty(""), "String.IsNullOrEmpty test empty string");
tap.ok(!String.IsNullOrEmpty("with text"), "String.IsNullOrEmpty test non-empty string");


After you are done testing, state it.
Code: ags
tap.done_testing();


TAP AGS Script API
Spoiler

tap.clean_test

Code: ags
void tap.clean_test()

This command removes previously outputed test log files if they exist.

tap.plan

Code: ags
void tap.plan(int n,  String skip_reason = 0)

This allows setting a plan, so we know how many tests will be run. Just use it once for the group of tests following. If you want to start a new test, after a plan has been set, remember to call tap.done_testing() before.

Alternatively, you can pass NO_PLAN if you have no plan.

If the following tests are to be skipped, pass [SKIP_ALL, so all tests until next plan are skipped. They will still be executed, they just won't output log  or be considered whether they fail or succeed.

tap.ok

Code: ags
void tap.ok(bool condition, String test_name = 0)

This test will succeed if the condition is true, and fail if the condition is  false.

tap.nok

Code: ags
void tap.nok(bool condition, String test_name = 0)

This test will succeed if the condition is false, and fail if the condition is  true.

tap.is

Code: ags
void tap.is(String got,  String expected, String test_name = 0)

This test will succeed if got and expected matches. If this test fails, we can have some more diagnostic information since we know both inputs and the premise they are supposed to match.

tap.isnt

Code: ags
void tap.isnt(String got,  String expected, String test_name = 0)

This test will succeed if got and expected does not matches. If this test  fails, we can have some more diagnostic information since we know both inputs  and the premise they are not supposed to match.

tap.is_int

Code: ags
void tap.is_int(int got,  int expected, String test_name = 0)

This test will succeed if got and expected matches. If this test fails, we can have some more diagnostic information since we know both inputs and the premise they are supposed to match. This is for int values.

tap.isnt_int

Code: ags
void tap.isnt_int(int got,  int expected, String test_name = 0)

This test will succeed if got and expected does not matches. If this test  fails, we can have some more diagnostic information since we know both inputs  and the premise they are not supposed to match. This is for int values.

tap.is_float

Code: ags
void tap.is_float(float got,  float expected, float epsilon, String test_name = 0)

This test will succeed if got and expected matches. If this test fails, we can have some more diagnostic information since we know both inputs and the premise they are supposed to match. This is for float values.

tap.isnt_float

Code: ags
void tap.isnt_float(float got,  float expected, float epsilon, String test_name = 0)

This test will succeed if got and expected does not matches. If this test  fails, we can have some more diagnostic information since we know both inputs  and the premise they are not supposed to match. This is for float values.

tap.done_testing

Code: ags
void tap.done_testing()

After you are done testing, before calling the next plan, state you are done testing.

tap.Harness

Code: ags
void tap.Harness()

This outputs a small summary of currently tests and failures. Don't depend on this output format.
[close]
Author
Made by eri0o

License
Distributed under MIT license. See  LICENSE for more information.
#65
jsonparser version 0.1.0


Get Latest Release jsonparser.scm | GitHub Repo | Demo Windows | Demo Linux | Download project .zip

I recently found a cool unrelated tool that I wanted to add to my workflow, but the tool output was a json file. There's no good object to represent a JSON object in AGS and I was too lazy to make a plugin.
So I made a parser! This is a JSON minimal parser for Adventure Game Studio. It's based on JSMN, which is a C JSON parser that had an easy to read code.

It's not well tested, but I thought to "formally" release it here in case someone had a need that it would be enough for it. I probably need to work on a better demo and write better docs for it too. But hey, first release!

Usage

If you wish to handle things more manually, you can use a thiner parser that is also faster:

Code: ags
String json_string = "{ \"name\":\"John\", \"age\":30, \"car\":null }";
JsonParser* parser = new JsonParser;

int token_count = 8;
JsonToken* t[] = JsonToken.NewArray(token_count);

int r = parser.Parse(json_string, t, token_count);

// now that you have the Tokens, you can use them to parse as you wish!
if (r < 0) Display("Failed to parse JSON: %d\n", r);
if (r < 1 || t[0].type != eJSON_Tok_OBJECT) Display("Object expected\n");

for(int i=0; i<r  ; i++){
  JsonToken* tok = t[i];
  Display(String.Format("%d ; %s ; %d ; %s ; %d ; %d ; %d", 
    i, tok.ToString(json_string), tok.size , tok.TypeAsString,  tok.start ,  tok.end ,  tok.parent ));
}

Display("JSON Parsing has FINISHED for string\n\n%s", json_string);

You can find here an example in-game usage of the parsing, which may be useful for giving ideas.

This module also packs a more approacheable (but less tested, probably buggy) parser:

Code: ags
String json_string = ""; json_string = json_string.Append("{\"squadName\":\"Super squad\",\"formed\":2016,\"active\":true,\"members\":[");
json_string = json_string.Append("{\"name\":\"Molecule Man\",\"age\":29,\"secretIdentity\":\"Dan Jukes\",\"powers\":[\"Radiation resistance\",\"Radiation blast\"]},");
json_string = json_string.Append("{\"name\":\"Madam Uppercut\",\"age\":39,\"secretIdentity\":\"Jane Wilson\",\"powers\":[\"Million punch\",\"Super reflexes\"]},");
json_string = json_string.Append("{\"name\":\"Eternal Flame\",\"age\":100,\"secretIdentity\":\"Unknown\",\"powers\":[\"Immortality\",\"Heat Immunity\",\"Interdimensional jump\"]}]}");

MiniJsonParser jp;
jp.Init(json_string); // parse json_string and internally generate the tokens

while(jp.NextToken()) // advance the current token and exit when there are no tokens left
{    
  if(jp.CurrentTokenIsLeaf)  // usually the interesting information is on the leafs
  {
    Display(String.Format("%s: %s", jp.CurrentFullKey, jp.CurrentTokenAsString));
  }    
}

Display("JSON Parsing has FINISHED for string\n\n%s", json_string);

Script API

JsonParser
JsonParser.Parse
Code: ags
int JsonParser.Parse(String json_string, JsonToken *tokens[], int num_tokens)
Parses a JSON data string into and array of tokens, each describing a single JSON object. Negative return is a JsonError, otherwise it's the number of used tokens.

You need to preallocate a number of tokens to be used by this method before calling it. Use JsonToken.NewArray(count) to help you.

JsonParser.Reset
Code: ags
void JsonParser.Reset()
Marks the parser for reset, useful if you want to use it again with a different file. Reset only actually happens when Parse is called.

JsonParser.pos
Code: ags
int JsonParser.pos
offset in the JSON string

JsonParser.toknext
Code: ags
int JsonParser.toknext
next token to allocate

JsonParser.toksuper
Code: ags
int JsonParser.toksuper
superior token node, e.g. parent object or array

JsonToken
JsonToken.NewArray
Code: ags
static JsonToken* [] JsonToken.NewArray(int count)
Static helper to ease Token Array creation. Ex: JsonToken* t[] = JsonToken.NewArray(token_count);

JsonToken.ToString
Code: ags
String JsonToken.ToString(String json_string)
pass the json_string that was parsed and generated this token to recover the string this token refers to

JsonToken.type
Code: ags
JsonTokenType JsonToken.type
The type of the token: object, array, string etc.

JsonToken.start
Code: ags
int JsonToken.start
The start position in JSON data string.

JsonToken.end
Code: ags
int JsonToken.end
The end position in JSON data string.

JsonToken.size
Code: ags
int JsonToken.size
The size tells about the direct children of the token, 0 if it's a leaf value, 1 or bigger if it's a key or object/array.

JsonToken.parent
Code: ags
int JsonToken.parent
If it's a child, is the index position of the parent in the token array.

JsonToken.TypeAsString
Code: ags
readonly attribute String JsonToken.TypeAsString
Utility function for debugging, returns the type of the token in a String format.

JsonTokenType
- eJSON_Tok_UNDEFINED, a valid token should never have this type.
- eJSON_Tok_OBJECT, an object, it holds keys and values, values can be any other type.
- eJSON_Tok_ARRAY, an array, the token will contain direct ordered children.
- eJSON_Tok_STRING, the token is a string, could be a key, could be a value, context is needed.
- eJSON_Tok_PRIMITIVE, the token is either a number (float or integer), a boolean (true or false) or null.

JsonError
Used to check parse results.
- eJSON_Error_InsuficientTokens, Not enough tokens were provided. Please use more tokens.
- eJSON_Error_InvalidCharacter, Invalid character inside JSON string.
- eJSON_Error_Partial, The string is not a full JSON packet, more bytes expected.

MiniJsonParser
MiniJsonParser.Init
Code: ags
void MiniJsonParser.Init(String json_string)
Initialize the parser passing a JSON as a string. Common usage is: MiniJsonParser jp; jp.Init(json_string);.

MiniJsonParser.NextToken
Code: ags
bool MiniJsonParser.NextToken()
Advances to the next token. Returns false if no tokens left.

MiniJsonParser.CurrentTokenAsString
Code: ags
readonly attribute String MiniJsonParser.CurrentTokenAsString
The current token content, as a String.

MiniJsonParser.CurrentTokenType
Code: ags
readonly attribute JsonTokenType MiniJsonParser.CurrentTokenType
The current token type.

MiniJsonParser.CurrentTokenSize
Code: ags
readonly attribute int MiniJsonParser.CurrentTokenSize
The current token size, 0 if it's a leaf value, 1 or bigger if it's a key or object/array.

MiniJsonParser.CurrentState
Code: ags
readonly attribute MiniJsonParserState MiniJsonParser.CurrentState
The current state of our mini parser. Helps understanding the JSON tokens we got when parsing.

MiniJsonParser.CurrentFullKey
Code: ags
readonly attribute String MiniJsonParser.CurrentFullKey
Gets the current dot separated key.

MiniJsonParser.CurrentTokenIsLeaf
Code: ags
readonly attribute bool MiniJsonParser.CurrentTokenIsLeaf
Checks if the state and key type currently are a leaf. True if it's, usually leafs are the interesting tokens we want when parsing.

MiniJsonParserState
- eJP_State_START, The parser just started.
- eJP_State_KEY, The current token is key in an object.
- eJP_State_VALUE, The current token is a value in an object.
- eJP_State_ARRVALUE, The current token is a value in an array.
- eJP_State_STOP, Don't parse anything in this state, but the parser is not necessarily done.


License

This code is made by eri0o and is licensed with MIT LICENSE. The code on this module is based on Serge's JSMN, which is also MIT licensed and is referenced in the license.
#66
So I have been doing a bit of a little pattern in my code:

Code: ags
  t = new JsonToken[MAX_TOKENS];
  for(int i=0; i<MAX_TOKENS; i++) t[i] = new JsonToken;


So I decided to make a little function for it:

Header
Code: ags
import JsonToken[] JsonTokenNewArray(int count); // this is line 33


Script
Code: ags
JsonToken[] JsonTokenNewArray(int count)
{
  JsonToken tks[];
  tks = new JsonToken[count];
  for(int i=0; i<count; i++) tks[i] = new JsonToken;
  return tks;
}


Unfortunately this gives me an error "Error (line 33): cannot pass non-pointer struct array". I also tried to make this function be part of the JsonToken managed struct.

Header
Code: ags
managed struct JsonToken {
  // ... a bunch of things ...
  import static JsonToken[] NewArray(int count); // $AUTOCOMPLETESTATICONLY$ // this is line 30
};


Script
Code: ags
static JsonToken[] JsonToken::NewArray(int count)
{
  JsonToken tks[];
  tks = new JsonToken[count];
  for(int i=0; i<count; i++) tks[i] = new JsonToken;
  return tks;
}


But this gives me the error "Error (line 30): Member variable cannot be struct".

I am starting to think it's not possible to return a dynamic array of managed structs from a function, is this the actual error? (it's for a little Json parser module that is kinda working but I am just polishing the API right now..)
#67
ImGi version 0.4.2


Get Latest Release imgi.scm | GitHub Repo | Demo Windows | Demo Linux | Download project .zip

AGS Script Module for Immediate Gui, uses script Overlays to render the interface.

This is beta quality, I predict Overlays will enter into fashion in one of the next seasons so this module is being written for when the time comes.  8-)



Usage
Spoiler

Code: ags
function repeatedly_execute() // In Room Script, use room_RepExec()
{
  ImGi.Begin();

  if(ImGi.BeginWindow("Hello World", 32, 32, 130, 60, eImGi_Opt_AlignCenter | eImGi_Opt_NoClose))
  {
    // makes following rows to have two columns of width 60 and 70, and let height be default
    ImGi.LayoutRow2(60, 70);

    ImGi.Label("A Label:");
    if(ImGi.Button("A Button!"))
    {
      player.Say("You clicked the button!");
    }

    ImGi.EndWindow();
  }

  ImGi.End();
}
[close]

Script API
Spoiler

ImGi entire API uses static functions and attributes

Basic

ImGi.Begin
Code: ags
void ImGi.Begin()

Call only once per frame and make sure to call End() after.

ImGi.End
Code: ags
void ImGi.End()

Call only once per frame, after Begin() is called.



Layout System

ImGi uses a row based layout system. Each row can have a number of columns (up to 16 columns), each with it's own width, and also a height.
Controls then will be placed in this cell. If you don't change how rows are, it's assumed to keep same LayoutRow.

If you don't specify a height, it will use your style size and padding. Places elements relative to the bottom. Controls may make height bigger than what you set.
If your column width is 0, it will your style size and padding. A negative width will try to place that element at that distance relative from the rights.

Some special Controls can force the width of it's specific column, ignoring the positive values you set, this can be useful if you don't know before hand their sizes.

ImGi.LayoutRow1
ImGi.LayoutRow2
ImGi.LayoutRow3
ImGi.LayoutRow4
Code: ags
void ImGi.LayoutRow1(int width, int height = 0)
void ImGi.LayoutRow2(int w1, int w2, int height = 0)
void ImGi.LayoutRow3(int w1, int w2, int w3, int height = 0)
void ImGi.LayoutRow4(int w1, int w2, int w3, int w4, int height = 0)

Functions to configure the next LayoutRow to use, from a single column (LayoutRow1) up to four columns (LayoutRow4). Use these if you know you want either of these number of columns.
You can optionally specify a height.

ImGi.LayoutRow
Code: ags
void ImGi.LayoutRow(int count, int widths[], int height = 0)

Pass an array of widths with count elements to configure the columns in a row. You can optionally specify a height.

This is useful if you are reading an array of things or have 5 or more columns in your layout. The maximum number of widths is 16.
Code: ags
int row[];

row = new int[2];
row[0] = 60; // set a predefined column width size per element in row
row[1] = 70; // this is the width of other column
ImGi.LayoutRow(2 /*n columns*/, row); // rows after this line have such column config


ImGi.LayoutBeginColumn
ImGi.LayoutEndColumn
Code: ags
void ImGi.LayoutBeginColumn()
void ImGi.LayoutEndColumn()

Allows subdividing a cell in a row in more rows and columns. You start the column with ImGi.LayoutBeginColumn(), and it's void, so ALWAYS call LayoutEndColumn() after (you don't check it's return value because it's void!).




Window

A window can be created by a BeginWindow and if this is successful (returns any non false value), it has to call EndWindow to specify where it logically ends.
All controls must exist within a window. An example of normal usage is below:

Code: ags
if(ImGi.BeginWindow("My First Window!", 32, 32, 130, 60))
{
  ImGi.Text("Hi!"); // your controls are here ...
  ImGi.EndWindow();
}



ImGi.BeginWindow
Code: ags
ImGi_Res ImGi.BeginWindow(String title, int x, int y, int width, int height, ImGi_Opt opt = 0)

Creates a window, make sure to call a matching EndWindow() if this method return is not false.

ImGi.EndWindow
Code: ags
void ImGi.EndWindow()

Has to be called each time a BeginWindow is successful once all elements inside the window are listed

ImGi.OpenWindow
Code: ags
void ImGi.OpenWindow(String title)

If a window of matching title is closed, it opens again.

ImGi.Close
Code: ags
void ImGi.Close()

Closes what is the current scope (Window, Popup, ...). Don't call it outside of a Window, a Popup, ...

ImGi.BeginPopup
ImGi.EndPopup
ImGi.OpenPopup
Code: ags
ImGi_Res ImGi.BeginPopup(String title)
void ImGi.EndPopup()
void OpenPopup(String name)


Popups are like windows, but you can't move or resize them, and they have no headers. They open where the mouse is when calling OpenPopup by default.
Popups open on top of everything, and they close if you click outside of them. Clicks outside of the popup that hit no window will be forwarded to the rest of your game to handle.

ImGi.BeginPanel
ImGi.EndPanel
Code: ags
ImGi_Res ImGi.BeginPanel(String name, ImGi_Opt opt = 0)
void ImGi.EndPanel()


If you need a scrollable area inside a window that is not the window itself, you can use panels! A panel has to be inside of a window, it will use the LayoutRow cell size for it's size. If it returns successful, you have to call EndPanel() after.
Code: ags
if(ImGi.BeginPanel("Pan")){
  ImGi.Text("Hi panel!"); // your controls are here ...
  ImGi.EndPanel();
}





Controls

Controls are things you can place inside a window. Controls cannot exist outside of windows.

Every controls takes a string as label, this string can't be empty and can't match the label of other control. The exception is if the control can take an icon or graphic, then if you pass null as the string and the control has an icon, it will use the icon if possible. Each window or similar will be a new scope, used to compose this identification, so two different windows can have controls with matching labels.

ImGi comes with a limited number of icons, whenever you can provide an icon as a parameter, you can alternatively pass a sprite number, and in this case it will use that sprite instead of the icon. The default icons use negative numbers, so they don't conflict with your sprite ID.

ImGi.Empty
Code: ags
void ImGi.Empty()

This control does nothing and is invisible. Use it when you don't want to place anything in cell to advance the layout.

ImGi.Label
Code: ags
void ImGi.Label(String label)



This control is a Label containing the specified text. It has no interaction.

ImGi.Text
Code: ags
void ImGi.Text(String text)



This control is a Multiline Label for visualization only. It has no interaction.

ImGi.TextBox
Code: ags
String ImGi.TextBox(String label, String buf, int bufsz, ImGi_Result* res = 0, ImGi_Opt opt = 0)

This control is an editable TextBox. Click on it to give focus and enter the text input with the keyboard. Enter exits focus.

The character limit is defined in bufsz. This function will return the buf String modified, just assign it to the same String so it's content can be updated.

ImGi.Button
Code: ags
ImGi_Res ImGi.Button(String label, ImGi_Icon icon = 0, ImGi_Opt opt = eImGi_Opt_AlignCenter)



This control is a Button. When clicked, it will return a value different than false.

ImGi.ButtonImage
Code: ags
ImGi_Res ImGi.ButtonImage(String label, int graphic_normal, int graphic_over, int graphic_pressed, ImGi_Opt opt = 0)


Pass a sprite for the Button normal state, one for when mouse is over, and a graphic for when it's clicked. You can set label null if it's the only button in the window with same graphics.

ImGi.CheckBox
Code: ags
ImGi_Res ImGi.CheckBox(String label, CheckBoxState* chkst, ImGi_Icon icon = eImGi_Icon_Check)



This control is a CheckBox. It doesn't store state, so make sure to pass it's state. You optionally pass a different icon to it.

ImGi.Number
ImGi.NumberI
Code: ags
ImGi_Res ImGi.Number(String label, ImGi_Real* value, float step = 0, String format = 0, ImGi_Opt opt = 0)
ImGi_Res ImGi.NumberI(String label, ImGi_Int* value, int step = 0, String format = 0, ImGi_Opt opt = 0)



This control shows a Number, set step to allow quick mouse drag adjustments. Holding shift and clicking it allows entering input with the keyboard.

You can pass a format string similar to the one used with String.Format to specify how the number should be rendered. It's a float, so make sure to use either "%f" or "%g".

NumberI is the same control but for integer (int) numbers, it's not the same control just wrapped, so it's format string default is "%d".

ImGi.Slider
ImGi.SliderI
Code: ags
ImGi_Res ImGi.Slider(String label, ImGi_Real* value, float low, float high, float step = 0,  String format = 0, ImGi_Opt opt = 0)
ImGi_Res ImGi.SliderI(String label, ImGi_Int* value, int low, int high, int step = 0, String format = 0, ImGi_Opt opt = 0)



This control is a Slider. You can adjust it manually with the mouse or you can hold shift and click to specify a value with the keyboard.

You can pass a format string similar to the one used with String.Format to specify how the number should be rendered. It's a float, so make sure to use either "%f" or "%g".

SliderI is the same control but for integer (int) numbers, it's not the same control just wrapped, so it's format string default is "%d".



Options

Some controls and other elements can take options. Below is the list of available options. If you wish to pass two or more options, you can combine them with the bitfield or operator |

Code: ags
ImGi.Button("centered text and non-interactive",0, eImGi_Opt_AlignCenter | eImGi_Opt_NoInteract)



eImGi_Opt_AlignCenterThe header of a window or the control will have text aligned to center.
eImGi_Opt_AlignRightThe header of a window or the control will have text aligned to right.
eImGi_Opt_NoInteractDisables interaction with the control.
eImGi_Opt_NoFrameIf the control or window has any frame, it's not drawn.
eImGi_Opt_NoResizeYou can't resize the window by click-dragging it's bottom right corner.
eImGi_Opt_NoScrollWindow has no scrollbars.
eImGi_Opt_NoCloseWindow has no close button.
eImGi_Opt_NoTitleWindow has to title bar.
eImGi_Opt_HoldFocusControls with this option will require clicking on a different control to remove focus. Default of some controls.
eImGi_Opt_AutoSizeMakes the window resize to fit content.
eImGi_Opt_PopUpCloses the container when clicking out of it. This is used by default in Popus.
eImGi_Opt_ClosedMakes the container start closed by default.
eImGi_Opt_ExpandedThese are for tree elements, which are not implemented yet.




Utilities

ImGi.SetFocusLastControl
Code: ags
void ImGi.SetFocusLastControl()

Places the focus on what is the last control. Some controls behave differently when focused - like Number and TextBox. Focus is only reggarding input, but won't scroll or move things at center.




Style and Design customization

ImGi.Style
Code: ags
ImGi_Style* ImGi.Style

Holds the Current Style for ImGi.

[close]

License

This code is licensed with MIT LICENSE. The code on this module is based on rxi's Microui, which is also MIT licensed, referenced in the license, this port though has many changes - lots of new bugs too.
#68
I want to do something like:

Code: ags
function modifyString (String s)
{
  // some way to modify the string
  s = s.Append("world");
}


And then run it like
Code: ags
 String str = "hello ";
  modifyString(str);
  Display(str);


Unfortunately the above gives me "hello " instead of "hello world". Is there a way in AGS to modify a String in place? The problem I have is my function has to return additional information - an integer with some status information. Unfortunately, I can't return a pointer to struct that contains the String since this would be pointer of pointer... So if I could modify the string in place it would be perfect.

So far my only solution is to return the String and have the function receive a pointer to a struct that has the int so it can modify the integer in place instead...
#69
After long hours working on the computer, and using free time playing games that are mouse first and making point and click games - a huge cemetery of dead prototypes - I ended up having some pain on my wrists. Looking to counter that, I recently got my hands on a vertical mouse for a more comfortable position.



I had some pain in my wrists for a month before getting the mouse - I broke my feet, which reduced my time doing exercises to 0, and then I spent even more time in the computer. The new device is a Logitech MX Vertical mouse, and it's been really comfortable to use this last week. The device is for medium to big sized hands, a small hand will have trouble reaching the buttons.

It works on Windows/Linux/MacOS without problems - except the top button apparently only does interesting things in Windows. The scrollwheel is very simple (doesn't have inertia or any cool feature :/). The back and advance buttons are easy to reach with the thumb. I find it comfortable to hold for long hours and had no problem with drawing softwares that can require more diverse moves of the mouse. Now I am a bit curious about trackballs but there are many different models and very little information on them...

Does anyone here uses a similar device or a trackball or touch pad instead of a traditional mouse to reduce pain in the wrist/arm/hands? Are there other interesting vertical mouses?
#70
agsserialport  version 0.1.0



Get Latest Release agsserialport.dll | libagsserialport.so | libagsserialport.dylib | GitHub Repo | Demo Windows | Demo Linux



A very untested Serial Port plug-in for Adventure Game Studio. I made this for myself to play with Arduino at home, and I will eventually document this better and fix it's bugs.

For now, here is an example that continuously reads incoming data on the serial port. Reading and Writing operations are non-blocking.

Code: ags
// room script file
SP_Port* _port;

function room_RepExec()
{
  if(_port == null) return;
  String read = "";

  
  for(int byte_count = _port.WaitingBytesRead;byte_count>0;byte_count = _port.WaitingBytesRead)
  {
    if(byte_count > 0)
    {
      String piece_read = _port.Read(0);
      if(piece_read!=null) read = read.Append(piece_read);
    }
  }
  
  if(!String.IsNullOrEmpty(read)) Display(read); 
}

function room_AfterFadeIn()
{
  // Port HAS to be open before configuring
  if(_port.Open(eAGSP_Mode_Read) == eAGSP_OK)
  {
    if(_port.SetBaudrate(9600) != eAGSP_OK) Display("Error setting baudrate");
    if(_port.SetBits(8) != eAGSP_OK) Display("Error setting bits");
    if(_port.SetParity(eAGSP_Parity_None) != eAGSP_OK) Display("Error setting parity");
    if(_port.SetStopBits(1) != eAGSP_OK) Display("Error setting stop bits");
    if(_port.SetFlowControl(eAGSP_FlowControl_None) != eAGSP_OK) Display("Error setting flow control");
  }
  else 
  {
    _port = null;
  }
}

function room_Load()
{
  String portname;
  for(int i=0; i<AGSP.PortNamesCount; i++)
  {
    portname = AGSP.PortNames[i];
  }

  if(portname == null || portname.Length < 1)
  {
    return;
  }
    
  _port = SP_Port.Create(portname);
  
}

function hGlowingOrb_AnyClick()
{
  if(_port == null) return;
  
  Display(String.Format("Serial Port Name: %s\nDescription: %s\n", _port.Name, _port.Description));
}


The library has three main structs:

  • AGSP - For listing available serial ports by name (named COM1, COM2, ... on Windows)
  • SP_Port - Represents a serial port Allows opening, configuring and reading and writing data on a serial port
  • SP_PortConfig - Holds a port configuration, can be retrieved from a serial port or applyied to a serial port.

A SP_Port has no default configuration, you always have to set a configuration after opening a port before reading or writing to it. In any operation that returns a value, a negative value is an error.

Here is a very simple Arduino example that can be used to verify you are reading correctly.

Code: c
void setup()                    // run once, when the sketch starts
{
  Serial.begin(9600);           // set up Serial library at 9600 bps, Arduino defaults to 8 bits, 1 Stop Bit, No Parity and No Flow Control
}

void loop()                       // run over and over again
{
  Serial.println("Hello serial world!");  // prints hello with ending line break
  delay(5000);
}


This should be mostly useless but may be fun to play with hardware at home. This is a port of libserialport for AGS.
#71
General Discussion / Tiny Houses
Sun 08/11/2020 00:46:52
This has nothing to do with games or anything, but it's something that recently I got some interest. I was looking into decoration and things that are interesting to pull inside of a house and google images kept me showing some really neat small kitchens and after adding them in my image list of things to look into, I decided to look from which houses these neat little kitchens were coming from, I discovered they were from tiny houses and went looking into them and this was a huge spiral in a bubble that had been invisible to me.

After looking into these tiny houses obsessively, I don't think I would have one but I found a very interesting YouTube channel that has some diverse tiny houses that was fun to watch, and I decided to share here to see if someone else find this cool. I am picking my favorite three houses - they don't produce the most interesting episodes necessarily but the houses are very cool.


The Earthship

Spoiler
[close]

This one I thought it was terrible at first but as the dude who built explained all the little tricks the house pulls it, I really admired it! I think the house looks like something from Tatooine. The inside feels super relaxing, the floor feels good to lay down. And also has a good bathroom!


The Modern Yurt

Spoiler
[close]

This one is a yurt. The young guy who did the design and build is super impressive, I found that he really did a good design and built something usable. I can totally see this as something you can rent for an escape on AirBnB, with a bit more things on the outside or depending where it's located.


The Black House

Spoiler
[close]

This is one I would actually live. It's not as tiny and has a real bathroom toilet which is super rare in a lot of builds. I loved the "outdoor" cat run to get the cats be able to walk out of the house!


Overall I liked that these are small so you can easily pickup the design of the whole house and the interesting ideas are condensed in a way you can quickly pick them up and mentally make notes of the things that are interesting or usable to repurposed at home.

Is one else here interested in these little houses? Have you seen any cool designs? Please share!
#72
Not sure if this already exists... Did it for myself because I needed a quick way to config something independently of save games. It's an extension of AGS Dictionaries so they can serialize and deserialize to ini files and strings.

dicttoini.ash
Code: ags
// DictToIni Module Header 
// Version 0.0.1 - needs testing!
// Note: currently spaces aren't trimmed

/// Creates a new ini-like string representing the dictionary.
import String ToIniString(this Dictionary*);

/// Overwrites the dictionary with the key value pairs in the ini-like string.
import void FromIniString(this Dictionary*, String ini_string);

/// Creates an ini file on the player's save directory, from current dictionary key value pairs.
import void ToIniFile(this Dictionary*, String filename);

/// Reads an ini file on the player's save directory and replaces current dictionary with file's key value pairs.
import void FromIniFile(this Dictionary*, String filename);


dicttoini.asc
Code: ags
// DictToIni Module Script

#region STRING_EXTENSIONS
// ---- START OF STRING EXTENSIONS ---------------------------------------------

int CountToken(this String*, String token)
{
  int count = 0, cur = 0, next = 0;
  String sub = this.Copy();

  while(sub.Length > 0)
  {
    if(sub.IndexOf(token)==-1) return count;
    sub = sub.Substring(sub.IndexOf(token)+token.Length, sub.Length);
    count++;
  }
  return count;
}

String[] Split(this String*, String token)
{
  int i = 0, cur = 0, count;
  count = this.CountToken(token);
  if(count<=0)
  {
    String r[] = new String[1];
    r[0] = null;
    return r;
  }

  String r[] = new String[count+2];
  String sub = this.Copy();

  while(i < count)
  {
    cur = sub.IndexOf(token);
    if(cur==-1) cur=sub.Length;
    r[i] = sub.Substring(0, cur);
    sub = sub.Substring(sub.IndexOf(token)+token.Length, sub.Length);
    i++;
  }

  r[i] = sub.Substring(0, sub.Length);
  i++;
  r[i] = null;
  return  r;
}
// ---- END OF STRING EXTENSIONS -----------------------------------------------
#endregion //STRING_EXTENSIONS

String ToIniString(this Dictionary*)
{
  String ini_string = "";
  int keycount;
  String keys[];
  
  keys = this.GetKeysAsArray();
  keycount = this.ItemCount;
  
  for(int i=0; i<keycount; i++)
  {
    String value = this.Get(keys[i]);
    ini_string = ini_string.Append(String.Format("%s=%s\n", keys[i], value));
  }
  
  return ini_string;
}

void FromIniString(this Dictionary*, String ini_string)
{
  this.Clear();
  if(String.IsNullOrEmpty(ini_string)) return;
  
  int linecount = ini_string.CountToken("\n");
  String lines[] = ini_string.Split("\n");
  
  for(int i=0; i<linecount; i++)
  {
    if(lines[i].IndexOf("=") <= -1) continue;
   
    String kv[] = lines[i].Split("=");
    if(String.IsNullOrEmpty(kv[0]) || String.IsNullOrEmpty(kv[1])) continue;

    this.Set(kv[0], kv[1]);
  }
}

void ToIniFile(this Dictionary*, String filename)
{
  if(this.ItemCount == 0) return;
  
  String filepath = "$SAVEGAMEDIR$/";
  filepath = filepath.Append(filename);
  File* file = File.Open(filepath, eFileWrite);
  
  String ini_string = this.ToIniString();
    
  file.WriteRawLine(ini_string);

  file.Close();
}

void FromIniFile(this Dictionary*, String filename)
{
  this.Clear();  
  if(String.IsNullOrEmpty(filename)) return;

  String filepath = "$SAVEGAMEDIR$/";
  filepath = filepath.Append(filename);
  File* file = File.Open(filepath, eFileRead);
  
  for(String line = ""; !file.EOF; line = file.ReadRawLineBack())
  {
    if(String.IsNullOrEmpty(line)) continue;
    if(line .IndexOf("=") <= -1) continue;
    
    String kv[] = line.Split("=");
    if(String.IsNullOrEmpty(kv[0]) || String.IsNullOrEmpty(kv[1])) continue;
    
    this.Set(kv[0], kv[1]);
  }
  
  file.Close();  
}


I am attaching below a demo game where you can save and restore the config on the Sierra Menu. It will create a file game_config.ini in your game's save directory with the contents below (numbers depends on your slider).
Code: ini
speech_mode=1
speed=40
gamma_value=100
voice_volume=255
audio_volume=28


Demo: dicttoini.zip

Relevant change from the Sierra Template menu, is on GlobalScript.asc:

Code: ags

function btnSaveConfig_OnClick(GUIControl *control, MouseButton button)
{
  Dictionary* dict = Dictionary.Create(eNonSorted, eCaseSensitive);
  dict.Set("audio_volume", String.Format("%d",sldAudio.Value));
  dict.Set("voice_volume", String.Format("%d",sldVoice.Value));
  dict.Set("gamma_value", String.Format("%d",sldGamma.Value));
  dict.Set("speed", String.Format("%d",sldSpeed.Value));
  dict.Set("speech_mode", String.Format("%d",Speech.VoiceMode));
  dict.ToIniFile("game_config.ini");
}

function btnLoadConfig_OnClick(GUIControl *control, MouseButton button)
{
  Dictionary* dict = Dictionary.Create(eNonSorted);

  dict.FromIniFile("game_config.ini");
  if(dict.Contains("audio_volume"))
  {
    String key = dict.Get("audio_volume");
    sldAudio.Value = key.AsInt;
    System.Volume = sldAudio.Value;
  }
  if(dict.Contains("voice_volume"))
  {
    String key = dict.Get("voice_volume");
    sldVoice.Value = key.AsInt;
    SetSpeechVolume(sldVoice.Value);
  }
  if(dict.Contains("gamma_value"))
  {
    String key = dict.Get("gamma_value");
    sldGamma.Value = key.AsInt;
    System.Gamma = sldGamma.Value;
  }
  if(dict.Contains("speed"))
  {
    String key = dict.Get("speed");
    sldSpeed.Value = key.AsInt;
    SetGameSpeed(sldSpeed.Value);
  }
  if(dict.Contains("speech_mode"))
  {
    String key = dict.Get("speech_mode");
    Speech.VoiceMode = key.AsInt;
    if (Speech.VoiceMode == eSpeechVoiceOnly) btnVoice.Text = "Voice only";
    else if (Speech.VoiceMode == eSpeechTextOnly) btnVoice.Text = "Text only";
    else btnVoice.Text = "Voice and Text";
  }
}

#73
https://offf.academy/?sdid=VY36K16R

QuoteCreative Mentorship

Remember when you dreamed about meeting your idol and working with them? Now is the time to make your project a reality, the day has finally arrived.

OFFF Academy, your new after school playground, is launching the 2020/2021 edition of the Creative Mentorship powered by Adobe, aimed to empower talented individuals like you to help you take a big step forward in your future and help you discover your creative superpowers.

Our goal is to connect younger talents under the age of 30 from around all Europe with their professional Mentor by providing six unique creative mentorships.

The above is for people in Europe only. It appears fun so I thought I would share in case someone here is interested.

I phrased the title in a way that if someone knows about other cool opportunities going on they could share here too.
#74
AGS game project: microgui.zip

Sometimes things doesn't work and we have to move on. I read the source code of microui here and it looked fairly simple so I decided, wait, maybe I can port this to AGS Script and have a reasonable Immediate Gui that can be scripted and doesn't relies on plugins, maybe it can even be customized later.

Problem is, I think some of the code relies on some gl concepts that I could not figure out how to reasonably translate to AGS Script and it's API, I think... It all is around 1k AGS Script code lines.

So a code like this:

room1.asc
Code: ags
// room script file
CheckBoxState* chkbox;

void repeatedly_execute_always()
{
  if(chkbox==null) chkbox = new CheckBoxState;
  
  ImGi.Begin(); 
  if(ImGi.BeginWindow("My Window", 32, 32, 160, 86))
  {
    int rows[] = new int[2];
    rows[0] = 60;
    rows[1] = -1;
    ImGi.LayoutRow(2, rows);
    
    ImGi.Label("First:");
    if(ImGi.Button("Button1"))
    {
      player.Say("Button1 pressed!");  
    }
    
    ImGi.Label("Second:");
    if(ImGi.Button("Button2"))
    {
      player.Say("Button2 pressed!");  
    }
  
    ImGi.EndWindow();
  }
  ImGi.End();
  
  r_render();
}


Is giving me this wrong output like this



Instead of something more like this:

Anyway, at least I learned a bit more about immediate mode GUIs. Decided to leave the attempt here even if failed in case someone has looked into something similar in the past. I find GUIs that are defined through code easier to respond dynamically, to be able to dynamically add controls, auto arranging the controls in the layout and similar.
#75
I am trying to have a base struct be extended to two struct types, and be able to return the appropriate struct by using a type identifier, so I could like "cast" a struct to other - like GuiControl does. I have done this in plugins, but I don't know if this is possible using script.

castExtender.ash
Code: ags
// new module header
#define MAX_TEXT_CMD_STR_SIZE 100

enum CmdType {
  eCmdBase = 0, 
  eCmdRect, 
  eCmdText, 
};

managed struct CmdRect;
managed struct CmdText;

managed struct CmdBase { 
  CmdType type;
  
  readonly import attribute CmdRect* AsCmdRect;
  readonly import attribute CmdText* AsCmdText;
    
  import CmdRect* get_AsCmdRect();  // $AUTOCOMPLETEIGNORE$
  import CmdText* get_AsCmdText();  // $AUTOCOMPLETEIGNORE$
};

managed struct CmdRect extends CmdBase {
  int rect_x;
  int rect_y;
  int rect_w;
  int rect_h;
  int color;
};

managed struct CmdText extends CmdBase {
  FontType font;
  int pos_x;
  int pos_y;
  int color;
  char str[MAX_TEXT_CMD_STR_SIZE];
};


castExtender.asc
Code: ags
// new module script
CmdRect* CmdBase::get_AsCmdRect() {
  if(this.type == eCmdRect) return this;
  return null;
}

CmdText* CmdBase::get_AsCmdText() {
  if(this.type == eCmdText) return this;
  return null;
}


Trying to compile this raises: castExtender.asc(2): Error (line 2): Size of identifier does not match prototype

Here is the game project: CastingExtendedFunction.zip
#76
I sort of needed a quicksort in AGS and decided to read Wikipedia, pick up the easiest algorithm and implement it.

this lead to:
qsort.ash
Code: ags
import void qsort(int arr[], int size);


qsort.asc
Code: ags
void _swap(int arr[], int p1, int p2){
  int temp = arr[p1];
  arr[p1] = arr[p2];
  arr[p2] = temp;  
}

int _partition(int arr[], int lo, int hi) {
  int pivot = arr[hi];
  int i = lo;
  int j;
  for (j = lo; j<hi; j++) {
    if(arr[j] < pivot) {
      _swap(arr, i, j);
      i = i + 1;
    }
  }
  _swap(arr, i, j);
  return i;
}

void _quicksort(int arr[], int lo, int hi){
  if( lo < hi) {
    int p = _partition(arr, lo, hi);
    _quicksort(arr, lo, p-1);
    _quicksort(arr, p+1, hi);
  }  
}

void qsort(int arr[], int size) {
  _quicksort(arr, 0, size-1);
}


so far so good, decided to make a little room experiment to verify this works.

room1.asc
Code: ags
// room script file
#define ARRAY_SIZE 32

int my_a[];
int my_a_size = ARRAY_SIZE;

String _print_array(int arr[], int size){
  String str = "";
  int column_count = 3;
  for(int i=0; i<size; i++){
    str = str.Append(String.Format("  \[%02d]: %02d ", i, arr[i]));
    if((i+1)%column_count == 0)str = str.Append("\n");
  }
  return str;
}

void _randomize_array(int arr[], int size){
  for(int i=0; i<size; i++){
    arr[i] = Random(40);
  }
}

function room_Load()
{
  my_a = new int[my_a_size];
  _randomize_array(my_a, my_a_size);
}


function room_AfterFadeIn()
{
  Display("This is an array we want to sort");
  Display(_print_array(my_a, my_a_size));
  Display("let's use qsort!");
  qsort(my_a, my_a_size);
  Display(_print_array(my_a, my_a_size));
  QuitGame(1);
}





Edit
: I previously had the following error, which CW picked up in the post below!  :-D The code above is fixed thanks to him, but file attached below here is the wrong old wrong code!




But my room script doesn't compile: room1.asc(25): Error (line 25): Type mismatch: cannot convert 'int*' to 'int[]'

I kinda forgot how pointers and things worked, I tried to switch int arr[] to int* arr in the function _randomize_array, but it led to "room1.asc(17): Error (line 17): Cannot declare pointer to non-managed type".

I am rusty, how should I pass array of integers in functions?

To make things easier, here is the project: Quicksort.zip
#77
https://github.com/chrismaltby/gb-studio
https://www.gbstudio.dev/

I recently found out about this project. Has anyone here built something with it? I thought it was really neat it can build real GB ROMs!
#78
eri0o's Adventure Game Studio Repositories!

Using AGS for some time now, here's a list of things I made!

Script Modules


Engine Plugins

  • agsimgui | Downloads ⇩ |

    • An ImGui plugin for Adventure Game Studio, so you can create beautiful and fast GUIs using AGS Script.
  • agsbox2d | Downloads ⇩ |

    • AgsBox2D is a physics plugin for Adventure Game Studio that gives access to the Box2D library created by Erin Catto. API is more inspired in the one used in Löve than the original Box2D one.
  • agssqlite | Downloads ⇩ |

    • SQLite plugin for Adventure Game Studio, adds way to store and retrieve information from local SQLite databases!

Editor Plugins

  • agsModuleList | Downloads ⇩ |

    • includes both the agsModuleList website and the agsget Editor Plugin, which makes finding and downloading AGS Modules easy. If you want to update or add your module, open a PR. Any questions, open an issue.

Open Source Games


Miscellaneous

  • Tea for Two Android Studio Project

    • This repository may be useful for one porting their own AGS game to Android. I added a README that tries to go throgh things step by step
  • ash2doc

    • From ashes to documentation, turn AGS Script Header files into friendly text in Markdown or BBCode. WIP, JS, help needed.

Forks

  • AGS-Controller | Downloads ⇩ |

    • AGS-Controller is a SDL2 Gamepad plugin for AGS, this fork has CI integration to provide automatic cross platform builds.
  • AGSWaves | Downloads ⇩ |

    • AGSWaves provides helpful features to work around ags3 sound limitation and other things. This fork has CI integration to provide automatic cross platform builds.
  • agslua | Downloads ⇩ |

    • agslua plugin adds lua support to 3.x AGS Editor. This fork adds CI integration to provide builds for Linux and MacOS.

Thank you for reading this list.
Markdown version | GitHub repo for this
#79
In a search for this answer Microsoft Bob was created



It puts the word fun back in computing, but apparently it didn't, because it got trapped in infunriating.
#80
Trying to build a Language Server and VSCode Client for AGS Script.

Repository:
https://github.com/ericoporto/ags-script-language-server

First tests:



Note the file is recognized as AGS Script and functions are picked when the extension is activated. How to load and develop it:

https://streamable.com/o5afou

Tasks in need to accomplish:
- tight up loose points and fix bugs in the extension
- add icon for recognized AGS Script Header files and AGS Script files
- fix function overview to include names
- add [agsdefn.sh](https://github.com/adventuregamestudio/ags/blob/master/Editor/AGS.Editor/Resources/agsdefns.sh) for better suggestions
- verify possibility of adding [ags-manual](https://adventuregamestudio.github.io/ags-manual/) information
- write AGS Script TextMate grammar for syntax highlighting

I will be working together in the tasks but could really use some else who is more comfortable with these tools and familiar with VSCode extension development overall.
SMF spam blocked by CleanTalk