Jump to content
Sign in to follow this  
ryfle

Various questions on scripting and the engine...

Recommended Posts

Hello everyone,

I've been playing around and doing lots of testing to figure out how to do everything before I start serious work on some rather ambitious mods I want to do. And I've run into a few more roadblocks and questions, as expected. So I'm hoping one of you ladies and gentlemen can help...

1) waypointAttachObject (and unit-follows-unit...)

I'm experimenting with some different ways of getting one unit/group to follow another without a jerky "stop-start" over and over. From the sound of it, waypointAttachObject may be helpful. From what I understand by reading the Wiki, it attaches the waypoint to an object, right, like when you set a waypoint on a house or unit in the editor by double-clicking it? If not, what exactly does it do? And where the heck do you get the objectID from? Searching for the answer has yielded nothing beyond the vague Wiki text... It seems like there should be a command like "objID <obj>", but apparently there is not. So...?

And has anyone come up with a good solution for following behaviour? doFollow is no good because it only works locally within a group. And I've already found, downloaded and tried several example scripts, and found all of them unsatisfactory (the unit stops and starts, often at a sprint, over and over). I really need smooth following behavior...

2) Programatically created waypoints

I cannot figure out (or find out) how to detect when a waypoint I've assigned programatically is completed, or how to capture the waypoint activation/completion as is so very easy to do in the editor. After banging my head against the wall, scouring the forums and the net at large and trying random things I'm fresh out of ideas/leads. Any ideas on this?

One reason I ask is because some scripts I'm working on are going to generate waypoints over and over on-the-fly. AFAIK, when a unit completes a waypoint it does not delete or discard it from the inner array of waypoints, and I worry about wasting memory (as well as keeping track of indices). Sure, a waypoint seems to be the equivalent of a DirectX Vector2 type or float[2] (two 32-bit floating-point values), which isn't a lot of memory in and of itself, but I'm sure the engine allocates more than just the coordinates. So I want to delete waypoints upon completion for the sake of RAM budgeting, just to be safe. Any ideas/info on that?

3) Functions

I'm going to implement a few functions, but I'm still not sure how to do it and can't find a decent example of the nuances of ARMA script functions. I'm quite familiar with functions in C/C++ and object methods in C++, C#, Java, etc... For example:

int clamp(int* value, int min, int max) {
int a = &value;
a = ( (a > max) ? max : a );
a = ( (a < min) ? min : a );

return a;
}

What might the equivalent to this simple C function look like in ARMA script?

The Wiki page on functions has given me a good idea of how to "compile" my function and call it, but hasn't made the syntax of writing my own functions so clear (or maybe I'm missing something). All I know is I can do something like this:

// my_fn_hint.sqf

comment "Blah, blah, blah...";

_text = _this select 0;

hint _text;

And then use it, per the directions, in client script. But I'm not understanding how to do more complicated functions that have to return a value, as there is no "return" keyword like I'm used to in C/C++/C# (and of course we can't pop/push memory on the stack, lol). Can anyone illuminate the subject? I really need a clearer picture of function syntax, usage, etc. I've looked at some examples of other people's functions which look nothing like the very basic examples on the Wiki, and it's just confused me even more, lol.

4) Script Execution Questions...

I'm also curious about some of the inner workings of the ARMA scripting engine:

A) Concurrency:

It's my understanding that scripts are executed by the engine concurrently, rather than synchronously. So something like this:

nul = ["hello"] execVM "printText.sqf";

nul = ["hi there"] execVM "printText.sqf";

...as far as I know, the engine will begin executing an instance of printText.sqf when the first execVM line is run. Then it will initiate and run
another
, totally separate, instance of it when the second execVM runs
immediately
... it does not wait for the first instance to return control or complete. Right?

But this begs the question: How is it done? Does the engine actually run scripts on their own OS thread? And if so, does it take advantage of multiple cores or allow us to control processor affinity? Or does it sort of "simulate" true concurrency through some clever TSS (task-state switching) or cooperative multitasking (e.g., scheduling time slices and yielding control between executing instances)?

And one other thing... Are scripts executed with some form of time synchronicity? In other words, let's just say I made a loop like this, that does nothing:

while{ true } do { };

Is that loop executed as fast as possible? Or is it run every frame? Or is there something else to it?

B) Script "thread" synchronization?

I've probably overlooked this, but just thought of it and wanted to ask. How can we synchronize script instances? Let's say I use execVM to spawn two script instances that begin running, and I need them to be synced. Now it's my understanding that execVM returns some sort of "thread handle" you can use for just this purpose, right? But how, exactly? Let's say my "thread handles" are named hThread1 and hThread2. In C#, I might do something like this in the code executing in hThread1:

