Some things I've been doing in AGS made possible by the power of the mighty Lua.

Started by Calin Leafshade, Thu 01/05/2014 16:40:38

Previous topic - Next topic

Calin Leafshade

Thing 1:

Dialogue *trees*.

AGS doesn't really have a true dialogue tree system in the traditional sense. Dialogues were a series of unconnected lists. So I made this:



True dialogue trees with link nodes and conditions. The code is loaded dynamically as lua scripts so any lua is valid within a dialogue script. Which brings me to...

Thing 2:

Dialogue scripting within main scripts.

There's no obvious reason why we should have to write cCharacter.Say("This is what I say"); all the time. It's a common enough thing to do and people dont write scripts like that. So what if we could do this:

Code: lua

if myValue > 1 then
    Billy: Gee, I sure hope myValue was bigger than 1
else
    Billy: Lol, myValue.. You cad.
end


Since we can preprocess lua code before running it this is pretty trivial to automatically convert to the correct code. Goodie time-saver!

Thing 3.

Rewriting the pathfinder.

AGS script is probably too slow to rewrite the pathfinder and the lack of a hashtable means looking up nodes would be perilously slow. In Lua we don't have this problem.



My new pathfinder is more accurate and has tunnel-based smoothing. Also, when walking, characters continue walking at the end of a node which avoids the nasty stopping that AGS characters have a tendency to do at sharp corners.

An added benefit of this is that the walking code is kept within the user domain which means you can dynamically edit the walking frames without that nasty 1 frame delay.

I think thats everything I've been playing with... THE POWER OF LUA! UNITE!

Andail


Calin Leafshade

4.

Use a sprite as a kind of "tint mask" to dictate the tinting/lighting of characters stood at that position which allows for gradual blending between tints.

Try starting with a blurred version of the background:




Eggie

I'm torn about that dialogue editor, it looks very powerful and modern and just like a big fancy pro modding kit but there was just something I always liked about it being one big lump of text you could dive into and edit...
I mean, unless I needed to move options around. Those times I REALLY, REALLY want a big fancy pro dialogue editor.

Like I said, I'm torn. TORN.

Peder 🚀

You're posting about these awesome things you've done, but will you ever share the tools you've made and example code/source codes? :)

Armageddon

But what about Adore? :-D

It would be amazing if you'd release the pathfinding code, I hate the AGS pathfinding.

Secret Fawful

Quote from: Calin Leafshade on Thu 01/05/2014 16:40:38
Thing 3.

Rewriting the pathfinder.

AGS script is probably too slow to rewrite the pathfinder and the lack of a hashtable means looking up nodes would be perilously slow. In Lua we don't have this problem.



My new pathfinder is more accurate and has tunnel-based smoothing. Also, when walking, characters continue walking at the end of a node which avoids the nasty stopping that AGS characters have a tendency to do at sharp corners.

An added benefit of this is that the walking code is kept within the user domain which means you can dynamically edit the walking frames without that nasty 1 frame delay.



Dear god, I hate the pathfinding in AGS.

Dropped Monocle Games

Quote from: Calin Leafshade on Thu 01/05/2014 20:53:37
4.

Use a sprite as a kind of "tint mask" to dictate the tinting/lighting of characters stood at that position which allows for gradual blending between tints.

Try starting with a blurred version of the background:





8-0
I WANT THIS!!

Grim

This TINT/lighting mask... I've been dreaming about it for YEARS...

Calin Leafshade

Thing 5.

Multiplicative tinting



AGS's tinting algorithm is weird and the lighting level is related to the saturation level which doesnt make a lot of sense. Lua is fast enough to tint character sprites on the fly, pixel by pixel, overwriting the default behaviour.

Re: tint mapping. You can do that easily enough without Lua. Just have a DrawingSurface open for your tintmap and grab the pixels every frame. It's the most simple of the things presented here and AGS Script is fast enough for it.

Calin Leafshade

Thing 6.

Functions as actions.

Remember this:

Code: ags

function hHotspot1_UseInv()
{
    if (player.ActiveInventory == iFoo)
    {
        player.Say("I don't think i should use the foo with that.");
    }
    else if (player.ActiveInventory == iBar)
    {
        player.Say("Gee, I shouldnt use the Bar with that just now.");
    }
    else if (adInfinitum)
    {
        dieOfBoredom();
    }
    else
    {
        player.Say("that doesn't work");
    }
}


And this has to be repeated for *every* *single* hotspot.

How about this instead:

Code: lua


hHotspot1.UseInv = {
    [iFoo] = function() player:Say("I don't think I should use the foo with that") end,
    [iBar] = function() player:Say("Gee, I shouldn't really use the bar with that.") end
}

--now call it in your main game code:

local func = hHotspot1.UseInv[player.ActiveInventory] or unhandled -- unhandled is our generic "that doesnt work" function
func() -- call the function



Much neater, and the act of using an inventory item with a hotspot is more clearly defined without a bazillion if/else statements.

Monsieur OUXX

