GameMaker Semi-complicated problem I'm stuck on?

D

ditherling

Guest
I... don't know how I can summarize what I'm asking for in the subject. So.

I'm trying to make a system that turns regular text files into visual novel scenes. Everything works up to the point of actually putting the data to use, and it's VEXING me because everything else has been relatively smooth sailing. I'm pretty new at doing this kind of problem-solving in code.

Basically, there's a line of text above the actual spoken dialogue which looks like this:

Code:
> ALCH[MOVE(-1,LEFT,1); WAIT(0.5); MOVE(3,0.3,1)]; PAGE[MOVE(-1,RIGHT,1); WAIT(0.5); MOVE(3,0.7,1)]
'ALCH' is a tag belonging to an element on the screen - here, it belongs to a sprite of the Alchemist character. The square brackets show all the commands for that element. Then you have actual commands inside.

Ideally, all these effects and movements will be done using the Novel_obj, using script_execute. The script, element tag, and arguments are passed into script_execute, and that works fine. It's the staging I'm struggling with.

Semi-colons are used to separate commands and command-sets that need to happen in sequence, one after the other. Without the semi-colons, they happen simultaneously.

BASICALLY IT WORKS LIKE THIS:

Code:
> ALCH[MOVE(3,LEFT,1);MOVE(3,RIGHT,1)]; PAGE[WAIT(0.5);MOVE(3,0.5,1)]

set_array[0,0] = "ALCH"
set_array[0,1] = alch_commmand_array
set_array[1,0] = "STAGE"
set_array[2,0] = "ALCH"
set_array[2,1] = page_commmand_array

alch_command_array[0,0] = "MOVE"
alch_command_array[0,1] = ["3","LEFT","1"]
alch_command_array[1,0] = "STAGE"
alch_command_array[2,0] = "MOVE"
alch_command_array[2,1] = ["3","RIGHT","1"]

alch_command_array[0,0] = "WAIT"
alch_command_array[0,1] = "0.5"
alch_command_array[1,0] = "STAGE"
alch_command_array[1,0] = "MOVE"
alch_command_array[1,0] = ["3","0.5","1"]
Then I'm trying to set up a system where there's 'command_stage[]' and 'set_stage' variables that start at 0, tick up when all the commands/sets in a stage are done, go back to 0 when the textbox clears.

Then I have 'command_stage_count[]' and 'set_stage_count', which track the stage the for loop is currently running through. Basically:

Code:
// RESET COUNT
set_stage_count = 0;
for (var i = 0; i < array_length_1d(command_stage_count); i++)
{
   command_stage_count = 0;
}

// EXECUTE COMMANDS
for (var set = 0; set < array_height_2d(set_array); set++)
{
   if set_array[set,0] != "STAGE"
   {
       var element = set_array[set,0];
       var commands = set_array[set,1];
       for (var cmd = 0; cmd < array_height_2d(commands); cmd++)
       {
           if commands[cmd,0] != "STAGE"
           {
               if set_stage = set_stage_count and command_stage[set] = command_stage_count[set]
               {
                   var command_script = commands[cmd,0];
                   var command_args = commands[cmd,1];
                   script_execute(command_script,element,command_args); // RETURNS TRUE IF DONE, FALSE IF NOT
               }
           }
           else
           {
               command_stage_count[set]++;
       }
       }
   }
   else
   {
       set_stage_count++;
   }
}
Can someone please help me tweak this so it does what I want? It's definitely not finished, but I'm stuck on what to do next. This is the most complicated thing I've ever tried to do. 0_0

also can someone show me how to do code formatting i've never used the forum before I'm big stupid.
 
Last edited by a moderator:

jo-thijs

Member
I... don't know how I can summarize what I'm asking for in the subject. So.

I'm trying to make a system that turns regular text files into visual novel scenes. Everything works up to the point of actually putting the data to use, and it's VEXING me because everything else has been relatively smooth sailing. I'm pretty new at doing this kind of problem-solving in code.

Basically, there's a line of text above the actual spoken dialogue which looks like this:

Code:
> ALCH[MOVE(-1,LEFT,1); WAIT(0.5); MOVE(3,0.3,1)]; PAGE[MOVE(-1,RIGHT,1); WAIT(0.5); MOVE(3,0.7,1)]
'ALCH' is a tag belonging to an element on the screen - here, it belongs to a sprite of the Alchemist character. The square brackets show all the commands for that element. Then you have actual commands inside.

Ideally, all these effects and movements will be done using the Novel_obj, using script_execute. The script, element tag, and arguments are passed into script_execute, and that works fine. It's the staging I'm struggling with.

