Post-Compilation Dynamic script events

Started by _PJ_, October 24, 2019, 19:31:14

Previous topic - Next topic

_PJ_

I see that BMax features LUA and wonder if this is possible and suitable.

I am hoping to implement dynamic, randomly accessed script events and am unsure of how best to approach this.

I hope I can explain this clearly enough...


Imagine a game includes various classes of in-game 'physical' objects. These objects' properties in the 'game world' are individually defined from template resource assets. These can be loaded dynamically. All normal, standard asset faire so far, right...
HOWEVER, interactions (through gameplay) with these objects is determined according to - at least in my intention - certain event scripts. The paricular script to be called is itself referred in the file asset for that template.

A pseudo-example of how such a template may look is:

OBJECT_TYPE=Button
OBJECT_NAME=Button1
OBJECT_ACTION=Push
EVENT_ONACTION="pushbutton.script"

Of course, the compiler is not going to have any way to interpret and compile "pushbutton.script" with any relation to whatever 'object' (as class object here) is generated in memory when that particualr template is loaded.

I am unsure how to approach this, but have thought about some kind of "pre-compilation" of all possible such scripts (even though it's not known if any are actually going to be used - if no template that contains a reference to a script is ever loaded to generate an object, that script will never be called for example) - and then when the gameplay processes actually require to call the necessary script function (i.e. when the BUTTON object in example is 'pushed' within the mechanics of gameplay) then the relevant compiled script is somehow referenced instead and called accordingly...

Is this a good approach? What alternatives might there be? Any advice, suggestions or otherwise relevant information is welcome.

I have toyed also with the possibility for having BMax code generated in the "script compilation", and then a full compilation of the entire application - protecting the core functionality of the language somewhat by encoding it under a particular packing algorithm whereby it will be secretly unpacked for the ACTUAL compilation which would necessitate bundling the relevant elements of BlitzMax (compiler & interpreter etc.) - For which I am unsure of what is absolutely minimum essential required for such.




Derron

Do as you did in your template


Type TButton
Field name:string
Field action:TAction = ACTION_PUSH
Field _onAction:func(sender:TButton)
Field _onActionScript:String
Field _onActionScriptFile:String
End Type

Function MyOnAction(sender:TButton)
Print "Button onaction : " + sender.name
EnD Function

local b1:TButton = new TButton
local b2:TButton = new TButton
b1._onAction = MyOnAction 'no brackets - so it is a pointer to the function!
b2._onActionScript = "my script code"
b2._onActionScriptFile = "myluafile.lua"


Of course you need to handle the lua file execution on your own (see how brl.MaxLua does do it). Same for your script code (you need your own script evaluator ...). The function pointer is useful if you have custom code you write in BlitzMax without the "TButton"-file having to know about the objects used in the function. But it is code compiled together with your other game code - not "precompiled" or so.
To have it truly customizable (aka you give the .exe and someone can alter the script file without recompiling the .exe) you need your own scripting language, a lua script - or .DLL/.so files (someone compiles a library which you then use).


bye
Ron

_PJ_

Hi Derron, Thanks for the really fast resposne.

Quote from: Derron on October 24, 2019, 19:59:42
you need your own scripting language, a lua script - or .DLL/.so files (someone compiles a library which you then use).
This is the crux of the matter for me.

So it sounds like it should be possible to have externally compiled Lua scripts that are executed from within the running BMax code. Those scripts should be taking advantage of function and var pointers to interact fully with the requirements of the BMax code.

This is why I hoped that Lua can be used because there exists the MaxLua and it's a popular enough scripting language.
Of course, the power andpotential of what is possible via those scripts would be determined by what function and var pointers I can prepare within the BMax codebase itself, but this seems to be the best option, compared to attempting my own scripting language which would be very limited as it would also entail writing my own compiler otherwise, the result would simply be a slow, runtime interpretation and "translation of script function".


Derron

your own "scripting language" would not need a compiler - just an interpreter :)

Lua: You need to expose functionality / helper classes to lua - the lua scripts could then call these functions.

helper classes: I mean something like


Type TLuaFunctions
  Function MyClick:int()
  End Function

  Function ExecuteFunctionXYZ:int()
     game.ExecuteFunctionXYZ()
  End Function
End Type


But if the button-click-event mostly consists of one of a handful candidates "GivePlayerItem(swordID)" or so, then you might better have some simple string interpreter:
OnClickString = "GivePlayerItem,23"
Then split your string - compare the first one and handle accordingly


