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 - monkey0506

#141
Quote from: Kumpel on Tue 26/01/2016 03:54:45The idea is to change outlandish looking parts of the displayed text into readable words immediately after being displayed to give it a look of instant decyphering.

You would definitely need a custom speech function for this to work anyway. What would probably work would be to use SayBackground, as you said, and catch the returned Overlay*. I don't actually know what happens if you remove that Overlay prematurely, but it's most likely fine. If the engine doesn't like that, then you'd have to calculate the size and position of the Overlay yourself (IIRC the PersistentSpeech module does this, by way of reference, or you could look to the engine source directly).

In either case, you would start a timer (int variable declared at the top of your module script, don't use the built-in SetTimer for this) when your custom speech function is first called, and update the timer in rep_ex_always. At certain intervals (1 second real time = GetGameSpeed()), you could do a "partial" decryption of the text, remove the old Overlay, and call SayBackground or draw a new Overlay (whichever works, I haven't tried this). You can do this in multiple phases, or just in two steps (encrypted and deciphered), but I wanted to make the point that you can't actually do this "instantly" or the player would probably never see the encrypted text (1 frame is a very short time to display anything, 1/40th of a second by default).

An additional benefit to the custom speech function is that (assuming you have a two-way cipher) you could encrypt the text directly in your speech function, so you could have a call such as:

Code: ags
player.SayEncrypted("Hello World!");


That would first encrypt the text, display the encrypted text, then show the text being deciphered.
#142
Quote from: Crimson Wizard on Mon 25/01/2016 14:12:55I got carried away with work on 3.3.5.



You're doing an amazing job CW. := I asked about this in part because I have time and motivation to try and get back into contributing... whatever little bit I can. In the mean time, are there any specific tasks that need undertaking that I could help with?
#143
3.3.5.1 loads fine on my home computer. Perhaps there is a mismatch in Native DLL version?
#144
Understood. I was mostly wanting to expose some additional properties that aren't available at run-time. Specifically, the "say" flag for dialog options and maybe the dialog bullet setting. I looked into the "say" flag a bit, and AFAICT it shouldn't impact anything else, just whether the option is spoken when the interaction is processed. I'm needing read and write access to that flag. The dialog bullet setting I really only need read access to so that I can initialize my module's bullet property.

FWIW, these would seem to complement the other changes to custom dialog option rendering (which is exactly what I need them for). I will take a look at this later, and base my changes on 3.4.0 branch.
#145
This version is feature-locked, yes? If I want to add a new feature, should I push up a 3.4.1 development branch? Should that first wait for 3.4.0 to pull in changes from master?

@cat: I'm not certain, but I seem to recall some quirkiness about MIDI audio... there might be something to be done, but I'm not sure (didn't want you to feel that you were ignored).
#146
*bump*
#147
Gives an error when loading:

Quote---------------------------
Error
---------------------------
An unexpected error occurred trying to start up the AGS Editor. Please consult the details below and post the error to the AGS Technical Forum.

System.IO.FileLoadException: Could not load file or assembly 'AGS.Native, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail. (Exception from HRESULT: 0x800736B1)

File name: 'AGS.Native, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' ---> System.Runtime.InteropServices.COMException (0x800736B1): The application has failed to start because its side-by-side configuration is incorrect. Please see the application event log or use the command-line sxstrace.exe tool for more detail. (Exception from HRESULT: 0x800736B1)

   at AGS.Editor.NativeProxy..ctor()

   at AGS.Editor.NativeProxy.get_Instance()

   at AGS.Editor.ApplicationController..ctor()

   at AGS.Editor.Program.startupTimer_Tick(Object sender, EventArgs e)

   at System.Windows.Forms.Timer.OnTick(EventArgs e)

   at System.Windows.Forms.Timer.TimerNativeWindow.WndProc(Message& m)

   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)




---------------------------
OK   
---------------------------

Then hangs on splash screen. Have to kill it from Task Manager. 3.3.4 runs fine on the same system.
#148
AGS affords some special privileges to eModeWalkto, but there's currently no way of "catching" this as an event on a per-object (hotspot, etc.) basis. What you can do instead is simply intercept it sooner, in your on_mouse_click function:

Code: ags
function on_mouse_click(MouseButton button)
{
  if (button == eMouseLeft)
  {
    // ...blah blah...
    LocationType locType = GetLocationType(mouse.x, mouse.y);
    if (((locType == eLocationHotspot) || (locType == eLocationObject)) && (mouse.Mode == eModeWalkto))
    {
      mouse.Mode = eModeInteract; // force interaction with hotspots and objects, rather than walking to them
    }
    ProcessClick(mouse.x, mouse.y, mouse.Mode); // newer versions use Room.ProcessClick
  }
  // ....
}
#149
You can accomplish this (WARNING: it's not "simple"!) if you're willing to render the GUI buttons yourself as an alternative to setting them up in the editor. This is similar to what you have to do to add controls to a custom dialog "GUI" (see custom dialog options rendering in the manual). Note that you lose a lot of the built-in GUIControl functionality with this method (like GUIControl.GetAtScreenXY), so you'll have to compensate for those factors as well. I would recommend setting up a GUI with whatever other controls you need. Create a DynamicSprite to use for the GUI background graphic, then you can use that sprite to draw the extra controls onto.

For each button, you'd need to know things like NormalGraphic, MouseOverGraphic, PushedGraphic, and possibly a DisabledGraphic. It's not officially released yet, but assuming you are using AGS 3.4.0.3 or later, you can use a managed struct for this. Otherwise, you'll have to create a dynamic array for each member separately (code below uses 3.4.* features).

Spoiler
Code: ags
// header
managed struct DynamicButton
{
  import static DynamicButton* GetAtScreenXY(int x, int y);
  GUI *OwningGUI; // TODO: protect me, probably using an attribute and a protected member
  int X;
  int Y;
  int NormalGraphic;
  int MouseOverGraphic;
  int PushedGraphic;
  int DisabledGraphic;
  bool Enabled;
};
[close]

Spoiler
Code: ags
// script -- basic definitions and GetAtScreenXY
DynamicSprite *guiBackground[]; // one per GUI that has dynamic buttons
DynamicButton *guiButtons[]; // one per dynamic button (regardless of owning GUI)
int guiButtonCapacity = 0; // total capacity of guiButtons array
int guiButtonCount = 0; // total dynamic buttons in game

DynamicButton* CreateDynamicButton(GUI *owningGUI)
{
  // TODO: check if guiBackground sprite exists for owningGUI, create it if not
  // TODO: check guiButtonCapacity, resize array if needed
  int i = guiButtonCount;
  guiButtonCount++;
  guiButtons[i] = new DynamicButton;
  guiButtons[i].OwningGUI = owningGUI; // TODO: write an extender method to set the protected data, instead of using a public member
  // etc., TODO: initialize other members?
  return guiButtons[i];
}

static DynamicButton* DynamicButton::GetAtScreenXY(int x, int y)
{
  // doesn't account for z-ordering, overlap, etc.
  for (int i = 0, x1, x2, y1, y2, graphic; i < guiButtonCount; i++)
  {
    if (!guiButtons[i].OwningGUI.Visible)
    {
      continue; // owning GUI not visible, ignore this button
    }
    x1 = guiButtons[i].OwningGUI.X + guiButtons[i].X;
    x2 = guiButtons[i].OwningGUI.X + guiButtons[i].OwningGUI.Width; // temporary x2
    y1 = guiButtons[i].OwningGUI.Y + guiButtons[i].Y;
    y2 = guiButtons[i].OwningGUI.Y + guiButtons[i].OwningGUI.Height; // temporary y2
    if (!guiButtons[i].Enabled) // disabled, there is only one graphic to check
    {
      x2 = x1 + Game.SpriteWidth[guiButtons[i].DisabledGraphic];
      y2 = y1 + Game.SpriteHeight[guiButtons[i].DisabledGraphic];
    }
    if ((x < x1) || (x > x2) || (y < y1) || (y > y2))
    {
      continue; // coords out of range for this button
    }
    // the button graphics might have different widths, we need to determine which one we're looking at
    graphic = guiButtons[i].MouseOverGraphic;
    if (mouse.IsButtonDown(eMouseLeft))
    {
      graphic = guiButtons[i].PushedGraphic;
    }
    x2 = x1 + Game.SpriteWidth[graphic];
    y2 = y1 + Game.SpriteHeight[graphic];
    if ((x <= x2) && (y <= y2))
    {
      return guiButtons[i]; // x1 and y1 were already validated earlier
    }
  } // end of for loop
  return null; // no matching button found!
}
[close]

Spoiler
Code: ags
// script -- render loop
function repeatedly_execute_always()
{
  // TODO: split this out to a separate function to avoid clutter
  // TODO: check paused state and interface enabled state?
  // TODO: clear GUI backgrounds and draw their appropriate background sprites, etc., for all visible GUIs with dynamic buttons
  DynamicButton *bat = DynamicButton.GetAtScreenXY(mouse.x, mouse.y);
  for (int i = 0, graphic; i < guiButtonCount; i++)
  {
    if (!guiButtons[i].OwningGUI.Visible)
    {
      continue; // owning gui not visible, ignore this button
    }
    DrawingSurface *surface = guiBackground[guiButtons[i].OwningGUI.ID].GetDrawingSurface(); // TODO: get appropriate background sprite if not just allocating one per GUI in the game
    // TODO: this is probably a terribly inefficient way of doing this, grabbing the surface just to draw one button, then releasing it
    // -- consider pooling DynamicButtons in relation to their owning GUI somehow
    graphic = guiButton[i].NormalGraphic;
    if (!guiButton[i].Enabled)
    {
      graphic = guiButton[i].DisabledGraphic;
    }
    else if (bat == guiButton[i]) // may need null check here? (I forget how AGS handles this)
    {
      if (mouse.IsButtonDown(eMouseLeft))
      {
        graphic = guiButton[i].PushedGraphic;
      }
      else
      {
        graphic = guiButton[i].MouseOverGraphic;
      }
    }
    surface.DrawImage(guiButton[i].X, guiButton[i].Y, graphic);
    surface.Release();
  }
}
[close]

Spoiler
Code: ags
// script -- click handlers

function gMyGui_OnClick(GUI *theGUI, MouseButton button)
{
  DynamicButton *bat = DynamicButton.GetAtScreenXY(mouse.x, mouse.y);
  if ((bat != null) && (bat.OwningGUI == theGUI))
  {
    // TODO: clicked on a dynamic button, handle that!
  }
  // TODO: handle other clicks on the GUI as normal
}
[close]

All that said, it's definitely much simpler, and probably more efficient in the long run to just stick to a fixed preset of buttons set up in the editor. The concept isn't totally flawed, but it would be a real beast to make this work properly in a manageable, efficient way. (I copied a fair amount of the concept from other code I'm working on with dialog options rendering, or else I may have omitted the code samples altogether)
#150
Even with the restriction, they're really very useful to have. It allows much more dynamic coding, like the dialog snippet I provided above. The alternative of public fields like ScrollingDialog.DownArrowPaddingTop, or worse, a public function like ScrollingDialog.SetDownArrowPaddingTop(int) seems really nasty in comparison. When we can have pointers as members of managed structs, and hopefully someday features like constructors and destructors, then obviously it will be all the more useful to have. Nevertheless, this (mostly) plugs a long-standing hole in the language, and I, for one, am grateful for that much. :=
#151
As a general rule of thumb, if you need to pass it as a function parameter or store it as a struct member, try to use managed struct so you can use pointers. However, managed struct cannot store pointers itself -- including String and dynamic arrays. There are ways of coping with that limitation of no pointers, depending what exactly you need, but that varies greatly on a case-by-case basis.

For example, if you can guarantee that the text won't exceed 200 characters, you can get away with faking a String member of a managed struct by using attributes:

Spoiler
Code: ags
// header

managed struct MyStruct
{
  import attribute String Name;
  protected string name; // where the data is actually stored, protected to prevent unwanted access
  // TODO: protected bool isNull; -- if preserving the "null" String state is important
};


Code: ags
// script

String get_Name(this MyStruct*)
{
  return this.name; // string -> String conversion is automatic
}

#ifdef STRICT_STRINGS
import void StrCopy(string dest, const string source); // import this if "Enforce new-style strings" is selected
// import other old-style string functions as needed, but for the most part you can get by with the new-style String methods and only use StrCopy just-in-time to save the results
#endif // STRICT_STRINGS

void set_Name(this MyStruct*, String value)
{
  if (value == null)
  {
    value = "";
  }
  else if (value.Length > 200)
  {
    // AbortGame("Attempted to set Name of more than 200 characters! Name must not exceed 200 characters max!"); // hard method
    value = value.Truncate(200); // soft method
  }
  StrCopy(this.name, value);
}


Code: ags
// usage

MyStruct *ms = new MyStruct;
ms.Name = "Hello";
ms.Name = ms.Name.Append(" World!");
Display("%s", ms.Name); // displays Hello World!
[close]

Dynamic arrays can't really be worked around in any reasonable way (creating some kind of memory pool would necessitatively eat up more memory to avoid copying/resizing than just using a static array, so it's self-defeating). Custom struct pointers could be worked around using a memory pool, similar to the String cache in the Stack module (but would require manual destruction or the memory pool could grow unnecessarily large). And likewise for Strings if you don't want to enforce the 200-char restriction.

So, there are options, but considering how long AGS got along without even being able to create pointers to custom structs, I'd say that you can probably manage without pointers inside managed structs for the time being...
#152
If it weren't for the fact that Java allows you to call methods directly on the result of a function, then I'd be inclined to call you a liar. Properties/attributes are really nice when you find yourself using compound operators like ++ or += frequently. Incremental assignment with direct accessor calls is beyond annoying.
#153
I use all of these features extensively in my modules, and I'm sure I've talked about most of them in some capacity. Things like attributes are definitely not something I'd consider essential to the course of your everyday adventure game project, and yet without them, code such as the following would be impossible:

Code: ags
  ScrollingDialog.ScrollArrows.Float = eFloatRight;
  ScrollingDialog.ScrollArrows.Up.NormalGraphic = 10;
  ScrollingDialog.ScrollArrows.Up.MouseOverGraphic = 9;
  ScrollingDialog.ScrollArrows.Up.PushedGraphic = 10;
  ScrollingDialog.ScrollArrows.Up.Padding.Top = 4;
  ScrollingDialog.ScrollArrows.Up.Padding.Bottom = 2;
  ScrollingDialog.ScrollArrows.Up.Padding.Right = 5;
  ScrollingDialog.ScrollArrows.Down.NormalGraphic = 8;
  ScrollingDialog.ScrollArrows.Down.MouseOverGraphic = 7;
  ScrollingDialog.ScrollArrows.Down.PushedGraphic = 8;
  ScrollingDialog.ScrollArrows.Down.Padding.Top = 2;
  ScrollingDialog.ScrollArrows.Down.Padding.Right = 5;


(That's actual, functioning code I'm working on for A Night at Camp Ravenwood, btw)

And I agree that a feature like attributes deserves an explanation in the manual.
#154
P.S. readonly predates 2.7 (confirmed that it existed in 2.62). Everything else that you asked about (except autoptr) was introduced in 2.7 (protected, writeprotected, managed, and attribute!! attributes can be used by user scripts in 2.7, but the accessors must be imports as extenders didn't come about until 3.0). autoptr was introduce in 2.71, with the String type (which is autoptr). :=

This means that literally everything you asked about is:

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55features and keywords from 10 years ago

Sauce 8-)  "2006-01-07 - ags_271.zip"
#155
Yes, most things can be stringified in some meaningful way. If you have to store a GUI*, then you could stringify it by using the GUI.ID. If you don't mind having a separate script for your struct definition though, you could do this:

Code: ags
// header

autoptr managed struct Thing
{
  import attribute GUI *TheGUI; // attributes are still okay, because they're functions, not actual pointers!
  protected int guiID; // int defaults to 0, so this will be GUI.ID + 1 instead -- 0 will be null GUI*
  // blah
};

import bool PushThing(this Stack*, Thing thing, int index=SCR_NO_VALUE, bool insert=true);
import Thing PopThing(this Stack*, int index=SCR_NO_VALUE, bool remove=true);


Code: ags
// script

GUI* get_TheGUI(this Thing*)
{
  if (this.guiID == 0)
  {
    return null;
  }
  return gui[this.guiID - 1];
}

void set_TheGUI(this Thing*, GUI *value)
{
  if (value == null)
  {
    this.guiID = 0;
  }
  else
  {
    this.guiID = value.ID + 1;
  }
}

bool PushThing(this Stack*, Thing thing, int index, bool insert)
{
  this.PushGUI(thing.get_TheGUI(), index, insert); // inside THIS SCRIPT ONLY, we must use the accessor functions directly
  // ...
  return true; // TODO: return correct result based on whether all members were pushed
}

Thing PopThing(this Stack*, int index, bool remove)
{
  Thing thing = new Thing;
  thing.set_TheGUI(this.PopGUI(index, remove));
  // ...
  return thing;
}


Code: ags
// some other script

Thing myThing = new Thing;
myThing.TheGUI = gMyThingGUI;
Stack stack;
stack.PushThing(myThing);
// ...
#156
Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55and yet for years Monkey has been doing stuff

Long before Chris Jones even considered opening up the AGS source code, I've prided myself on pushing the limits of what could and could not be done. I discovered a lot of hidden potential, well before it could be seen on Github. :=

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55- what's the difference between protected, writeprotected, and readonly?

protected: This struct member can only be accessed from within a struct function (including extender functions) by using the this keyword:

Spoiler
Code: ags
struct MyStruct
{
  protected int bar;
  import void Foo();
};

void MyStruct::Foo()
{
  this.bar = 5; // inside a struct function, using this keyword -- fine
  Display("%d", this.bar); // fine
}

MyStruct myStruct; // instance
myStruct.bar = 10; // outside a struct function, not using this -- will not compile!
Display("%d", myStruct.bar); // not using this, will not compile
[close]

writeprotected: Like protected, except you can get the value from anywhere. Only writing the value is protected.

Spoiler
Code: ags
struct MyStruct2
{
  writeprotected int bar;
  import void Foo();
};

void MyStruct2::Foo()
{
  this.bar = 5; // using this, fine
  Display("%d", this.bar); // fine
}

MyStruct2 myStruct2;
myStruct2.bar = 10; // writeprotected -- will not compile
Display("%d", myStruct2.bar); // reading the value -- fine
[close]

readonly: A readonly member can never be written to, even if using this. It is not useful for regular members at all, and is only useful for attributes.

Spoiler
Code: ags
struct MyStruct3
{
  readonly int bar;
  import void Foo();
};

void MyStruct3::Foo()
{
  this.bar = 5; // will not compile
  Display("%d", this.bar); // fine, but always displays 0 (int default value)
}

MyStruct3 myStruct3;
myStruct3.bar = 10; // will not compile
Display("%d", myStruct3.bar); // fine, displays 0
[close]

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55- what does "autoptr" mean? (please tell me even if it's not supported?) -- when I google "ags autoptr" I only get results from git, but Monkey uses it all the time.

autoptr means automatic pointer, and is useful in AGS when working with managed struct types, which are the only struct types in AGS which you can create pointers to. In AGS, literally all this does is prevent you from having to type the asterisk when defining an instance or reference.

Spoiler
Code: ags
managed struct MyManagedStruct // no autoptr
{
  // ... members ...
};

MyManagedStruct *mms = new MyManagedStruct; // create a new instance, and store a pointer to it (with asterisk)

autoptr managed struct MyManagedAutoptrStruct // with autoptr
{
  // ... members ...
};

MyManagedAutoptrStruct mmas = new MyManagedAutoptrStruct; // creates a new instance, and store a pointer to it (without asterisk)
[close]

Literally everything else about the way you use these pointers is the same.

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55- please direct me to the post about "managed struct" which I can't seem to find anymore? (EDIT: found this)

I'm not sure if there's a specific post you're trying to recall, but let me explain...

managed struct means that AGS is handling the memory management for the instances of this struct. The latest versions of AGS allow user-defined structs to be dynamically instanciated (e.g., with the new keyword), if they are managed structs (this is a relatively recent change, in the 3.4.* branch). Due to the way the AGS compiler works, a dynamic instance cannot have pointers to other dynamic instances, or else it will leak that memory. This is something that hopefully will be changed in future versions of AGS, but for now, a managed struct cannot have any pointers. The built-in types like GUI are permitted to have pointers via a new builtin keyword, but that is because those pointers are guaranteed to be cleaned up by AGS (things like GUIs and GUIControls aren't even dynamically allocated). A builtin struct cannot be dynamically instanciated (nor can it be statically instanciated either, you can only store a pointer to an existing instance, like gui[0]).

Additionally, I will note that historically managed had similar behavior to the way that builtin works now. Most of the modules in which I used the managed keyword, I did so simply to prevent users from instanciating the struct, rather than to indicate that the user was meant to somehow use pointers to that struct (recent modules being an exception). The above example of autoptr should be sufficient to show how to instanciate a managed struct. Everything else works exactly as you would expect when using a pointer to that struct instance (meaning, yes, managed struct pointers are passed by reference when used as function parameters, and can modify the original object!).

Quote from: Monsieur OUXX on Mon 18/01/2016 15:42:55- what's the benefit of adding "attribute", inside a struct? (EDIT: OK so apparently this one is not new at all! I've found this.)

The thread you linked to, and CW here in this thread, both link to the wiki article I wrote on the attribute keyword. For the record, that keyword has existed and worked exactly the same way since AGS 2.7. In fact, if you use the first method the wiki article describes (importing the get_* and set_* functions directly in the struct definition), then you can use attributes in AGS 2.7, no problem. The functionality hasn't changed. You do seem to still be confused as to what it means though, so I'll try to clear that up.

When you have some member like "int Value;" in a struct definition, you are allocating enough memory for an int named "Value". Every struct instance allocates its own memory, so every struct instance has a separate int named "Value". An attribute works similarly, with the important distinction that it does not allocate memory. If it doesn't allocate memory, then what does a statement like "inst.Value = 5;" mean? Well, instead of allocating memory, an attribute generates a function call. Every attribute has at least one function associated to it, the getter. The getter is not defined for you. You must provide one, or else your scripts that rely on it will fail. My personal preference is to define the getter as an extender method. This prevents having to type an import declaration in the script header, preventing clutter, and it also makes sure that the user doesn't see the function. The user seeing the getter function wouldn't hurt them, but it might be confusing. The getter always takes the name of the attribute, with the prefix get_.

Spoiler
Code: ags
// header

struct MyStruct
{
  import attribute int Value; // note that attributes are always imports; they can be protected, writeprotected, readonly, or none of the above
};

// script

int get_Value(this MyStruct*) // getter for MyStruct.Value; return type is the type of Value, and there are no additional parameters
{
  return SOME_VALUE; // TODO: return some meaningful result...
}
[close]

Bear in mind what I said that the attribute does not allocate memory. So now we have a getter function, but what are we getting? With no memory allocated, we don't have a "Value" to return. This can be useful if the value is some calculated result, or even if you're returning some value stored elsewhere, such as if your struct has a reason to return one of the game options, as returned by GetGameOption. You could make your attribute "Value" work as a synonym for calling GetGameOption.

Since "Value" can be written to, we'll also provide a setter, a function to set the value. This also takes the name of the attribute, with the prefix set_.

Spoiler
Code: ags
// header

struct MyStruct
{
  import attribute bool PixelPerfect; // attributes can be of any type that is valid as a return or parameter type
};

// script

bool get_PixelPerfect(this MyStruct*) // return type for PixelPerfect is bool
{
  return GetGameOption(OPT_PIXELPERFECT);
}

void set_PixelPerfect(this MyStruct*, bool value) // setter: return type is void and takes exactly one parameter of the same type as the attribute
{
  SetGameOption(OPT_PIXELPERFECT, value);
}
[close]

I don't mean to reiterate the entire wiki article here, but I just wanted to show that attributes can be used pretty much any place you would otherwise use a function. The real benefit of attributes comes in the fact that they are used like regular data members, not functions.

Spoiler
Code: ags
MyStruct myStruct;
myStruct.PixelPerfect = !myStruct.PixelPerfect; // toggle pixel perfect


Without attributes, this example would look something more like this:

Code: ags
SetGameOption(OPT_PIXELPERFECT, !GetGameOption(OPT_PIXELPERFECT));


These both do the same thing.
[close]

Their importance becomes more apparent when modifying the existing value. Being able to use compound operators like += or -- is much more efficient than nested function calls.

The wiki article goes more into attributes, of course, but as a final note on them I'll point out that a common use case would be to have an attribute and a protected member with similar names. The attribute could be used as the public access to the protected member. When the user sets some value using the attribute, you can check if the value is valid in the setter, and immediately alert the user if something has gone wrong. If all is well, then you can store the value in the protected member using the this keyword to access the member.
#157
I've deleted a rather large post that I had typed up, so please bear with me on this, but in order to be stored into a Stack instance, you have to have some way of referring back to the individual instances of your custom struct type. There are two ways of doing this:

  • Provide serialization methods for your struct to "stringify" it.
  • Make your struct a "managed struct" and use pointers to it (NOTE: this method only works if your struct does not store any pointers, including Strings).

    If you provide serialization methods, then then you do not modify the Stack functions at all:

    Code: ags
    StackData Serialize(this MyStruct*)
    {
      String buffer = String.Format("%d,%f...", this.myInt, this.myFloat);
      return StackData.StringToData(buffer);
    }
    
    bool Deserialize(this MyStruct*, StackData data)
    {
      // explode the CSV string....
      this.myInt = iValue;
      this.myFloat = fValue;
    }


    However, if your struct does not store any pointers, you can simply use extender methods to Push/Pop your type directly:

    Code: ags
    bool PushMyStack(this Stack*, MyStack *value, int index, bool insert)
    {
      bool result = this.PushInt(value.myInt, index, insert);
      result = ((result) && (this.PushFloat(value.myFloat, index + 1, insert)); // TODO: check if index is SCR_NO_VALUE first....
      // ...TODO: lazy eval if any push op fails?
      return result;
    }
    
    MyStack* PopMyStack(this Stack*, int index, bool remove)
    {
      MyStack *result = new MyStack;
      result.myInt = this.PopInt(index, remove);
      result.myFloat = this.PopFloat(index + 1, remove); // TODO: check if index is SCR_NO_VALUE or if removing items, update offset appropriately
      return result;
    }


    Quote from: Monsieur OUXX on Mon 18/01/2016 16:39:07also, an unrelated question: why did you define Grow and set_Capacity as extenders?

    Grow was implemented as an extender for simplicity and logical grouping. Simplicity in that it's simpler to not have to type the import line, and also in that it's simpler to type "this" than "StringCache". Logical grouping, meaning, keeping it grouped together with the StringCache_t type, as AGS does not have namespaces except for struct namespaces.

    set_Capacity was defined as an extender for the purpose of hiding the method. It could easily be imported by any script, but there's really no need to do so as the Capacity attribute is writable. See the wiki article on attributes, particularly, the section on extenders. The point is just to not have the function listed in the script header, and thereby not have it show up in autocomplete or be automatically imported.
#158
Quote from: Neo_One on Sun 17/01/2016 09:41:50
Last versión (3.4.6) the Linux port option has dissapear in general settings. Why?

It seems CW has had some trouble rebuilding for Linux. You can grab the 3.4.0.6 Linux binaries here. Extract to AGS editor folder, and the Linux build option should reappear in General Settings.
#159
It's really kind of a crap-shoot as to whether the user has any given C++ runtime installed, but I could say at least that the 2008 runtime is very common and required to run the engine, so that's probably a safe bet. There appears to be a "universal" runtime that can be distributed as an additional DLL file, though I'm not clear on exactly how that works... I'd rather use that option than revert to an older Visual Studio, but 2008 is a good fallback plan. I'll look into these options and update the plugin builds soon.
#160
Just a head's up on the plugin binaries I've provided... The Windows DLLs were built with Visual Studio 2015, which means that you (and anyone playing your game) must have the Visual C++ 2015 Redistributable installed. This is a one-time installation that will become more common, but may not (as yet) be a "common" installation. I have been trying to better assess whether I need to downgrade the project files and rebuild with an earlier version of Visual Studio (if you know or have advice what the "best practice" here should be, please let me know!).
SMF spam blocked by CleanTalk