GMLscripts.com

Discuss and collaborate on GML scripts
Invert

You are not logged in.

#1 Script Submission » sort_list_of_lists(); Also, quicksort. » 2019-02-25 11:35:21

Tsa05
Replies: 0

As I understand it, GameMaker uses Quicksort to sort ds_lists. Cool!

Supposing I needed to Quicksort something else? Orrrrrrrrr, in this case, I have a very specific need: A ds_list filled with ds_lists, and I want to sort by size of list. Using ds_list_sort would sort by list reference number...gotta implement quicksort.

This is 2 scripts, and recursive:

Script sort_list_of_lists(list, low, high)

Expand///	@desc	Accepts a ds_list and sorts it via a partition function
///	@arg	{real}	list	List of list of arrays
///	@arg	{real}	low		Low value of list part
///	@arg	{real}	high	High value of list part
var pat = argument[0];
var low = argument[1];
var hig = argument[2];

if(low<hig){
	var part = sort_partition(pat, low, hig);
	sort_list_of_lists( pat, low, part-1);
	sort_list_of_lists( pat, part+1, hig);	
}

So, we sort around a partition value, then sort before and after. The sort happens in the next function:
Script sort_partition(list, low, high)

Expand///	@arg	{real}	List
///	@arg	{real}	low
///	@arg	{real}	high

var lis = argument[0];
var low = argument[1];
var hig = argument[2];

var pivot = ds_list_size(lis[|hig]);
var i = low-1;

for(var j=low; j<hig; j++){
	// If curr element is <= pivot
	var subLis = lis[|j];
	var sz = ds_list_size(subLis);
	if(sz <= pivot){
		i++;
		
		// Swap i and j
		var swap = lis[|i];
		lis[|i] = lis[|j];
		lis[|j] = swap;
		
		// Optional
		ds_list_mark_as_list(lis, i);
		ds_list_mark_as_list(lis, j);
		
	}
}

var swap = lis[|i+1];
lis[|i+1] = lis[|hig];
lis[|hig] = swap;

// Optional
ds_list_mark_as_list(lis, i+1);
ds_list_mark_as_list(lis, hig);

return (i+1);

Some fun little things here. The partition script sets pivot equal to the size of a list instead of the value of an entry because I want to sort by that. Similarly, the script has "var subLis = lis[|j];     var sz = ds_list_size(subLis);" and then compares sz to pivot. In a usual quicksort you'd just compare lis[|j] to pivot.

Other thing; in my particular implementation, I have a scenario where I want to have GMS clean up my lists for me--using ds_list_mark_as_list() causes GMS to know that whenever the parent list is destroyed, the child lists should be as well. But, ofc, GMS can't seem to detect when the lists it's moving around were already marked as lists, so I have to re-mark them each time I move them. Wooooooo

#2 Re: Script Submission » is_between(test, min, max); The one we do every day » 2019-02-09 23:16:45

Fair enough biggrin  I was going for legibility with the if-else structure, but it probably does add execution time!

The use of median is clever!--though I wanted a solution that could be inclusive or exclusive, thanks to the ambiguity of the English language biggrin

#3 Script Submission » is_between(test, min, max); The one we do every day » 2019-02-09 17:14:08

Tsa05
Replies: 3

Couldn't find on the search, but since we test things between a range a million times a day, why not scriptify it?

Expand///	@description	Returns whether a test value is between 2 other values
///	@arg	{real}	test		The value to test
///	@arg	{real}	minimum		The low value in the range
///	@arg	{real}	maximum		The high value in the range
///	@arg	{bool}	*Inclusive	Include min and max in the check
///	@returns	{bool}	Whether the test value is between the min and max
/*
*	Simply turns the all-too-common check into a single plain-English command. Inclusive flag optional
*	Also works with inverted ranges, eg the min is really the max 
*	and the max is really the min. This saves the trouble of needing
*	to pre-sort the values before testing.
*/
var test = argument[0];
var low = argument[1];
var hig = argument[2];
var inc = false;
if(argument_count>3){
	inc = argument[3];
}
if(inc){
	if( (test>=low && test<=hig) || (test<=low && test>=hig) ){
		return true;
	}else{ return false; }
}else{
	if( (test>low && test<hig) || (test<low && test>hig) ){
		return true;
	}else{ return false; }
}

#4 Re: Script Submission » is_in » 2018-06-24 17:43:14

Just added a small update to the script, so that it takes in an array *or* list of arguments. This way, you can build a set of test values instead of hard-coding the names to test against.

#5 Re: Script Submission » draw_rectarray() Make rectangles, get mouse » 2018-06-24 17:38:19

xot wrote:

That's a nice idea. But something weird is going on here:

Expandretval[array_length_1d(retval)] = i div 4;

This seems to return an array/list of colliding rectangles, though I'm not sure if array_length_1d(-1) is valid.

Should it not just be this?

Expandretval = i div 4;

Good point! The code does work; array_length_1d of a non-array returns 0....but that's not what the script says it does!~~
Fixing description. Basically, the idea is that sometimes more than one overlapping rectangle is "hit"; this script actually returns a list of all "hit" boxes!

#6 Script Submission » draw_rectarray() Make rectangles, get mouse » 2018-06-01 10:04:22

Tsa05
Replies: 2

Simple script for things like menus. You create an array with coordinates of rectangles; the script draws rectangles, then tells you which one the mouse is colliding with (last collision returned in multiple case).

Example:

Expanddraw_rectarray([0,0,100,50, 0,50,100,100, 0,100,100,150], 0)

Draws 3 filled rectangles.
If the mouse is within (0,0) to (100,50), the script returns 0
If the mouse is within (0,50) to (100,100), the script returns 1
If the mouse is within (0,100) to (100,150), the script returns 2

Expand///	@arg	{real}	array
///	@arg	{bool}	outline
///	@returns	The index of the rectangle currently intersected by mouse or -1
/// GMLscripts.com/license

/*
*	Part of SLIDGE Engine
*	Redistribution is permitted
*	Submitting to gmlscripts
*/
/*
*	Draws a rectangle for each set of coordinates in an array
*	Returns an array containing the # of each box that the mouse hit
*/
var A = argument0;
var O = argument1;
var retval = -1;
for(var i=0;i<array_length_1d(A); i+=4){
	draw_rectangle(A[i+0], A[i+1], A[i+2], A[i+3], O);
	if(point_in_rectangle(mouse_x, mouse_y, A[i+0], A[i+1], A[i+2], A[i+3])){
		retval[array_length_1d(retval)] = i div 4;
	}
}
return retval;

#7 Script Submission » point_on_rectangle » 2018-04-19 16:41:33

Tsa05
Replies: 0

Didn't see this after a bit of searching around, so I've gotta post it.

Expand///@description		point_on_rectangle(x, y, minX, minY, maxX, maxY)
///@arg	{real}	x		Coordinate of point outside rectangle
///@arg	{real}	y		Coordinate of point outside rectangle
///@arg	{real}	minX	Left X of box
///@arg	{real}	minY	Top Y of box
///@arg	{real}	maxX	Right X of box
///@arg	{real}	maxY	Bottom Y of box
/*
*	Note that this is not point_IN_rectangle
*
*	Returns the coordinates where a line between the
*	center of a rectangle and a given point intersects
*	the rectangle.
*/

var px = argument[0];
var py = argument[1];
var minX = argument[2];
var minY = argument[3];
var maxX = argument[4];
var maxY = argument[5];
var r = -1;
	
if (minX <= px && px <= maxX) && (minY <= py && py <= maxY){
	// Point should not be inside the rectangle!
	return r;
}
var midX = (minX + maxX) / 2;
var midY = (minY + maxY) / 2;
// if (midX - x == 0) -> m == ±Inf -> minYx/maxYx == x (because value / ±Inf = ±0)
var m = (midY - py) / (midX - px);

if (px <= midX) { // check "left" side
	var minXy = m * (minX - px) + py;
	if (minY <= minXy && minXy <= maxY){
		r[0] = minX; r[1] = minXy;
		return r;
	}
}

if (px >= midX) { // check "right" side
	var maxXy = m * (maxX - px) + py;
	if (minY <= maxXy && maxXy <= maxY){
		r[0] = maxX; r[1] = maxXy;
		return r;
	}
}

if (py <= midY) { // check "top" side
	var minYx = (minY - py) / m + px;
	if (minX <= minYx && minYx <= maxX){
		r[0] = minYx; r[1] = minY;
		return r;
	}
}

if (py >= midY) { // check "bottom" side
	var maxYx = (maxY - py) / m + px;
	if (minX <= maxYx && maxYx <= maxX){
		r[0] = maxYx; r[1] = maxY;
		return r;
	}
}

// Somehow...
show_debug_message("Something didn't work in point_on_rectangle");
return r;

This is basically like... the "Smash Brothers Code"... Something is outside of a defined rectangular area, and you want to show where it has gone with a visual indicator that is on or within the rectangle.
Icon drawn on a rectangle boundary

#8 Re: Script Submission » is_in » 2018-01-30 09:15:52

Ahhhh. Took me a moment to realize what you meant; absolutely right. I put variableName in the explanation comment in order to illustrate the difference in what gets typed into your code in comparison to how you'd have to type it without the script. But to anyone who's not me, it looks like the example of the function "in use" requires a string with the variable name due to my naming schema... Adjusting now for clarity!

