Added a caution in the manual for GurobiFindAllSolutions when using a group

# Gurobify: Gurobify provides an interface to Gurobi from GAP.
#
# Copyright (c) 2017 Jesse Lansdown
#
# This Source Code Form is subject to the terms of the Mozilla
# Public License, v. 2.0. If a copy of the MPL was not distributed
# with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

# Declarations
#

DeclareCategory( "IsGurobiModel", IsObject );

GurobiObjectFamily := NewFamily( "GurobiObjectFamily" );

BindGlobal("TheTypeGurobiModel", NewType( GurobiObjectFamily, IsGurobiModel ));

DeclareOperation( "GurobiNewModel",
	[IsList]);

#! @Chapter Using Gurobify
#! @Section Adding And Deleting Constraints
#! @Arguments Model, ConstraintName
#! @Returns true/false
#! @Description
#!	Deletes all constraints with the name ConstraintName
DeclareOperation("GurobiDeleteConstraintsWithName",
	[IsGurobiModel, IsString]);

#! @Chapter Using Gurobify
#! @Section Adding And Deleting Constraints
#! @Arguments Model, ConstraintEquation, ConstraintSense, ConstraintRHSValue[, ConstraintName]
#! @Returns true
#! @Description
#!	Same as below, except that ConstraintRHS value takes an integer value.
DeclareOperation( "GurobiAddConstraint",
	[ IsGurobiModel, IsList, IsString, IsInt, IsString]);

#! @Chapter Using Gurobify
#! @Section Adding And Deleting Constraints
#! @Arguments Model, ConstraintEquation, ConstraintSense, ConstraintRHSValue[, ConstraintName]
#! @Returns true
#! @Description
#!	Adds a constraint to a gurobi model. ConstraintEquation must be a list,
#!	such that each entry is the coefficient (including $0$ coefficents) of the corresponding variable
#! 	in the constraint equation. The ConstraintSense must be one of "<", ">" or "=",
#!	where Gurobi interprets < as <= and > as >=. The ConstraintRHSValue is the value on the
#!	right hand side of the constraint. A constraint may optionally be given a name, which helps to identify
#!	the constraint if it is to be deleted at some point. If no constraint name is given, then a constraint is
#!	simply assigned the name "UnNamedConstraint".
#!	Note that a model must be updated or optimised before any additional constraints become effective.
DeclareOperation( "GurobiAddConstraint",
	[ IsGurobiModel, IsList, IsString, IsFloat, IsString] );

DeclareOperation( "GurobiAddConstraint",
	[ IsGurobiModel, IsList, IsString, IsFloat]);

DeclareOperation( "GurobiAddConstraint",
	[ IsGurobiModel, IsList, IsString, IsInt]);