Deadly.
I think the thing that comes immediately handy to beginners is the dialogs thing. The community slowly got used to all the awkwardness of the built-in dialog editor, but to a newcomer, it's seriously like hell.

I wish AGS wasn't in that sort of in-between situation between old and new, because I would totally consider migrating Seven Cities of Gold to Lua.

 

Tournk

Quote from: Calin Leafshade on Sat 03/05/2014 22:15:07

How about this instead:

Code: lua


hHotspot1.UseInv = {
    [iFoo] = function() player:Say("I don't think I should use the foo with that") end,
    [iBar] = function() player:Say("Gee, I shouldn't really use the bar with that.") end
}

--now call it in your main game code:

local func = hHotspot1.UseInv[player.ActiveInventory] or unhandled -- unhandled is our generic "that doesnt work" function
func() -- call the function



Much neater, and the act of using an inventory item with a hotspot is more clearly defined without a bazillion if/else statements.


Wonderful! I like it neat and compact!  :)
Reaction is always funny.

Vince Twelve

Calin, is there any up-to-date documentation for the Lua plugin?  All the examples on the site that I've tried have not compiled.  I was able to call a lua function and gat it to display text to the screen, but I couldn't get the functions to return anything.  The compiler says LuaValue doesn't exist, or when I try LuaValueList, I'm not sure how to get it to a string.  There are contradicting examples out there, but they don't seem to work.

Also, I haven't looked into this, but does using this rule out using the iOS/Mac/Android ports as they currently exist?

Calin Leafshade

Hi Vince,

As far as I know all the documentation should still hold true. Nothing has changed since inception.

Generally speaking however, I don't return values from Lua to AGS. Because AGS has no way of dynamically casting objects it makes things very messy so I keep my entire game in the Lua domain.

For example of how I do this see the following link (cached from google because i stupidly deleted the database yesteday)  It includes a full description of interoperation between AGS and Lua with code examples.

http://webcache.googleusercontent.com/search?q=cache:s48HXgtCbOoJ:www.themccarthychronicles.co.uk/+&cd=1&hl=en&ct=clnk&gl=uk

With regards to the ports, the plugins are open source and currently included in the AGS git repo so there's no reason why the current ports couldn't use them (Lua can be compiled on a toaster). I'm not sure if the plugin is compiled by default though. You'd have to ask Crimson Wizard.

Edit: If you'd prefer something more complete, heres my entire AGS-Lua Interop system:

AGS: http://hastebin.com/sixikoruna.c (This is the *only* code in my AGS project)

Lua: http://hastebin.com/eqewuneceq.lua

abstauber

Hey Calin,
somehow your hastebin content went missing. Any chance for a second glimpse?

Calin Leafshade

Sure thing.

heres my globalscript

Code: ags
int lastRoom = -1;

#ifdef USE_LUA

function game_start()
{

  int i = 1;
  while (i < Game.MouseCursorCount) 
  {
    Mouse.DisableMode(i);
    i++;
  }
  
  Mouse.Mode = eModeWalkto;
  
  Lua.SetVar("DebugMode", Lua.BoolValue(false));
  #ifdef DEBUG
  //Debug(4, 1);
  Lua.SetVar("DebugMode", Lua.BoolValue(true));
  #endif
  
  Lua.RunScript("main.lua");

  
  Lua.Call("FireEvent", Lua.StringValue("game_start"));
  
}



function on_key_press(eKeyCode keycode)
{  


#ifdef DEBUG
  if (keycode == eKeyCtrlX)
    Debug(3, 0);
#endif
  LuaValueList *l = Lua.NewValueList();
  l.Add(Lua.StringValue("on_key_press"));
  l.Add(Lua.IntValue(keycode));
  Lua.Call("FireEvent",l);
}

function on_mouse_click (MouseButton button)
{
  LuaValueList *l = Lua.NewValueList();
  l.Add(Lua.StringValue("on_mouse_click"));
  l.Add(Lua.IntValue(button));
  Lua.Call("FireEvent",l);
  
}

function on_event(EventType ev, int data)
{
  
  LuaValueList *l = Lua.NewValueList();
  if (ev == eEventAddInventory) l.Add(Lua.StringValue("AddInventory"));
  else if (ev == eEventEnterRoomBeforeFadein) l.Add(Lua.StringValue("EnterRoomBeforeFadein"));
  else if (ev == eEventLeaveRoom) l.Add(Lua.StringValue("LeaveRoom"));
  else if (ev == eEventGUIMouseDown) 
  {
      GUIControl *gc = GUIControl.GetAtScreenXY(mouse.x, mouse.y);
      LuaValueList *lvl = Lua.NewValueList();
      if (Mouse.IsButtonDown(eMouseLeft))
      {
        on_mouse_click(eMouseLeft);
        lvl.Add(Lua.IntValue(eMouseLeft));
      }
      if (Mouse.IsButtonDown(eMouseRight))
      {
        on_mouse_click(eMouseRight);
        lvl.Add(Lua.IntValue(eMouseRight));
      }
      
      if (gc == null)
      {
        GUI *g = GUI.GetAtScreenXY(mouse.x, mouse.y);
        if (g != null)
          g.LuaMethod("OnClick",lvl);
      }
      else if(gc.AsButton != null) 
      {
        gc.AsButton.LuaMethod("OnClick",lvl);
      }
      return;
      
  }
  
  l.Add(Lua.IntValue(data));
  Lua.Call("FireEvent",l);
  
  
}