Semi-colons are used to separate commands and command-sets that need to happen in sequence, one after the other. Without the semi-colons, they happen simultaneously.

BASICALLY IT WORKS LIKE THIS:

Code:
> ALCH[MOVE(3,LEFT,1);MOVE(3,RIGHT,1)]; PAGE[WAIT(0.5);MOVE(3,0.5,1)]

set_array[0,0] = "ALCH"
set_array[0,1] = alch_commmand_array
set_array[1,0] = "STAGE"
set_array[2,0] = "ALCH"
set_array[2,1] = page_commmand_array

alch_command_array[0,0] = "MOVE"
alch_command_array[0,1] = ["3","LEFT","1"]
alch_command_array[1,0] = "STAGE"
alch_command_array[2,0] = "MOVE"
alch_command_array[2,1] = ["3","RIGHT","1"]

alch_command_array[0,0] = "WAIT"
alch_command_array[0,1] = "0.5"
alch_command_array[1,0] = "STAGE"
alch_command_array[1,0] = "MOVE"
alch_command_array[1,0] = ["3","0.5","1"]
Then I'm trying to set up a system where there's 'command_stage[]' and 'set_stage' variables that start at 0, tick up when all the commands/sets in a stage are done, go back to 0 when the textbox clears.

Then I have 'command_stage_count[]' and 'set_stage_count', which track the stage the for loop is currently running through. Basically:

Code:
// RESET COUNT
set_stage_count = 0;
for (var i = 0; i < array_length_1d(command_stage_count); i++)
{
   command_stage_count = 0;
}

// EXECUTE COMMANDS
for (var set = 0; set < array_height_2d(set_array); set++)
{
   if set_array[set,0] != "STAGE"
   {
       var element = set_array[set,0];
       var commands = set_array[set,1];
       for (var cmd = 0; cmd < array_height_2d(commands); cmd++)
       {
           if commands[cmd,0] != "STAGE"
           {
               if set_stage = set_stage_count and command_stage[set] = command_stage_count[set]
               {
                   var command_script = commands[cmd,0];
                   var command_args = commands[cmd,1];
                   script_execute(command_script,element,command_args); // RETURNS TRUE IF DONE, FALSE IF NOT
               }
           }
           else
           {
               command_stage_count[set]++;
       }
       }
   }
   else
   {
       set_stage_count++;
   }
}
Can someone please help me tweak this so it does what I want? It's definitely not finished, but I'm stuck on what to do next. This is the most complicated thing I've ever tried to do. 0_0

also can someone show me how to do code formatting i've never used the forum before I'm big stupid.
Hi and welcome to the GMC!

I like your approach of creating your own mini language to deal with this.

Your issue is that you're having some trouble implementing an asynchronous system to perform commands.