#! @Chapter Using Gurobify
#! @Section Adding And Deleting Constraints
#! @Arguments Model, ConstraintEquations, ConstraintSenses, ConstraintRHSValues[, ConstraintNames]
#! @Returns true
#! @Description
#!	Add multiple constraints to a model at one time. The arguments (except Model)
#!	are lists, such that the i-th entries of each list determine a single constraint in
#!	the same manner as for the operation GurobiAddConstraint. ConstraintNames is an optional argument,
#!	and must be given for all constraints, or not at all.
DeclareOperation( "GurobiAddMultipleConstraints",
	[ IsGurobiModel, IsList, IsList, IsList, IsList] );

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model
#! @Returns Solution
#! @Description
#!	Display the solution found for a successfuly optimised model. Note that if a solution has not been optimised, is infeasible, or
#!	the optimisation was not completed, then this will return an error. Thus it is advisable to first check the optimisation status.
DeclareOperation( "GurobiSolution",
	[ IsGurobiModel] );

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, TimeLimit
#! @Returns true
#! @Description
#!	Set a time limit for a Gurobi model. Note that TimeLimit should be a float, however an integer value can be given
#!	which will be automatically converted to a float.
DeclareOperation( "GurobiSetTimeLimit",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, BestObjectiveBoundStop
#! @Returns true
#! @Description
#!	Optimisation will terminate if a feasible solution is found with objective value at least as good as BestObjectiveBoundStop.
#!	Note that BestObjectiveBoundStop should be a float, however an integer value can be given
#!	which will be automatically converted to a float.
DeclareOperation( "GurobiSetBestObjectiveBoundStop",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, CutOff
#! @Returns true
#! @Description
#!	Optimisation will terminate if the objective value is worse than CutOff.
#!	Note that CutOff should be a float, an integer value can be given
#!	which will be automatically converted to a float.
DeclareOperation( "GurobiSetCutOff",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Adding And Modifying Objective Functions
#! @Arguments Model
#! @Returns true
#! @Description
#!	Sets the model sense to maximise. When the model is optimised, it will try to maximise the objective function.
DeclareOperation("GurobiMaximiseModel",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Adding And Modifying Objective Functions
#! @Arguments Model
#! @Returns true
#! @Description
#!	Sets the model sense to minimise. When the model is optimised, it will try to minimise the objective function.
DeclareOperation("GurobiMinimiseModel",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Adding And Modifying Objective Functions
#! @Arguments Model, ObjectiveValues
#! @Returns true
#! @Description
#!	Set the objective function for a model. ObjectiveValues is a list of coefficients (including $0$ coefficeints)
#!	corresponding to each of the variables
DeclareOperation( "GurobiSetObjectiveFunction",
	[ IsGurobiModel, IsList] );

#! @Chapter Using Gurobify
#! @Section Adding And Modifying Objective Functions
#! @Arguments Model
#! @Returns List of coefficients of the objective function
#! @Description
#!	View the objective function for a model.
DeclareOperation( "GurobiObjectiveFunction",
	[ IsGurobiModel] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Number of variables
#! @Description
#!	Returns the number of variables in the model.
DeclareOperation("GurobiNumberOfVariables",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Number of linear constraints
#! @Description
#!	Returns the number of linear constraints in the model.
DeclareOperation("GurobiNumberOfConstraints",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model
#! @Returns objective value
#! @Description
#!	Returns the objective value of the current solution.
DeclareOperation("GurobiObjectiveValue",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns objective bound
#! @Description
#!	Returns the best known bound on the objective value of the model.
DeclareOperation("GurobiObjectiveBound",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns run time of optimisation
#! @Description
#!	Returns the wall clock runtime in seconds for the most recent optimisation.
DeclareOperation("GurobiRunTime",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model
#! @Returns optimisation status code
#! @Description
#!	Returns the optimisation status code of the most recent optimisation. Refer to the Gurobi documentation for more
#!	on the optimisation statuses, or alternatively refer to the Appendix of this manual.
DeclareOperation("GurobiOptimisationStatus",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns numeric focus
#! @Description
#!	Returns the numeric focus value of the model. The numeric focus is a value in the set [0,1,2,3]. A numeric focus of $0$ sets the
#!	numeric focus automatically, preferancing speed. Values between 1 and 3 increase the care taken in computations
#!	as the value increases, but also take longer. The default value is 0.
DeclareOperation("GurobiNumericFocus",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, NumericFocus
#! @Returns true
#! @Description
#!	Set the numeric focus for a model. Numeric focus must be in the set [0,1,2,3]. A numeric focus of $0$ sets the
#!	numeric focus automatically, preferancing speed. Values between 1 and 3 increase the care taken in computations
#!	as the value increases, but also take longer. The default value is 0.
DeclareOperation( "GurobiSetNumericFocus",
	[ IsGurobiModel, IsInt] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns time limit
#! @Description
#!	Returns the time limit for the model. The default value is infinity.
DeclareOperation("GurobiTimeLimit",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns cutoff value
#! @Description
#!	Returns the cutoff value for the model. Optimisation will terminate if the objective value is worse than CutOff.
#!	The default value is infinity for minimisation, and negative infinity for maximisation.
DeclareOperation("GurobiCutOff",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns best objective bound limit value
#! @Description
#!	Returns the best objective bound limit value for the model.
#!	Optimisation will terminate if a feasible solution is found with objective value at least as good as the best objective bound.
#!	The default value is negative infinity.
DeclareOperation("GurobiBestObjectiveBoundStop",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, MIPFocus
#! @Returns true
#! @Description
#!	Set the MIP focus for a model. The mip focus must be in the set [0,1,2,3], and the default value is 0.
#!	The MIP focus alows you to prioritise finding solutions or proving their optimality. See the Gurobi
#!	documentation for more information.
DeclareOperation( "GurobiSetMIPFocus",
	[ IsGurobiModel, IsInt] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns MIP focus
#! @Description
#!	Returns the MIP focus value for the model. The mip focus must be in the set [0,1,2,3], and the default value is 0.
#!	The MIP focus alows you to prioritise finding solutions or proving their optimality.  See the Gurobi
#!	documentation for more information.
DeclareOperation("GurobiMIPFocus",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, BestBdStop
#! @Returns true
#! @Description
#!	Set the best bound stopping value for a model. Terminates optimisation as soon as the value
#!	of the best bound is determined to be at least as good as the best bound stopping value. Default value is infinity.
DeclareOperation( "GurobiSetBestBoundStop",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Best bound stopping value
#! @Description
#!	Returns the best bound stopping value for the model. Optimisation terminates as soon as the value
#!	of the best bound is determined to be at least as good as the best bound stopping value. Default value is infinity.
DeclareOperation("GurobiBestBoundStop",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, BestBdStop
#! @Returns true
#! @Description
#!	Set the limit for the maximum number of MIP solutions to find.
#!	Default value is $2,000,000,000$.
DeclareOperation( "GurobiSetSolutionLimit",
	[ IsGurobiModel, IsInt] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns solution limit value
#! @Description
#!	Returns the solution limit value for the model. This value limits the maximum number of MIP solutions that will be found.
#!	Default value is $2,000,000,000$.
DeclareOperation("GurobiSolutionLimit",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, IterationLimit
#! @Returns true
#! @Description
#!	Set the limit for the maximum number of simplex iterations performed. Default value is infinity.
DeclareOperation( "GurobiSetIterationLimit",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Iteration limit
#! @Description
#!	Returns the iteration limit value for the model, which limits the number of simplex iterations performed during optimisation.
#!	Default value is infinity.
DeclareOperation("GurobiIterationLimit",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, NodeLimit
#! @Returns true
#! @Description
#!	Set the limit for the maximum number of MIP nodes explored.
#!	The default value is infinity.
DeclareOperation( "GurobiSetNodeLimit",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Node limit
#! @Description
#!	Returns the node limit value for the model, which limits the number of MIP nodes explored during optimisation.
#!	The default value is infinity.
DeclareOperation("GurobiNodeLimit",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns
#! @Description
#!	Returns the names of the variables in the model. This list acts as the index set for
#!	any lists of variable coefficients, such as in GurobiAddConstraint or GurobiSetObjectiveFunction.
DeclareOperation("GurobiVariableNames",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Creating Or Reading A Model
#! @Arguments VariableTypes[, VariableNames]
#! @Returns A Gurobi model
#! @Description
#!  Creates a gurobi model with variables defined by VariableTypes. VariableTypes must be a list of strings,
#!	where each entry is the type of the corresponding variable.
#!  Accepted variable types are "CONTINUOUS", "BINARY", "INTEGER", "SEMICONT", or "SEMIINT". The variable types are not case sensitive.
#!  Refer to the Gurobi documentation for more information on the variable types.
#!  Optionally takes the names of the variables as a list of strings.
DeclareOperation( "GurobiNewModel",
	[IsList, IsList]);

#! @Chapter Using Gurobify
#! @Section Creating Or Reading A Model
#! @Arguments Model, VariableNames
#! @Returns true
#! @Description
#!  Assigns each variable a new name from a list of names. The names must be strings.
DeclareOperation("GurobiSetVariableNames",
		[IsGurobiModel, IsList]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, Switch
#! @Returns true
#! @Description
#!  Turns console logging on or off. If Switch is true the output of Gurobi will be printed to the screen,
#!	and if it is false the output will be supressed. The default for Gurobify is to supress the output.
DeclareOperation("GurobiSetLogToConsole",
		[IsGurobiModel, IsBool]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns true/false
#! @Description
#!	Returns true if the Gurobi output is set to display to the screen, and false if the output is supressed.
#!	Gurobify suppresses the output by default.
DeclareOperation("GurobiLogToConsole",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model
#! @Returns Set of all solutions.
#! @Description
#!	Takes a Gurobi model and repeatedly optimises it, each time adding the previous solution as a
#!	constraint so that it isn't found again. This continues until all solutions are found, and
#!	then they are returned as a set. During the process the number of found solutions is displayed.
DeclareOperation("GurobiFindAllSolutions",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model, Group
#! @Returns Set of all solutions.
#! @Description
#!	(Caution: This function is intended for use with binary variables only and may behave unexpectedly
#!	with other variable types).
#!	Same as above, except that it also takes a permutation group acting on the index set of variables.
#!	Instead of finding all solutions directly, the group is used to find the orbit of each new
#!	solution, and these are then all returned at the end, and used as constraints until then.
#!	An option value may also be given which will only return the representatives of each orbit of the
#!	solutions. Hence it returns all the unique solutions up to equivalence under the group.
#!	This saves on memory, and the remaing solutions may be refound by generating the
#!	orbit under the group. To invoke this option place a colon after the group argument and then put
#!	representatives:=true so for example GurobiFindAllSolutions(model, gp : representatives:=true);
DeclareOperation("GurobiFindAllSolutions",
	[IsGurobiModel, IsGroup]);

#! @Chapter Using Gurobify
#! @Section Additional Functionality
#! @Arguments IndexSet, NumberOfIndices
#! @Returns Characterisitc vector
#! @Description
#!	Takes a list of integers which form a subset of the set [1 .. n], where n is the second argument,
#!	and converts the set of indices to its characteristic vector. For example, if n = 5, the set
#!	[1,3] would be converted to [1, 0, 1, 0, 0]. It is useful to be able to convert between the two,
#!	since Gurobify always takes the characteristic vector (for example when taking constraints),
#!	yet the set of indices is generally more helpful for the user.
DeclareOperation("IndexSetToCharacteristicVector",
	[IsList, IsPosInt]);

#! @Chapter Using Gurobify
#! @Section Additional Functionality
#! @Arguments CharacteristicVector
#! @Returns Characterisitc vector
#! @Description
#!	Takes a characteristic vector and returns the set of indices corresponding to it. This reverses the
#!	process which occurs with IndexSetToCharacteristicVector. It is particularly useful to convert the
#!	output of a Gurobi solution back in terms of the variables. For example, the characteristic vector
#!	[1, 0, 1, 0, 0] would return the index set [1,3]. Note that since the function expects a characteristic
#!	vector it doesn't account for any weightings, and is only interested in whether or not the corresponding
#!	index is present, and as such it rounds each entry to the nearest integer and checks that it is non-zero.
#!	Hence it is particularly suitable for use with binary variables.
DeclareOperation("CharacteristicVectorToIndexSet",
	[IsList]);

#! @Chapter Using Gurobify
#! @Section Additional Functionality
#! @Arguments Subset, FullSet
#! @Returns Characterisitc vector
#! @Description
#!	Takes a subset of some set, and returns the characteristic vector where the entries of the characteristic
#!	vector are indexed by the full set. For example, the subset ["c"] of ["a", "c", "n", "q"] would give the
#!	characteristic vector [0, 1, 0, 0]. This removes the need to first find the index set of the subset.
DeclareOperation("SubsetToCharacteristicVector",
	[IsList, IsList]);

#! @Chapter Using Gurobify
#! @Section Additional Functionality
#! @Arguments CharacteristicVector
#! @Returns Characterisitc vector
#! @Description
#!	Takes a characteristic vector and some set which it takes to be indexing the entries of the characteristic
#!	vector. It then returns the subset of the full set corresponding to the non-zero entries of the characteristic
#!	vector. This is the reverse process to SubsetToCharacteristicVector. Note again that the characteristic vector
#!	is rounded to an integer before being compared to 0. As an example, the characteristic vector [0, 1, 0, 0] with
#!	the set ["a", "c", "n", "q"] would return ["c"]. This removes the need to first return an index set before
#!	finding the subset.
DeclareOperation("CharacteristicVectorToSubset",
	[IsList, IsList]);

# Gurobify: Gurobify provides an interface to Gurobi from GAP.
#
# Copyright (c) 2017 Jesse Lansdown
#
# This Source Code Form is subject to the terms of the Mozilla
# Public License, v. 2.0. If a copy of the MPL was not distributed
# with this file, You can obtain one at https://mozilla.org/MPL/2.0/.

# Declarations
#

DeclareCategory( "IsGurobiModel", IsObject );

GurobiObjectFamily := NewFamily( "GurobiObjectFamily" );

BindGlobal("TheTypeGurobiModel", NewType( GurobiObjectFamily, IsGurobiModel ));

DeclareOperation( "GurobiNewModel",
	[IsList]);

#! @Chapter Using Gurobify
#! @Section Adding And Deleting Constraints
#! @Arguments Model, ConstraintName
#! @Returns true/false
#! @Description
#!	Deletes all constraints with the name ConstraintName
DeclareOperation("GurobiDeleteConstraintsWithName",
	[IsGurobiModel, IsString]);

#! @Chapter Using Gurobify
#! @Section Adding And Deleting Constraints
#! @Arguments Model, ConstraintEquation, ConstraintSense, ConstraintRHSValue[, ConstraintName]
#! @Returns true
#! @Description
#!	Same as below, except that ConstraintRHS value takes an integer value.
DeclareOperation( "GurobiAddConstraint",
	[ IsGurobiModel, IsList, IsString, IsInt, IsString]);

#! @Chapter Using Gurobify
#! @Section Adding And Deleting Constraints
#! @Arguments Model, ConstraintEquation, ConstraintSense, ConstraintRHSValue[, ConstraintName]
#! @Returns true
#! @Description
#!	Adds a constraint to a gurobi model. ConstraintEquation must be a list,
#!	such that each entry is the coefficient (including $0$ coefficents) of the corresponding variable
#! 	in the constraint equation. The ConstraintSense must be one of "<", ">" or "=",
#!	where Gurobi interprets < as <= and > as >=. The ConstraintRHSValue is the value on the
#!	right hand side of the constraint. A constraint may optionally be given a name, which helps to identify
#!	the constraint if it is to be deleted at some point. If no constraint name is given, then a constraint is
#!	simply assigned the name "UnNamedConstraint".
#!	Note that a model must be updated or optimised before any additional constraints become effective.
DeclareOperation( "GurobiAddConstraint",
	[ IsGurobiModel, IsList, IsString, IsFloat, IsString] );

DeclareOperation( "GurobiAddConstraint",
	[ IsGurobiModel, IsList, IsString, IsFloat]);

DeclareOperation( "GurobiAddConstraint",
	[ IsGurobiModel, IsList, IsString, IsInt]);

#! @Chapter Using Gurobify
#! @Section Adding And Deleting Constraints
#! @Arguments Model, ConstraintEquations, ConstraintSenses, ConstraintRHSValues[, ConstraintNames]
#! @Returns true
#! @Description
#!	Add multiple constraints to a model at one time. The arguments (except Model)
#!	are lists, such that the i-th entries of each list determine a single constraint in
#!	the same manner as for the operation GurobiAddConstraint. ConstraintNames is an optional argument,
#!	and must be given for all constraints, or not at all.
DeclareOperation( "GurobiAddMultipleConstraints",
	[ IsGurobiModel, IsList, IsList, IsList, IsList] );

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model
#! @Returns Solution
#! @Description
#!	Display the solution found for a successfuly optimised model. Note that if a solution has not been optimised, is infeasible, or
#!	the optimisation was not completed, then this will return an error. Thus it is advisable to first check the optimisation status.
DeclareOperation( "GurobiSolution",
	[ IsGurobiModel] );

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, TimeLimit
#! @Returns true
#! @Description
#!	Set a time limit for a Gurobi model. Note that TimeLimit should be a float, however an integer value can be given
#!	which will be automatically converted to a float.
DeclareOperation( "GurobiSetTimeLimit",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, BestObjectiveBoundStop
#! @Returns true
#! @Description
#!	Optimisation will terminate if a feasible solution is found with objective value at least as good as BestObjectiveBoundStop.
#!	Note that BestObjectiveBoundStop should be a float, however an integer value can be given
#!	which will be automatically converted to a float.
DeclareOperation( "GurobiSetBestObjectiveBoundStop",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, CutOff
#! @Returns true
#! @Description
#!	Optimisation will terminate if the objective value is worse than CutOff.
#!	Note that CutOff should be a float, an integer value can be given
#!	which will be automatically converted to a float.
DeclareOperation( "GurobiSetCutOff",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Adding And Modifying Objective Functions
#! @Arguments Model
#! @Returns true
#! @Description
#!	Sets the model sense to maximise. When the model is optimised, it will try to maximise the objective function.
DeclareOperation("GurobiMaximiseModel",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Adding And Modifying Objective Functions
#! @Arguments Model
#! @Returns true
#! @Description
#!	Sets the model sense to minimise. When the model is optimised, it will try to minimise the objective function.
DeclareOperation("GurobiMinimiseModel",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Adding And Modifying Objective Functions
#! @Arguments Model, ObjectiveValues
#! @Returns true
#! @Description
#!	Set the objective function for a model. ObjectiveValues is a list of coefficients (including $0$ coefficeints)
#!	corresponding to each of the variables
DeclareOperation( "GurobiSetObjectiveFunction",
	[ IsGurobiModel, IsList] );

#! @Chapter Using Gurobify
#! @Section Adding And Modifying Objective Functions
#! @Arguments Model
#! @Returns List of coefficients of the objective function
#! @Description
#!	View the objective function for a model.
DeclareOperation( "GurobiObjectiveFunction",
	[ IsGurobiModel] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Number of variables
#! @Description
#!	Returns the number of variables in the model.
DeclareOperation("GurobiNumberOfVariables",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Number of linear constraints
#! @Description
#!	Returns the number of linear constraints in the model.
DeclareOperation("GurobiNumberOfConstraints",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model
#! @Returns objective value
#! @Description
#!	Returns the objective value of the current solution.
DeclareOperation("GurobiObjectiveValue",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns objective bound
#! @Description
#!	Returns the best known bound on the objective value of the model.
DeclareOperation("GurobiObjectiveBound",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns run time of optimisation
#! @Description
#!	Returns the wall clock runtime in seconds for the most recent optimisation.
DeclareOperation("GurobiRunTime",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model
#! @Returns optimisation status code
#! @Description
#!	Returns the optimisation status code of the most recent optimisation. Refer to the Gurobi documentation for more
#!	on the optimisation statuses, or alternatively refer to the Appendix of this manual.
DeclareOperation("GurobiOptimisationStatus",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns numeric focus
#! @Description
#!	Returns the numeric focus value of the model. The numeric focus is a value in the set [0,1,2,3]. A numeric focus of $0$ sets the
#!	numeric focus automatically, preferancing speed. Values between 1 and 3 increase the care taken in computations
#!	as the value increases, but also take longer. The default value is 0.
DeclareOperation("GurobiNumericFocus",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, NumericFocus
#! @Returns true
#! @Description
#!	Set the numeric focus for a model. Numeric focus must be in the set [0,1,2,3]. A numeric focus of $0$ sets the
#!	numeric focus automatically, preferancing speed. Values between 1 and 3 increase the care taken in computations
#!	as the value increases, but also take longer. The default value is 0.
DeclareOperation( "GurobiSetNumericFocus",
	[ IsGurobiModel, IsInt] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns time limit
#! @Description
#!	Returns the time limit for the model. The default value is infinity.
DeclareOperation("GurobiTimeLimit",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns cutoff value
#! @Description
#!	Returns the cutoff value for the model. Optimisation will terminate if the objective value is worse than CutOff.
#!	The default value is infinity for minimisation, and negative infinity for maximisation.
DeclareOperation("GurobiCutOff",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns best objective bound limit value
#! @Description
#!	Returns the best objective bound limit value for the model.
#!	Optimisation will terminate if a feasible solution is found with objective value at least as good as the best objective bound.
#!	The default value is negative infinity.
DeclareOperation("GurobiBestObjectiveBoundStop",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, MIPFocus
#! @Returns true
#! @Description
#!	Set the MIP focus for a model. The mip focus must be in the set [0,1,2,3], and the default value is 0.
#!	The MIP focus alows you to prioritise finding solutions or proving their optimality. See the Gurobi
#!	documentation for more information.
DeclareOperation( "GurobiSetMIPFocus",
	[ IsGurobiModel, IsInt] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns MIP focus
#! @Description
#!	Returns the MIP focus value for the model. The mip focus must be in the set [0,1,2,3], and the default value is 0.
#!	The MIP focus alows you to prioritise finding solutions or proving their optimality.  See the Gurobi
#!	documentation for more information.
DeclareOperation("GurobiMIPFocus",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, BestBdStop
#! @Returns true
#! @Description
#!	Set the best bound stopping value for a model. Terminates optimisation as soon as the value
#!	of the best bound is determined to be at least as good as the best bound stopping value. Default value is infinity.
DeclareOperation( "GurobiSetBestBoundStop",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Best bound stopping value
#! @Description
#!	Returns the best bound stopping value for the model. Optimisation terminates as soon as the value
#!	of the best bound is determined to be at least as good as the best bound stopping value. Default value is infinity.
DeclareOperation("GurobiBestBoundStop",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, BestBdStop
#! @Returns true
#! @Description
#!	Set the limit for the maximum number of MIP solutions to find.
#!	Default value is $2,000,000,000$.
DeclareOperation( "GurobiSetSolutionLimit",
	[ IsGurobiModel, IsInt] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns solution limit value
#! @Description
#!	Returns the solution limit value for the model. This value limits the maximum number of MIP solutions that will be found.
#!	Default value is $2,000,000,000$.
DeclareOperation("GurobiSolutionLimit",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, IterationLimit
#! @Returns true
#! @Description
#!	Set the limit for the maximum number of simplex iterations performed. Default value is infinity.
DeclareOperation( "GurobiSetIterationLimit",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Iteration limit
#! @Description
#!	Returns the iteration limit value for the model, which limits the number of simplex iterations performed during optimisation.
#!	Default value is infinity.
DeclareOperation("GurobiIterationLimit",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, NodeLimit
#! @Returns true
#! @Description
#!	Set the limit for the maximum number of MIP nodes explored.
#!	The default value is infinity.
DeclareOperation( "GurobiSetNodeLimit",
	[ IsGurobiModel, IsFloat] );

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns Node limit
#! @Description
#!	Returns the node limit value for the model, which limits the number of MIP nodes explored during optimisation.
#!	The default value is infinity.
DeclareOperation("GurobiNodeLimit",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns
#! @Description
#!	Returns the names of the variables in the model. This list acts as the index set for
#!	any lists of variable coefficients, such as in GurobiAddConstraint or GurobiSetObjectiveFunction.
DeclareOperation("GurobiVariableNames",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Creating Or Reading A Model
#! @Arguments VariableTypes[, VariableNames]
#! @Returns A Gurobi model
#! @Description
#!  Creates a gurobi model with variables defined by VariableTypes. VariableTypes must be a list of strings,
#!	where each entry is the type of the corresponding variable.
#!  Accepted variable types are "CONTINUOUS", "BINARY", "INTEGER", "SEMICONT", or "SEMIINT". The variable types are not case sensitive.
#!  Refer to the Gurobi documentation for more information on the variable types.
#!  Optionally takes the names of the variables as a list of strings.
DeclareOperation( "GurobiNewModel",
	[IsList, IsList]);

#! @Chapter Using Gurobify
#! @Section Creating Or Reading A Model
#! @Arguments Model, VariableNames
#! @Returns true
#! @Description
#!  Assigns each variable a new name from a list of names. The names must be strings.
DeclareOperation("GurobiSetVariableNames",
		[IsGurobiModel, IsList]);

#! @Chapter Using Gurobify
#! @Section Modifying Attributes And Parameters
#! @Arguments Model, Switch
#! @Returns true
#! @Description
#!  Turns console logging on or off. If Switch is true the output of Gurobi will be printed to the screen,
#!	and if it is false the output will be supressed. The default for Gurobify is to supress the output.
DeclareOperation("GurobiSetLogToConsole",
		[IsGurobiModel, IsBool]);

#! @Chapter Using Gurobify
#! @Section Querying Attributes And Parameters
#! @Arguments Model
#! @Returns true/false
#! @Description
#!	Returns true if the Gurobi output is set to display to the screen, and false if the output is supressed.
#!	Gurobify suppresses the output by default.
DeclareOperation("GurobiLogToConsole",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model
#! @Returns Set of all solutions.
#! @Description
#!	Takes a Gurobi model and repeatedly optimises it, each time adding the previous solution as a
#!	constraint so that it isn't found again. This continues until all solutions are found, and
#!	then they are returned as a set. During the process the number of found solutions is displayed.
DeclareOperation("GurobiFindAllSolutions",
	[IsGurobiModel]);

#! @Chapter Using Gurobify
#! @Section Optimising A Model
#! @Arguments Model, Group
#! @Returns Set of all solutions.
#! @Description
#!	Same as above, except that it also takes a permutation group acting on the index set of variables.
#!	Instead of finding all solutions directly, the group is used to find the orbit of each new
#!	solution, and these are then all returned at the end, and used as constraints until then.
#!	An option value may also be given which will only return the representatives of each orbit of the
#!	solutions. Hence it returns all the unique solutions up to equivalence under the group.
#!	This saves on memory, and the remaing solutions may be refound by generating the
#!	orbit under the group. To invoke this option place a colon after the group argument and then put
#!	representatives:=true so for example GurobiFindAllSolutions(model, gp : representatives:=true);
DeclareOperation("GurobiFindAllSolutions",
	[IsGurobiModel, IsGroup]);

#! @Chapter Using Gurobify
#! @Section Additional Functionality
#! @Arguments IndexSet, NumberOfIndices
#! @Returns Characterisitc vector
#! @Description
#!	Takes a list of integers which form a subset of the set [1 .. n], where n is the second argument,
#!	and converts the set of indices to its characteristic vector. For example, if n = 5, the set
#!	[1,3] would be converted to [1, 0, 1, 0, 0]. It is useful to be able to convert between the two,
#!	since Gurobify always takes the characteristic vector (for example when taking constraints),
#!	yet the set of indices is generally more helpful for the user.
DeclareOperation("IndexSetToCharacteristicVector",
	[IsList, IsPosInt]);

#! @Chapter Using Gurobify
#! @Section Additional Functionality
#! @Arguments CharacteristicVector
#! @Returns Characterisitc vector
#! @Description
#!	Takes a characteristic vector and returns the set of indices corresponding to it. This reverses the
#!	process which occurs with IndexSetToCharacteristicVector. It is particularly useful to convert the
#!	output of a Gurobi solution back in terms of the variables. For example, the characteristic vector
#!	[1, 0, 1, 0, 0] would return the index set [1,3]. Note that since the function expects a characteristic
#!	vector it doesn't account for any weightings, and is only interested in whether or not the corresponding
#!	index is present, and as such it rounds each entry to the nearest integer and checks that it is non-zero.
#!	Hence it is particularly suitable for use with binary variables.
DeclareOperation("CharacteristicVectorToIndexSet",
	[IsList]);

#! @Chapter Using Gurobify
#! @Section Additional Functionality
#! @Arguments Subset, FullSet
#! @Returns Characterisitc vector
#! @Description
#!	Takes a subset of some set, and returns the characteristic vector where the entries of the characteristic
#!	vector are indexed by the full set. For example, the subset ["c"] of ["a", "c", "n", "q"] would give the
#!	characteristic vector [0, 1, 0, 0]. This removes the need to first find the index set of the subset.
DeclareOperation("SubsetToCharacteristicVector",
	[IsList, IsList]);

#! @Chapter Using Gurobify
#! @Section Additional Functionality
#! @Arguments CharacteristicVector
#! @Returns Characterisitc vector
#! @Description
#!	Takes a characteristic vector and some set which it takes to be indexing the entries of the characteristic
#!	vector. It then returns the subset of the full set corresponding to the non-zero entries of the characteristic
#!	vector. This is the reverse process to SubsetToCharacteristicVector. Note again that the characteristic vector
#!	is rounded to an integer before being compared to 0. As an example, the characteristic vector [0, 1, 0, 0] with
#!	the set ["a", "c", "n", "q"] would return ["c"]. This removes the need to first return an index set before
#!	finding the subset.
DeclareOperation("CharacteristicVectorToSubset",
	[IsList, IsList]);


using saveDir instead

using saveDir instead

# Provides functionality for the puzzle itself

extends Spatial

var DataMan = preload( "res://scripts/DataManager.gd" ).new()
var PuzzleManScript = preload( "res://scripts/PuzzleManager.gd" )
var PuzzleScn = preload("res://puzzle.scn")
var puzzleMan
var seed
var otherPuzzle
var generateRandom = true
var mainPuzzle = false

var time = {
		on = true,
		val = 0.0,
		label = Label.new(),
		tween = Tween.new() }

func addTimer():
	add_child(time.label)
	add_child(time.tween)
	time.label.set_pos(Vector2(15,15))

	time.label.set_theme(load("res://themes/MainTheme.thm"))
	time.tween.interpolate_method(time.label, "set_pos", \
			time.label.get_pos(), time.label.get_pos()*2, 0.5, \
			Tween.TRANS_BOUNCE, Tween.EASE_OUT)
	time.tween.start()
	time.label.set_text(str(time.val))

static func formatTime(t):
	var mins = floor(t / 60)
	var secs = fmod(floor(t), 60)
	var millis = floor((t - floor(t)) * 1000)
	return str(mins) + ":" + str(secs).pad_zeros(2) + ":" + str(millis).pad_zeros(3)

func _process(dTime):
	# start label tween on
	time.val += dTime
	time.label.set_text(formatTime(time.val))
	if fmod(time.val, 10) < 0.1:
		time.tween.seek(0.0)

# Called for initialization
func _ready():
	if time.on:
		set_process(true) # needed for time keeping
	addTimer()

	#set up network stuffs
	add_child(load("res://networkProxy.scn").instance())
	var Network = Globals.get("Network")
	if Network != null:
		Network.proxy.set_process(Network.isClient or Network.isHost)

	# generate puzzle
	puzzleMan = PuzzleManScript.new()
	var puzzle

	if generateRandom:
		seed = OS.get_unix_time() # unix time
		seed *= OS.get_ticks_msec() # initial time
		seed *= 1 + OS.get_time().second
		seed *= 1 + OS.get_date().weekday
		seed = abs(seed) % 7919 # 1000th prime

		rand_seed(seed)

		puzzle = puzzleMan.generatePuzzle( 2, puzzleMan.DIFF_HARD )
		puzzle.puzzleMan = puzzleMan
		var steps = puzzle.solvePuzzleSteps()
		print("Generated ", puzzle.shape.size(), " blocks." )
		print( "PUZZLE IS SOLVEABLE?: ", steps.solveable )

		var gridMan = get_node( "GridView/GridMan" )
		DataMan.savePuzzle(DataMan.saveDir + "test.pzl", puzzle)
		var pCopy = DataMan.loadPuzzle(DataMan.saveDir + "test.pzl")
		gridMan.set_puzzle(pCopy)

	# make a new puzzle, embed using Viewport
	if mainPuzzle:
		if (not Network == null and Network.isNetwork):
			print("Sending puzzle")
			Network.sendStart(puzzle)

		var p = PuzzleScn.instance()
		p.get_node("GridView").active = false
		p.get_node("GridView/GridMan").set_puzzle(puzzle)
		p.mainPuzzle = false
		p.set_scale(Vector3(0.5, 0.5, 0.5))
		p.set_translation(Vector3(10, 5, -20))

		var v = Viewport.new()
		var c = Control.new()

		v.set_world(p.get_world())
		v.set_render_target_to_screen_rect(Rect2(20,20,40,40))
		v.set_physics_object_picking(false)
		v.add_child(p)
		c.add_child(v)
		add_child(c)

		p.get_node("GridView").set_process_input(false)

		otherPuzzle = p #for use with network

	#music attempt
	var musicPlayer = Globals.get("StreamPlayer")
	var songs = [load("res://main_theme_antris.ogg")]
	add_child(StreamPlayer)
	add_child(musicPlayer)
	musicPlayer.set_stream(songs[0])
	musicPlayer.play()

# Provides functionality for the puzzle itself

extends Spatial

var DataMan = preload( "res://scripts/DataManager.gd" ).new()
var PuzzleManScript = preload( "res://scripts/PuzzleManager.gd" )
var PuzzleScn = preload("res://puzzle.scn")
var puzzleMan
var seed
var otherPuzzle
var generateRandom = true
var mainPuzzle = false

var time = {
		on = true,
		val = 0.0,
		label = Label.new(),
		tween = Tween.new() }

func addTimer():
	add_child(time.label)
	add_child(time.tween)
	time.label.set_pos(Vector2(15,15))

	time.label.set_theme(load("res://themes/MainTheme.thm"))
	time.tween.interpolate_method(time.label, "set_pos", \
			time.label.get_pos(), time.label.get_pos()*2, 0.5, \
			Tween.TRANS_BOUNCE, Tween.EASE_OUT)
	time.tween.start()
	time.label.set_text(str(time.val))

static func formatTime(t):
	var mins = floor(t / 60)
	var secs = fmod(floor(t), 60)
	var millis = floor((t - floor(t)) * 1000)
	return str(mins) + ":" + str(secs).pad_zeros(2) + ":" + str(millis).pad_zeros(3)

func _process(dTime):
	# start label tween on
	time.val += dTime
	time.label.set_text(formatTime(time.val))
	if fmod(time.val, 10) < 0.1:
		time.tween.seek(0.0)

# Called for initialization
func _ready():
	if time.on:
		set_process(true) # needed for time keeping
	addTimer()

	#set up network stuffs
	add_child(load("res://networkProxy.scn").instance())
	var Network = Globals.get("Network")
	if Network != null:
		Network.proxy.set_process(Network.isClient or Network.isHost)

	# generate puzzle
	puzzleMan = PuzzleManScript.new()
	var puzzle

	if generateRandom:
		seed = OS.get_unix_time() # unix time
		seed *= OS.get_ticks_msec() # initial time
		seed *= 1 + OS.get_time().second
		seed *= 1 + OS.get_date().weekday
		seed = abs(seed) % 7919 # 1000th prime

		rand_seed(seed)

		puzzle = puzzleMan.generatePuzzle( 2, puzzleMan.DIFF_HARD )
		puzzle.puzzleMan = puzzleMan
		var steps = puzzle.solvePuzzleSteps()
		print("Generated ", puzzle.shape.size(), " blocks." )
		print( "PUZZLE IS SOLVEABLE?: ", steps.solveable )

		var gridMan = get_node( "GridView/GridMan" )
		DataMan.savePuzzle("test.pzl", puzzle)
		var pCopy = DataMan.loadPuzzle("test.pzl")
		gridMan.set_puzzle(puzzle)

	# make a new puzzle, embed using Viewport
	if mainPuzzle:
		if (not Network == null and Network.isNetwork):
			print("Sending puzzle")
			Network.sendStart(puzzle)

		var p = PuzzleScn.instance()
		p.get_node("GridView").active = false
		p.get_node("GridView/GridMan").set_puzzle(puzzle)
		p.mainPuzzle = false
		p.set_scale(Vector3(0.5, 0.5, 0.5))
		p.set_translation(Vector3(10, 5, -20))

		var v = Viewport.new()
		var c = Control.new()

		v.set_world(p.get_world())
		v.set_render_target_to_screen_rect(Rect2(20,20,40,40))
		v.set_physics_object_picking(false)
		v.add_child(p)
		c.add_child(v)
		add_child(c)

		p.get_node("GridView").set_process_input(false)

		otherPuzzle = p #for use with network

	#music attempt
	var musicPlayer = Globals.get("StreamPlayer")
	var songs = [load("res://main_theme_antris.ogg")]
	add_child(StreamPlayer)
	add_child(musicPlayer)
	musicPlayer.set_stream(songs[0])
	musicPlayer.play()


Mapa Adama.

Mapa Adama.


mainPuzzle off by default. seed random number gen. save puzzles by seed into SavedPuzzles/. GridMan in charge of its own shape.

extends Spatial

# Is there a better way to do this?
const BLOCK_LASER	= 0
const BLOCK_WILD	= 1
const BLOCK_PAIR	= 2
const BLOCK_GOAL	= 3
const BLOCK_BLOCK   = 4

# Shape dictionary to access blocks quickly by position.
var shape = {}

# Block selection handling.
var offClick = false
var selectedBlocks = []

# Puzzle vars.
var puzzle
var puzzleLoaded = false

# Beam stuff.
const beamScn = preload( "res://blocks/block.scn" )
const Beam = preload("res://scripts/Blocks/Beam.gd")

# sounds
var samplePlayer = SamplePlayer.new()

func addPickledBlock(block):
	# TODO add to puzzle
	var b = block.toNode()

	print ("ADDED")
	shape[b.blockPos] = b

	# TODO  any special treatment for the wild blocks?
	# TODO  keep track of puzzle.lasers ? How to?

	if puzzleLoaded:
		# keep pickled block
		puzzle.blocks.append(block)

		# keep track of puzzle.pairCounts
		var layer = calcBlockLayerVec(b.blockPos)
		if b.getBlockType() == 2:
			while puzzle.pairCount.size() <= layer:
				puzzle.pairCount.append(0)
			puzzle.pairCount[layer] += 0.5

	add_child(b)

func get_block(pos):
	if shape.has(pos):
		return shape[pos]
	else:
		return null

# unused key argument needed for the tween_complete signal
func remove_block(block_node, key=null):
	if block_node == null:
		return
	print ("REMOVED")

	shape[block_node.blockPos] = null
	for child in block_node.get_children():
		block_node.remove_and_delete_child(child)
	remove_and_delete_child(block_node)

# Sets the puzzle for this GridMan.
func set_puzzle(puzz):
	# delete all current nodes
	for pos in shape:
		remove_block(shape[pos])
	puzzleLoaded = false

	# Store the puzzle.
	puzzle = puzz

	# Set the camera range to be relative to the layer count.
	var cam = get_tree().get_root().get_node( "Spatial" ).get_node( "Camera" )
	print( cam )
	var totalSize = ( puzzle.puzzleLayers * 2 + 1 )
	cam.distance.val = 4.5 * totalSize
	cam.distance.min_ = 3 * totalSize
	cam.distance.max_ = 10 * totalSize
	cam.recalculate_camera()

		# # I can do my own counting!
	# needed for adding blocks in the editor
	for block in puzzle.blocks:
		# Create a block node, add it to the tree
		addPickledBlock(block)
	puzzleLoaded = true

# Clears any selected blocks. WE SHOULD FIX THIS, THERE CAN ONLY BE ONE BLOCK SELECTED AT ANY ONE TIME, NO NEED FOR AN ARRAY!
func clearSelection():
	for bl in selectedBlocks:
		var blo = get_node( bl )
		blo.setSelected(false)
		blo.scaleTweenNode(1.0).start()
	selectedBlocks = []

# Add the selected block to the selected list. WE SHOULD FIX THIS, IT ONLY NEEDS TO STORE IT, NOT AN ARRAY!
func addSelected(bl):
	selectedBlocks.append(bl)

# Used for the multiplayer mode to force click a block on their side.
func forceClickBlock( pos ):
	shape[pos].forceClick()

func clickBlock( name ):
	#now check if that was the second block we picked. If it was, we want to
	#unselect the blocks again
	if (offClick):
		offClick = false
		addSelected(name)
		clearSelection()
	else:
		offClick = true;
		addSelected(name)

# Calculates the layer that a block is on.
# COPY OF FUNCTION IN PUZZLEMAN, IS THERE A BETTER WAY TO DO THIS?!
func calcBlockLayer( x, y, z ):
	return max( max( abs( x ), abs( y ) ), abs( z ) )
func calcBlockLayerVec( pos ):
	return max( max( abs( pos.x ), abs( pos.y ) ), abs( pos.z ) )

# Handles keeping track of pairs being removed.
func popPair( pos ):
	var blayer = calcBlockLayerVec( pos )
	puzzle.pairCount[blayer] -= 1

	if( puzzle.pairCount[blayer] == 0 ):
		print("LAYER CLEARED")
		if blayer == 1:
			print( "GAME OVER!" )
			var pauseMenu = get_tree().get_root().get_node( "Spatial" ).get_node( "Camera" ).pauseMenu
			pauseMenu.set_text("GAME OVER\nSCORE:1,000,000")
			# TODO set timeout!!!
			pauseMenu.popup_centered()

		for b in shape:
			if not ( shape[b] == null ):
				if calcBlockLayerVec( b ) == blayer:
					if shape[b].getBlockType() == BLOCK_LASER:
						shape[b].forceActivate()

		# Fire beams.
		var beamNum = 0
		for l in range( puzzle.lasers.size() ):
			if calcBlockLayerVec( puzzle.lasers[l][0] ) == blayer:
				# Firin mah lazerz!
				var beam = beamScn.instance()

				beam.set_name( str(blayer) + "_beam_" + str(beamNum) )
				beamNum += 1
				beam.set_script( Beam )

				add_child( beam )
				beam.fire( puzzle.lasers[l][0], puzzle.lasers[l][1] )

				samplePlayer.play( "soundslikewillem_hitting_slinky" )

func _init():
	# Load sound
	samplePlayer.set_voice_count(10)
	samplePlayer.set_sample_library(ResourceLoader.load("new_samplelibrary.xml"))
	print("GridMan initialized")

extends Spatial

# Is there a better way to do this?
const BLOCK_LASER	= 0
const BLOCK_WILD	= 1
const BLOCK_PAIR	= 2
const BLOCK_GOAL	= 3
const BLOCK_BLOCK   = 4

# Shape dictionary to access blocks quickly by position.
var shape = {}

# Block selection handling.
var offClick = false
var selectedBlocks = []

# Puzzle vars.
var puzzle

# Beam stuff.
const beamScn = preload( "res://blocks/block.scn" )
const Beam = preload("res://scripts/Blocks/Beam.gd")

# sounds
var samplePlayer = SamplePlayer.new()

func addPickledBlock(block):
	# TODO add to puzzle
	var b = block.toNode()

	print ("ADDED")
	shape[b.blockPos] = b

	# TODO  any special treatment for the wild blocks?
	# TODO  keep track of puzzle.lasers ? How to?

	if not puzzleLoaded:
		# keep pickled block
		puzzle.blocks.append(block)

		# keep track of puzzle.pairCounts
		var layer = calcBlockLayerVec(b.blockPos)
		if b.getBlockType() == 2:
			while puzzle.pairCount.size() <= layer:
				puzzle.pairCount.append(0)
			puzzle.pairCount[layer] += 0.5

	add_child(b)

func get_block(pos):
	if shape.has(pos):
		return shape[pos]
	else:
		return null

# unused key argument needed for the tween_complete signal
func remove_block(block_node, key=null):
	if block_node == null:
		return
	print ("REMOVED")

	shape[block_node.blockPos] = null
	for child in block_node.get_children():
		block_node.remove_and_delete_child(child)
	remove_and_delete_child(block_node)

# Sets the puzzle for this GridMan.
func set_puzzle(puzz):
	# delete all current nodes
	for pos in shape:
		remove_block(shape[pos])
	puzzleLoaded = false

	# Store the puzzle.
	puzzle = puzz

	# Set the camera range to be relative to the layer count.
	var cam = get_tree().get_root().get_node( "Spatial" ).get_node( "Camera" )
	print( cam )
	var totalSize = ( puzzle.puzzleLayers * 2 + 1 )
	cam.distance.val = 4.5 * totalSize
	cam.distance.min_ = 3 * totalSize
	cam.distance.max_ = 10 * totalSize
	cam.recalculate_camera()

		# # I can do my own counting!
	# needed for adding blocks in the editor
	for block in puzzle.blocks:
		# Create a block node, add it to the tree
		addPickledBlock(block)
	puzzleLoaded = true

# Clears any selected blocks. WE SHOULD FIX THIS, THERE CAN ONLY BE ONE BLOCK SELECTED AT ANY ONE TIME, NO NEED FOR AN ARRAY!
func clearSelection():
	for bl in selectedBlocks:
		var blo = get_node( bl )
		blo.setSelected(false)
		blo.scaleTweenNode(1.0).start()
	selectedBlocks = []

# Add the selected block to the selected list. WE SHOULD FIX THIS, IT ONLY NEEDS TO STORE IT, NOT AN ARRAY!
func addSelected(bl):
	selectedBlocks.append(bl)

# Used for the multiplayer mode to force click a block on their side.
func forceClickBlock( pos ):
	shape[pos].forceClick()

func clickBlock( name ):
	#now check if that was the second block we picked. If it was, we want to
	#unselect the blocks again
	if (offClick):
		offClick = false
		addSelected(name)
		clearSelection()
	else:
		offClick = true;
		addSelected(name)

# Calculates the layer that a block is on.
# COPY OF FUNCTION IN PUZZLEMAN, IS THERE A BETTER WAY TO DO THIS?!
func calcBlockLayer( x, y, z ):
	return max( max( abs( x ), abs( y ) ), abs( z ) )
func calcBlockLayerVec( pos ):
	return max( max( abs( pos.x ), abs( pos.y ) ), abs( pos.z ) )

# Handles keeping track of pairs being removed.
func popPair( pos ):
	var blayer = calcBlockLayerVec( pos )
	puzzle.pairCount[blayer] -= 1

	if( puzzle.pairCount[blayer] == 0 ):
		print("LAYER CLEARED")
		if blayer == 1:
			print( "GAME OVER!" )
			var pauseMenu = get_tree().get_root().get_node( "Spatial" ).get_node( "Camera" ).pauseMenu
			pauseMenu.set_text("GAME OVER\nSCORE:1,000,000")
			# TODO set timeout!!!
			pauseMenu.popup_centered()

		for b in shape:
			if not ( shape[b] == null ):
				if calcBlockLayerVec( b ) == blayer:
					if shape[b].getBlockType() == BLOCK_LASER:
						shape[b].forceActivate()

		# Fire beams.
		var beamNum = 0
		for l in range( puzzle.lasers.size() ):
			if calcBlockLayerVec( puzzle.lasers[l][0] ) == blayer:
				# Firin mah lazerz!
				var beam = beamScn.instance()

				beam.set_name( str(blayer) + "_beam_" + str(beamNum) )
				beamNum += 1
				beam.set_script( Beam )

				add_child( beam )
				beam.fire( puzzle.lasers[l][0], puzzle.lasers[l][1] )

				samplePlayer.play( "soundslikewillem_hitting_slinky" )

func _init():
	# Load sound
	samplePlayer.set_voice_count(10)
	samplePlayer.set_sample_library(ResourceLoader.load("new_samplelibrary.xml"))
	print("GridMan initialized")


fix : character sprite flips slide while falling and moving in an opposite direction

extends Node

signal controls_changed

var debug = false

const CONTROL_UP = "move_up"
const CONTROL_DOWN = "move_down"
const CONTROL_LEFT = "move_left"
const CONTROL_RIGHT = "move_right"
const CONTROL_RUN = "run"
const CONTROL_JUMP = "jump"

export(String, "move_up", "move_down", "move_left", "move_right", "run", "jump") var control_1 = "move_right"
export(String, "move_up", "move_down", "move_left", "move_right", "run", "jump") var control_2 = "move_down"

onready var climb_area = preload("res://sources/scripts/levels/climb_area.gd")

onready var player_sprites = get_node("sprites")
onready var interaction_area = get_node("interaction_area")

const WALK_SPEED = 200
const JUMP_SPEED = 300
const RUN_SPEED = 500
const CLIMB_SPEED = 200
const GROUND_FRICTION_MULTIPLIER = 0.7
const GROUND_ACCELERATION_MULTIPLIER = 0.2
const AIR_FRICTION_MULTIPLIER = 0.95
const AIR_DIRECTION_MULTIPLIER = 0.15
const JUMP_TIME_MAX = 0.2
const FALLING_JUMP_DELAY = 0.2

var jumping = false
var jump_time = 0
var falling_time = 0

const IDLE = 0
const WALKING = 1
const RUNNING = 2
const JUMPING = 3
const FALLING = 4
const CLIMBING = 5
var fsm = IDLE

var touchscreen_left = -1
var touchscreen_right = -1

const JUMP_NOT_PRESSED = -1
const JUMP_PRESSED_LEFT = 0
const JUMP_PRESSED_RIGHT = 1
var jump_pressed = JUMP_NOT_PRESSED

func _ready():
	set_process(true)
	set_process_input(true)

func _input(event):
	if event.type == InputEvent.SCREEN_TOUCH:
		if event.pressed:
			if event.pos.y > 200:
				if event.pos.x > 1024 / 2 && touchscreen_right == -1:
					touchscreen_right = event.index
				elif event.pos.x <= 1024 / 2 && touchscreen_left == -1:
					touchscreen_left = event.index
		else:
			if event.index == touchscreen_right:
				touchscreen_right = -1
			elif event.index == touchscreen_left:
				touchscreen_left = -1

			#if we release jump button we can jump again
			if touchscreen_left == -1 && jump_pressed == JUMP_PRESSED_LEFT:
				jump_pressed = JUMP_NOT_PRESSED
			elif touchscreen_right == -1 && jump_pressed == JUMP_PRESSED_RIGHT:
				jump_pressed = JUMP_NOT_PRESSED
	elif event.type == InputEvent.KEY:
		if event.is_action_released("jump"):
			jump_pressed = JUMP_NOT_PRESSED

func release_controls():
	touchscreen_right = -1
	touchscreen_left = -1

func _integrate_forces(state):
	if fsm == IDLE:
		integrate_idle(state)
	elif fsm == WALKING:
		integrate_walking(state)
	elif fsm == RUNNING:
		integrate_running(state)
	elif fsm == JUMPING:
		integrate_jumping(state)
	elif fsm == FALLING:
		integrate_falling(state)
	elif fsm == CLIMBING:
		integrate_climbing(state)

func integrate_idle(state):
	player_sprites.play("stand")
	var velocity = state.get_linear_velocity()
	velocity.x *= GROUND_FRICTION_MULTIPLIER
	state.set_linear_velocity(velocity)

func integrate_walking(state):
	player_sprites.play("walk")
	var velocity = state.get_linear_velocity()

	if abs(velocity.x) > WALK_SPEED:
		if is_action_pressed_and_available(CONTROL_LEFT):
			player_sprites.set_flip_h(true)
		elif is_action_pressed_and_available(CONTROL_RIGHT):
			player_sprites.set_flip_h(false)
		velocity.x = clamp(velocity.x * (1 - GROUND_ACCELERATION_MULTIPLIER), -RUN_SPEED, RUN_SPEED)
	else:
		if is_action_pressed_and_available(CONTROL_LEFT):
			velocity.x -= WALK_SPEED * GROUND_ACCELERATION_MULTIPLIER
			player_sprites.set_flip_h(true)
		elif is_action_pressed_and_available(CONTROL_RIGHT):
			velocity.x += WALK_SPEED * GROUND_ACCELERATION_MULTIPLIER
			player_sprites.set_flip_h(false)

		velocity.x = clamp(velocity.x, -WALK_SPEED, WALK_SPEED)

	state.set_linear_velocity(velocity)

func integrate_running(state):
	player_sprites.play("walk")
	var velocity = state.get_linear_velocity()

	if is_action_pressed_and_available(CONTROL_LEFT):
		velocity.x -= RUN_SPEED * GROUND_ACCELERATION_MULTIPLIER
		player_sprites.set_flip_h(true)
	elif is_action_pressed_and_available(CONTROL_RIGHT):
		velocity.x += RUN_SPEED * GROUND_ACCELERATION_MULTIPLIER
		player_sprites.set_flip_h(false)

	velocity.x = clamp(velocity.x, -RUN_SPEED, RUN_SPEED)

	state.set_linear_velocity(velocity)

func integrate_falling(state):
	player_sprites.play("jump")
	var velocity = state.get_linear_velocity()

	if velocity.x > -WALK_SPEED && is_action_pressed_and_available(CONTROL_LEFT):
		velocity.x -= WALK_SPEED * AIR_DIRECTION_MULTIPLIER
	elif velocity.x < WALK_SPEED && is_action_pressed_and_available(CONTROL_RIGHT):
		velocity.x += WALK_SPEED * AIR_DIRECTION_MULTIPLIER
	elif !is_action_pressed_and_available(CONTROL_LEFT) && !is_action_pressed_and_available(CONTROL_RIGHT):
		velocity.x *= AIR_FRICTION_MULTIPLIER

	velocity.x = clamp(velocity.x, -RUN_SPEED, RUN_SPEED)
	state.set_linear_velocity(velocity)

func integrate_jumping(state):
	player_sprites.play("jump")
	var velocity = state.get_linear_velocity()

	if velocity.x > -WALK_SPEED && is_action_pressed_and_available(CONTROL_LEFT):
		player_sprites.set_flip_h(true)
		velocity.x -= WALK_SPEED * AIR_DIRECTION_MULTIPLIER
	elif velocity.x < WALK_SPEED && is_action_pressed_and_available(CONTROL_RIGHT):
		player_sprites.set_flip_h(false)
		velocity.x += WALK_SPEED * AIR_DIRECTION_MULTIPLIER
	elif !is_action_pressed_and_available(CONTROL_LEFT) && !is_action_pressed_and_available(CONTROL_RIGHT):
		velocity.x *= AIR_FRICTION_MULTIPLIER

	velocity.x = clamp(velocity.x, -RUN_SPEED, RUN_SPEED)

	velocity.y = -JUMP_SPEED
	state.set_linear_velocity(velocity)

func integrate_climbing(state):
	player_sprites.play("jump")
	var velocity = state.get_linear_velocity()

	if is_action_pressed_and_available(CONTROL_UP):
		velocity.y = -CLIMB_SPEED
	elif is_action_pressed_and_available(CONTROL_DOWN):
		velocity.y = CLIMB_SPEED

	if is_action_pressed_and_available(CONTROL_LEFT):
		velocity.x = - WALK_SPEED * 0.6
		player_sprites.set_flip_h(true)
	elif is_action_pressed_and_available(CONTROL_RIGHT):
		velocity.x = WALK_SPEED * 0.6
		player_sprites.set_flip_h(false)
	else:
		velocity.x = 0

	state.set_linear_velocity(velocity)

func _process(delta):
	var can_climb = false

	var overlapping_areas = interaction_area.get_overlapping_areas()

	for area in overlapping_areas:
		if can_climb:
			break
		can_climb = area extends climb_area

	if fsm == IDLE:
		if is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED:
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("idle -> jump")
		elif is_action_pressed_and_available(CONTROL_LEFT) || is_action_pressed_and_available(CONTROL_RIGHT):
			fsm = WALKING
			if debug: print("idle -> walk")
		elif can_climb && ((is_action_pressed_and_available(CONTROL_UP) && !test_motion(Vector2(0,-5))) || (is_action_pressed_and_available(CONTROL_DOWN) && !test_motion(Vector2(0,5)))):
			#check if can climb and if thera are obstacles where we want to climb
			fsm = CLIMBING
			if debug: print("idle -> climb")
		elif !test_motion(Vector2(0,5)):
			fsm = FALLING
			if debug: print("idle -> fall")

	elif fsm == WALKING:
		if !is_action_pressed_and_available(CONTROL_LEFT) && !is_action_pressed_and_available(CONTROL_RIGHT):
			fsm = IDLE
			if debug: print("walk -> idle")
		elif is_action_pressed_and_available(CONTROL_RUN):
			fsm = RUNNING
			if debug: print("walk -> run")
		elif is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED:
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("walk -> jump")
		elif can_climb && ((is_action_pressed_and_available(CONTROL_UP) && !test_motion(Vector2(0,-5))) || (is_action_pressed_and_available(CONTROL_DOWN) && !test_motion(Vector2(0,5)))):
			fsm = CLIMBING
			if debug: print("walk -> climb")
		elif !test_motion(Vector2(0,5)):
			fsm = FALLING
			if debug: print("walk -> fall")

	elif fsm == RUNNING:
		if !is_action_pressed_and_available(CONTROL_RUN):
			fsm = WALKING
			if debug: print("run -> walk")
		elif !is_action_pressed_and_available(CONTROL_LEFT) && !is_action_pressed_and_available(CONTROL_RIGHT):
			fsm = IDLE
			if debug: print("run -> idle")
		elif is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED :
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("run -> jump")
		elif can_climb && ((is_action_pressed_and_available(CONTROL_UP) && !test_motion(Vector2(0,-5))) || (is_action_pressed_and_available(CONTROL_DOWN) && !test_motion(Vector2(0,5)))):
			#check if can climb and if thera are obstacles where we want to climb
			fsm = CLIMBING
			if debug: print("run -> climb")
		elif !test_motion(Vector2(0,5)):
			fsm = FALLING
			if debug: print("run -> fall")

	elif fsm == CLIMBING:
		if !can_climb:
			fsm = FALLING
			if debug: print("climb -> fall")
		elif is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED :
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("climb -> jump")
		elif !is_action_pressed_and_available(CONTROL_UP) && !is_action_pressed_and_available(CONTROL_DOWN) && test_motion(Vector2(0,5)):
			fsm = IDLE
			if debug: print("climb -> idle")

	elif fsm == FALLING:
		falling_time += delta
		if can_climb && ((is_action_pressed_and_available(CONTROL_UP) && !test_motion(Vector2(0,-5))) || (is_action_pressed_and_available(CONTROL_DOWN) && !test_motion(Vector2(0,5)))):
			#check if can climb and if thera are obstacles where we want to climb
			fsm = CLIMBING
			falling_time = 0
			if debug: print("fall -> climb")
		elif test_motion(Vector2(0,5)):
			fsm = IDLE
			falling_time = 0
			if debug: print("fall -> idle")
		elif is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED && falling_time < FALLING_JUMP_DELAY:
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("fall -> jump")

	elif fsm == JUMPING:
		jump_time += delta
		if !is_action_pressed_and_available(CONTROL_JUMP) || jump_time > JUMP_TIME_MAX:
			fsm = FALLING
			jump_time = 0
			falling_time = FALLING_JUMP_DELAY
			if debug: print("jump -> fall")

func is_action_pressed_and_available(action):
	return ((action == control_1 || action == control_2) && Input.is_action_pressed(action)) || (touchscreen_left != -1 && action == control_1) || (touchscreen_right != -1 && action == control_2)

func change_controls(control_1, control_2):
	self.control_1 = control_1
	self.control_2 = control_2
	emit_signal("controls_changed", self)

func set_jump_pressed():
	if jump_pressed == JUMP_NOT_PRESSED:
		if control_1 == "jump":
			jump_pressed = JUMP_PRESSED_LEFT
		elif control_2 == "jump":
			jump_pressed = JUMP_PRESSED_RIGHT

extends Node

signal controls_changed

var debug = false

const CONTROL_UP = "move_up"
const CONTROL_DOWN = "move_down"
const CONTROL_LEFT = "move_left"
const CONTROL_RIGHT = "move_right"
const CONTROL_RUN = "run"
const CONTROL_JUMP = "jump"

export(String, "move_up", "move_down", "move_left", "move_right", "run", "jump") var control_1 = "move_right"
export(String, "move_up", "move_down", "move_left", "move_right", "run", "jump") var control_2 = "move_down"

onready var climb_area = preload("res://sources/scripts/levels/climb_area.gd")

onready var player_sprites = get_node("sprites")
onready var interaction_area = get_node("interaction_area")

const WALK_SPEED = 200
const JUMP_SPEED = 300
const RUN_SPEED = 500
const CLIMB_SPEED = 200
const GROUND_FRICTION_MULTIPLIER = 0.7
const GROUND_ACCELERATION_MULTIPLIER = 0.2
const AIR_FRICTION_MULTIPLIER = 0.95
const AIR_DIRECTION_MULTIPLIER = 0.15
const JUMP_TIME_MAX = 0.2
const FALLING_JUMP_DELAY = 0.2

var jumping = false
var jump_time = 0
var falling_time = 0

const IDLE = 0
const WALKING = 1
const RUNNING = 2
const JUMPING = 3
const FALLING = 4
const CLIMBING = 5
var fsm = IDLE

var touchscreen_left = -1
var touchscreen_right = -1

const JUMP_NOT_PRESSED = -1
const JUMP_PRESSED_LEFT = 0
const JUMP_PRESSED_RIGHT = 1
var jump_pressed = JUMP_NOT_PRESSED

func _ready():
	set_process(true)
	set_process_input(true)

func _input(event):
	if event.type == InputEvent.SCREEN_TOUCH:
		if event.pressed:
			if event.pos.y > 200:
				if event.pos.x > 1024 / 2 && touchscreen_right == -1:
					touchscreen_right = event.index
				elif event.pos.x <= 1024 / 2 && touchscreen_left == -1:
					touchscreen_left = event.index
		else:
			if event.index == touchscreen_right:
				touchscreen_right = -1
			elif event.index == touchscreen_left:
				touchscreen_left = -1

			#if we release jump button we can jump again
			if touchscreen_left == -1 && jump_pressed == JUMP_PRESSED_LEFT:
				jump_pressed = JUMP_NOT_PRESSED
			elif touchscreen_right == -1 && jump_pressed == JUMP_PRESSED_RIGHT:
				jump_pressed = JUMP_NOT_PRESSED
	elif event.type == InputEvent.KEY:
		if event.is_action_released("jump"):
			jump_pressed = JUMP_NOT_PRESSED

func release_controls():
	touchscreen_right = -1
	touchscreen_left = -1

func _integrate_forces(state):
	if fsm == IDLE:
		integrate_idle(state)
	elif fsm == WALKING:
		integrate_walking(state)
	elif fsm == RUNNING:
		integrate_running(state)
	elif fsm == JUMPING:
		integrate_jumping(state)
	elif fsm == FALLING:
		integrate_falling(state)
	elif fsm == CLIMBING:
		integrate_climbing(state)

func integrate_idle(state):
	player_sprites.play("stand")
	var velocity = state.get_linear_velocity()
	velocity.x *= GROUND_FRICTION_MULTIPLIER
	state.set_linear_velocity(velocity)

func integrate_walking(state):
	player_sprites.play("walk")
	var velocity = state.get_linear_velocity()

	if abs(velocity.x) > WALK_SPEED:
		if is_action_pressed_and_available(CONTROL_LEFT):
			player_sprites.set_flip_h(true)
		elif is_action_pressed_and_available(CONTROL_RIGHT):
			player_sprites.set_flip_h(false)
		velocity.x = clamp(velocity.x * (1 - GROUND_ACCELERATION_MULTIPLIER), -RUN_SPEED, RUN_SPEED)
	else:
		if is_action_pressed_and_available(CONTROL_LEFT):
			velocity.x -= WALK_SPEED * GROUND_ACCELERATION_MULTIPLIER
			player_sprites.set_flip_h(true)
		elif is_action_pressed_and_available(CONTROL_RIGHT):
			velocity.x += WALK_SPEED * GROUND_ACCELERATION_MULTIPLIER
			player_sprites.set_flip_h(false)

		velocity.x = clamp(velocity.x, -WALK_SPEED, WALK_SPEED)

	state.set_linear_velocity(velocity)

func integrate_running(state):
	player_sprites.play("walk")
	var velocity = state.get_linear_velocity()

	if is_action_pressed_and_available(CONTROL_LEFT):
		velocity.x -= RUN_SPEED * GROUND_ACCELERATION_MULTIPLIER
		player_sprites.set_flip_h(true)
	elif is_action_pressed_and_available(CONTROL_RIGHT):
		velocity.x += RUN_SPEED * GROUND_ACCELERATION_MULTIPLIER
		player_sprites.set_flip_h(false)

	velocity.x = clamp(velocity.x, -RUN_SPEED, RUN_SPEED)

	state.set_linear_velocity(velocity)

func integrate_falling(state):
	player_sprites.play("jump")
	var velocity = state.get_linear_velocity()

	if velocity.x > -WALK_SPEED && is_action_pressed_and_available(CONTROL_LEFT):
		player_sprites.set_flip_h(true)
		velocity.x -= WALK_SPEED * AIR_DIRECTION_MULTIPLIER
	elif velocity.x < WALK_SPEED && is_action_pressed_and_available(CONTROL_RIGHT):
		player_sprites.set_flip_h(false)
		velocity.x += WALK_SPEED * AIR_DIRECTION_MULTIPLIER
	elif !is_action_pressed_and_available(CONTROL_LEFT) && !is_action_pressed_and_available(CONTROL_RIGHT):
		velocity.x *= AIR_FRICTION_MULTIPLIER

	velocity.x = clamp(velocity.x, -RUN_SPEED, RUN_SPEED)
	state.set_linear_velocity(velocity)

func integrate_jumping(state):
	player_sprites.play("jump")
	var velocity = state.get_linear_velocity()

	if velocity.x > -WALK_SPEED && is_action_pressed_and_available(CONTROL_LEFT):
		player_sprites.set_flip_h(true)
		velocity.x -= WALK_SPEED * AIR_DIRECTION_MULTIPLIER
	elif velocity.x < WALK_SPEED && is_action_pressed_and_available(CONTROL_RIGHT):
		player_sprites.set_flip_h(false)
		velocity.x += WALK_SPEED * AIR_DIRECTION_MULTIPLIER
	elif !is_action_pressed_and_available(CONTROL_LEFT) && !is_action_pressed_and_available(CONTROL_RIGHT):
		velocity.x *= AIR_FRICTION_MULTIPLIER

	velocity.x = clamp(velocity.x, -RUN_SPEED, RUN_SPEED)

	velocity.y = -JUMP_SPEED
	state.set_linear_velocity(velocity)

func integrate_climbing(state):
	player_sprites.play("jump")
	var velocity = state.get_linear_velocity()

	if is_action_pressed_and_available(CONTROL_UP):
		velocity.y = -CLIMB_SPEED
	elif is_action_pressed_and_available(CONTROL_DOWN):
		velocity.y = CLIMB_SPEED

	if is_action_pressed_and_available(CONTROL_LEFT):
		velocity.x = - WALK_SPEED * 0.6
		player_sprites.set_flip_h(true)
	elif is_action_pressed_and_available(CONTROL_RIGHT):
		velocity.x = WALK_SPEED * 0.6
		player_sprites.set_flip_h(false)
	else:
		velocity.x = 0

	state.set_linear_velocity(velocity)

func _process(delta):
	var can_climb = false

	var overlapping_areas = interaction_area.get_overlapping_areas()

	for area in overlapping_areas:
		if can_climb:
			break
		can_climb = area extends climb_area

	if fsm == IDLE:
		if is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED:
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("idle -> jump")
		elif is_action_pressed_and_available(CONTROL_LEFT) || is_action_pressed_and_available(CONTROL_RIGHT):
			fsm = WALKING
			if debug: print("idle -> walk")
		elif can_climb && ((is_action_pressed_and_available(CONTROL_UP) && !test_motion(Vector2(0,-5))) || (is_action_pressed_and_available(CONTROL_DOWN) && !test_motion(Vector2(0,5)))):
			#check if can climb and if thera are obstacles where we want to climb
			fsm = CLIMBING
			if debug: print("idle -> climb")
		elif !test_motion(Vector2(0,5)):
			fsm = FALLING
			if debug: print("idle -> fall")

	elif fsm == WALKING:
		if !is_action_pressed_and_available(CONTROL_LEFT) && !is_action_pressed_and_available(CONTROL_RIGHT):
			fsm = IDLE
			if debug: print("walk -> idle")
		elif is_action_pressed_and_available(CONTROL_RUN):
			fsm = RUNNING
			if debug: print("walk -> run")
		elif is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED:
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("walk -> jump")
		elif can_climb && ((is_action_pressed_and_available(CONTROL_UP) && !test_motion(Vector2(0,-5))) || (is_action_pressed_and_available(CONTROL_DOWN) && !test_motion(Vector2(0,5)))):
			fsm = CLIMBING
			if debug: print("walk -> climb")
		elif !test_motion(Vector2(0,5)):
			fsm = FALLING
			if debug: print("walk -> fall")

	elif fsm == RUNNING:
		if !is_action_pressed_and_available(CONTROL_RUN):
			fsm = WALKING
			if debug: print("run -> walk")
		elif !is_action_pressed_and_available(CONTROL_LEFT) && !is_action_pressed_and_available(CONTROL_RIGHT):
			fsm = IDLE
			if debug: print("run -> idle")
		elif is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED :
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("run -> jump")
		elif can_climb && ((is_action_pressed_and_available(CONTROL_UP) && !test_motion(Vector2(0,-5))) || (is_action_pressed_and_available(CONTROL_DOWN) && !test_motion(Vector2(0,5)))):
			#check if can climb and if thera are obstacles where we want to climb
			fsm = CLIMBING
			if debug: print("run -> climb")
		elif !test_motion(Vector2(0,5)):
			fsm = FALLING
			if debug: print("run -> fall")

	elif fsm == CLIMBING:
		if !can_climb:
			fsm = FALLING
			if debug: print("climb -> fall")
		elif is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED :
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("climb -> jump")
		elif !is_action_pressed_and_available(CONTROL_UP) && !is_action_pressed_and_available(CONTROL_DOWN) && test_motion(Vector2(0,5)):
			fsm = IDLE
			if debug: print("climb -> idle")

	elif fsm == FALLING:
		falling_time += delta
		if can_climb && ((is_action_pressed_and_available(CONTROL_UP) && !test_motion(Vector2(0,-5))) || (is_action_pressed_and_available(CONTROL_DOWN) && !test_motion(Vector2(0,5)))):
			#check if can climb and if thera are obstacles where we want to climb
			fsm = CLIMBING
			falling_time = 0
			if debug: print("fall -> climb")
		elif test_motion(Vector2(0,5)):
			fsm = IDLE
			falling_time = 0
			if debug: print("fall -> idle")
		elif is_action_pressed_and_available(CONTROL_JUMP) && jump_pressed == JUMP_NOT_PRESSED && falling_time < FALLING_JUMP_DELAY:
			fsm = JUMPING
			set_jump_pressed()
			if debug: print("fall -> jump")

	elif fsm == JUMPING:
		jump_time += delta
		if !is_action_pressed_and_available(CONTROL_JUMP) || jump_time > JUMP_TIME_MAX:
			fsm = FALLING
			jump_time = 0
			falling_time = FALLING_JUMP_DELAY
			if debug: print("jump -> fall")

func is_action_pressed_and_available(action):
	return ((action == control_1 || action == control_2) && Input.is_action_pressed(action)) || (touchscreen_left != -1 && action == control_1) || (touchscreen_right != -1 && action == control_2)

func change_controls(control_1, control_2):
	self.control_1 = control_1
	self.control_2 = control_2
	emit_signal("controls_changed", self)

func set_jump_pressed():
	if jump_pressed == JUMP_NOT_PRESSED:
		if control_1 == "jump":
			jump_pressed = JUMP_PRESSED_LEFT
		elif control_2 == "jump":
			jump_pressed = JUMP_PRESSED_RIGHT