Thread.CurrentThread.Join( hThread2 );

...to make my code wait for hThread2 to complete. It may be quite important, such as synchronizing access to non-thread-safe variables or waiting for important calculation results. How are such things accomplished in ARMA scripting? And is there a Wiki page or something for this, which I'm sure I've missed? I can accept that in lieu of an explanation.

C) Variable instances

I still don't have a good grasp on the way variable scope, instancing, etc works in ARMA scripting. Any Wiki page or other resource that can help me? Or do you have some info which could illuminate the subject? Better yet, I'd really love to see a full language specification for the ARMA scripting language and execution engine. It's kind of an "oddball" to me, and unlike anything else I've ever played with, so I still have lots of unanswered questions...

5) External Code / API?

For starters, I'm still curious as to whether or not BI has provided us any sort of programming API... a good C or C++ API? A portal to writing our own DLLs to do all sorts of amazing things? Ways to "talk" to the engine, and even accomplish low-level tasks? Well, I don't have my hopes up for such a thing, but just wanted to ask. My curiousity has been peaked after seeing a Wiki page about custom memory allocators/managers. But what exactly is the purpose of it, and what can be done with such a thing? And in the abscence of any kind of true programming API, is there any known "open door" to have the engine load/run external code -- or go so far as to communicate with it (maybe through shared memory or LAN?)? Or is it just completely impossible?

Conclusion...

Sorry for this being such a long post. But I'll stop there, even though I have a million more questions, heheh. Your time and help is greatly appreciated!

Regards,

P.S. --

I'm always very hungry to learn more about how the engine and game ticks at the very lowest levels, and appreciate being pointed to any resources which cover such topics. :)

Share this post


Link to post
Share on other sites

1- As far as i know there is no easy way to do that. You can temporally asing that unit to the group and remove it after so it will follow the formation...

2- Just use http://community.bistudio.com/wiki/setWaypointStatements

3- Well first of all, there is no way to pass reference/pointers in arma script, so the int* value would be not posible in arma. The "compile" its only if you have the function as a string (usually when you read it from other sqf file, but it can be usefull in other cases too).

The function in arma2 would be something like


clamp ={
private['_value', '_min','_max'];
_value= _this select 0;
_min= _this select 1;
_max= _this select 2;

_a = _value; 
_a = if (_a > _max) then { _max} else {_a};
_a = if (_a < min) then { _max} else {_a};

_a;
}

4-Well you are right the execvm doesn't wait to that script to finish after continuing the execution, but it isn't a new OS thread its just aonther instance on the game script scheduler, so it has its limitations. "while{ true } do { };" will only run as fast as the script scheduler lets it ^^. And as far as i know there is no 100% safe way to synchronize threads but i think that i have never got a script deadlock in arma2 , just by using gobal variables between scripts :P

About the variable scope, variables starting by a character count as global variables, that are recogniced anywhere on any script. For example:

ThisIsAglobalVariable="OMG";

There is no special way to define them just asing them a value.

Local variables are defined starting with _ and are local where you define them. For example if you define it at the start of the script, they will be local to all that script. If you define it inside a function would be local to that funcion and you can even define it inside a while loop and will be only local there , a bit weird but not hard to understand. You can define the variables by using the "private" as i wrote before(on the clamp example), or just asinging a value to the variable.. but i think that defining then with "private" its cleaner.

5- the only way to comunitate with arma 2 by default its by copytoclipboard and copyFromClipboard script commands, but... as you may gess its not very efficient. But there is also a addon that allows you to comunicate to arma2 throught named pipes called "Jayarmalib". http://forums.bistudio.com/showthread.php?t=98647

Finally i think that there was some kind of page on the wiki that answers most of this questions, but i haven't found it :P

Edited by columdrum

Share this post


Link to post
Share on other sites

1) You can do it that way but it has problems.

The following units will move to the waypoint but if the original unit moves they still move to the old position.

You could continually delete the waypoint and create a new one repeatedly but this causes some strange animation problems.

If it's for a static object then that isn't a problem.

ID numbers can be seen in the editor just click on IDs and zoom into the map a little. Luckily you don't need them anyway, I find it easier to give the house/object a name and the you can use it like any placed object.

If you wanted to destroy a house without using the ID number you could use this

house = NearestObject [this,"house"];house setdamage 1 

you would place a game logic over the house and place the code in the init.

If you wanted to attach a waypoint you would use

[grp, 0] waypointAttachObject house 

Share this post


Link to post
Share on other sites

