Modding, Mission Design, and Coding > The Scripting Workshop

Where to go from here

(1/3) > >>

It's late and I don't feel like sleeping quite yet, so I figured I'd type out a few major/serious ideas I've been considering for the scripting system.

Central tenets...
1) Functions should be as flexible as possible, accepting a minimum of arguments, while using optional parameters to greatly extend their capabilities. This is to minimize the learning curve, without cutting off functionality.
2) The scripting environment should be safe and never crash the executable. This is probably an ideal that can't really be realized; however it does have two main applications:
[*]Release builds should be able to recover from any unexpected failures without throwing up any errors. A ship dying would be considered an unexpected failure; the scripting designer not providing required arguments to a function or making gross syntax errors would not, because there's virtually no test the script without running into them. (For larger scripts and systems of scripts this may not be the case)
[*]Debug builds, on the other hand, should be able to recover but should not err away from telling the user what is wrong, as long as it's obvious that something _is_ wrong. If it's iffy as to whether or not there would be a practical purpose of doing something unusual (Writing an empty string, perhaps) then the system may spit something out in the debug spew, but overall will fail silently and let the user figure it out.
3) The system should attempt to take into account future expansion of the codebase.
4) Although sticking with the fs2_open codebase's way of doing things does provide a lot of compatibility, and would help make scripting a sort of stepping stone to coding, the most important thing should be whether it meets with the above guidelines for ease of use; as it's worthless if the only people who can understand what's going on with it are qualified to actually code features into fs2_open!

1. Simplified function return values
Kind of simple, really. Most functions in Lua have at least two main return types, if not 3. All functions return Nil if the arguments are satisfied; of course, they also throw up an error in this case, but within the scripting environment, the only evidence is that the functions returned a nil value. In addition, many functions and variables that are either members of an object, or take an object as a (critical) argument will return nil if the object is invalid. (eg the ship is destroyed)

The problem comes with 2a. If a ship is killed and a modder does something irresponsible like:

--- Code: ---ship = mn.Ships['Beta 4']
if ship.HitpointsLeft < 40 then
     --Play a warning sound that a mission critical objective is about to die.
--- End code ---

then at the point that Beta 4 is killed, Lua will crash because it's trying to use "<" on a nil value. If, on the other hand, the function returned an arbitrary value instead of nil (If we know that Beta 4 doesn't exist, we can reasonably assume that it does not, in fact, have any hitpoints at all) such as 0, then the code would continue to execute and there would be some oddities, but altogether there's a good chance that it would survive.

On the other hand, I've heard it said that it's handy to be able to check for the nil values to see whether a handle is valid or not. Since I've provided the isValid() function on all handles for just such a reason, I'm skeptical of that...but it is still a valid reason nonetheless, and it's a pain in the ass to change _all_ the return values for functions at once. Though if I did that, I would probably create some kind of special function for future use that would let you view the error, and replace the return values with that.

2. Greater focus on OOP
It's the difference between

--- Code: -----Set a drawing color
gr.setColor(255, 255, 0)
--Get mouse coords
x = io.getMouseX()
--- End code ---


--- Code: -----Set a drawing color
gr.CurrentColor = ba.createColor(255, 255, 0)
--Get mouse coords
x = io.CurrentMouse.X
--- End code ---

The latter is more OO-friendly, and uses variable names rather than specialized function names which would be (in theory) easier to remember. But in a sense, it makes less sense to create a new color and set the current color to it, than to simply call a setColor() function. I could do it on a case-by-case basis, but then you have to remember which case applies when. This may be grounds for reconsidering the object creation method, since in theory I could provide color(), vector(), and orientation() functions that would simplify things somewhat. It makes sense to create ships and such from within the mission library - since the objects in Lua are handles to items that are basically contained within the mission library, in a theoretical sense anyway, but basic types...I'm not so sure. Although that would make it a case-by-case basis.

--- Code: -----Set a drawing color
gr.CurrentColor = color(255, 255, 0)
--- End code ---

3. Error handling system
I've thought it'd be nice to have a better error handling system for Lua, so you could specify a warning/error level and see as many errors as you wanted - from critical, script-go-boom ones, to noncritical autocorrected ones.

4. Console
Given the fad of having consoles in-game, with new games like Half-life 2, and even as far back as the original Half-Life, I've thought about expanding teh debug console to possibly run in release mode, or at least switch it over to interface with the lua interpreter rather than the existing system. There's a certain irony because the way the current Lua implementation is done is inspired, heavily, from the debug console code - but it's rarely used. Whether or not it would support branching and conditionals and all that might require more work than I really want to do at this point in time, but I can still see a lot of uses for being able to arbitrarily type code in and execute functions from scripting at will, with arbitrary arguments.

Of course I don't know how widely used the debug console is, and if I did swap it for a Lua version, I wouldn't support the old system. I'd add a debug library for debug functions, but supporting two different systems at the same time seems pointlessly redundant and an opportunity for confusion and bugs.

