Ir al contenido

Foto

Semantics and limitations for structures?


  • Por favor identifícate para responder
8 respuestas en este tema

#1
DarthGizka

DarthGizka
  • Members
  • 867 mensajes
I have a lot of trouble figuring out how structures work in Dragon Age script (as opposed to structures anywhere else). Assuming they do, which I'm not certain about. Do you have any advice, or do you know any resources on the web or discussion threads here that might shed some light on this?
 
When I tried using structures I got a lot of inexplicable quiet script failures, and in some case the game crashed instantly. The last test case in the script below is the most baffling, as it would hint at something like delayed reference semantics.
 
The most problematic aspect is that it seems impossible to get at the values of fields in sub structures. If I try to access them using syntax like outer.inner.field then the script fails quietly, and if I copy out the inner struct (copy = outer.inner; value = copy.field) then the value is lost.
 
struct TPVWeapon
{
   int weapon;
   int rune_1;
   int rune_2;
   int rune_3;
};

struct TPVWeaponSet
{
   struct TPVWeapon mhand;
   struct TPVWeapon ohand;
   int ammo;
};

struct TPVEquip
{
   int helmet;
   int gloves;
   int armour;
   int boots;

   int belt;
   int amulet;
   int ring_1;
   int ring_2;

   struct TPVWeaponSet set_1;
   struct TPVWeaponSet set_2;
};

struct TPVConfig
{
   string tag;
   int    level;
   string build;
   struct TPVEquip equip;
};

void floaty_ (string s, int colour = 0xFFFFFF, float duration = 3.0f)
{
   DisplayFloatyMessage(GetMainControlled(), s, FLOATY_MESSAGE, colour, duration);
}

void main ()
{
   struct TPVWeapon weapon;
   struct TPVWeaponSet set;
   struct TPVEquip   equip;
   struct TPVConfig    cfg;
   int rune;

/** /
   weapon = cfg.equip.set_1.mhand;  // script aborts quietly
   floaty_("weapon.rune_1 = " + IntToString(weapon.rune_1));
/**/

/** /                
   rune = cfg.equip.set_1.mhand.rune_1;  // game crashes instantly, without fail
   floaty_("rune = " + IntToString(rune));
/**/   

   equip = cfg.equip;
   set = equip.set_1;
   weapon = set.mhand;

   weapon.rune_1 = 1;
   set.mhand.rune_1 = 2;
   equip.set_1.mhand.rune_1 = 3;
   cfg.equip.set_1.mhand.rune_1 = 4;
   
   floaty_("weapon.rune_1 = " + IntToString(weapon.rune_1)); // prints '1'   
   
   // okay, clearly not reference semantics

   cfg.equip.set_1.mhand.rune_1 = 666;

   equip = cfg.equip;
   set = equip.set_1;
   weapon = set.mhand;

   floaty_("weapon.rune_1 = " + IntToString(weapon.rune_1)); // prints '0' 
   
   // WTF?
      
   struct TPVWeapon copy_source;
   
   copy_source.rune_1 = 42;
   weapon = copy_source;
   copy_source.rune_1 = -1;

   floaty_("weapon.rune_1 = " + IntToString(weapon.rune_1)); // prints '42'
                                                                           
   set.mhand.rune_1 = 666;
   weapon = set.mhand;

   floaty_("weapon.rune_1 = " + IntToString(weapon.rune_1)); // prints '-1'

   // WTF?!
}
Edit: added 'in Dragon Age script' etc. to the first sentence, for clarification. Mea culpa.

#2
Guest_JujuSamedi_*

Guest_JujuSamedi_*
  • Guests

When using a language like C, structs are basically data structures that contain a set of information. When you declare a struct in C, you have this memory address of size struct which contains your variables.

struct numbers
{

    int integerNumber;
    double doubleNumber;


};

When you create a variable like this, you are creating a place in memory of size int + double which has variables that reference these values.

 

Typical in C they are two ways to create a struct variable.

struct numbers numberValues;

The above is done in  a scope and is destroyed automatically by the compiler.

struct number * numberValues = (struct number*)malloc(sizeof(struct number));

The above is done in a scope and will have to be destroyed manually. This is a concept known as dynamic memory allocation and you can do some really cool **** with it.

 

The above description is from a C programming perspective. I don't do scripts myself but from what I can tell the structs in the scripting language are quite different. This is an excerpt from the wiki

struct quaternion
{
    float w, x, y, z;
};
 
// constructor
struct quaternion Quaternion(float fW, float fX, float fY, float fZ)
{
    struct quaternion q;
 
    q.w = fW;
    q.x = fX;
    q.y = fY;
    q.z = fZ;
 
    return q;
}

It looks like before you use these structs, you need to create a constructor for them. I take it this constructor is the thing responsible for memory allocation. The above struct's constructor will be called like this

struct quaternion q = Quaternion(0.0, 0.0, 0.0, 0.0);

This allocates the resources available for the struct and references it to your variable. Well then in your problem, it looks like you need to allocate the structs before you use them in a struct. An example of how it would be done in DA script is

struct a(int a)
{

    int a;


};

struct a aConstructor(int a)
{
    struct a value;
    value.a = a;
    return value;

};

struct b
{

    struct a b;


};

struct bConstructor(struct a valueIn)
{
     struct b value;
     value.b = aConstructor(valueIn);
     return value;


}
void main()
{

    struct b value = bConstructor(aConstructor(13));



}

I might be wrong, I am giving this feedback from a C programming perspective and not a dragon age script perspective. I have never written code in Dragon age script.


  • A luna1124 le gusta esto

#3
DarthGizka

DarthGizka
  • Members
  • 867 mensajes