Thank you both for your posts; very helpful and informative. :)

@ columdrum:

For #3, disregard my use of a pointer in the C example. I didn't even write the function correctly anyway, haha. :D

Should've been more like this:

int clamp(int* value, int min, int max) {
int a = *value;
a = ( (a > max) ? max : a );
a = ( (a < min) ? min : a );
*value = a;

return 0x00; //! success
}

Not sure what I was smoking when I wrote it the first time. Lol, I must have been tired if I used the address op instead of dereferencing the pointer to "value" and then modifying the underlying value. Sometimes the code in my head doesn't map so well to my keyboard... thank God for Intellisense, lol! :o

But anyway... I suppose ARMA script also has no concept of references, as in:

C++:

void myFunc( int &a ) { ++a; }

C#:

void myFunc( ref int a ) { ++a; }

?

Again, thank you both and if anyone has more to add I'm all ears! :)

Share this post


Link to post
Share on other sites
Well first of all, there is no way to pass reference/pointers in arma script

That's not true. Sure, there are no explicit pointers. But all arrays are passed by reference by default! So there you have it. Implicit pointers.

Observe:

_a = [1, 2, 3];

_transform = {
  private ["_list"];
  _list = _this;

  for "_i" from 0 to ((count _list) - 1) do
  {
     _list set [_i, ((_list select _i) + 1)];
  };

  _list
};

diag_log format["_a: %1", _a];
diag_log format["_a: %1", (_a call _transform)];
diag_log format["_a: %1", _a];

In this example the array _a gets passed by reference. That is, the variable _a is not the array, but merely a pointer to that array, somewhere in memory...

If you don't want to modify the original/passed array, you'd need to make a copy of that array first inside the _transform code-block with the + operator.

And really, if we have pointers for our arrays, we have pointers for everything, aslong as you put your stuff into arrays! Scalar, boolean, no matter what. So there is a difference between these two calls here:

// scalar get's passed by value
37 call _someFunction;

// anything in an array, respectively the array itself,
// get's passed by reference
[37] call _someFunction;

Share this post


Link to post
Share on other sites
That's not true. Sure, there are no explicit pointers. But all arrays are passed by reference by default! So there you have it. Implicit pointers.

In this example the array _a gets passed by reference. That is, the variable _a is not the array, but merely a pointer to that array, somewhere in memory...

If you don't want to modify the original/passed array, you'd need to make a copy of that array first inside the _transform code-block with the + operator.

And really, if we have pointers for our arrays, we have pointers for everything, aslong as you put your stuff into arrays! Scalar, boolean, no matter what. So there is a difference between these two calls here:

// scalar get's passed by value
37 call _someFunction;

// anything in an array, respectively the array itself,
// get's passed by reference
[37] call _someFunction;

Very useful information! :)

Most programming languages, afaik, work this way too. To be technical, arrays are nothing but an abstraction to represent a contiguous piece of memory storing a series of elements of a known size (even if we don't know the size, the ARMA engine does)... just as a string is an abstraction for a char array and time, as we know it in the non-scientific sense, is an abstraction for the position of the sun over our part of the earth. ;)

In C, C++ and many other languages an array actually is a pointer. It is the address of the beginning of the block of memory which stores the elements we allocate. Consider the following:

// Create an array of chars...
char myName[] = { 'A', 'a', 'r', 'o', 'n', '\0' };

// Allocate block of memory...
char* myName_ptr = (char *) malloc( (sizeof(char) * 6) );

/*----------------------------------------------------
* Here we treat a pointer like an array to copy chars 
*----------------------------------------------------*/

for( int i = 0; i < 6; ++i )
myName_ptr[i] = myName[i];

// Print the result...
cout << myName_ptr << endl;

/*----------------------------------------------------
* Now we can treat the array like a pointer... 
*----------------------------------------------------*/

// Like this...
for( char* p = myName; *p != '\0'; ++p )
cout << *p;
cout << endl;

// Or this...
int n = -1;
while( (n++ < 6) && ( *(myName + n) != '\0' ) )
cout << *(myName + n);
cout << endl;

The result of this code is that my name, "Aaron", is printed 3x's to the console:

Aaron

Aaron

Aaron

Maybe you already knew this, but surely someone else didn't. But the point is that arrays actually are pointers, in a technical sense and very real sense in most languages. The indexing operator "[#]" is really a "shortcut" for dereferencing (pointer + offset). So this:

myArray[2]

...is the same as this:

*(myArray + 2)

Cheers! :cool:

Edited by ryfle

Share this post


Link to post
Share on other sites

This was originally an edit appended to my last post, but more and more language questions keep popping into my head. :D

1) No 'continue' keyword?