Well, I had a go at it (and I'm in a little bit of a rush) and I ended up with this:
Code:
// INITIALIZE
active_set_queue = ds_queue_create();
next_set_queue = ds_queue_create();

ds_queue_enqueue(active_set_queue, [0, 0, [], 0]);

// RESET
ds_queue_clear(active_set_queue);
ds_queue_clear(next_set_queue);

ds_queue_enqueue(active_set_queue, [0, 0, [], 0]);

// EXECUTE
while !ds_queue_empty(active_set_queue) {
    var pointer = ds_queue_dequeue(active_set_queue);
    var set = pointer[0];
    
    if set_array[set, 0] == "STAGE" {
        if !ds_queue_empty(ds_queue_next) {
            ds_queue_enqueue(next_set_queue, pointer);
            break;
        }
    } else {
        var element = set_array[set, 0];
        var commands = set_array[set, 1];
        
        var progress_array = pointer[2];
        var progress_count = pointer[3];
        
        for (var cmd = pointer[1]; cmd < array_length_1d(commands); cmd++) {
            if commands[cmd, 0] == "STAGE" {
                var new_index = 0;
                for (var progress_index = 0; progress_index < progess_count; progress_index++) {
                    var progress_command = progress_array[progress_index];
                    
                    if !script_execute(progress_command[0], element, progress_command[1]) {
                        progress_array[new_index++] = progress_command;
                    }
                }
                progress_count = new_index;
                
                if progress_count != 0 {
                    break;
                }
            } else {
                var command_script = commands[cmd, 0]; // make sure these are scripts and not just strings
                var command_args = commands[cmd, 1];
                
                var progess_command = script_execute(command_scipt, element, command_args);
                
                progress_array[progress_count++] = progess_command;
            }
        }
        
        if cmd < array_length_1d(commands) {
            ds_queue_enqueue(next_set_queue, [set, cmd, progress_array, progress_count]);
        }
    }
    
    if set + 1 < array_length_1d(set_array) {
        ds_queue_enqueue(active_set_queue, [set + 1, 0, [], 0]);
    }
}

var temp = active_set_queue;
active_set_queue = next_set_queue;
next_set_queue = temp;
I'll quickly explain the main idea behind it.

There are 2 queues: active_set_queue and next_set_queue.
The former keeps track of which element-command combos still have to be executed in the current step and the latter keeps track of which element-command combos need to be executed in the next step.
These queues contain arrays of the form: [set, cmd, progress_array, progress_count]
The former two elements "set" and "cmd" have the same meaning as in your code.
The latter two elements "progress_array" and "progress_count" keep track of which commands inside a certain set are still being executed.

The "INITIALIZE" code initializes these queues and should only be run once.
The "RESET" code resets the queues and should only occasionally be run.
The "EXECUTE" code executes a single step worth of commands and thus should be run every step.

Now, you mentioned in a comment that the scripts representing commands return either 1 or 0, depending on whether they finished or not.
This is not a system that really allows you to work asynchronously, so I propose a different system (that the code above is based on).
The scripts in the command arrays set things up to execute the command, but don't execute it yet.
They then return an array of the form: [progress_script, progress_params]
where "progress_script" is a script that when given the elment and "progress_params" as parameters,
performs a single step of execution for the command and then returns in a boolean whether the command as finished.

To illustrate this concept, suppose we have a command "JUMP".
We could assign it the following script:
Code:
///command_jump_init(element, params)

agument0.vspd = -10;

return [command_jump_step, []];
where:
Code:
///command_jump_step(element, params)

with argument0 {
    y += vspd;
    vspd += 0.5;
    
    return vspd > 10;
}
Continuing the main idea of the code,
when a set is encountered, we start looping through the commands in it.
For each command, we execute the initializing script and keep track of the returned step script and its parameters (the combo which I called "progress_command") in the array "progress_array".
"progress_count" keeps track of the part of the array that is actually used.
When a "STAGE" command is encountered, all the step scripts in "progress_array" are executed and both "progress_array" and "progress_count" are updated to only keep tack of the commands that are not finished yet.

That's the main idea behind it.

If you have any question, feel free to ask them!
 
D

ditherling

Guest
i am THIS close

I sort of smooshed your way and my old way together. The commands are now pre-emptively stored in arrays and queues, and the queues get whittled down to nothing as they play out. All the commands or sets in a stage are stored in an array that can be iterated through:

Code:
if !ds_exists(STAGE_QUEUE,ds_type_queue) { STAGE_QUEUE = ds_queue_create(); }

var set_array = argument0;
var command_queue_count = 0;
var set_in_stage = 0;
var command_in_stage = 0;
var current_set;
var command_array;
for (var set = 0; set < array_height_2d(set_array); set++)
{
   if set_array[set,0] != "STAGE"
   {
       var command_queue = ds_queue_create();
       global.active_command_queues[command_queue_count] = command_queue;
       command_queue_count++;
       var element = set_array[set,0];
       var commands = set_array[set,1];
       var commands_in_stage = 0;
       for (var cmd = 0; cmd < array_height_2d(commands); cmd++)
       {
           if commands[cmd,0] != "STAGE"
           {
               command_array[commands_in_stage,0] = commands[cmd,0]; // GATHER COMMANDS FOR THIS STAGE INTO COMMAND_ARRAY
               command_array[commands_in_stage,1] = element;
               command_array[commands_in_stage,2] = commands[cmd,1];
               commands_in_stage++;
           }
           else
           {
               ds_queue_enqueue(command_queue,command_array);
               commands_in_stage = 0;
               command_array = 0;
           }
       }
       ds_queue_enqueue(command_queue,command_array);
       commands_in_stage = 0;
       command_array = 0;
       current_set[set_in_stage] = command_queue;
       set_in_stage++;
   }
   else
   {
       ds_queue_enqueue(STAGE_QUEUE,current_set);
       set_in_stage = 0;
       current_set = 0;
   }
}
ds_queue_enqueue(STAGE_QUEUE,current_set);
set_in_stage = 0;
current_set = 0;
So I put in this:
Code:
> ALCH[MOVE(-1,LEFT,1); MOVE(3,0.3,1)] PAGE[MOVE(-1,RIGHT,1); MOVE(3,0.7,1)];
Which is supposed to instantly move two different sprites to opposite sides off-screen, then move them towards the middle simultaneously.

And I SHOULD get (and I seem to get):
Code:
STAGE_QUEUE
[0] set_array
// Which means there's only one overall stage, basically.

set_array
[0] alch_queue
[1] page_queue

// Then the actual commands are pretty much the same as they used to be, i.e. script, element, argument array.

alch_queue
[0] snap far left
[1] move middle left

page_queue
[0] snap far right
[1] move middle right
This is what the Step event looks like now:
Code:
if !all_commands_done
{
   var current_stage = ds_queue_head(STAGE_QUEUE);
   for (var set = 0; set < array_height_2d(current_stage); set++)
   {
       var current_set = ds_queue_head(current_stage[set]);
       var command_done = true;
       if is_array(current_set[1])
       {
           for (var cmd = 0; cmd < array_height_2d(current_set); cmd++)
           {
               var script = current_set[cmd,0];
               var element_tag = current_set[cmd,1];
               var args = current_set[cmd,2];
               if !script_execute(script,element_tag,args) { command_done = false; }
               // DEBUG DRAW
               var str = "> " + string(element_tag) + " = " + string(script) + " ( ";
               for (var i = 0; i < array_length_1d(args); i++)
               {
                   if i != 0 { str += ","; }
                   str += " " + string(args[i]);
               }
               str += " )";
               DrawSetDebug(c_black);
               draw_text(30,30+(30*cmd)+(90*set),str);
               // DEBUG DRAW
           }
       }
       else
       {
           var script = current_set[0];
           var element_tag = current_set[1];
           var args = current_set[2];
           if !script_execute(script,element_tag,args) { command_done = false; }   
       }
       if command_done
       {
           ds_queue_dequeue(current_stage[set]);
       }
   }
   var stage_done = true;
   for (var set = 0; set < array_height_2d(current_stage); set++)
   {
       if !ds_queue_empty(current_stage[set])
       {
           stage_done = false;
       }
   }   
   if stage_done
   {
       ds_queue_dequeue(STAGE_QUEUE);
   }
   if ds_queue_empty(STAGE_QUEUE)
   {
       all_commands_done = true;
   }
   var debug = true;
}
When I play the game, Alch does what it's supposed to, moves from off-screen left to middle left. But Page never shows up, because it's stuck at 0,0 where sprites are instantiated. :|
I'm pretty sure this is a problem with the Step event and not how the commands are stored, but I'm eh about it. I am probably also a bit out of my depth.
 

jo-thijs

Member
i am THIS close

I sort of smooshed your way and my old way together. The commands are now pre-emptively stored in arrays and queues, and the queues get whittled down to nothing as they play out. All the commands or sets in a stage are stored in an array that can be iterated through:

Code:
if !ds_exists(STAGE_QUEUE,ds_type_queue) { STAGE_QUEUE = ds_queue_create(); }

var set_array = argument0;
var command_queue_count = 0;
var set_in_stage = 0;
var command_in_stage = 0;
var current_set;
var command_array;
for (var set = 0; set < array_height_2d(set_array); set++)
{
   if set_array[set,0] != "STAGE"
   {
       var command_queue = ds_queue_create();
       global.active_command_queues[command_queue_count] = command_queue;
       command_queue_count++;
       var element = set_array[set,0];
       var commands = set_array[set,1];
       var commands_in_stage = 0;
       for (var cmd = 0; cmd < array_height_2d(commands); cmd++)
       {
           if commands[cmd,0] != "STAGE"
           {
               command_array[commands_in_stage,0] = commands[cmd,0]; // GATHER COMMANDS FOR THIS STAGE INTO COMMAND_ARRAY
               command_array[commands_in_stage,1] = element;
               command_array[commands_in_stage,2] = commands[cmd,1];
               commands_in_stage++;
           }
           else
           {
               ds_queue_enqueue(command_queue,command_array);
               commands_in_stage = 0;
               command_array = 0;
           }
       }
       ds_queue_enqueue(command_queue,command_array);
       commands_in_stage = 0;
       command_array = 0;
       current_set[set_in_stage] = command_queue;
       set_in_stage++;
   }
   else
   {
       ds_queue_enqueue(STAGE_QUEUE,current_set);
       set_in_stage = 0;
       current_set = 0;
   }
}
ds_queue_enqueue(STAGE_QUEUE,current_set);
set_in_stage = 0;
current_set = 0;
So I put in this:
Code:
> ALCH[MOVE(-1,LEFT,1); MOVE(3,0.3,1)] PAGE[MOVE(-1,RIGHT,1); MOVE(3,0.7,1)];
Which is supposed to instantly move two different sprites to opposite sides off-screen, then move them towards the middle simultaneously.

And I SHOULD get (and I seem to get):
Code:
STAGE_QUEUE
[0] set_array
// Which means there's only one overall stage, basically.

set_array
[0] alch_queue
[1] page_queue

// Then the actual commands are pretty much the same as they used to be, i.e. script, element, argument array.

alch_queue
[0] snap far left
[1] move middle left

page_queue
[0] snap far right
[1] move middle right
This is what the Step event looks like now:
Code:
if !all_commands_done
{
   var current_stage = ds_queue_head(STAGE_QUEUE);
   for (var set = 0; set < array_height_2d(current_stage); set++)
   {
       var current_set = ds_queue_head(current_stage[set]);
       var command_done = true;
       if is_array(current_set[1])
       {
           for (var cmd = 0; cmd < array_height_2d(current_set); cmd++)
           {
               var script = current_set[cmd,0];
               var element_tag = current_set[cmd,1];
               var args = current_set[cmd,2];
               if !script_execute(script,element_tag,args) { command_done = false; }
               // DEBUG DRAW
               var str = "> " + string(element_tag) + " = " + string(script) + " ( ";
               for (var i = 0; i < array_length_1d(args); i++)
               {
                   if i != 0 { str += ","; }
                   str += " " + string(args[i]);
               }
               str += " )";
               DrawSetDebug(c_black);
               draw_text(30,30+(30*cmd)+(90*set),str);
               // DEBUG DRAW
           }
       }
       else
       {
           var script = current_set[0];
           var element_tag = current_set[1];
           var args = current_set[2];
           if !script_execute(script,element_tag,args) { command_done = false; }  
       }
       if command_done
       {
           ds_queue_dequeue(current_stage[set]);
       }
   }
   var stage_done = true;
   for (var set = 0; set < array_height_2d(current_stage); set++)
   {
       if !ds_queue_empty(current_stage[set])
       {
           stage_done = false;
       }
   }  
   if stage_done
   {
       ds_queue_dequeue(STAGE_QUEUE);
   }
   if ds_queue_empty(STAGE_QUEUE)
   {
       all_commands_done = true;
   }
   var debug = true;
}
When I play the game, Alch does what it's supposed to, moves from off-screen left to middle left. But Page never shows up, because it's stuck at 0,0 where sprites are instantiated. :|
I'm pretty sure this is a problem with the Step event and not how the commands are stored, but I'm eh about it. I am probably also a bit out of my depth.
There is a problem at this part:
Code:
       if is_array(current_set[1])
       {
           for (var cmd = 0; cmd < array_height_2d(current_set); cmd++)
           {
               var script = current_set[cmd,0];
               var element_tag = current_set[cmd,1];
               var args = current_set[cmd,2];
               if !script_execute(script,element_tag,args) { command_done = false; }
           }
       }
       else
       {
           var script = current_set[0];
           var element_tag = current_set[1];
           var args = current_set[2];
           if !script_execute(script,element_tag,args) { command_done = false; }   
       }
It looks like you want to figure out whether current_set is a 1D array or a 2D array.
That's not what you wrote however.

GameMaker actually doesn't make a distinction between 1D and 2D arrays.
For GameMaker, a 1D array is just simply a 2D array with a height of 1.

When you write this:
Code:
current_set[1]
GameMaker will actually interprete it as though you had written:
Code:
current_set[0, 1]
So, what your if-statement above does, is check whether the second element of the first entry in the 2D "current_array" is an array.
This is the element tag of the first command in the current set.
If this element tag is an array, the remaining commands are skipped.

You probably want to use this instead:
Code:
       for (var cmd = 0; cmd < array_height_2d(current_set); cmd++)
       {
           var script = current_set[cmd,0];
           var element_tag = current_set[cmd,1];
           var args = current_set[cmd,2];
           if !script_execute(script,element_tag,args) { command_done = false; }
       }
A similar issue occurs here:
Code:
   for (var set = 0; set < array_height_2d(current_stage); set++)
"current_stage" seems to be a 1D array, so has a height of 1, whereas you were interested in the length, so it should be:
Code:
   for (var set = 0; set < array_length_1d(current_stage); set++)
This mistake of course skipped every set except for the first one, causing your issue.

I don't know how you keep track in your code of which commands are still in progress,
but I guess you have a working system for that in place, because you said sequencing commands worked for the first set.
 
Top