#9 Script Submission » is_in » 2018-01-29 17:44:48

Tsa05
Replies: 4

It's just silly but saves me a lot of time; mostly posting to remind everyone that we can do this: Basically, comparing multiple variables to one value.

Expand///	@description		Returns whether a value is in the supplied set of arguments
///	@function			is_in( value, test0, [test1], [...] )
///	@param				value	The value to search for
///	@param				test0	Value to compare to
///	@param				[test1]	Add parameters as needed to extend the set
///	@returns	{bool}	True when value was found in set
/*
*	Suppose we want to test whether x, y, or z is equal to value.
*	GameMaker:
*		if(x==value || y==value || z==value)
*	Python:
*		if value in {x, y, z}
*	This script shortens what you'd have to type in GameMaker like so:
*		if is_in(value, x, y, z)
*		Also accepts an array: 
*			array = [x, y, z];
*			if is_in(value, array)
*/
// Created for SLIDGE engine
// GMLscripts.com/license

var test = argument[0];
var argz = 0;
for(var z=1; z<argument_count; z+=1){
	argz = argument[z];
	if(is_array(argz)){
		for(var i=0; i<array_length_1d(argz); i+=1){
			if(test == argz[i]){
				return true;
			}
		}
	}else{
		if(test == argument[z]){
			return true;
		}
	}
}
return false;

Have fun saving keystrokes; feel free to balloon this out to test data type mismatches and all manner of other fun things as needed.

#10 Script Submission » draw_slider(x,y,size,params,orientation) » 2017-05-15 14:15:17

Tsa05
Replies: 1

Horizontal Slider
Draws a horizontal or vertical slider bar on the screen, handling mouse clicking along the way.
Returns a simple array containing the current position of the slider and also the click-dragging state in order to help it play well with other UI elements.

Typical usage in draw event:

Expandslide = draw_slider(x,y, 300, slide, 1);
amount = slide[0];

Where slide is set up prior to drawing like so:
slide = [defaultStartingPercent, 0]

Expand///@description			draw_slider(x,y,size,params,orientation)
///@arg		{real}	x		Starting x-coord of slider
///@arg		{real}	y		Starting y-coord of slider
///@arg		{real}	size	Pixels wide or tall for slider
///@arg		{array}	params	Parameters for persistent settings
///@arg		{real}	orient	0 for horizontal, 1 for vertical
/*
*	Returns:
*		Parameter array of data generated by slider
*
*	Parameter array:
*		0: The default amount to slide (0-1)
*		1: Whether the slider is engaged (0,1)
*
*	The return array's zero index value is the percent of slide.
*	This varies with distance from the origin coords, so:
*	Horizontal slide goes 0-1 from left to right
*	Vertical slide goes 0-1 from top to bottom.
*/
/// GMLscripts.com/license
{
var sx = argument[0];
var sy = argument[1];
var ss = argument[2];
var sp = argument[3];
var so = argument[4];

var barColor = make_color_rgb(204,204,204);
var borderColor = make_color_rgb(112,112,112);
var shadowColor = make_color_rgb(145,145,145);
var lightGrey = make_color_rgb(237,237,237);
var darkGrey = make_color_rgb(168,168,168);

var thickness = 10;
var snapPercent = .04;

var mLeft = mouse_check_button(mb_left);

var sa,se,x1,y1,x2,y2,sx1,sy1,sx2,sy2,mx1,my1,mx2,my2,cx,cy,r, retVal;
if(is_array(sp)){
	sa = sp[0]; se = sp[1];
}else{
	sa = real(sp); se = 0;
}
if(!so){
	// Horizontal
	x1 = sx; y1 = sy;
	x2 = x1+ss; y2 = y1+thickness;
	
	sx1 = x1; sy1 = y1;
	sx2 = x2; sy2 = y1+thickness/5;
	
	var mThick = thickness/5;
	mx1 = (x1+x2)/2-mThick/2; my1 = y1;
	mx2 = (x1+x2)/2+mThick/2; my2 = y2;
	
	// Adjust amount
	if(se || (mLeft && 
	point_in_rectangle(mouse_x,mouse_y, x1,y1-thickness/2, x2, y2+thickness/2))){
		sa = clamp((mouse_x-x1)/ss,0,1);
		se = 1;
	}
	
	r = thickness;
	cy = (y1+y2)/2;
	cx = x1+sa*ss;
	
	// Snap
	if(!mLeft){
		se=0;
		if( (abs(cx- (x1+x2)/2))/ss<=snapPercent){
			cx = (x1+x2)/2;
			sa = clamp((cx-x1)/ss,0,1);
		}
	}
}else{
	// Vertical
	x1 = sx; y1 = sy;
	x2 = x1+thickness; y2 = y1+ss;
	
	sx1 = x1; sy1 = y1;
	sx2 = x1+thickness/5; sy2 = y2;
	
	var mThick = thickness/5;
	mx1 = x1; my1 = (y1+y2)/2-mThick/2;
	mx2 = x2; my2 = (y1+y2)/2+mThick/2;
	
	// Adjust amount
	if(se || (mLeft && 
	point_in_rectangle(mouse_x,mouse_y, x1-thickness/2,y1, x2+thickness/2, y2))){
		sa = clamp((mouse_y-y1)/ss,0,1);
		se = 1;
	}
	
	r = thickness;
	cx = (x1+x2)/2;
	cy = y1+sa*ss;
	
	// Snap
	if(!mLeft){
		se=0;
		if( (abs(cy- (y1+y2)/2))/ss<=snapPercent){
			cy = (y1+y2)/2;
			sa = clamp((cy-y1)/ss,0,1);
		}
	}
}
retVal[0] = sa;
retVal[1] = se;
draw_set_circle_precision(64);
draw_set_color(barColor);	
draw_rectangle(x1,y1,x2,y2,0);
draw_set_color(shadowColor);
draw_rectangle(sx1,sy1,sx2,sy2,0);
draw_set_color(borderColor);
draw_rectangle(x1,y1,x2,y2,1);
draw_rectangle(mx1,my1,mx2,my2,0);
draw_circle_color(cx,cy,r,lightGrey,darkGrey,0);
draw_set_color(borderColor);
draw_circle(cx,cy,r,1);


return retVal;
}