I have another question now though... Is there no "continue" keyword for loops, as found in most every C-family language? If not, how do we get a loop to skip to the next cycle, like this:

for(i = 0; i < count; ++i) {
if(myArray[i] >= 10)
	continue;

myArray[i]++;
}

FYI, this loop would iterate through the members of "myArray". If the item at index 'i' on the current iteration is greater than or equal to 10, the loop would go back to the top and start on the next cycle. If not, it runs to completion and increments myArray. If myArray held the following integers:

int[] myArray = { 1, 5, 6, 3, 11, 4, 15, 9, 21 };

Then the loop would modify these values to:

{ 2, 6, 7, 4, 11, 5, 15, 10, 21 }

...just wanted to clarify, in case someone is not familiar with C-like languages.

But how do we achieve this in ARMA script? :confused:

2) Array indexing...

I really, really, really hate the array indexing operator syntax in ARMA scripting... being forced to type the word 'select' followed by the index. Like this:

_x = myArray select 3;

To me, that's just unnecessary verbosity and breaks the readability of what is a rather simple and understandable language. I'm used to (and like) the C-style index operator:

_x = myArray[3];

Much shorter, less typing, easier to read... and also consistent with array indexing form in mathematics.

So I'm wondering, has anyone come up with a good preprocessor trick that will allow me to use the C-style syntax in my own code (so long as I #include my macros file)? Basically I'd like to come up with a good macro shortcut so if I write this in my script:

_weapon = _items[3];

The preprocessor would expand it to this:

_weapon = _items select 3;

Just an "aesthetic" syntactical difference, but one I would thoroughly enjoy. Knowing how to accomplish this, if possible, would let me invent a few more scripting syntax shortcuts. My more complicated scripts are just a pain to read (and type), so this could be a huge help!

3) Objects in ARMA script

I'm not sure I quite understand how objects (reference types, like classes) work in ARMA script. I've seen some scripts where people define their own classes, and I see the language has some notion of inheritance/polymorphism, but my understanding is insufficient.

In a language like C# or C++, declaring a class type is syntactically similar to what I've seen in AS (ARMA script):

class Dog { ... };

And classes can have members -- variables and methods. Consider this in C#:

public enum DogSize
{
Teacup	= 0x00,
Toy		= 0x01,
Small		= 0x02,
Medium	= 0x04,
Large		= 0x08,
Giant		= 0x16
};

public abstract class Dog
{
/* Constructors: */
public Dog( string Name ) {
	this._name = Name;
}

public Dog( string Name, int Age, DogSize Size ) 
	: this(Name) 
{
	this._age = Age;
	this._size = Size;
}

/* Private Fields: */
private string _name;
private int _age;
private DogSize _size;

/* Public Properties: */
public string Name {
	get { return _name; }
}

public int Age {
	get { return _age; }
	set { _age = value }
}

public DogSize Size {
	get { return _size; }
}

/* Instance Methods: */
void increaseAge() { _age++; }

internal abstract void Bark(int volume);

public void Sleep(int time) {
	// ...some sleeping code
}

/* Static Methods */
public static void ChangeSize( Dog dog, DogSize newSize ) {
	dog._size = newSize;
}
};

The above code demonstrates many features of classes in C# (most of which come from its C++ heritage). I threw in the enum example because I want to know if enums, or something like it, exists in AS? But anyway, I'll describe what you're seeing above for those who don't know C#, Java, C++ or something similar (just ignore the enum bit in that case -- it's basically just a way of having a type constrained to predefined constants)...

What we have here is a brand new type, called "Dog". The code above is not a Dog in and of itself, but rather the definition of what a Dog is. Just like "int" is not actually an integer, because it's a type. 10 is an integer. And we can create variables of type int like this: "int i = 10;"... Likewise, we create instances of classes in C#. That's where those constructors come in handy!

Dog myDog = new Dog("Rover");

Constructors, which look similar to a method or C function, return a new instance of the class type and the code within performs initialization logic. But what about in ARMA scripting? All the class definitions I've yet seen seem to be more of a "configuration" type of thing, rather than OOP object types. Can we create instances of classes? Can we have constructors, destructors, etc?

Skimming further down, we see the private fields. These are just variable fields that every instance of Dog will have. All instances of type Dog will have a name ("_name"), age ("_age") and dog size ("_size"). Like this:

Dog myDog = new Dog( "Rover", 2, DogSize.Medium );

Dog yourDog = new Dog( "Spot", 3, DogSize.Large );