Select OnClickStringFirstPart.ToLower()
  case "giveplayeritem"
     GivePlayerItem( int(secondPart) )
  case "sellinventoryitem"
     SellInventoryItem( int(secondPart) )
'...
End Select


Next to strings you can also use predefined numbers (constants)
GIVEPLAYERITEM:int = 1
SELLINVENTORYITEM:int = 2
...
as comparisons of numbers is less cpu and memory manager hungry.


bye
Ron

_PJ_

#4
Quote from: Derron on October 25, 2019, 19:12:08

Lua: You need to expose functionality / helper classes to lua - the lua scripts could then call these functions.

helper classes: I mean something like


Type TLuaFunctions
  Function MyClick:int()
  End Function

  Function ExecuteFunctionXYZ:int()
     game.ExecuteFunctionXYZ()
  End Function
End Type


Well that's just amazing. It's so much simpler than I'd anticipated! Still not to be underestimated though, but you've really helped and given me a lot of confidence about tackling this! Thank you kindly!

It really wwould necessitate more than a basic interpreter, since the intention (despite my button example) would be more complex than a "If this do that" kind of singular function. Parsing the strings would be cumbersome I think.

The intention might be something more like (pseudoscript)

[Complex button push script]
cl_Object thisObject = GetSelfObject();

if ((GetObjectType(cl_Object))=BUTTON_TYPE_CONSTANT){
if (GetButtonState(cl_Object)!=TRUE){
  cl_Effect=GetParticleEffect(1234);
  SpawnParticleEffect(cl_Effect);
  SetButtonState(cl_Object,TRUE);
}
}

Which despite its apparent simiplicity already calls into a slew of classes and methods that would be required. But...yeah - you get the idea and the capacity for Lua to directly "utilise" the helper functions is extremely powerful and just what I need!





Derron

Your script would become way easier if you registered the script when knowing what button it is

SpawnParticlesButton.OnClickScript = ...
---
SpawnParticleEffect,1234
---

Your blitzmax logic would then split into "SpawnParticleEffect" and "1234"
It could then call

  local cl_Effect:TEffect = GetParticleEffect(thatID)
  SpawnParticleEffect(cl_Effect)


And if you needed more configuration you could always add more ",value,value" which you would tackle within BlitzMax.


But yes - I would prefer to use "lua" or another scripting language. Then I would expose all "possible" functions (maybe create them just for that purpose - so you create a kind of "API" to have defined points of access for the scripts and only a single file instead of having a dozen of exposed functions across dozens of bmx files).


bye
Ron

_PJ_

Quote from: Derron on October 25, 2019, 20:31:16
But yes - I would prefer to use "lua" or another scripting language. Then I would expose all "possible" functions (maybe create them just for that purpose - so you create a kind of "API" to have defined points of access for the scripts and only a single file instead of having a dozen of exposed functions across dozens of bmx files).
This is the general intent I think.

There would be a generality of object and interactivity which would (hopefully) be more determined by the Lua, but this will need a wealth of "infrastructure" inplace within the BMax to facilitate the relationships between the BMax and the Lua references etc.

My initial post was because I wasn't sure if this was possible with BlitzMax's integration with Lua, but now that you've shown me that it is and in a far more direct way thatn I initially thought, it's really galvanised me in this direction. Much appreciated!

Derron

I use Lua to code my game's AI.

the game calls lua script functions - and these functions are enabled to call certain exposed Lua functions. Just check the "MaxLua"-examples - is pretty straight forward then.


bye
Ron

_PJ_

I have revisited this problem, but am clearly not understanding the functionality correctly. I'm not sure whether to use MaxLUA or Pub.Lua but I encountered far fewer issues trying Pub.Lua

The call to "register" functions seems to actrually run the associated function, so not sure when/how this is supposed to work.

The following example consists of 4 files
"Main.bmx" - The core executable BMax program
"Script.bmx" - A dependency containing all the LUA Scripting functionality
"objdef.txt" - A simple example representing a gameobject data file which contains the names of associated scripts
"onspawn.lua" - An simple, single-line script for testing purposes

The intention is that within the program, an Object is created with information from "objdef" - this information is restricted to a scriptfile name for the purpose of this example.

The scriptfile name is attached to an "OnSpawn" event, so when the object is 'Spawned", the corresponding script should fire

unfortunately, whilst apparently the correct file is recognised, I am unsure how to correctly use the Lua functionality.

