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 - Denzil Quixode

#121
It looks to me like it must be desktop-only, because it says that it uses SDL for graphics and that is not something that works over the Web. (The Ruby language has actually been around since 1995 as an obscure general-purpose scripting language, it's only in the last couple of years that someone said "hey, we could use this to power web applications" and that shot it to fame.)
#122
Cool! I'm glad to see you're getting use out of that code already :)

One thing though, do I need a box.net account or something to download it? The URL doesn't seem to give me any obvious option to download the file, I just see a generic landing page
#123
I do plan to, yeah, but I haven't been able to find the time yet.

It's sort of difficult making an effective demo. Unlike a plugin for 3D graphics or something, using Lua will not really make anything possible that used to be impossible - any single example of Lua being used to do something can always be replicated using AGS's standard scripting. The "demo" would really be in looking at how it was coded, not necessarily what it's like for the player.
#124
If you have the full version of Visual Studio, you should have an "Attach to Process..." option in the Debug menu. If you compile a Debug version of the DLL, drop that to the editor folder, run the editor then use "Attach to Process..." to attach to AGSEditor.exe, that should let you set breakpoints and stuff. (On the other hand if you're like me and only have the free "Express" edition, you don't have that "Attach" option.)
#125
Another one - I have an Editor plugin and a Run-Time plugin that I intend to work together as a unit, but the user must treat them as separate things (for example, when enabling/disabling the feature they have to do so in two different places in the project tree). Would it be possible to have some way for one of the plugins to "link" itself as dependent on another one? I don't know exactly the right way to do this. I have some ideas:


  • Editor plugin can get the current state of a Run-Time plugin (whether it is enabled/disabled) and can register a callback so that if the user changes that, the Editor plugin knows about it and can enable/disable itself as well.
  • Run-Time plugin has the option to hide itself from the project tree, and the Editor plugin has the ability to enable/disable it - so the user never has the option to enable the Run-Time plugin if the Editor plugin is not present, and enabling/disabling is done through a custom option on the Editor plugin. (I prefer this idea, it seems more flexible.)
#126
As 3.2 is now entering the RC stage, it is probably too late to start adding wishlist things for it, but anyway...

If a function returns a (pointer to a) struct, you currently must assign that to a variable before you can access its methods and fields. Could that be changed?

For example, say CreateMyStruct() is a function that returns a MyStruct* pointer, and MyStructs have a method called MyMethod(). If all I want to do is create a MyStruct, call a method on it and then no longer care about it, I'd try to do it like this:

Code: ags
CreateMyStruct().MyMethod();


...but the compiler complains that it was expecting a structure before the dot, and will only allow this:

Code: ags
MyStruct* myStruct = CreateMyStruct();
myStruct.MyMethod();


I know that may not seem like much difference, but I would prefer the option if possible.
#127
Quote from: clarvalon on Fri 09/10/2009 10:36:29
  • It doesn't export the xml unless you export it to the same directory as the *.crm files.
That's odd. I can't think of any reason why. Maybe a permissions thing?
Quote
  • It would be preferable to have the background images exported as .png files as opposed to embedded in the xml.
Sure. Change this bit:
Code: ags
for (int j = 0; j < room.BackgroundCount; j++)
{
	using (Bitmap bmp = new Bitmap(room.Width, room.Height))
	using (Graphics g = Graphics.FromImage(bmp))
	{
		editor.RoomController.DrawRoomBackground(g, 0, 0, j, 1);
		using (MemoryStream pngStream = new MemoryStream())
		{
			bmp.Save(pngStream, ImageFormat.Png);
			output.WriteStartElement("Background");
			output.WriteAttributeString("ID", j.ToString());
			byte[] buf = pngStream.ToArray();
			output.WriteBase64(buf, 0, buf.Length);
			output.WriteEndElement();
		}
	}
}

...to something like this (untested):
Code: ags
for (int j = 0; j < room.BackgroundCount; j++)
{
	using (Bitmap bmp = new Bitmap(room.Width, room.Height))
	using (Graphics g = Graphics.FromImage(bmp))
	{
		editor.RoomController.DrawRoomBackground(g, 0, 0, j, 1);
		bmp.Save("room" + room.Number + "-bg" + j + ".png", ImageFormat.Png);
	}
}

Quote
  • Is it possible to also export the walkable areas, hotspots etc. as .pngs?