Now we have two instances of type Dog: myDog and yourDog. myDog goes by the name "Rover", has an age of 2 and a dog size of Medium, while yourDog goes by the name "Spot", has an age of 3 and is Large. AFAIK, ARMA script classes certainly have variable fields. That is the bread and butter of classes. But how does it compare to a language like C#, C++ or Java? How do we access instance fields? static fields? Well, are static fields even possible, or is everything static because there is no instancing concept at all?

The next thing is the incredibly powerful feature called properties. Properties act as accessor methods for fields within a class (instance or static). Just by looking at the code, you should understand what it does. All of the fields I declared, _name, _age and _size are private... they cannot be read or touched by anything other than the instance of Dog itself. They're not exposed to the outside world (if I'd used the 'public' scope they would be, but that's considered bad practice). So we use properties, which are quite like methods/functions at heart. Properties can have two parts: a 'get' accessor and 'set' accessor, which are like sub-functions of the property. A get accessor returns a value, while a set accessor modifies a value. Which one is called simply depends on the context we use the property in:

string s = myDog.Name; // calls Name >> get, which returns the field '_name'

myDog.Name = "Rex"; // calls Name >> set... details below..

The second line triggers the 'set' accessor in the Name property, which contains the code: "_name = value;". The 'value' keyword is a special keyword, used only in property set accessors. It stores the value given when an assignment operator (=) is used on a property implicitly (yes, the C# compiler and CLR are quite smart). To the outside world, the property "Name" works just like a regular string variable. But we can not only control access to it better, but we can do other things in our code every time the property is accessed to read or modify it. Let's say we change the definition like so:

public string Name {

get

{

Console.WriteLine("_name has been read...");

return _name;

}

set

{

Console.WriteLine("_name has been changed to '{0}'...", value);

_name = value;

}

}

Now something cool will happen any time Name is used. The following code:

string str = myDog.Name;

myDog.Name = "Rex";

Would not only copy the literal "Rover" into the variable 'str' and change the underlying field '_name' to "Rex", but would generate the following output to the console:

_name has been read...

_name has been changed to 'Rex'...

A contrived example, which has little real-world application (save debug logging), but you hopefully get the point: it's an incredibly powerful feature. Furthermore, you can define just a 'get' accessor or just a 'set' accessor in the body of your property (as you see in the others), to constrain the property to being read-only or write-only. You can also modify accessibility by preceding the keyword 'get' or 'set' with a modifier like 'public', 'private', 'internal' or 'protected'. And as you might have guessed, properties can also be virtual (allowing overriding implementations in derived classes) or abstract (no default implementation and requires implementation in derived class types). But what about ARMA script? It doesn't appear there is such a feature, but do we have anything remotely close to it?

Now we come to the methods. They are quite similar to the free-standing functions you see in C/C++ and ARMA script. However, they are members of a class. They are accessed, like variables, by using the "dot accessor", the "." operator. And all members of the class, such as other methods, variables, properties, etc are local in scope within a method body. In other words, to access _name we can just write "_name"... we don't need to use "this._name" (or something like myDog._name, which is invalid) unless it's to disambiguate (e.g., distinguish a field from a parameter with the same identifier (name)). The first method, "increaseAge", can only be called inside of the class itself. "myDog.increaseAge();" would be invalid, as it's private. But in any (non-static) method, property or constructor within Dog, it can be used as simply "increaseAge();" or "this.increaseAge();". The public method "Sleep" can be used inside of Dog the same way, but since it's public it can be used by external code... in another class you may call it with "myDog.Sleep(1);". I won't get into a lengthy explanation of the one above that, "Bark", but it is different. You'll notice it has no actual body/implementation, but is merely a declaration of a method (its signature). This is because it is an abstract method. Abstract methods have no implementation. They are similar to virtual methods, which can be overridden and reimplemented in derived classes that inherit from the base class. But abstract methods: A) have no implementation B) must be overridden and implemented in any class that inherits from the defining parent class. So if we wanted to make a class called "Beagle" that inherited from Dog, the "Bark" method must be overridden and implemented:

public class Beagle : Dog { }; // does not compile! Error!

// This is correct:

public class Beagle : Dog

{

public override void Bark(int volume) {

// ...implement logic to actually do something here

}

};

Now if we'd made that method virtual instead of abstract, there would be some differences. First of all, we could drop the "abstract" modifier on class Dog, and overriding Bark in type Beagle would be optional, not required. And it would need a body inside of our Dog definition:

public virtual void Bark(int volume) { }

...instead of

public abstract void Bark(int volume);

And inside the body, we could have an implementation. As a contrived example, let's say we made it like this:

public virtual void Bark(int volume) {

Console.WriteLine( "Dog.Bark( {0} ) called!", volume.ToString() );

}

...then we override it inside of Beagle, like so:

public override void Bark(int volume) {

Console.WriteLine( "Beagle.Bark( {0} ) called!, volume.ToString() );

}

Then we write a simple program with this in our Main() method:

Beagle myBeagle = new Beagle( "Jackson", 4, DogSize.Small );
myBeagle.Bark(1);

We would just get "Beagle.Bark(1) called!" printed to the console. But C# has a special feature. What if we want the implementation with Dog to run too? Then we can change our Beagle.Bark implementation to:

public override void Bark(int volume) {

Console.WriteLine( "Beagle.Bark( {0} ) called!, volume.ToString() );

base.Bark(volume);

}

Now the above program would generate this output:

Beagle.Bark(1) called!

Dog.Bark(1) called!

Cool, eh? Again, these are quite useless, contrived examples I made up for simplicity's sake. But you can probably see how this puts immense power in a programmer's hands! The only other thing to remark on is the "static" keyword you see in the original Dog definition, which is used to mark the "ChangeSize" method static. What does it do? Well, it makes the method static, lol. In other words, it's a member of type Dog itself rather than a member of Dog instances. Consider this:

Dog myDog = new Dog("Rover");

myDog.ChangeSize( myDog, DogSize.Small ); //! Error!

That wouldn't work. ChangeSize is static, and not a member of the myDog instance but is a member of Dog itself. The correct way to use it is as follows:

Dog myDog = new Dog("Rover");

Dog.ChangeSize( myDog, DogSize.Small );

Now that's pretty much everything... I think... I've demonstrated most of the Object-Oriented (OO) features of classes in my favorite language, C#, and I'm very curious how these concepts and syntax can be paralleled in ARMA script. And I apologize for the deep C# OOP lesson, but I wanted the things I'm asking to make sense to non-programmers so they'll know what on earth I'm talking about. :)

So what do I need to know about this to be a better ARMA scripter? What features do we or don't we have at our disposal? What key differences might I need to be aware of? Every little bit of info helps. And if someone could mirror back my C# above in ARMA script with some explanations, that would be a godsend indeed! :D

Thanks again for your time and help!

P.S. --

I'm pretty sure ARMA script has an #include directive? How to use it? Just like in C/C++? Wiki page? Can't find it...

Edited by ryfle

Share this post


Link to post
Share on other sites

There is a lot to digest in your post and I am not the one to answer all of it, but here is an untested soultion to "1) No 'continue' keyword?"

 
// Loop through all of the elements in the local array _myArray.
for "_i" from 0 to count _myArray do
{
   // If the value of element at the current index in the loop is < 10 (i.e. opposite of >=10) then execute the code in the block otherwise skip and go to next iteration.
   if (_myArray select _i < 10) then
   {
       // Get the value of the element at the current index and increment by 1.
       _new = _myArray select _i + 1;
      // Set the new value of the element to the current index.
       _myArray set [_i, _new];
   };
   // Go to next iteration.
};

For you PS, here is where you can find macros and such:

http://community.bistudio.com/wiki/PreProcessor_Commands

Edited by Loyalguard
Code typos

Share this post


Link to post
Share on other sites

I did not read your entire post (already know C#).

1.

Any loop where you would normally use a 'continue' statement can be rewritten using if's and elses.

This

for(i = 0; i < count; ++i) {
if(myArray[i] >= 10)
	continue;

myArray[i]++;
}

can be rewritten as:

for "_i" from 0 to (count - 1) {
   if (myArray select _i < 10)  {
       myArray set [_i, (myArray select _i)+1];
   };
};

Of course you can use your condition and just reverse the bool using ! or not.

2.

While I agree that bracket indexing would be nicer there is no such thing. I don't think you should try to (re)invent language syntax using macros IMO. SQF is not your average mainstream language - I think you'll be more happy writing scripts when you accept that.

3.

There are objects in SQF. There are no real classes AFAIK. There is no language features supporting encapsulation, inheritance or polymorphishm. While you could simulate polymorphishm using nested arrays you would still call the "methods" using normal functional style.

I've demonstrated most of the Object-Oriented (OO) features of classes in my favorite language, C#, and I'm very curious how these concepts and syntax can be paralleled in ARMA script.

I'm sure many, including myself, would be happy if you could script OO properly in Arma, however, it is not possible at the moment.

So what do I need to know about this to be a better ARMA scripter? What features do we or don't we have at our disposal? What key differences might I need to be aware of? Every little bit of info helps.

I think you need to learn that OOP is not properly supported in Arma scripting at the moment IMO and live with it. SQF and C are more alike:

There are ordinary control structures, if, for, while.

Simple types: numbers, strings, arrays, objects (that is objects you can control through the arma engine. Think of them like pointers).

Preprocessor (Use with care, please)

Edited by Muzzleflash

Share this post


Link to post
Share on other sites

I see, Muzzleflash, thanks. And if you already know C#, then you didn't miss anything by not reading it all, so no worries. And thanks to you too, Loyal.

Pity there is no 'continue'. While the proposed solution you've given can work, and I was aware of it, it does make code bulkier when you've got some sophisticated inner workings in your loop. My example was chosen for simplicity, so you worked around it easily by just using if-then. But I wish we had such a thing for when it counts. :P

Anyways... Some other things I'm curious about now... I found the Wiki page for the SQF preprocessor, and found the #include directive. But does it just work "as is", like in C? What I'm trying to do is write a file full of all my own SQF functions, called "atcfn.sqf". And I'm also writing a macros file called "atc_macros.?" <-- that's where I'm confused. What type of file should that header be? SQF? HPP? Maybe even .h, or anything I so choose? And do I just #include what I want, and no need for the "compile" or "preprocessFile" commands?

Also, I want to have my own global scripts folder. I have scripts I use all the time and: A) I hate copying them into every mission folder B) I don't want to put them somewhere on my HD and have to use a long, full path to get at them. The Wiki page mentions this. But... my ARMA 2 OA folder doesn't have the path "ca\data\scripts\" anywhere. I have no scripts folder at all, as far as I can tell. So I suppose I must create it? Where?

----------------------------------------

P.S. to Muzzleflash:

----------------------------------------

I also think SQF is a pretty cool scripting language, and as a game developer I know how monumental the task of writing a good script engine/language is. So I couldn't knock on BIS for anything. ARMA and everything about it is incredibly impressive. But I still see room for improvement in future versions. In fact, I'm planning to soon make a suggestion thread (aimed at ARMA 3) about enhancements I'd like to see in the scripting language.

One of the main things I think could make the language better is streamlining the syntax. While the language is simple and elegant in many ways, some parts of it are just, imho, hideously verbose. As I said earlier, the array indexing operator 'select' is one thing I hate. I think that maybe 'select' could remain valid syntax, for those who like it, but the C-like [#] indexing op should be added. I also think that the "then" keyword, used in compound with if/else statements, should be eliminated. It serves no purpose, and just adds clutter and more typing. The same thing goes for the "do" keyword following loop constructs. Ironically, 'do' doesn't seem to (afaik) actually do anything in SQF like it does in C. So I deem it, too, unnecessary verbosity. I'd also like to see an increment/decrement operator. And also more streamlining and consistency in the use of parenthesis, brackets and braces. And they should also eliminate the over-use of semicolons (e.g., terminating an if/else branch). If it were up to me, my enhanced SQF syntax would be like this:

Array indexing

Original Syntax:

x = myArray select 3;

Enhanced Syntax

x = myArray[3];

If/Else

Original Syntax:

if( condition ) then { 

} else {

};

Enhanced Syntax

if( condition ) { 

} else {

}

While

Original Syntax:

while { condition } do {

};

Enhanced Syntax

while( condition ) {

}

-- or --

do {

} while( condition );

For loop

Original Syntax:

for[ {i = 0;}, {i < n;}, {n = (n + 1);} ] do {
...
};

// -- OR -- //

for "_i" from 0 to n do {
...
};

Enhanced Syntax

for( int i = 0; i < n; n++) {
...
}

Foreach loop

Original Syntax:

{
_x doSomething;
} forEach units grp;

Enhanced Syntax

foreach(Unit u in grp) {
u doSomething;
}

Just a little preview of the type of syntax enhancements I think would be a major win. Makes things a bit more C-like, which I think is a good thing. The C-family syntax is ubiquitous for a reason -- it's elegant, concise and powerful. Plus, programmers like me don't have to relearn basics to script ARMA, and newbies can pick it up just as fast and walk away with basic knowledge that could be employed in learning programming. :cool:

Language features, on the other hand, are a different story and can be tough to implement. So I won't whine about there being no true OOP yet. :D

Edited by ryfle

Share this post


Link to post
Share on other sites

#include works pretty much as in C. I believe there is some small differences in how it understands some special cases, although I'm not sure. For missions you can't (technically I think someone found a quiter complicated and fragile way) #include a file from a parent folder; only from the current folder or below.

Typically config files and macro files have the extension .hpp. I'm guessing the reason this convention arose was because then many editors will syntax highlight as C++ header files. If you are not using a fancy IDE for sqf then I can recommend setting your editor to treat .sqf files like .cpp files. Gives a bit of syntax highlighthing. Typically you don't #include sqf files into other sqf files. Instead they are most often (call) compile'd or execVM'd.

If you want to have your scripts available all the time then you should look into creating an addon. I primarily focus on mission scripting and I soft link my script folder into the missions I work on. Then if I improve or fix something then it is immediately available to all missions. If you do this just be careful when you delete since you might delete the real folder.

Regarding the folder "ca\data\scripts\"; I don't mess with the original game folders.

Share this post


Link to post
Share on other sites

First a quote from Miguel de Icaza (nice guy btw, was a mono contributor myself for some years):

The worst kind of language tends to be the in-house custom language. These in-house and ad-hoc languages tend to be quick hacks that evolve over the life time of a program. The authors of those languages are not language designers or experts in language design nor compiler developers, so their languages suffer as a result. The languages tend to be slow, buggy, poorly documented and packed with quirks.

sqf is such an example.

What you should do is download addons/missions/scripts and learn from them and check how things are done (there are tools to de-pbo addons and missions).

It helps you much more to understand how things work in this game than trying to compare everything to "real" programming languages like C/C++/C#/Java/whatever.

Of course, having a background in this area makes things much easier.

Xeno

Share this post


Link to post
Share on other sites

Thanks again, Muzzleflash. I'll look into creating my own addon that the game will load, as I plan to create my own modules and such anyway. Kinda new in this department though.

If anyone else knows about the "global scripts folder", please illuminate the subject for us. :)

First a quote from Miguel de Icaza (nice guy btw, was a mono contributor myself for some years):

sqf is such an example.

Exactly. :)