#11 Re: Script Submission » string_truncate » 2017-01-21 11:12:02

Agreed, in which case it returns "..."

#12 Script Submission » string_truncate » 2016-12-22 13:02:59

Tsa05
Replies: 2

For cases when you want to show a string within a specific area (like a button), but keep the size constrained.
You supply the string and size; this supplies a portion of your string plus "..."

If your desired width is smaller than "..." then I can't help you.

Expand/// @desc string_truncate(string, width);
/*
*   Shortens a string and appends an ellipsis if the string is
*   wider than width. Resulting string fits within width.
*/
///   @arg {string} string The string to truncate
///   @arg {real}   width  The width to fit within
/*  
*	Returns:
*   Truncated string
*/

var txt = argument[0];
var extra = 0;
while(string_width(txt)>argument[1]-extra && string_length(txt)>1){
    extra = string_width("...");
    txt = string_copy(txt,1,string_length(txt)-1);
}
if(extra) txt+="...";
return txt;

#13 Script Submission » array_insert_2d » 2016-11-16 16:27:47

Tsa05
Replies: 0

Using the new(ish) array accessors to directly manipulate a 2D array, avoiding large copies.
This script accepts a 2D array and inserts a value into the 2nd dimension position of the first dimension array that you specify.

Expand/// array_insert_2d(array, value, row, column);
//
//  Accepts a 2D array and modifies the array to insert
//  a desired value. If the insertion location is
//  outside of the array, devault values are padded.
//
//      array   : The name of a 2D array
//      value   : The value to insert
//      row     : The first dimension index to insert into
//      column  : The second dimension index to insert into
//
//  To avoid duplicating large 2D arrays, the @ accessor is used.
//  No value is returned from the script, as the array is already modified
//
//  This script performs an Insertion, not an overwrite, so expect the 
//  length of the second dimension to change.
//
/// GMLscripts.com/license

var defaultValue = 0; // Used when inserting past the end of an array

var ar = argument[0];
var val = argument[1];
var row = argument[2];
var col = argument[3];

var L1 = array_height_2d(ar);
while(row>=L1){
    // Pad rows up to the one needed
    ar[@L1,0] = defaultValue;
    L1 = array_height_2d(ar);
}

var L2 = array_length_2d(ar,row);
while(col>=L2){
    // Pad columns up to the one needed
    ar[@row,L2] = defaultValue;
    L2 = array_length_2d(ar,row);
}
/*
*   Note on padding:
*
*   Given:
*   |1|2|
*   |3|4|
*
*   array_insert_2d(array, "A", 2, 1)
*
*   Results in:
*   |1|2|
*   |3|4|
*   |0|A|0|
*
*   An extra row was padded, then data up to index 1 was padded
*   Then, the data is inserted (below)
*/

for(var z=L2; z>col; z-=1){
    // Ripple all data forward by one at the desired row,col
    ar[@row, z] = ar[row, z-1];
}
// Set the value at the intended coordinates.
ar[@row,col] = val;

Board footer

Powered by FluxBB