Oh, I forgot about those. Hmm. Yes, but with a bit of a complication - that function I'm using above to draw the room background (editor.RoomController.DrawRoomBackground) has some optional parameters at the end I'm not using that add those things - but unfortunately it looks like you can only have them drawn on top of a background image, not just on a black background, so you would have to manually separate the background, which gets even more complicated if your background happens to use the exact same colour as one of the regions...
QuoteDo you have any objection to me playing around with the source code to overcome these minor issues?  The thought occurs that using the Plugin I could automate a whole lot more of the pre-conversion preparation, which would make things even simpler/quicker.  Many thanks for your help!
Please, feel free. I hereby release that export plugin source code as public domain.
#128
Hi clarvalon, I've created an editor plugin that dumps room information for you. I'm afraid I have no idea how good the performance is on a large game, because I don't have the source files for one to try it out on. I made it quite quickly and wouldn't be surprised if it has problems, but I hope it is of some help to you.

Download it from:

If all goes well the plugin in that folder should add an "Export" menu to the AGS editor with a "Room Data" option. I developed this plugin against version 3.1.2SP1, I don't know whether it works on other versions.

The backgrounds are dumped as Base64-encoded PNG image data blocks in the XML.

I've included the C# project source code as well as the DLL in the zip in case you or anyone else want to take a look at it.
#129
OK! It's time to publicise the new version of Lua for AGS.
I've updated the first post a bit and set up this place as the official site for now: http://lua-for-ags.wikispaces.com/

There's more details there but here are some of the main changes:

IDE Features
  • Nicer Lua code editor
  • Syntax check button on the toolbar (clicking it when you are editing a script that tells you whether there are any syntax errors)
  • Subfolders in "Lua Scripts" for easier organisation

    Accessing Lua From AGS-Script
  • Changed syntax to call Lua functions.
    Code: ags
    LuaValueList* lparams = Lua.NewValueList();
    lparams.Add(Lua.StringValue("test"));
    lparams.Add(Lua.IntValue(255));
    LuaValueList* lresults = Lua.Call("My_Function", lparams);
    int result = lresults.AsInts[1];

  • LuaValueLists also used for getting and setting global variables with Lua.GetVar() and Lua.SetVar()

    Accessing AGS-Script From Lua
  • "ags" table that contains the standard global AGS functions and some AGS global variables.
    Code: ags
    ags.GiveScore(100)
    ags.cEgo:Say("Woohoo!")

  • Ability to attach custom Lua fields and methods to individual AGS objects


    Misc.
  • The weight that Lua for AGS adds to a save game file has been reduced - the "baseline" (when you have not added any of your own functions or variables to Lua) is now just 1.5kb.
#130
Is repeatedly_execute_always() guaranteed to either


  • ...get run before/after repeatedly_execute() (or on_key_press() etc.) if that function does not make any blocking calls, or
  • ...get run while one of those functions is waiting for a blocking call to return (and the blocking call will not return while repeatedly_execute_always() is still running)?
In other words, am I right in thinking that even though it is run in a different execution thread, calls to repeatedly_execute_always() are not run at completely unpredictable times that could potentially overlap with any part of the "other" code?

The reason that I ask is I want to be able to call blocking AGS functions from some of my plugin functions, and I'd also like to be reasonably confident that other plugin functions will work safely if called from repeatedly_execute_always(). So, I could potentially have one plugin function waiting for a blocking call to finish, at the same time that another plugin func gets called. If all I need to do is be careful that my plugin functions are "re-entrant" at the point that they make any blocking calls, I think I'm already all right, but if I actually need to make them all fully re-entrant that makes things trickier...
#132
Ah, okay, I was just impatient then :) Great! Thank you for clarifying, I see what's going on now.

(I suppose it could conceivably be a problem if the plugin allocated a lot of memory for each of these objects, right? Since AGS knows the number of managed objects, but not how much memory they are using, individually. That isn't the case here, though - I'm just thinking out loud.)
#133
I have a question about what happens, or should happen, when a plugin function returns a pointer to a custom script managed object, and that pointer does not get assigned to a variable. It seems like this causes problems with reference counting - but maybe I am just doing something wrong...?

So, say I have a plugin that adds to the script header:

Code: ags
struct MyLib {
  // ... static methods ...
  import static ResultObject* PerformAction();
};


MyLib.PerformAction() is a plugin function that returns an object that contains information about the result of this action. Sometimes it might be useful to look at this object, to make sure the results are what is expected:

Code: ags
function my_ags_function()
{
  ResultObject* result = MyLib.PerformAction();
  // ... examine fields of "result" ...
}


...and that works fine. I've put a breakpoint in the Dispose() method of the IAGSScriptManagedObject that controls ResultObjects, and it seems to consistently get called as my_ags_function() completes.

But sometimes you might not care about anything in the ResultObject, so you wouldn't bother to assign the return value to a result variable:

Code: ags
function another_ags_function()
{
  MyLib.PerformAction();
}


The problem is that in this case, the Dispose() breakpoint for the ResultObject doesn't seem to get hit until the game is terminated. This could be a big problem if MyLib.PerformAction() gets called from repeatedly_execute() or something like that - hundreds of ResultObjects would get created every minute, eventually taking up way too much memory. I'm not sure how to combat this.

It also seems to happen if I want to use one function call directly inside the parameter for another function call. For example, say I have another function void MyLib.ErrorIfResultIsBad(ResultObject* resultObj); which checks a ResultObject and shows an error if there is a problem, or otherwise does nothing, and I want to use it like this:

Code: ags
function yet_another_ags_function()
{
  MyLib.ErrorIfResultIsBad( MyLib.PerformAction() );
}


...the Dispose() for the ResultObject returned by MyLib.ReturnAction() here again does not seem to get called until the game is quit.
#134
That would be very useful! Could there be an AGSE_BEFORESAVE, as well? (The way I'm storing managed objects means I don't always know immediately when I should be decrementing the reference counts, but I can force a full "sweep" at a moment when it isn't likely to cause a noticeable pause, which is why I was asking about calling DecrementRefCount in AGSE_SAVEGAME before - AGSE_BEFORESAVE would be good for that.)
#135
Quote from: Pumaman on Mon 21/09/2009 21:56:33
Not currently, no. I'll add this to my list.
Thanks!

QuoteThe script interpreter is in an inconsistent state during the save, so I would strongly advise not calling any methods like that in the middle of the AGSE_SAVEGAME event, as it could well lead to memory corruption.

Okay, good thing I asked, I will avoid that then.

When is the right time to use engine->GetManagedAddressByKey() to "link up" related managed objects as the player loads up a savegame? The Plugin docs say "after de-serialization", but there doesn't seem to be a good way to say "OK, I want to do this bit right after de-serialization" - AGSE_RESTOREGAME and the Unserialize() method of the Object reader class are both called before/during de-serialization, and they are the only relevant places, as far as I can make out.
#136
Okay... I promise this is the last one  :=


Is it possible for an editor plugin to request to be notified whenever any room has just been saved? Sort of like an equivalent for IEditorComponent.BeforeSaveGame() for the current room instead of the whole game. I noticed there is a "RoomModifiedChanged" event on _editor.RoomController.CurrentRoom, is there a way to use that for any room?

(The reason I ask is this: I would really like the plugin to be able to know the script names of each hotspot and object in all of the rooms, at the time that IEditorComponent.BeforeSaveGame() is called. All the other (non-room-specific) script names for things are quite easy to get hold of, but these are a challenge. Now... I know that the way that the AGS Editor works, you only have one room open at a time. Loading every room with _editor.RoomController.LoadRoom() in turn and then using _editor.RoomController.CurrentRoom to get at this info does seem to work OK - if done inside IEditorComponent.RefreshDataFromGame(), before the user will have had a chance to open a room editor themselves. Doing it at any other point is a bad idea since the user won't be expecting the inevitable "Do you want to save changes to this room?" message, and will probably just cancel it (then immediately uninstall the plugin).

So, my idea was for the plugin to maintain its own set of simple records about all the rooms. First, these records would get created and populated in IEditorComponent.RefreshDataFromGame(), then whenever a room is saved, the plugin would replace only the record for the current room. If there is a much simpler way to do all this, please let me know.)


Edit: Promise already broken... I have 2 questions related to the order that things happen, internally:

1) I have an Editor plugin and a Runtime plugin, working as a team. When you save changes to a game in the AGS Editor, the Editor plugin's IEditorComponent.BeforeSaveGame() currently always seems to get run before AGS_EditorSaveGame() in the Runtime plugin - which is good, that's what I want, but is it unwise to rely on it always happening that way around?