Thanks for your perspective from the world outside Dragon Age script. I am somewhat familiar with these concepts, having programmed on a daily basis ever since I left the army in the fall of '89 and having written a couple of decompilers along the way.

The use of 'constructors' makes no difference at all. It is one of the many things that I've tried without incorporating them into the posted script.

#4
Sunjammer

Sunjammer
  • Members
  • 925 mensajes

You can't nest structs in dascript.


  • A luna1124 le gusta esto

#5
DarthGizka

DarthGizka
  • Members
  • 867 mensajes
Thanks, Sunjammer. Guess I'm back to square one then. Good bye structs, hello arrays!

Is there a place where I could read up on gotchas like this? I've poked around in the toolset wiki and the CHM file, but there wasn't a lot of hard info.

#6
Sunjammer

Sunjammer
  • Members
  • 925 mensajes

Unfortunately there is a lack of authoritative resources for dascript. I suspect the only reason I know it is because dascript is an evolution of nwscript and you couldn't do it there.

 

I did some quick tests and have found the following, in a struct:

  • you can include the command, effect, event, float, int, location, object or string data types and they all appear to work
  • you can include the itemproperty data type however there is no point (it is not used by anything other than its Get/SetLocal functions)
  • you can include the vector data type or another struct but it will either fail to compile (can generate a "Void expression where non void required" error) or fail at runtime when you try to access it/its members
  • you cannot include the resource data type: it will not compile (generates a "Bad type specifier" error)
  • you cannot include an array (can generate an "Incorrect variable state left on stack" or "Void expression where non void required" error)

  • A MerAnne y a DarthGizka les gusta esto

#7
DarthGizka

DarthGizka
  • Members
  • 867 mensajes

Thanks, that's splendid.

 

The biggest stumbling block is that we can have neither arrays of structs nor structs of arrays, which makes it very difficult to deal with interesting amounts of heterogeneous data. As regards nested structs, a couple of years back I read something about an improved Aurora compiler that could deal with nested structs as long as you didn't do nested accesses. But I guess that must have been either a community thing or dated after the release of Dragon Age (or both).

 

It's also curious that structs use value (copy) semantics in DA, whereas they usually tend to have reference semantics in languages of this level. The latter makes it possible to build linked data structures, stuff them into arrays etc. pp.

 

Anyway, for my current project I'll stick with what the standard toolkit can do safely.

 

I'm working around the restrictions for the resource type by using integer ids internally and converting the ids to the actual resource things via a monster switch statement. The supporting code (include files) is generated by scripts, so there is no pain involved beyond hitting a hotkey and pasting the new code into the toolkit after an update. I tried blasting the generated code into the database directly but that requires admin access and I hate working with programs that are running in admin mode under newer Windows versions. They do not really play ball with the rest of the world (i.e. things like drag and drop).



#8
Sunjammer

Sunjammer
  • Members
  • 925 mensajes

There are possibly a few (hacktastic) workarounds that might worth trying:

  • wrapping a vector in a dummy location
  • wrapping a resource in a dummy event
  • a pseudo-array could be created using an effect (limited to floats, ints, objects and/or strings)

  • A MerAnne le gusta esto

#9
DarthGizka

DarthGizka
  • Members
  • 867 mensajes

Thanks, you're brilliant! An event can house resources and it can be stuffed into an array. Since arrays are passed by reference this allows it to farm out functionality to separate procedures without getting bogged down by exploding parameter lists.

I've implemented a major piece of my logic using event arrays and it works like a charm. And it is still reasonably readable.

Example for a mid-level procedure that reads and modifies data (an 'event' stands for an item to be equipped):

int add_rune_tags_to_weapon_nodes_ (event[] ee)
{
   int weapons_with_runes = 0, array_size = GetArraySize(ee), i;
   
   for (i = 0; i < array_size; ++i)
   {
      int item_slot = GetEventInteger(ee[i], EE_BACKLINK_);
      
      if (!slot_can_have_items_with_runes_(item_slot))
      {
         continue;
      }
      
      // runes occupy the three slots immediately following the containing item
      int max_check = Min(i + 3, array_size);
      int min_rune = item_slot + 1;
      int max_rune = item_slot + 3;
      int j;
      string rune_tags;
      
      for (j = i + 1; j < max_check; ++j)
      {
         int slot = GetEventInteger(ee[j], EE_BACKLINK_);
         
         if (min_rune <= slot && slot <= max_rune)
         {                                           
            string tag = GetEventString(ee[j], EE_RESOURCE_);

            rune_tags = insert_tag_into_sorted_list_(tag, rune_tags);
         }
      }
      
      if (rune_tags != "")
      {
         ee[i] = SetEventString(ee[i], EE_RUNES_, rune_tags);
         
         ++weapons_with_runes;
      }
   }
   
   return weapons_with_runes;
}

Top-level procedure that owns the complicated data:

int process_equipment_ (object target, int[] equipment, int do_equip = FALSE)
{                                                                     
   event[] ee;
   int status = preprocess_equipment_(equipment, ee);

   if (status <= 0)  return status;

   add_rune_tags_to_weapon_nodes_(ee);
   
   status = match_items_from_inventory_(target, ee);
   
   if (status <= 0)  return status;           
   
   int need_Sandal = status > 1;
   
   status = create_and_level_missing_items_(target, ee);

   if (status <= 0)  return status;

   if (do_equip)
   {
      status = equip_(target, ee);
      
      if (status <= 0)  return status;
   }
   
   return need_Sandal ? 2 : 1;
} 

Needless to say, writing this code was a strong reminder of why I like languages with exception handling or macro capabilities (preferrably both). :D

 

Anyway, thanks again for putting me on the right track!