function repeatedly_execute_always()
{  
  Lua.Call("FireEvent", Lua.StringValue("repeatedly_execute_always"));
  Lua.Call("FireEvent", Lua.StringValue("late_repeatedly_execute_always"));
  Lua.SetVar("Blocked", Lua.BoolValue(true));
}

function repeatedly_execute()
{
  
    if (player.Room != lastRoom)
  {
      Lua.Call("FireEvent", Lua.StringValue("EnterRoomAfterFadein"));
      lastRoom = player.Room;
  }
  
    LuaValueList *l = Lua.NewValueList();
  l.Add(Lua.StringValue("LeaveEdge"));
  
  if (player.y > Room.BottomEdge)
  {
    l.Add(Lua.StringValue("bottom"));
    Lua.Call("FireEvent", l);
  }
  else if (player.y < Room.TopEdge)
  {
    l.Add(Lua.StringValue("top"));
    Lua.Call("FireEvent", l);
  }
  else if (player.x < Room.LeftEdge)
  {
    l.Add(Lua.StringValue("left"));
    Lua.Call("FireEvent", l);
  }
  else if (player.x > Room.RightEdge)
  {
    l.Add(Lua.StringValue("right"));
    Lua.Call("FireEvent", l);
  }
  
  Lua.Call("FireEvent", Lua.StringValue("repeatedly_execute"));
  Lua.SetVar("Blocked", Lua.BoolValue(false));
}



function unhandled_event (int what, int type) 
{
  LuaValueList *l = Lua.NewValueList();
  l.Add(Lua.StringValue("unhandled_event"));
  l.Add(Lua.IntValue(what));
  l.Add(Lua.IntValue(type));
  Lua.Call("FireEvent",l);
}


#endif
function dialog_request(int param) {
}

and the lua module called interop.lua

Code: lua
-----------------------------------------------------
--
--	Lua Control Module 
--
--  Calin Leafshade 
--
--	A module to register global events in lua
--
-----------------------------------------------------

local events =
{
	delegates = {},
	objects = {}
}

function events:registerObject(obj)
	table.insert(self.objects,obj)
end

function events:register(event, priority, callback)
  
	if type(priority) == "function" then
		callback = priority
		priority = 100
	end
  
	local callbacks = self.delegates[event] or {}
	self.delegates[event] = callbacks
	table.insert(callbacks, {f = callback, priority = priority})
  
	table.sort(callbacks, function(a,b)
		return a.priority < b.priority
	end)
  
end

function events:fire(event, ...)
	for i,v in ipairs(self.objects) do
		if type(v[event]) == "function" then
			if v[event](v,...) then break end
		end
	end
	for i,v in ipairs(self.delegates[event] or {}) do
		if v.f(...) then break end
	end
end

return events

then my main.lua looks something like this

Code: lua
Events = require('interop.lua')

function FireEvent(...) -- this is a global function to allow AGS easier access to it
	Events:fire(...)
end

--then you can use the events module like this in any lua file:

Events:register("game_start", function()
    aAudio:Play()
end)

--you can also make objects like this:

local myObject = {}

function myObject:repeatedly_execute_always()
    -- do this every frame
end

Events:registerObject(myObject)

-- This means that all the events in this object will be run when triggered by interop (if they exist in the object, they dont have to)

These events basically give you everything you need. You need to make your own hotspot interaction system but thats very easy and I can show you how if you need me to.

Any questions let me know!

abstauber

Sorry to bump this...
Calin, is it possible to have 8-bit palette rotation done by the mighty powers of lua? That's really a thing I've been missing for ages. In 8-bit it's half broken (as you can't control the speed) and in 16,32 bit it not possible at all.

So cycling colors of a dynamic sprite would be super duper cool and a number one reason to finally move to lua.

For all those not familiar with this effect, be stunned right here:
http://www.effectgames.com/demos/canvascycle/


-- edit: it's not half broken in 8-bit ;)

Calin Leafshade

Well Lua can't *do* anything that the main engine can't do really. It's just much faster so you can do things that are prohibitively slow in the main engine.

So if the api exposes the functions (I dont know anything about 8bit stuff) then it would be faster in lua.

abstauber

Ah, okay - thanks for the clarification. I thought that lua could maybe take the sprite, convert it to 8bit, shift the colors around and hand it back to AGS.
There also seems to be ags.palette.setRGB in lua, but I suppose it only works for real 8bit games.
I just want to save some time not to pre-render everthing :)

SMF spam blocked by CleanTalk