2) I hope this one makes sense: In a Runtime Plugin, does it work to call engine->DecrementManagedObjectRefCount() on a managed object (like e.g. an Overlay) during the plugin's handler for the AGSE_SAVEGAME event? My worry is if it is "too late" at that point, and the managed object is already serialized, so when the game gets loaded again the object gets deserialized into memory with a nonzero RefCount, causing a memory leak. Or is it even dangerous to call DecrementManagedObjectRefCount from this context?
#137
QuoteAssuming your content panel inherits from the AGS.Types.EditorContentPanel class, there is a OnCommandClick method that you can override to get these notifications.
Ah, cool! That works perfectly. (I was only trying the CommandClick on the IEditorComponent...)
Quote3.2 beta 5 adds the IGUIController.ActivePane property to allow you to find this out.
Nice! :D
#138
Ah, oops  :-[. Sorry, SSH - I haven't actually tried a lot of modules or plugins myself before, so I'm not really aware what else is already out there. I should probably do that before I try to give more examples...
#139
I just thought of another example. One thing that I think is quite common is you'll have a list of messages in response to some action, and you'd like to select one each time according to a pattern, like:

  • Pick each one in turn, until the last one, then just keep showing that one
  • Pick each one in turn, then return to the first when you go past the end
  • Pick one at random
This is how you might implement the first one in AGS:

Code: ags
//(AGS-Script)

int MESSAGE_INC = 0;
#define LAST_MESSAGE 5
String NextMessage() {
	if (MESSAGE_INC < LAST_MESSAGE) {
		MESSAGE_INC = MESSAGE_INC + 1;
	}
	if (MESSAGE_INC == 1) {
		return "I don't want to.";
	}
	if (MESSAGE_INC == 2) {
		return "I really don't want to.";
	}
	if (MESSAGE_INC == 3) {
		return "I really REALLY don't want to.";
	}
	if (MESSAGE_INC == 4) {
		return "Okay. Oh, wait, no.";
	}
	if (MESSAGE_INC == 5) {
		return "No.";
	}
}

It's easily possible, but you do have to deal with numbers all the time, which feels sort of unnecessary. If you want to add or remove one early on in the list, you have to go through fixing the rest of the numbers, and remembering to set LAST_MESSAGE as well.

The second pattern is really just a minor change to the first one, so I won't bother with that. What about random order?
Code: ags

//(AGS-Script)
String RandomMessage() {
	int rand = Random(4);
	if (rand == 0) {
		return "Boring.";
	}
	else if (rand == 1) {
		return "Irrelevant.";
	}
	else if (rand == 2) {
		return "Mind-numbing.";
	}
	else if (rand == 3) {
		return "Yawn.";
	}
        else if (rand == 4) {
                return "It's a picture of Gordon Brown.";
        }
}

Still dealing with a lot of numbers, although it's never a pain to add more in this case, since order doesn't matter.




With Lua, I would first write a script that adds "selector factories" - special reusable functions that do the grunt work of implementing these patterns for you - and then use them to create NextMessage and RandomMessage (which will be ordinary Lua functions that you can call with Lua.Call()):

Code: ags
--(Lua)

NextMessage = selector.oneshot({
	"I don't want to.";
	"I really don't want to.";
	"I really REALLY don't want to.";
	"Okay. Oh, wait, no.";
	"No.";
})

RandomMessage = selector.random({
	"Boring.";
	"Irrelevant.";
	"Mind-numbing.";
	"Yawn.";
        "It's a picture of Gordon Brown.";
})


With the message lists written like this, you can very easily add, remove, and reorder the items, and also, change one selector pattern to another. Again - nothing that is impossible to do in AGS-Script - but you probably wouldn't bother.

(Like the NonBlockingCutscene stuff in the previous example, selector.oneshot and selector.random are not standard existing functions, I will include a script that implements them in the next version.)
#140
I am hoping to get a new version out in the next couple of days. In the meantime I'm going to return to that question of why using Lua in AGS would ever be useful. One Lua feature that I haven't mentioned yet is coroutines. I'm not going to describe what they actually are here, or how to directly create and use them - instead, I'm going to try to outline something quite tricky to do in AGS that could be easier to do with Lua, and (for now) just say that coroutines are the magic that would let it work.




Say you're making a game where the PC is a member of crew on board a space ship. You want to give the impression in-game that the crew is a real community of people having independent lives. You don't want the NPCs to just stand/sit around, waiting for the PC to approach them before they do anything. They may move around, from room to room, start conversations of their own that you can eavesdrop on (or wander away from, if you're not interested). If you interrupt people in the middle of a conversation, they might carry it on where they left off, later. And so on.

An important part of being able to achieve that is being able to easily set up a chain of simple events that run like a cutscene, but unlike a cutscene, you don't want the game to block while it's going on - there might even be several of these things going on simultaneously in the same room, and the PC should be free to walk around and do stuff. Let's call them non-blocking cutscenes.

So, how would you implement that? Let's start with a fairly simple chain of events like: an NPC walks across the room to a particular point, pauses a second, runs an animation loop, walks across to another point, pauses again, faces the PC, says something, then walks out of the room. The best way I can see how to do it is to split this up into a sequence of steps, give them all a number, and do a big if-else-if block in the main script's repeatedly_execute() function, where each step waits for the previous one to finish before continuing. Something like this:

Code: ags
//(AGS Script)

// Counter to keep track of where we are in the sequence
int SEQUENCE_1_STEP = 0;
// Timer ID to use when waiting for NPC to finish pausing
#define PAUSE_TIMER  10
// Overlay for background speech
Overlay* bgSpeechOverlay;

function repeatedly_execute() {
	// Set SEQUENCE_1_STEP to 1 to kick things off
        if (SEQUENCE_1_STEP == 0) {
                // do nothing
        }
	else if (SEQUENCE_1_STEP == 1) {
		// Walk over to a point...
		cNPC.Walk(50, 100, eNoBlock);
		SEQUENCE_1_STEP = 2;
	}
	else if (SEQUENCE_1_STEP == 2) {
		if (!cNPC.Moving) {
			// ... pause ...
			SetTimer(PAUSE_TIMER, 80);
			SEQUENCE_1_STEP = 3;
		}
	}
	else if (SEQUENCE_1_STEP == 3) {
		if (IsTimerExpired(PAUSE_TIMER)) {
			// ... run an animation ...
			cNPC.Animate(3, 1, eOnce, eNoBlock);
			SEQUENCE_1_STEP = 4;
		}
	}
	else if (SEQUENCE_1_STEP == 4) {
		if (!cNPC.Animating) {
			// ... walk across to another point ...
			cNPC.Walk(240, 80);
			SEQUENCE_1_STEP = 5;
		}
	}
	else if (SEQUENCE_1_STEP == 5) {
		if (!cNPC.Walking) {
			// ... pause ...
			SetTimer(PAUSE_TIMER, 80);
			SEQUENCE_1_STEP = 6;
		}
	}
	else if (SEQUENCE_1_STEP == 6) {
		if (IsTimerExpired(PAUSE_TIMER)) {
			// ... face the PC ...
			cNPC.FaceCharacter(cEgo, eNoBlock);
			SEQUENCE_1_STEP = 7;
		}
	}
	else if (SEQUENCE_1_STEP == 7) {
		if (!cNPC.Animating) {
			// ... say something ...
			bgSpeechOverlay = cNPC.SayBackground("...are you responsible for this?");
			SEQUENCE_1_STEP = 8;
		}
	}
	else if (SEQUENCE_1_STEP == 8) {
		if (!bgSpeechOverlay.IsValid) {
			bgSpeechOverlay = null;
			// ... then walk out of the room.
			cNPC.Walk(0, 150, eNoBlock);
			SEQUENCE_1_STEP = 9;
		}
	}
	else if (SEQUENCE_1_STEP == 9) {
		if (!cNPC.Moving) {
			cNPC.ChangeRoom(10);
                        SEQUENCE_1_STEP = 0;
		}
	}
}


So that's the first one done. In about sixty lines of code, with an int and an Overlay* to keep track of. We've also used up one of only 20 available timers. It is starting to look daunting to have dozens of these throughout the game. Now, obviously, you can start trying to make it a bit more managable by moving this code out of repeatedly_execute() into a new script, dedicated to these non-blocking cutscene things, and have repeatedly_execute() just call a handler function for each one in turn. You can try to squish each if-else-if step to be on a single line, to try and make it easier to see what's going on throughout the sequence at a glance, but that will probably end up making it even less readable. In order to get around that limited number of timers, you could try using another int variable, that you decrement every turn during the right SEQUENCE_x_STEP until it reaches zero. But it's starting to get annoying just naming all these variables.

It's also no fun tinkering with the sequences, since adding or removing steps means changing all of the SEQUENCE_x_STEP numbers after that point, which takes ages and it's easy to make a mistake. Then there's one sequence where you'd like to repeat part of it in the middle three or four times before continuing, but there's no way of doing it that feels right. You could just copy and paste the steps, then fix up all the SEQUENCE_x_STEP numbers (time-consuming, since there's quite a lot of steps involved - and also, what if you decide later you want to change the number of loops, or alter the steps inside the loop...?). Or, you could create another decrementing int variable, one that counts down the number of loops and decides whether to send SEQUENCE_x_STEP "back in time" to the beginning of the loop or not (which would hurt code-readability even more, as you have to read it back carefully to see where the loop starts and finishes - also, if you want to run the whole sequence multiple times, it's easy to forget to reset this loop variable, so the looped part only plays once the next time...)




Okay, enough hypothetical lecturing, I'm sure you get the picture - this is a case where you'd probably end up compromising quite heavily, to avoid making too much work for yourself, and to keep the code from getting unmanageable. So how could Lua help?

(First of all, this isn't yet possible with the currently-released version, because you can't call AGS system functions from Lua. But you will be able to in the next one.)

I would write a Lua script for handling non-blocking cutscenes, that adds:

  • NonBlockingCutscene_Start(), a function that kicks off a new nonblocking cutscene
  • NonBlockingCutscene_Wait(), a special version of Wait() for non-blocking cutscenes (it only works inside one)
  • several Character extender methods for non-blocking-cutscene versions of standard Character methods - i.e. Character:NonBlockingCutscene_Walk(), Character:NonBlockingCutscene_Animate(), etc.
  • NonBlockingCutscenes_Update(), a function that keeps all currently-running cutscenes ticking over (to be called once every frame)
I won't give you the code for this script now, but it really isn't that long or complex - I will include something like it as an example script in a later version of the plugin.

Only one new line in repeatedly_execute() would be necessary:

Code: ags

//(AGS Script)
function repeatedly_execute() {
	Lua.Call("NonBlockingCutscenes_Update", null, null);
	// (... rest of repeatedly_execute ...)
}


...and the implementation of a single cutscene using this system would be something like:

Code: ags

--(Lua)
function ags.cNPC:MyCutscene()

	NonBlockingCutscene_Start(function()
		ags.cNPC:NonBlockingCutscene_Walk(50, 100)
		NonBlockingCutscene_Wait(80)
		ags.cNPC:NonBlockingCutscene_Animate(3, 1)
		ags.cNPC:NonBlockingCutscene_Walk(240, 80)
		NonBlockingCutscene_Wait(80)
		ags.cNPC:NonBlockingCutscene_FaceCharacter(ags.cEgo)
		ags.cNPC:NonBlockingCutscene_Say("...are you responsible for this?")
		ags.cNPC:NonBlockingCutscene_Walk(0, 150)
		ags.cNPC:ChangeRoom(10)
	end)

end


When you want to start this cutscene from AGS Script, you would call the "MyCutscene" Lua method on cNPC, like this:

Code: ags

//(AGS-Script)
cNPC.LuaMethod("MyCutscene", null, null);


Changing it to loop over part of the sequence a couple of times, using a for-loop:

Code: ags

--(Lua)
function ags.cNPC:MyCutscene()

	NonBlockingCutscene_Start(function()
		-- repeat this bit 3 times before continuing
		for i = 1, 3 do
			ags.cNPC:NonBlockingCutscene_Walk(50, 100)
			NonBlockingCutscene_Wait(80)
			ags.cNPC:NonBlockingCutscene_Animate(3, 1)
			ags.cNPC:NonBlockingCutscene_Walk(240, 80)
			NonBlockingCutscene_Wait(80)
		end
		ags.cNPC:NonBlockingCutscene_FaceCharacter(ags.cEgo)
		ags.cNPC:NonBlockingCutscene_Say("...are you responsible for this?")
		ags.cNPC:NonBlockingCutscene_Walk(0, 150)
		ags.cNPC:ChangeRoom(10)
	end)

end


Part of my goal for this plugin is to make it as unobtrusive as possible, so you can use it for just one section of the game (even just one NPC) and just ignore it the rest of the time, if you wish. I don't pretend that Lua will make it easier to write any part of a game, but I believe it does have the potential to make some very cool things much easier.
SMF spam blocked by CleanTalk