I have been going over the documentation, but I'm just not getting it.


Within the example, the function name per Lua source "LUA_FN_MESSAGE" should refer to the BMax function ScriptFunctionSENDMESSAGE(LuaState,"SENDMESSAGETEMP")




MAIN.bmx
'Main.bmx
Include "Script.bmx"

Global DIR:String=CurrentDir()+"/"

InitialiseLua
Runtime
UninitialiseLua

Type GAMEOBJECT
Field NAME:String

Field OnSpawnScript:String

Function Load:GAMEOBJECT(Filename:String)
Local F:TStream=ReadFile(Filename)
If (F=Null)
DebugLog("Cannot open file: "+Filename)
Return Null
End If

Local O:GAMEOBJECT = New GameObject
O.OnSpawnScript=ReadLine(F)

DebugLog("Loaded object: "+Filename)

Return O
End Function

Method Spawn()
DebugLog("Spawning object")
Self.OnSpawn
End Method

Method OnSpawn()
DebugLog("Calling OnSpawn event: "+Self.OnSpawnScript)
CallWrapper(DIR+Self.OnSpawnScript)
End Method
End Type

Function Runtime()
Local O:GAMEOBJECT = GAMEOBJECT.Load(DIR+"objdef.txt")

O.Spawn

End Function

SCRIPT.bmx
'SCRIPT.bmx
Global LuaState:Byte Ptr

Function InitialiseLua()
LuaState=luaL_newstate()
luaL_openlibs(LuaState)

' Set a LUA global var (string)
' lua_pushstring(LuaState, "STRING_CONTENT")
' lua_setglobal (LuaState, "GLOBAL_STRING_VAR_NAME")

' lua_pushboolean(LuaState, 1)'Where value is actually is 4-byte INTEGER
' lua_setglobal (LuaState, "GLOBAL_BOOL_VAR_NAME")

lua_register(LuaState, "LUA_FN_MESSAGE", ScriptFunctionSENDMESSAGE(LuaState,"SENDMESSAGETEMP"))

End Function

'Set and register functions
Function ScriptFunctionSENDMESSAGE:Byte Ptr(LuaState:Byte Ptr,sMsg:String)
Print("Script sending message: "+sMsg)

lua_pushstring(LuaState,sMsg)
lua_setglobal(LuaState,"SENDMESSAGETEMP")

lua_pop(LuaState,1)
Return 0
End Function

Function CallWrapper(Name:String)
If (Name="")
DebugLog("No script to run")
Return 1
End If
Local File:String=Name+".lua"

If (FileType(File)<>1)
DebugLog(File+" Invalid script")
Return 1
End If

Local Result:Int = luaL_loadstring(LuaState,File)

If (Result <> 0)
lua_close(LuaState)
End
End If
End Function

Function UnInitialiseLua()
lua_close(LuaState)
End Function

OBJDEF.txt
onspawn.lua


OnSpawn.lua
LUA_FN_MESSAGE("Script Running!")

_PJ_

Okay so I finally found:
https://blitzmax.org/docs/en/api/brl/brl.maxlua/

Much better than the offline docs package!


So I coped the example and changed it from using a literal string to define the scripted functions, Instead, I read in line by line from a file:

Strict

'Our TDemo type...
'
Type TDemo

    Method SayHello$( name$ )
        Return "Hello "+name+"! Peace be with you..."
    End Method

End Type

'Register a demo object with Lua.
'
'Lua code can now access the object using the identifier "Demo".
'
Local demo:TDemo=New TDemo
LuaRegisterObject demo,"Demo"

'source code to our little Lua program...
'
Local source:String
Local f:TStream = ReadFile("F:\bb\BMax64\Misc\Scripting Tests\LUAScript.lua")
While (Not (Eof(f)))
source:+ReadLine(f)+Chr(13)
Wend
CloseFile f

'create a Lua 'class' and set it's source code...
'
Local class:TLuaClass=TLuaClass.Create( source )

'Now, create an instance of the class.
'
Local instance:TLuaObject=TLuaObject.Create( class,Null )

'We can now invoke methods of the class.
'
instance.Invoke "hello",Null
instance.Invoke "goodbye",Null


So this is in essence just what I was hoping to achieve. But I am still a little unsure of a couple points:
1) Is there a limit to the length of "source" string?
2) Would every "scriptable entity" require multiple LuaObject instances if they require different script files for different actions? Or should the differentiation be handled only as separate functions within a single "Source" per entity?