That's not to knock on BIS, but it's true. And actually, compared to many others I've seen SQF is far better. But it's still an "in-house" language, as your friend calls them. I often wonder though why script engine programmers just don't stick to what's already out there and they're familiar with. Obviously they know C and C++. So why not just copy the syntax and basic constructs, and omit complicated language features like polymorphism if need be? That I don't know. Maybe people who embark on such projects have ambitious plans when they get started, and the hope of making a full, powerful language, and their dreams quickly get battered by production schedules, investor demands, etc.

As far as "in-house" languages go, I think the best one I've seen comes from a little Company called Conitec. They produce a relatively inexpensive and simple "hobbyist" game engine called "3D Game Studio", which I used to play with a bit years ago. And they've implemented their own scripting language called "Lite-C". They essentially did what I said: copied the basics of a working language, C++, omitted a few things and added a few. And the result is impressive considering that they're not a language and compiler design company! You can Google it if you're curious about it's features, or see a syntax example on Wikipedia. But it should suffice to say it compiles the script to machine code on-the-fly, has reasonable performance, can work in tandem with C and C++ code, is "COM-aware" and is somewhat extensible.

I'm no expert, by any stretch of imagination, but I know a thing or two about language design and writing compilers, assemblers, linkers and even disassemblers. In my teen years, I fancied myself something of a hacker... or "h4x0rZ" as I might have called it back then, lol. :rolleyes: I've never worked in the field on a professional level, but have done some pretty cool hobby projects. I wrote a minimal x86 assembler, a C preprocessor (never finished my C compiler), a C#/.NET compiler, a couple interpreters, and I've even implemented a barebones .NET language + compiler of my own and purpose-built interpreted scripting languages. None of these were commercial quality, lacked features, had bugs, etc... But weren't bad for a one-man-job in a short period of time. With a little compensation for my time, I'd be more than happy to help BIS spruce up their SQF language and engine (seriously), heheh. Probably will never actually happen, but I put the offer out there. ;)

What you should do is download addons/missions/scripts and learn from them and check how things are done (there are tools to de-pbo addons and missions).

Yep, I've got the BIS tools and a few unofficial ones, and I do this. It's how I implemented my basic spotter script where the spotter uses a rangefinder and tells you the range to whatever target you aim at -- as seen in the single mission for OA.

It helps you much more to understand how things work in this game than trying to compare everything to "real" programming languages like C/C++/C#/Java/whatever.

Of course, having a background in this area makes things much easier.

This is true. And I'm still learning not only how the game and engine works and operates, but how to play the game and enjoy all of its features. And that's what this thread is for -- spilling at questions, discussing and learning more! :)

And yes, having programming experience makes learning scripting super easy. The main roadblock is lack of documentation/examples, difficulty in finding things and some of SQF's oddities.

Regards,

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
Sign in to follow this  

×