With a more widely-used debug console, I would also have an excuse to add an LuaConsolePrint() function or something, for noncritical errors or what-have-you, and have an interface for setting the errorlevel.

Anyway, I feel like I'm forgetting something, but numbers one and two I've been see-sawing back and forth on. Mostly I hate to make such a critical change to the language again, but I feel like it makes more sense in the long run.

for  #1 i dont see them as overly complex to have multiple return types. there are some annoyances with :getScreenCoords(), id rather it return off-screen coords rather than nil or false. like if i wanted to draw a line or a shape that was partly off the screen. then theres the matter of having output coords at 1 of 2 resolution scales, id rather have it return actual screen coordinates. thats an old debate there but i think its just something to confuse new scripters who scratch their heads cause theyre gauges pop up in the wrong sopt. same can be said for mouse coords. the hud scaling should be done by use of factors in the draw functions.

when it comes down to weather or not the function returns nil or a value. i think some instances where you need to call a function twice, the first time to see if the function works with a conditional and the second to make it actually do something. then it might be better to get a placeholder value. it makes little sence to run it twice. :getScreenCoords(), picking on it some more (i use it alot and cant currently think about another you need to call twice), might be better off with a sister function :checkScreenCoords() to see if its on screen or not. then we can decide wether we want to use them. just think of that function as a good example of when an arbitrary value may be a good idea.

as for oop, its not a big deal. id rather use a function unless the value can be changed. like if i cant tell the mouse where to go with io.CurrentMouse.X, id rather just use io.getMouseX(). this makes it simpler cause somone new to scripting would be wondering why they cant change a variable, if its a function they know calling it and getting a result is all it does. its rather good right now. as it stands its powerfull enough to give a tc scripter alot of control. its way more powerfull than quake c was. also this being script, you would want to make it faster and if i understand oop going all the way would just make the code less effietient to run.

another area of simplification is the use of orientations on things like createWeapon(), where a normal vector will do. i dont think your average scripter can figure out orientation matricies, if they could they might as well work on the engine. orientation objects are also somewhat limiting. you have to borrow something elses orientation and cant really make one from scratch. from what i understand you can create a matrix with front, up, and side vectors, which i could deal with. its fairly easy to get a vector off of a subobject with an orientation but its almost impossible to tell that object to face a point in 3d space. a createOrigin(fvec, uvec, svec) would be very handy here sence vectors are alot easyer to deal with.

well, there is a function in the code for makeing a matrix out of a vector (actualy there are sevral)
it shouldn't be too big of a deal to get the component vectors of a matrix (that's actualy how I access matriciese almost exclusively), but I don't realy get the scripting code, so I might be missing something.

autoscaling shouldn't exist in the new scripting functions, do you have a list of ones that don't scale properly? (X/Y or width/height) I tried to get rid of that when I removed the resize parameters.

checkScreenCoords makes sense; although that does raise an interesting issue because the calculation must be done, and to my knowledge, the calculation is quite expensive. All I have to check with checkScreenCoords is a flag, but it'd require running the full calculation anyways, so, hmm. I'm sure there's a way to do it properly, but I'm not sure if it'd mean combining the Lua vector object with the C vertex object, and if that'd be advisable. More research required.

And yeah, OOP would make things slower, at least in some cases. If you always used io.CurrentMouse.X, it'd have that extra index to go through. (Finding the CurrentMouse function in the io library). If, on the other hand, there came a time in the future to add multiple mice, it would come in handy. You could have a Mouses[] array (Which I believe is the technical term for computer mice...err mouses) and do stuff like CurrentMouse = Mouses[1] to switch it from a mouse to a table, for instance. But that would only be if the backend supported it, but it does demonstrate how much more sense it would make than adding a mouse number argument to every single function.

The more I talk, the more I convince myself that going more OOP is a good idea...especially if I implement concatenable enumerations support.

I hate the orientation object with a passion. Not because it's a bad piece of work, but because it's inherently bad. Matrices are not designed for access via p, b, and h. Period. Every single time one of those variables is changed, and one of the internal functions needs the actual matrix object, it has to rebuild the actual matrix based on those three values. That involves a lot of floating point math. This requires two function calls to make sure that the up-to-date values are calculated (for the matrix and pbh halves of the orientation object), as well as setting a flag every time either is changed so the computer knows that it needs to update the other in order to use's a mess, and I think that making every single 'direction' variable in the code a vector normal is a great idea.

the only funcs ive used that dont properly scale to the game resolution are the io.getMouseX() io.getMouseY() and :getScreenCoords(). theyre might be others that im not familiar with.

as for the matrix would it be better to split the pbh and matrix into seprate objects? phb would be used on local stuff, like turrets, subobjects, physics ect. its hard to do things like turn rate with normals, but for stuff like creating weapons and cameras a normal is far more preferable. a matrix still has its uses for vector rotations and such but only as a read only entity or at least write all or nothing. for operations that require orientations you still need conversion functions. if i have a matrix that works i might as well use it but if i dont i can call a conversion function to generate a matrix off of a phb or a normal.


[0] Message Index

[#] Next page

Go to full version