added the game
21
addons/JSON_Schema_Validator/JSON_Schema_validator_LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Sahedo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
516
addons/JSON_Schema_Validator/json_schema_validator.gd
Normal file
@@ -0,0 +1,516 @@
|
||||
class_name JSONSchema
|
||||
extends RefCounted
|
||||
|
||||
|
||||
# JSON Schema main script
|
||||
# Inherits from Reference for easy use
|
||||
|
||||
const SMALL_FLOAT_THRESHOLD = 0.001
|
||||
const MAX_DECIMAL_PLACES = 3
|
||||
|
||||
const DEF_KEY_NAME = "schema root"
|
||||
const DEF_ERROR_STRING = "##error##"
|
||||
|
||||
const JST_ARRAY = "array"
|
||||
const JST_BOOLEAN = "boolean"
|
||||
const JST_INTEGER = "integer"
|
||||
const JST_NULL = "null"
|
||||
const JST_NUMBER = "number"
|
||||
const JST_OBJECT = "object"
|
||||
const JST_STRING = "string"
|
||||
|
||||
const JSKW_TYPE = "type"
|
||||
const JSKW_PROP = "properties"
|
||||
const JSKW_REQ = "required"
|
||||
const JSKW_TITLE = "title"
|
||||
const JSKW_DESCR = "description"
|
||||
const JSKW_DEFAULT = "default"
|
||||
const JSKW_EXAMPLES = "examples"
|
||||
const JSKW_COMMENT = "$comment"
|
||||
const JSKW_ENUM = "enum"
|
||||
const JSKW_CONST = "const"
|
||||
const JSKW_PREFIX_ITEMS = "prefixItems"
|
||||
const JSKW_ITEMS = "items"
|
||||
const JSKW_MIN_ITEMS = "minItems"
|
||||
const JSKW_MAX_ITEMS = "maxItems"
|
||||
const JSKW_CONTAINS = "contains"
|
||||
const JSKW_ADD_ITEMS = "additionalItems"
|
||||
const JSKW_UNIQUE_ITEMS = "uniqueItems"
|
||||
const JSKW_MULT_OF = "multipleOf"
|
||||
const JSKW_MINIMUM = "minimum"
|
||||
const JSKW_MIN_EX = "exclusiveMinimum"
|
||||
const JSKW_MAXIMUM = "maximum"
|
||||
const JSKW_MAX_EX = "exclusiveMaximum"
|
||||
const JSKW_PROP_ADD = "additionalProperties"
|
||||
const JSKW_PROP_PATTERN = "patternProperties"
|
||||
const JSKW_PROP_NAMES = "propertyNames"
|
||||
const JSKW_PROP_MIN = "minProperties"
|
||||
const JSKW_PROP_MAX = "maxProperties"
|
||||
const JSKW_DEPEND = "dependencies"
|
||||
const JSKW_LENGTH_MIN = "minLength"
|
||||
const JSKW_LENGTH_MAX = "maxLength"
|
||||
const JSKW_PATTERN = "pattern"
|
||||
const JSKW_FORMAT = "format"
|
||||
const JSKW_COLOR = "color"
|
||||
|
||||
const JSM_GREATER = "greater"
|
||||
const JSM_GREATER_EQ = "greater or equal"
|
||||
const JSM_LESS = "less"
|
||||
const JSM_LESS_EQ = "less or equal"
|
||||
const JSM_OBJ_DICT = "object (dictionary)"
|
||||
|
||||
const JSL_AND = "%s and %s"
|
||||
const JSL_OR = "%s or %s"
|
||||
|
||||
const ERR_SCHEMA_FALSE = "Schema declared as deny all"
|
||||
const ERR_WRONG_SCHEMA_GEN = "Schema error: "
|
||||
const ERR_WRONG_SCHEMA_TYPE = "Schema error: schema must be empty object or object with 'type' keyword or boolean value"
|
||||
const ERR_WRONG_SHEMA_NOTA = "Schema error: expected that all elements of '%s.%s' must be '%s'"
|
||||
const ERR_WRONG_PROP_TYPE = "Schema error: any schema item must be object with 'type' keyword"
|
||||
const ERR_REQ_PROP_GEN = "Schema error: expected array of required properties for '%s'"
|
||||
const ERR_REQ_PROP_MISSING = "Missing required property: '%s' for '%s'"
|
||||
const ERR_NO_PROP_ADD = "Additional properties are not required: found '%s'"
|
||||
const ERR_FEW_PROP = "%d propertie(s) are not enough properties, at least %d are required"
|
||||
const ERR_MORE_PROP = "%d propertie(s) are too many properties, at most %d are allowed"
|
||||
const ERR_FEW_ITEMS = "%s item(s) are not enough items, at least %s are required"
|
||||
const ERR_MORE_ITEMS = "%s item(s) are too many items, at most %s are allowed"
|
||||
const ERR_INVALID_JSON_GEN = "Validation fails with message: %s"
|
||||
const ERR_INVALID_JSON_EXT = "Invalid JSON data passed with message: %s"
|
||||
const ERR_TYPE_MISMATCH_GEN = "Type mismatch: expected %s for '%s'"
|
||||
const ERR_INVALID_NUMBER = "The %s key that equals %s should have a maximum of %s decimal places"
|
||||
const ERR_INVALID_MULT = "Multiplier in key %s that equals %s must be greater or equal to %s"
|
||||
const ERR_MULT_D = "Key %s that equal %d must be multiple of %d"
|
||||
const ERR_MULT_F = "Key %s that equal %f must be multiple of %f"
|
||||
const ERR_RANGE_D = "Key %s that equal %d must be %s than %d"
|
||||
const ERR_RANGE_F = "Key %s that equal %f must be %s than %f"
|
||||
const ERR_RANGE_S = "Length of '%s' (%d) %s than declared (%d)"
|
||||
const ERR_WRONG_PATTERN = "String '%s' does not match its corresponding pattern"
|
||||
const ERR_FORMAT = "String '%s' does not match its corresponding format '%s'"
|
||||
|
||||
# This is one and only function that need you to call outside
|
||||
# If all validation checks passes, this OK
|
||||
func validate(json_data : String, schema: String) -> String:
|
||||
var error: int
|
||||
|
||||
var json = JSON.new()
|
||||
# General validation input data as JSON file
|
||||
error = json.parse(json_data)
|
||||
if error: return ERR_INVALID_JSON_EXT % error_string(error)
|
||||
|
||||
# General validation input schema as JSONSchema file
|
||||
error = json.parse(schema)
|
||||
if not error == OK : return ERR_WRONG_SCHEMA_GEN + error_string(error)
|
||||
var test_json_conv = JSON.new()
|
||||
test_json_conv.parse(schema)
|
||||
var parsed_schema = test_json_conv.get_data()
|
||||
match typeof(parsed_schema):
|
||||
TYPE_BOOL:
|
||||
if !parsed_schema:
|
||||
return ERR_INVALID_JSON_GEN % ERR_SCHEMA_FALSE
|
||||
else:
|
||||
return ""
|
||||
TYPE_DICTIONARY:
|
||||
if parsed_schema.is_empty():
|
||||
return ""
|
||||
elif parsed_schema.keys().size() > 0 && !parsed_schema.has(JSKW_TYPE):
|
||||
return ERR_WRONG_SCHEMA_TYPE
|
||||
_: return ERR_WRONG_SCHEMA_TYPE
|
||||
|
||||
# All inputs seems valid. Begin type validation
|
||||
# Normal return empty string, meaning OK
|
||||
return _type_selection(json_data, parsed_schema)
|
||||
|
||||
func _to_string():
|
||||
return "[JSONSchema:%d]" % get_instance_id()
|
||||
|
||||
# TODO: title, description, default, examples, $comment, enum, const
|
||||
func _type_selection(json_data: String, schema: Dictionary, key: String = DEF_KEY_NAME) -> String:
|
||||
# If the schema is an empty object it always passes validation
|
||||
if schema.is_empty():
|
||||
return ""
|
||||
|
||||
if typeof(schema) == TYPE_BOOL:
|
||||
# If the schema is true it always passes validation
|
||||
if schema:
|
||||
return ""
|
||||
# If the schema is false it always vales validation
|
||||
else:
|
||||
return ERR_INVALID_JSON_GEN + "false is always invalid"
|
||||
|
||||
var typearr: Array = _var_to_array(schema.type)
|
||||
var test_json_conv = JSON.new()
|
||||
test_json_conv.parse(json_data)
|
||||
var parsed_data = test_json_conv.get_data()
|
||||
var error: String = ERR_TYPE_MISMATCH_GEN % [typearr, key]
|
||||
for type in typearr:
|
||||
match type:
|
||||
JST_ARRAY:
|
||||
if typeof(parsed_data) == TYPE_ARRAY:
|
||||
error = _validate_array(parsed_data, schema, key)
|
||||
else:
|
||||
error = ERR_TYPE_MISMATCH_GEN % [[JST_ARRAY], key]
|
||||
JST_BOOLEAN:
|
||||
if typeof(parsed_data) != TYPE_BOOL:
|
||||
return ERR_TYPE_MISMATCH_GEN % [[JST_BOOLEAN], key]
|
||||
else:
|
||||
error = ""
|
||||
JST_INTEGER:
|
||||
if typeof(parsed_data) == TYPE_INT:
|
||||
error = _validate_integer(parsed_data, schema, key)
|
||||
if typeof(parsed_data) == TYPE_FLOAT && parsed_data == int(parsed_data):
|
||||
error = _validate_integer(int(parsed_data), schema, key)
|
||||
JST_NULL:
|
||||
if typeof(parsed_data) != TYPE_NIL:
|
||||
return ERR_TYPE_MISMATCH_GEN % [[JST_NULL], key]
|
||||
else:
|
||||
error = ""
|
||||
JST_NUMBER:
|
||||
if typeof(parsed_data) == TYPE_FLOAT:
|
||||
error = _validate_number(parsed_data, schema, key)
|
||||
else:
|
||||
error = ERR_TYPE_MISMATCH_GEN % [[JST_NUMBER], key]
|
||||
JST_OBJECT:
|
||||
if typeof(parsed_data) == TYPE_DICTIONARY:
|
||||
error = _validate_object(parsed_data, schema, key)
|
||||
else:
|
||||
error = ERR_TYPE_MISMATCH_GEN % [[JST_OBJECT], key]
|
||||
JST_STRING:
|
||||
if typeof(parsed_data) == TYPE_STRING:
|
||||
error = _validate_string(parsed_data, schema, key)
|
||||
else:
|
||||
error = ERR_TYPE_MISMATCH_GEN % [[JST_STRING], key]
|
||||
return error
|
||||
|
||||
|
||||
func _var_to_array(variant) -> Array:
|
||||
var result : Array = []
|
||||
if typeof(variant) == TYPE_ARRAY:
|
||||
result = variant
|
||||
else:
|
||||
result.append(variant)
|
||||
return result
|
||||
|
||||
func _validate_array(input_data: Array, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
|
||||
# TODO: contains minContains maxContains uniqueItems
|
||||
|
||||
# Initialize variables
|
||||
var error : String = "" # Variable to store any error messages
|
||||
var items_array : Array # Array of items in the schema
|
||||
var suberror : Array = [] # Array of suberrors in each item
|
||||
var additional_items_schema: Dictionary # Schema for additional items in the input data
|
||||
var is_additional_item_allowed: bool # Flag to check if additional items are allowed
|
||||
|
||||
# Check if minItems key exists in the schema
|
||||
if input_schema.has(JSKW_MIN_ITEMS):
|
||||
# Check if non negative number
|
||||
if input_schema.minItems < 0:
|
||||
return ERR_WRONG_SCHEMA_GEN + "minItems must be a non-negative number."
|
||||
|
||||
if input_data.size() < input_schema.minItems:
|
||||
return ERR_FEW_ITEMS % [input_data.size(), input_schema.minItems]
|
||||
|
||||
# Check if maxItems key exists in the schema
|
||||
if input_schema.has(JSKW_MAX_ITEMS):
|
||||
# Check if non negative number
|
||||
if input_schema.maxItems < 0:
|
||||
return ERR_WRONG_SCHEMA_GEN + "minItems must be a non-negative number."
|
||||
|
||||
if input_data.size() > input_schema.maxItems:
|
||||
return ERR_MORE_ITEMS % [input_data.size(), input_schema.maxItems]
|
||||
|
||||
# Check if prefixItems key exists in the schema
|
||||
if input_schema.has(JSKW_PREFIX_ITEMS):
|
||||
# Check if items key exists in the schema
|
||||
if not input_schema.has(JSKW_ITEMS):
|
||||
return ERR_REQ_PROP_MISSING % [JSKW_ITEMS, JSKW_PREFIX_ITEMS]
|
||||
|
||||
# Return error if items key is not a bool or a dictionary
|
||||
if not typeof(input_schema.items) == TYPE_DICTIONARY and not typeof(input_schema.items) == TYPE_BOOL:
|
||||
return ERR_WRONG_SCHEMA_TYPE
|
||||
|
||||
if typeof(input_schema.items) == TYPE_BOOL:
|
||||
# Check if additional items in the input data are allowed
|
||||
if input_schema.items == false:
|
||||
# Check if there are more items in the input data than specified in prefixItems
|
||||
if input_data.size() > input_schema.prefixItems.size():
|
||||
# Create an error message if there are more items than allowed
|
||||
var substr := "Array '%s' is of size %s but no addition items allowed." % [input_data, input_data.size()]
|
||||
return ERR_INVALID_JSON_GEN % substr
|
||||
# If the 'items' key is set to true all types are allowed for addition items.
|
||||
else:
|
||||
additional_items_schema = {}
|
||||
|
||||
# Check if items key is a dictionary
|
||||
if typeof(input_schema.items) == TYPE_DICTIONARY:
|
||||
# Any items after the specified ones in prefixItems have to be validated with this schema
|
||||
# Set the schema for additional array items
|
||||
additional_items_schema = input_schema.items
|
||||
|
||||
# Check if all entries in prefixItems are a dictionary
|
||||
for schema in input_schema.prefixItems:
|
||||
if typeof(schema) != TYPE_DICTIONARY:
|
||||
return ERR_WRONG_SHEMA_NOTA % [property_name, JSKW_ITEMS, JST_OBJECT]
|
||||
|
||||
# Check every item in the input data
|
||||
for index in input_data.size():
|
||||
var item = input_data[index]
|
||||
var current_schema: Dictionary
|
||||
var key_substr: String
|
||||
|
||||
if index <= input_schema.prefixItems.size() - 1:
|
||||
# As long as there are prefixItems in the array work with those
|
||||
current_schema = input_schema.prefixItems[index]
|
||||
key_substr = ".prefixItems"
|
||||
else:
|
||||
# After that use the items schema
|
||||
current_schema = additional_items_schema
|
||||
key_substr = ".items"
|
||||
|
||||
var sub_error_message := _type_selection(JSON.stringify(item), current_schema, property_name + key_substr + "[" + str(index) + "]")
|
||||
if not sub_error_message == "":
|
||||
suberror.append(sub_error_message)
|
||||
|
||||
if suberror.size() > 0:
|
||||
return ERR_INVALID_JSON_GEN % str(suberror)
|
||||
|
||||
# Return inside this if block, because we don't want to validate the items key twice.
|
||||
return error
|
||||
|
||||
# Check if items key exists in the schema
|
||||
if input_schema.has(JSKW_ITEMS):
|
||||
#'items' must be an object
|
||||
if not typeof(input_schema.items) == TYPE_DICTIONARY:
|
||||
return ERR_WRONG_SHEMA_NOTA % [property_name, JSKW_ITEMS, JST_OBJECT]
|
||||
|
||||
# Check every item of input Array on
|
||||
for index in input_data.size():
|
||||
index = index - 1
|
||||
|
||||
# Validate the array item with the schema defined by the 'items' key
|
||||
var sub_error_message := _type_selection(JSON.stringify(input_data[index]), input_schema.items, property_name + "[" + str(index) + "]")
|
||||
if not sub_error_message == "":
|
||||
suberror.append(sub_error_message)
|
||||
|
||||
if suberror.size() > 0:
|
||||
return ERR_INVALID_JSON_GEN % str(suberror)
|
||||
|
||||
return error
|
||||
|
||||
func _validate_boolean(input_data: bool, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
|
||||
# nothing to check
|
||||
return ""
|
||||
|
||||
func _validate_integer(input_data: int, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
|
||||
# all processing is performed in
|
||||
return _validate_number(input_data, input_schema, property_name)
|
||||
|
||||
func _validate_null(input_data, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
|
||||
# nothing to check
|
||||
return ""
|
||||
|
||||
func _validate_number(input_data: float, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
|
||||
var types: Array = _var_to_array(input_schema.type)
|
||||
# integer mode turns on only if types has integer and has not number
|
||||
var integer_mode: bool = types.has(JST_INTEGER) && !types.has(JST_NUMBER)
|
||||
|
||||
# Processing multiple check
|
||||
if input_schema.has(JSKW_MULT_OF):
|
||||
var mult: float
|
||||
var mod: float
|
||||
var is_zero: bool
|
||||
|
||||
# Get the multipleOf value from the schema and convert to float
|
||||
mult = float(input_schema[JSKW_MULT_OF])
|
||||
# Convert to integer if integer_mode is enabled
|
||||
mult = int(mult) if integer_mode else mult
|
||||
|
||||
# Check if the number has more decimal places then allowed
|
||||
var decimal_places := str(input_data).get_slice('.', 1)
|
||||
if not decimal_places.is_empty() and decimal_places.length() > MAX_DECIMAL_PLACES:
|
||||
return ERR_INVALID_NUMBER % [property_name, input_data, str(MAX_DECIMAL_PLACES)]
|
||||
|
||||
# Check if multipleOf is smaller than SMALL_FLOAT_THRESHOLD
|
||||
if not mult >= SMALL_FLOAT_THRESHOLD:
|
||||
return ERR_INVALID_MULT % [property_name, mult, str(SMALL_FLOAT_THRESHOLD)]
|
||||
|
||||
# Multiply by a big number if input is smaller than 1 to prevent float issues
|
||||
if input_data < 1.0 or mult < 1.0:
|
||||
mod = fmod(input_data * 1000, mult * 1000)
|
||||
else:
|
||||
mod = fmod(input_data, mult)
|
||||
|
||||
# Check if the remainder is close to zero
|
||||
is_zero = is_zero_approx(mod)
|
||||
|
||||
# Return error message if remainder is not close to zero
|
||||
if not is_zero:
|
||||
if integer_mode:
|
||||
return ERR_MULT_D % [property_name, input_data, mult]
|
||||
else:
|
||||
return ERR_MULT_F % [property_name, input_data, mult]
|
||||
|
||||
# processing minimum check
|
||||
if input_schema.has(JSKW_MINIMUM):
|
||||
var minimum = float(input_schema[JSKW_MINIMUM])
|
||||
minimum = int(minimum) if integer_mode else minimum
|
||||
if input_data < minimum:
|
||||
if integer_mode:
|
||||
return ERR_RANGE_D % [property_name, input_data, JSM_GREATER_EQ, minimum]
|
||||
else:
|
||||
return ERR_RANGE_F % [property_name, input_data, JSM_GREATER_EQ, minimum]
|
||||
|
||||
# processing exclusive minimum check
|
||||
if input_schema.has(JSKW_MIN_EX):
|
||||
var minimum = float(input_schema[JSKW_MIN_EX])
|
||||
minimum = int(minimum) if integer_mode else minimum
|
||||
if input_data <= minimum:
|
||||
if integer_mode:
|
||||
return ERR_RANGE_D % [property_name, input_data, JSM_GREATER, minimum]
|
||||
else:
|
||||
return ERR_RANGE_F % [property_name, input_data, JSM_GREATER, minimum]
|
||||
|
||||
# processing maximum check
|
||||
if input_schema.has(JSKW_MAXIMUM):
|
||||
var maximum = float(input_schema[JSKW_MAXIMUM])
|
||||
maximum = int(maximum) if integer_mode else maximum
|
||||
if input_data > maximum:
|
||||
if integer_mode:
|
||||
return ERR_RANGE_D % [property_name, input_data, JSM_LESS_EQ, maximum]
|
||||
else:
|
||||
return ERR_RANGE_F % [property_name, input_data, JSM_LESS_EQ, maximum]
|
||||
|
||||
# processing exclusive minimum check
|
||||
if input_schema.has(JSKW_MAX_EX):
|
||||
var maximum = float(input_schema[JSKW_MAX_EX])
|
||||
maximum = int(maximum) if integer_mode else maximum
|
||||
if input_data >= maximum:
|
||||
if integer_mode:
|
||||
return ERR_RANGE_D % [property_name, input_data, JSM_LESS, maximum]
|
||||
else:
|
||||
return ERR_RANGE_F % [property_name, input_data, JSM_LESS, maximum]
|
||||
|
||||
return ""
|
||||
|
||||
func _validate_object(input_data: Dictionary, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
|
||||
# TODO: patternProperties
|
||||
var error : String = ""
|
||||
|
||||
# Process dependencies
|
||||
if input_schema.has(JSKW_DEPEND):
|
||||
for dependency in input_schema.dependencies.keys():
|
||||
if input_data.has(dependency):
|
||||
match typeof(input_schema.dependencies[dependency]):
|
||||
TYPE_ARRAY:
|
||||
if input_schema.has(JSKW_REQ):
|
||||
for property in input_schema.dependencies[dependency]:
|
||||
input_schema.required.append(property)
|
||||
else:
|
||||
input_schema.required = input_schema.dependencies[dependency]
|
||||
TYPE_DICTIONARY:
|
||||
for key in input_schema.dependencies[dependency].keys():
|
||||
if input_schema.has(key):
|
||||
match typeof(input_schema[key]):
|
||||
TYPE_ARRAY:
|
||||
for element in input_schema.dependencies[dependency][key]:
|
||||
input_schema[key].append(element)
|
||||
TYPE_DICTIONARY:
|
||||
for element in input_schema.dependencies[dependency][key].keys():
|
||||
input_schema[key][element] = input_schema.dependencies[dependency][key][element]
|
||||
_:
|
||||
input_schema[key] = input_schema.dependencies[dependency][key]
|
||||
else:
|
||||
input_schema[key] = input_schema.dependencies[dependency][key]
|
||||
_:
|
||||
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JSL_OR % [JST_ARRAY, JSM_OBJ_DICT], property_name]
|
||||
|
||||
# Process properties
|
||||
if input_schema.has(JSKW_PROP):
|
||||
|
||||
# Process required
|
||||
if input_schema.has(JSKW_REQ):
|
||||
if typeof(input_schema.required) != TYPE_ARRAY: return ERR_REQ_PROP_GEN % property_name
|
||||
for i in input_schema.required:
|
||||
if !input_data.has(i): return ERR_REQ_PROP_MISSING % [i, property_name]
|
||||
|
||||
# Continue validating schema subelements
|
||||
if typeof(input_schema.properties) != TYPE_DICTIONARY:
|
||||
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JSM_OBJ_DICT, property_name]
|
||||
|
||||
# Process property items
|
||||
for key in input_schema.properties:
|
||||
if !input_schema.properties[key].has(JSKW_TYPE):
|
||||
return ERR_WRONG_PROP_TYPE
|
||||
if input_data.has(key):
|
||||
error = _type_selection(JSON.stringify(input_data[key]), input_schema.properties[key], key)
|
||||
else:
|
||||
pass
|
||||
if error: return error
|
||||
|
||||
# Process additional properties
|
||||
if input_schema.has(JSKW_PROP_ADD):
|
||||
match typeof(input_schema.additionalProperties):
|
||||
TYPE_BOOL:
|
||||
if not input_schema.additionalProperties:
|
||||
for key in input_data:
|
||||
if not input_schema.properties.has(key):
|
||||
return ERR_NO_PROP_ADD % key
|
||||
TYPE_DICTIONARY:
|
||||
for key in input_data:
|
||||
if not input_schema.properties.has(key):
|
||||
return _type_selection(JSON.stringify(input_data[key]), input_schema.additionalProperties, key)
|
||||
_:
|
||||
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JSL_OR % [JST_BOOLEAN, JSM_OBJ_DICT], property_name]
|
||||
|
||||
# Process properties names
|
||||
if input_schema.has(JSKW_PROP_NAMES):
|
||||
if typeof(input_schema.propertyNames) != TYPE_DICTIONARY:
|
||||
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JSM_OBJ_DICT, property_name]
|
||||
for key in input_data:
|
||||
error = _validate_string(key, input_schema.propertyNames, key)
|
||||
if error: return error
|
||||
|
||||
# Process minProperties maxProperties
|
||||
if input_schema.has(JSKW_PROP_MIN):
|
||||
if typeof(input_schema[JSKW_PROP_MIN]) != TYPE_FLOAT:
|
||||
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JST_INTEGER, property_name]
|
||||
if input_data.keys().size() < input_schema[JSKW_PROP_MIN]:
|
||||
return ERR_FEW_PROP % [input_data.keys().size(), input_schema[JSKW_PROP_MIN]]
|
||||
|
||||
if input_schema.has(JSKW_PROP_MAX):
|
||||
if typeof(input_schema[JSKW_PROP_MAX]) != TYPE_FLOAT:
|
||||
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JST_INTEGER, property_name]
|
||||
if input_data.keys().size() > input_schema[JSKW_PROP_MAX]:
|
||||
return ERR_MORE_PROP % [input_data.keys().size(), input_schema[JSKW_PROP_MAX]]
|
||||
|
||||
return error
|
||||
|
||||
func _validate_string(input_data: String, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
|
||||
# TODO: format
|
||||
var error : String = ""
|
||||
if input_schema.has(JSKW_LENGTH_MIN):
|
||||
if not (typeof(input_schema[JSKW_LENGTH_MIN]) == TYPE_INT || typeof(input_schema[JSKW_LENGTH_MIN]) == TYPE_FLOAT):
|
||||
return ERR_TYPE_MISMATCH_GEN % [JST_INTEGER, property_name+"."+JSKW_LENGTH_MIN]
|
||||
if input_data.length() < input_schema[JSKW_LENGTH_MIN]:
|
||||
return ERR_INVALID_JSON_GEN % ERR_RANGE_S % [property_name, input_data.length(), JSM_LESS ,input_schema[JSKW_LENGTH_MIN]]
|
||||
|
||||
if input_schema.has(JSKW_LENGTH_MAX):
|
||||
if not (typeof(input_schema[JSKW_LENGTH_MAX]) == TYPE_INT || typeof(input_schema[JSKW_LENGTH_MAX]) == TYPE_FLOAT):
|
||||
return ERR_TYPE_MISMATCH_GEN % [JST_INTEGER, property_name+"."+JSKW_LENGTH_MAX]
|
||||
if input_data.length() > input_schema[JSKW_LENGTH_MAX]:
|
||||
return ERR_INVALID_JSON_GEN % ERR_RANGE_S % [property_name, input_data.length(), JSM_GREATER, input_schema[JSKW_LENGTH_MAX]]
|
||||
|
||||
if input_schema.has(JSKW_PATTERN):
|
||||
if not (typeof(input_schema[JSKW_PATTERN]) == TYPE_STRING):
|
||||
return ERR_TYPE_MISMATCH_GEN % [JST_STRING, property_name+"."+JSKW_PATTERN]
|
||||
var regex = RegEx.new()
|
||||
regex.compile(input_schema[JSKW_PATTERN])
|
||||
if regex.search(input_data) == null:
|
||||
return ERR_INVALID_JSON_GEN % ERR_WRONG_PATTERN % property_name
|
||||
|
||||
if input_schema.has(JSKW_FORMAT):
|
||||
# validate "color" format
|
||||
if input_schema.format.to_lower() == JSKW_COLOR:
|
||||
if not input_data.is_valid_html_color():
|
||||
return ERR_INVALID_JSON_GEN % ERR_FORMAT % [property_name, JSKW_COLOR]
|
||||
|
||||
return error
|
@@ -0,0 +1 @@
|
||||
uid://b2ogwk8l310et
|
258
addons/better-terrain/BetterTerrain.cs
Executable file
@@ -0,0 +1,258 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
#nullable disable
|
||||
|
||||
/*
|
||||
|
||||
This is a lightweight wrapper for Better Terrain in C#.
|
||||
|
||||
It is not a C# implementation, it merely provides a type safe interface to access
|
||||
the BetterTerrain autoload from C#. If you are not using Godot in C#, you can ignore
|
||||
this file.
|
||||
|
||||
The interface is created for a specific tilemap node, which it uses to locate the
|
||||
autoload, and to fill in as a parameter to simplify all the subsequent calls.
|
||||
Very simple example:
|
||||
|
||||
```
|
||||
BetterTerrain betterTerrain;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
TileMapLayer tileMapLayer = GetNode<TileMapLayer>("TileMapLayer");
|
||||
betterTerrain = new BetterTerrain(tm);
|
||||
|
||||
var coordinates = new Vector2I(0, 0);
|
||||
betterTerrain.SetCell(coordinates, 1);
|
||||
betterTerrain.UpdateTerrainCell(coordinates);
|
||||
}
|
||||
```
|
||||
|
||||
The functions available are the same as BetterTerrain's, though the TileMapLayer or
|
||||
TileSet parameters are automatically filled in. The help is not duplicated here,
|
||||
refer to the GDScript version for specifics.
|
||||
|
||||
*/
|
||||
|
||||
public class BetterTerrain
|
||||
{
|
||||
public enum TerrainType
|
||||
{
|
||||
MatchTiles = 0,
|
||||
MatchVertices = 1,
|
||||
Category = 2,
|
||||
Decoration = 3
|
||||
}
|
||||
|
||||
public enum SymmetryType
|
||||
{
|
||||
None = 0,
|
||||
Mirror = 1, // Horizontally mirror
|
||||
Flip = 2, // Vertically flip
|
||||
Reflect = 3, // All four reflections
|
||||
RotateClockwise = 4,
|
||||
RotateCounterClockwise = 5,
|
||||
Rotate180 = 6,
|
||||
RotateAll = 7, // All four rotated forms
|
||||
All = 8 // All rotated and reflected forms
|
||||
}
|
||||
|
||||
private static readonly NodePath nodePath = new("/root/BetterTerrain");
|
||||
private readonly Node betterTerrain;
|
||||
private readonly TileMapLayer tileMapLayer;
|
||||
|
||||
public BetterTerrain(TileMapLayer tileMapLayer)
|
||||
{
|
||||
this.tileMapLayer = tileMapLayer;
|
||||
betterTerrain = tileMapLayer.GetNode(nodePath);
|
||||
}
|
||||
|
||||
public Array<Godot.Collections.Dictionary<string, Variant>> GetTerrainCategories()
|
||||
{
|
||||
return (Array<Godot.Collections.Dictionary<string, Variant>>)betterTerrain.Call(MethodName.GetTerrainCategories, tileMapLayer.TileSet);
|
||||
}
|
||||
|
||||
public bool AddTerrain(string name, Color color, TerrainType type, Array<int> categories = null, Godot.Collections.Dictionary<Variant, Variant> icon = null)
|
||||
{
|
||||
categories ??= new Array<int>();
|
||||
icon ??= new Godot.Collections.Dictionary<Variant, Variant>();
|
||||
return (bool)betterTerrain.Call(MethodName.AddTerrain, tileMapLayer.TileSet, name, color, (int)type, categories, icon);
|
||||
}
|
||||
|
||||
public bool RemoveTerrain(int index)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.RemoveTerrain, tileMapLayer.TileSet, index);
|
||||
}
|
||||
|
||||
public int TerrainCount()
|
||||
{
|
||||
return (int)betterTerrain.Call(MethodName.TerrainCount, tileMapLayer.TileSet);
|
||||
}
|
||||
|
||||
public Godot.Collections.Dictionary<string, Variant> GetTerrain(int index)
|
||||
{
|
||||
return (Godot.Collections.Dictionary<string, Variant>)betterTerrain.Call(MethodName.GetTerrain, tileMapLayer.TileSet, index);
|
||||
}
|
||||
|
||||
public bool SetTerrain(int index, string name, Color color, TerrainType type, Array<int> categories = null, Godot.Collections.Dictionary<Variant, Variant> icon = null)
|
||||
{
|
||||
categories ??= new Array<int>();
|
||||
icon ??= new Godot.Collections.Dictionary<Variant, Variant>();
|
||||
return (bool)betterTerrain.Call(MethodName.SetTerrain, tileMapLayer.TileSet, index, name, color, (int)type, categories, icon);
|
||||
}
|
||||
|
||||
public bool SwapTerrains(int index1, int index2)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.SwapTerrains, tileMapLayer.TileSet, index1, index2);
|
||||
}
|
||||
|
||||
public bool SetTileTerrainType(TileData tileData, int type)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.SetTileTerrainType, tileMapLayer.TileSet, tileData, type);
|
||||
}
|
||||
|
||||
public int GetTileTerrainType(TileData tileData)
|
||||
{
|
||||
return (int)betterTerrain.Call(MethodName.GetTileTerrainType, tileData);
|
||||
}
|
||||
|
||||
public bool SetTileSymmetryType(TileData tileData, SymmetryType type)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.SetTileSymmetryType, tileMapLayer.TileSet, tileData, (int)type);
|
||||
}
|
||||
|
||||
public SymmetryType GetTileSymmetryType(TileData tileData)
|
||||
{
|
||||
return (SymmetryType)(int)betterTerrain.Call(MethodName.GetTileSymmetryType, tileData);
|
||||
}
|
||||
|
||||
public Array<TileData> GetTilesInTerrain(int type)
|
||||
{
|
||||
return (Array<TileData>)betterTerrain.Call(MethodName.GetTilesInTerrain, tileMapLayer.TileSet, type);
|
||||
}
|
||||
|
||||
public Array<Godot.Collections.Dictionary<string, Variant>> GetTileSourcesInTerrain(int type)
|
||||
{
|
||||
return (Array<Godot.Collections.Dictionary<string, Variant>>)betterTerrain.Call(MethodName.GetTileSourcesInTerrain, tileMapLayer.TileSet, type);
|
||||
}
|
||||
|
||||
public bool AddTilePeeringType(TileData tileData, TileSet.CellNeighbor peering, int type)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.AddTilePeeringType, tileMapLayer.TileSet, tileData, (int)peering, type);
|
||||
}
|
||||
|
||||
public bool RemoveTilePeeringType(TileData tileData, TileSet.CellNeighbor peering, int type)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.RemoveTilePeeringType, tileMapLayer.TileSet, tileData, (int)peering, type);
|
||||
}
|
||||
|
||||
public Array<TileSet.CellNeighbor> TilePeeringKeys(TileData tileData)
|
||||
{
|
||||
return (Array<TileSet.CellNeighbor>)betterTerrain.Call(MethodName.TilePeeringKeys, tileData);
|
||||
}
|
||||
|
||||
public Array<int> TilePeeringTypes(TileData tileData, TileSet.CellNeighbor peering)
|
||||
{
|
||||
return (Array<int>)betterTerrain.Call(MethodName.TilePeeringTypes, tileData, (int)peering);
|
||||
}
|
||||
|
||||
public Array<TileSet.CellNeighbor> TilePeeringForType(TileData tileData, int type)
|
||||
{
|
||||
return (Array<TileSet.CellNeighbor>)betterTerrain.Call(MethodName.TilePeeringForType, tileData, type);
|
||||
}
|
||||
|
||||
public bool SetCell(Vector2I coordinate, int type)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.SetCell, tileMapLayer, coordinate, type);
|
||||
}
|
||||
|
||||
public bool SetCells(Array<Vector2I> coordinates, int type)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.SetCells, tileMapLayer, coordinates, type);
|
||||
}
|
||||
|
||||
public bool ReplaceCell(Vector2I coordinate, int type)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.ReplaceCell, tileMapLayer, coordinate, type);
|
||||
}
|
||||
|
||||
public bool ReplaceCells(Array<Vector2I> coordinates, int type)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.ReplaceCells, tileMapLayer, coordinates, type);
|
||||
}
|
||||
|
||||
public int GetCell(Vector2I coordinate)
|
||||
{
|
||||
return (int)betterTerrain.Call(MethodName.GetCell, tileMapLayer, coordinate);
|
||||
}
|
||||
|
||||
public void UpdateTerrainCells(Array<Vector2I> cells, bool updateSurroundingCells = true)
|
||||
{
|
||||
betterTerrain.Call(MethodName.UpdateTerrainCells, tileMapLayer, cells, updateSurroundingCells);
|
||||
}
|
||||
|
||||
public void UpdateTerrainCell(Vector2I cell, bool updateSurroundingCells = true)
|
||||
{
|
||||
betterTerrain.Call(MethodName.UpdateTerrainCell, tileMapLayer, cell, updateSurroundingCells);
|
||||
}
|
||||
|
||||
public void UpdateTerrainArea(Rect2I area, bool updateSurroundingCells = true)
|
||||
{
|
||||
betterTerrain.Call(MethodName.UpdateTerrainArea, tileMapLayer, area, updateSurroundingCells);
|
||||
}
|
||||
|
||||
public Godot.Collections.Dictionary<Variant, Variant> CreateTerrainChangeset(Godot.Collections.Dictionary<Vector2I, int> paint)
|
||||
{
|
||||
return (Godot.Collections.Dictionary<Variant, Variant>)betterTerrain.Call(MethodName.CreateTerrainChangeset, tileMapLayer, paint);
|
||||
}
|
||||
|
||||
public bool IsTerrainChangesetReady(Godot.Collections.Dictionary<Variant, Variant> changeset)
|
||||
{
|
||||
return (bool)betterTerrain.Call(MethodName.IsTerrainChangesetReady, changeset);
|
||||
}
|
||||
|
||||
public void WaitForTerrainChangeset(Godot.Collections.Dictionary<Variant, Variant> changeset)
|
||||
{
|
||||
betterTerrain.Call(MethodName.WaitForTerrainChangeset, changeset);
|
||||
}
|
||||
|
||||
public void ApplyTerrainChangeset(Godot.Collections.Dictionary<Variant, Variant> changeset)
|
||||
{
|
||||
betterTerrain.Call(MethodName.ApplyTerrainChangeset, changeset);
|
||||
}
|
||||
|
||||
private static class MethodName
|
||||
{
|
||||
public static readonly StringName GetTerrainCategories = "get_terrain_categories";
|
||||
public static readonly StringName AddTerrain = "add_terrain";
|
||||
public static readonly StringName RemoveTerrain = "remove_terrain";
|
||||
public static readonly StringName TerrainCount = "terrain_count";
|
||||
public static readonly StringName GetTerrain = "get_terrain";
|
||||
public static readonly StringName SetTerrain = "set_terrain";
|
||||
public static readonly StringName SwapTerrains = "swap_terrains";
|
||||
public static readonly StringName SetTileTerrainType = "set_tile_terrain_type";
|
||||
public static readonly StringName GetTileTerrainType = "get_tile_terrain_type";
|
||||
public static readonly StringName SetTileSymmetryType = "set_tile_symmetry_type";
|
||||
public static readonly StringName GetTileSymmetryType = "get_tile_symmetry_type";
|
||||
public static readonly StringName GetTilesInTerrain = "get_tiles_in_terrain";
|
||||
public static readonly StringName GetTileSourcesInTerrain = "get_tile_sources_in_terrain";
|
||||
public static readonly StringName AddTilePeeringType = "add_tile_peering_type";
|
||||
public static readonly StringName RemoveTilePeeringType = "remove_tile_peering_type";
|
||||
public static readonly StringName TilePeeringKeys = "tile_peering_keys";
|
||||
public static readonly StringName TilePeeringTypes = "tile_peering_types";
|
||||
public static readonly StringName TilePeeringForType = "tile_peering_for_type";
|
||||
public static readonly StringName SetCell = "set_cell";
|
||||
public static readonly StringName SetCells = "set_cells";
|
||||
public static readonly StringName ReplaceCell = "replace_cell";
|
||||
public static readonly StringName ReplaceCells = "replace_cells";
|
||||
public static readonly StringName GetCell = "get_cell";
|
||||
public static readonly StringName UpdateTerrainCells = "update_terrain_cells";
|
||||
public static readonly StringName UpdateTerrainCell = "update_terrain_cell";
|
||||
public static readonly StringName UpdateTerrainArea = "update_terrain_area";
|
||||
public static readonly StringName CreateTerrainChangeset = "create_terrain_changeset";
|
||||
public static readonly StringName IsTerrainChangesetReady = "is_terrain_changeset_ready";
|
||||
public static readonly StringName WaitForTerrainChangeset = "wait_for_terrain_changeset";
|
||||
public static readonly StringName ApplyTerrainChangeset = "apply_terrain_changeset";
|
||||
}
|
||||
}
|
1159
addons/better-terrain/BetterTerrain.gd
Normal file
1
addons/better-terrain/BetterTerrain.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://b5wvl76qnagcj
|
598
addons/better-terrain/BetterTerrainData.gd
Executable file
@@ -0,0 +1,598 @@
|
||||
@tool
|
||||
|
||||
## Data functions for [TileSet] properties.
|
||||
##
|
||||
## This data class has functions for retrieving data regarding the mathematical
|
||||
## properties of a tile set.
|
||||
|
||||
const _terrain_peering_square_tiles : Array[int] = [0, 3, 4, 7, 8, 11, 12, 15]
|
||||
const _terrain_peering_square_vertices : Array[int] = [3, 7, 11, 15]
|
||||
const _terrain_peering_isometric_tiles : Array[int] = [1, 2, 5, 6, 9, 10, 13, 14]
|
||||
const _terrain_peering_isometric_vertices : Array[int] = [1, 5, 9, 13]
|
||||
const _terrain_peering_horiztonal_tiles : Array[int] = [0, 2, 6, 8, 10, 14]
|
||||
const _terrain_peering_horiztonal_vertices : Array[int] = [3, 5, 7, 11, 13, 15]
|
||||
const _terrain_peering_vertical_tiles : Array[int] = [2, 4, 6, 10, 12, 14]
|
||||
const _terrain_peering_vertical_vertices : Array[int] = [1, 3, 7, 9, 11, 15]
|
||||
const _terrain_peering_non_modifying : Array[int] = []
|
||||
|
||||
const _terrain_peering_hflip : Array[int] = [8, 9, 6, 7, 4, 5, 2, 3, 0, 1, 14, 15, 12, 13, 10, 11]
|
||||
const _terrain_peering_vflip : Array[int] = [0, 1, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3]
|
||||
const _terrain_peering_transpose : Array[int] = [4, 5, 2, 3, 0, 1, 14, 15, 12, 13, 10, 11, 8, 9, 6, 7]
|
||||
|
||||
const symmetry_mapping := {
|
||||
BetterTerrain.SymmetryType.NONE: [0],
|
||||
BetterTerrain.SymmetryType.MIRROR: [0, TileSetAtlasSource.TRANSFORM_FLIP_H],
|
||||
BetterTerrain.SymmetryType.FLIP: [0, TileSetAtlasSource.TRANSFORM_FLIP_V],
|
||||
BetterTerrain.SymmetryType.REFLECT: [
|
||||
0,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_H,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_V,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V
|
||||
],
|
||||
BetterTerrain.SymmetryType.ROTATE_CLOCKWISE: [0, TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_TRANSPOSE],
|
||||
BetterTerrain.SymmetryType.ROTATE_COUNTER_CLOCKWISE: [0, TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE],
|
||||
BetterTerrain.SymmetryType.ROTATE_180: [0, TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V],
|
||||
BetterTerrain.SymmetryType.ROTATE_ALL: [
|
||||
0,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_TRANSPOSE,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE
|
||||
],
|
||||
BetterTerrain.SymmetryType.ALL: [
|
||||
0,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_H,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_V,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V,
|
||||
TileSetAtlasSource.TRANSFORM_TRANSPOSE,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_TRANSPOSE,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE,
|
||||
TileSetAtlasSource.TRANSFORM_FLIP_H | TileSetAtlasSource.TRANSFORM_FLIP_V | TileSetAtlasSource.TRANSFORM_TRANSPOSE
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
## Returns an [Array] of ints of type [enum TileSet.CellNeighbor] which represent
|
||||
## the valid neighboring tiles for a terrain of [code]type[/code] in TileSet
|
||||
static func get_terrain_peering_cells(ts: TileSet, type: int) -> Array[int]:
|
||||
if !ts or type < 0 or type >= BetterTerrain.TerrainType.MAX:
|
||||
return []
|
||||
|
||||
if type == BetterTerrain.TerrainType.CATEGORY:
|
||||
return _terrain_peering_non_modifying
|
||||
if type == BetterTerrain.TerrainType.DECORATION:
|
||||
type = BetterTerrain.TerrainType.MATCH_TILES
|
||||
|
||||
match [ts.tile_shape, type]:
|
||||
[TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_TILES]:
|
||||
return _terrain_peering_square_tiles
|
||||
[TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_VERTICES]:
|
||||
return _terrain_peering_square_vertices
|
||||
[TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_TILES]:
|
||||
return _terrain_peering_isometric_tiles
|
||||
[TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_VERTICES]:
|
||||
return _terrain_peering_isometric_vertices
|
||||
|
||||
match [ts.tile_offset_axis, type]:
|
||||
[TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_TILES]:
|
||||
return _terrain_peering_vertical_tiles
|
||||
[TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_VERTICES]:
|
||||
return _terrain_peering_vertical_vertices
|
||||
[TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_TILES]:
|
||||
return _terrain_peering_horiztonal_tiles
|
||||
[TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_VERTICES]:
|
||||
return _terrain_peering_horiztonal_vertices
|
||||
|
||||
return []
|
||||
|
||||
|
||||
## Returns true if [code]peering[/code] is a valid neighboring cell for a terrain of
|
||||
## [code]type[/code] in [TileSet]
|
||||
static func is_terrain_peering_cell(ts: TileSet, type: int, peering: int) -> bool:
|
||||
return peering in get_terrain_peering_cells(ts, type)
|
||||
|
||||
|
||||
static func _peering_polygon_square_tiles(peering: int) -> PackedVector2Array:
|
||||
const t := 1.0 / 3.0
|
||||
var result : PackedVector2Array
|
||||
match peering:
|
||||
TileSet.CELL_NEIGHBOR_RIGHT_SIDE: result.append(Vector2(2*t, t))
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: result.append(Vector2(2*t, 2*t))
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_SIDE: result.append(Vector2(t, 2*t))
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: result.append(Vector2(0, 2*t))
|
||||
TileSet.CELL_NEIGHBOR_LEFT_SIDE: result.append(Vector2(0, t))
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER: result.append(Vector2(0, 0))
|
||||
TileSet.CELL_NEIGHBOR_TOP_SIDE: result.append(Vector2(t, 0))
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER: result.append(Vector2(2*t, 0))
|
||||
-1: result.append(Vector2(t, t))
|
||||
result.append(result[0] + Vector2(t, 0))
|
||||
result.append(result[0] + Vector2(t, t))
|
||||
result.append(result[0] + Vector2(0, t))
|
||||
return result
|
||||
|
||||
|
||||
static func _peering_polygon_square_vertices(peering: int) -> PackedVector2Array:
|
||||
const t := 1.0 / 2.0
|
||||
var result : PackedVector2Array
|
||||
match peering:
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
|
||||
result.append(Vector2(1, t))
|
||||
result.append(Vector2(1, 1))
|
||||
result.append(Vector2(t, 1))
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
|
||||
result.append(Vector2(0, t))
|
||||
result.append(Vector2(t, 1))
|
||||
result.append(Vector2(0, 1))
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER:
|
||||
result.append(Vector2(0, 0))
|
||||
result.append(Vector2(t, 0))
|
||||
result.append(Vector2(0, t))
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER:
|
||||
result.append(Vector2(t, 0))
|
||||
result.append(Vector2(1, 0))
|
||||
result.append(Vector2(1, t))
|
||||
-1:
|
||||
result.append(Vector2(t, 0))
|
||||
result.append(Vector2(1, t))
|
||||
result.append(Vector2(t, 1))
|
||||
result.append(Vector2(0, t))
|
||||
return result
|
||||
|
||||
|
||||
static func _peering_polygon_isometric_tiles(peering: int) -> PackedVector2Array:
|
||||
const t := 1.0 / 4.0
|
||||
match peering:
|
||||
-1: return PackedVector2Array([Vector2(2 * t, t), Vector2(3 * t, 2 * t), Vector2(2 * t, 3 * t), Vector2(t, 2 * t)])
|
||||
TileSet.CELL_NEIGHBOR_RIGHT_CORNER:
|
||||
return PackedVector2Array([Vector2(3 * t, 2 * t), Vector2(1, t), Vector2(1, 3 * t)])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
|
||||
return PackedVector2Array([Vector2(3 * t, 2 * t), Vector2(1, 3 * t), Vector2(3 * t, 1), Vector2(2 * t, 3 * t)])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_CORNER:
|
||||
return PackedVector2Array([Vector2(2 * t, 3 * t), Vector2(3 * t, 1), Vector2(t, 1)])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
|
||||
return PackedVector2Array([Vector2(t, 2 * t), Vector2(2 * t, 3 * t), Vector2(t, 1), Vector2(0, 3 * t)])
|
||||
TileSet.CELL_NEIGHBOR_LEFT_CORNER:
|
||||
return PackedVector2Array([Vector2(0, t), Vector2(t, 2 * t), Vector2(0, 3 * t)])
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE:
|
||||
return PackedVector2Array([Vector2(t, 0), Vector2(2 * t, t), Vector2(t, 2 * t), Vector2(0, t)])
|
||||
TileSet.CELL_NEIGHBOR_TOP_CORNER:
|
||||
return PackedVector2Array([Vector2(t, 0), Vector2(3 * t, 0), Vector2(2 * t, t)])
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE:
|
||||
return PackedVector2Array([Vector2(3 * t, 0), Vector2(1, t), Vector2(3 * t, 2 * t), Vector2(2 * t, t)])
|
||||
return PackedVector2Array()
|
||||
|
||||
|
||||
static func _peering_polygon_isometric_vertices(peering: int) -> PackedVector2Array:
|
||||
const t := 1.0 / 4.0
|
||||
const ttt := 3.0 * t
|
||||
match peering:
|
||||
-1: return PackedVector2Array([Vector2(t, t), Vector2(ttt, t), Vector2(ttt, ttt), Vector2(t, ttt)])
|
||||
TileSet.CELL_NEIGHBOR_RIGHT_CORNER:
|
||||
return PackedVector2Array([Vector2(ttt, t), Vector2(1, 0), Vector2(1, 1), Vector2(ttt, ttt)])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_CORNER:
|
||||
return PackedVector2Array([Vector2(t, ttt), Vector2(ttt, ttt), Vector2(1, 1), Vector2(0, 1)])
|
||||
TileSet.CELL_NEIGHBOR_LEFT_CORNER:
|
||||
return PackedVector2Array([Vector2(0, 0), Vector2(t, t), Vector2(t, ttt), Vector2(0, 1)])
|
||||
TileSet.CELL_NEIGHBOR_TOP_CORNER:
|
||||
return PackedVector2Array([Vector2(0, 0), Vector2(1, 0), Vector2(ttt, t), Vector2(t, t)])
|
||||
return PackedVector2Array()
|
||||
|
||||
|
||||
static func _peering_polygon_horizontal_tiles(peering: int) -> PackedVector2Array:
|
||||
const e := 1.0 / (2.0 * sqrt(3.0))
|
||||
const w := sqrt(3.0) / 8.0
|
||||
const t := 1.0 / 2.0
|
||||
const s := 1.0 / 8.0
|
||||
match peering:
|
||||
-1:
|
||||
return PackedVector2Array([
|
||||
Vector2(t, 2 * s),
|
||||
Vector2(t + w, t - s),
|
||||
Vector2(t + w, t + s),
|
||||
Vector2(t, 6 * s),
|
||||
Vector2(t - w, t + s),
|
||||
Vector2(t - w, t - s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_RIGHT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t + w, t - s),
|
||||
Vector2(1, t - e),
|
||||
Vector2(1, t + e),
|
||||
Vector2(t + w, t + s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t + w, t + s),
|
||||
Vector2(1, t + e),
|
||||
Vector2(t, 1),
|
||||
Vector2(t, 6 * s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t, 6 * s),
|
||||
Vector2(t, 1),
|
||||
Vector2(0, t + e),
|
||||
Vector2(t - w, t + s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_LEFT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - w, t + s),
|
||||
Vector2(0, t + e),
|
||||
Vector2(0, t - e),
|
||||
Vector2(t - w, t - s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - w, t - s),
|
||||
Vector2(0, t - e),
|
||||
Vector2(t, 0),
|
||||
Vector2(t, 2 * s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t, 2 * s),
|
||||
Vector2(t, 0),
|
||||
Vector2(1, t - e),
|
||||
Vector2(t + w, t - s)
|
||||
])
|
||||
return PackedVector2Array()
|
||||
|
||||
|
||||
static func _peering_polygon_horizontal_vertices(peering: int) -> PackedVector2Array:
|
||||
const e := 1.0 / (2.0 * sqrt(3.0))
|
||||
const w := sqrt(3.0) / 8.0
|
||||
const t := 1.0 / 2.0
|
||||
const s := 1.0 / 8.0
|
||||
match peering:
|
||||
-1:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - s, t - w),
|
||||
Vector2(t + s, t - w),
|
||||
Vector2(6 * s, t),
|
||||
Vector2(t + s, t + w),
|
||||
Vector2(t - s, t + w),
|
||||
Vector2(2 * s, t)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(6 * s, t),
|
||||
Vector2(1, t),
|
||||
Vector2(1, t + e),
|
||||
Vector2(t + e, 1 - s),
|
||||
Vector2(t + s, t + w)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - s, t + w),
|
||||
Vector2(t + s, t + w),
|
||||
Vector2(t + e, 1 - s),
|
||||
Vector2(t, 1),
|
||||
Vector2(t - e, 1 - s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(0, t),
|
||||
Vector2(2 * s, t),
|
||||
Vector2(t - s, t + w),
|
||||
Vector2(t - e, 1 - s),
|
||||
Vector2(0, t + e)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - e, s),
|
||||
Vector2(t - s, t - w),
|
||||
Vector2(2 * s, t),
|
||||
Vector2(0, t),
|
||||
Vector2(0, t - e)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(t, 0),
|
||||
Vector2(t + e, s),
|
||||
Vector2(t + s, t - w),
|
||||
Vector2(t - s, t - w),
|
||||
Vector2(t - e, s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(t + e, s),
|
||||
Vector2(1, t - e),
|
||||
Vector2(1, t),
|
||||
Vector2(6 * s, t),
|
||||
Vector2(t + s, t - w)
|
||||
])
|
||||
return PackedVector2Array()
|
||||
|
||||
|
||||
static func _peering_polygon_vertical_tiles(peering: int) -> PackedVector2Array:
|
||||
const e := 1.0 / (2.0 * sqrt(3.0))
|
||||
const w := sqrt(3.0) / 8.0
|
||||
const t := 1.0 / 2.0
|
||||
const s := 1.0 / 8.0
|
||||
match peering:
|
||||
-1:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - s, t - w),
|
||||
Vector2(t + s, t - w),
|
||||
Vector2(6 * s, t),
|
||||
Vector2(t + s, t + w),
|
||||
Vector2(t - s, t + w),
|
||||
Vector2(2 * s, t)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(6 * s, t),
|
||||
Vector2(1, t),
|
||||
Vector2(t + e, 1),
|
||||
Vector2(t + s, t + w)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - s, t + w),
|
||||
Vector2(t + s, t + w),
|
||||
Vector2(t + e, 1),
|
||||
Vector2(t - e, 1)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(0, t),
|
||||
Vector2(2 * s, t),
|
||||
Vector2(t - s, t + w),
|
||||
Vector2(t - e, 1)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - e, 0),
|
||||
Vector2(t - s, t - w),
|
||||
Vector2(2 * s, t),
|
||||
Vector2(0, t)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - e, 0),
|
||||
Vector2(t + e, 0),
|
||||
Vector2(t + s, t - w),
|
||||
Vector2(t - s, t - w)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_SIDE:
|
||||
return PackedVector2Array([
|
||||
Vector2(t + e, 0),
|
||||
Vector2(1, t),
|
||||
Vector2(6 * s, t),
|
||||
Vector2(t + s, t - w)
|
||||
])
|
||||
return PackedVector2Array()
|
||||
|
||||
|
||||
static func _peering_polygon_vertical_vertices(peering: int) -> PackedVector2Array:
|
||||
const e := 1.0 / (2.0 * sqrt(3.0))
|
||||
const w := sqrt(3.0) / 8.0
|
||||
const t := 1.0 / 2.0
|
||||
const s := 1.0 / 8.0
|
||||
match peering:
|
||||
-1:
|
||||
return PackedVector2Array([
|
||||
Vector2(t, 2 * s),
|
||||
Vector2(t + w, t - s),
|
||||
Vector2(t + w, t + s),
|
||||
Vector2(t, 6 * s),
|
||||
Vector2(t - w, t + s),
|
||||
Vector2(t - w, t - s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_RIGHT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(1 - s, t - e),
|
||||
Vector2(1, t),
|
||||
Vector2(1 - s, t + e),
|
||||
Vector2(t + w, t + s),
|
||||
Vector2(t + w, t - s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(t + w, t + s),
|
||||
Vector2(1 - s, t + e),
|
||||
Vector2(t + e, 1),
|
||||
Vector2(t, 1),
|
||||
Vector2(t, 6 * s)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - w, t + s),
|
||||
Vector2(t, 6 * s),
|
||||
Vector2(t, 1),
|
||||
Vector2(t - e, 1),
|
||||
Vector2(s, t + e)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_LEFT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(s, t - e),
|
||||
Vector2(t - w, t - s),
|
||||
Vector2(t - w, t + s),
|
||||
Vector2(s, t + e),
|
||||
Vector2(0, t)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(t - e, 0),
|
||||
Vector2(t, 0),
|
||||
Vector2(t, 2 * s),
|
||||
Vector2(t - w, t - s),
|
||||
Vector2(s, t - e)
|
||||
])
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER:
|
||||
return PackedVector2Array([
|
||||
Vector2(t, 0),
|
||||
Vector2(t + e, 0),
|
||||
Vector2(1 - s, t - e),
|
||||
Vector2(t + w, t - s),
|
||||
Vector2(t, 2 * s)
|
||||
])
|
||||
return PackedVector2Array()
|
||||
|
||||
|
||||
static func _peering_non_modifying() -> PackedVector2Array:
|
||||
const t := 1.0 / 3.0
|
||||
return PackedVector2Array([
|
||||
Vector2(t, 0),
|
||||
Vector2(2 * t, 0),
|
||||
Vector2(1, t),
|
||||
Vector2(1, 2 * t),
|
||||
Vector2(2 * t, 1),
|
||||
Vector2(t, 1),
|
||||
Vector2(0, 2 * t),
|
||||
Vector2(0, t)
|
||||
])
|
||||
|
||||
|
||||
## Returns a parameterized polygon (coordinated are between 0 and 1) for [code]peering[/code]
|
||||
## direction for a terrain of [code]type[/code] in [TileSet]
|
||||
static func peering_polygon(ts: TileSet, type: int, peering: int) -> PackedVector2Array:
|
||||
if type == BetterTerrain.TerrainType.CATEGORY:
|
||||
return _peering_non_modifying()
|
||||
if type == BetterTerrain.TerrainType.DECORATION:
|
||||
type = BetterTerrain.TerrainType.MATCH_TILES
|
||||
|
||||
match [ts.tile_shape, type]:
|
||||
[TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_TILES]:
|
||||
return _peering_polygon_square_tiles(peering)
|
||||
[TileSet.TILE_SHAPE_SQUARE, BetterTerrain.TerrainType.MATCH_VERTICES]:
|
||||
return _peering_polygon_square_vertices(peering)
|
||||
[TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_TILES]:
|
||||
return _peering_polygon_isometric_tiles(peering)
|
||||
[TileSet.TILE_SHAPE_ISOMETRIC, BetterTerrain.TerrainType.MATCH_VERTICES]:
|
||||
return _peering_polygon_isometric_vertices(peering)
|
||||
|
||||
match [ts.tile_offset_axis, type]:
|
||||
[TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_TILES]:
|
||||
return _peering_polygon_vertical_tiles(peering)
|
||||
[TileSet.TILE_OFFSET_AXIS_VERTICAL, BetterTerrain.TerrainType.MATCH_VERTICES]:
|
||||
return _peering_polygon_vertical_vertices(peering)
|
||||
[TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_TILES]:
|
||||
return _peering_polygon_horizontal_tiles(peering)
|
||||
[TileSet.TILE_OFFSET_AXIS_HORIZONTAL, BetterTerrain.TerrainType.MATCH_VERTICES]:
|
||||
return _peering_polygon_horizontal_vertices(peering)
|
||||
|
||||
return PackedVector2Array()
|
||||
|
||||
|
||||
## Returns as polygon centered on 0, 0 which represents the shape of the cell of
|
||||
## a tile from [TileSet].
|
||||
static func cell_polygon(ts: TileSet) -> PackedVector2Array:
|
||||
const t := 1.0 / 2.0
|
||||
if ts.tile_shape in [TileSet.TILE_SHAPE_SQUARE, TileSet.TILE_SHAPE_HALF_OFFSET_SQUARE]:
|
||||
return PackedVector2Array([Vector2(-t, -t), Vector2(t, -t), Vector2(t, t), Vector2(-t, t)])
|
||||
if ts.tile_shape == TileSet.TILE_SHAPE_ISOMETRIC:
|
||||
return PackedVector2Array([Vector2(0, -t), Vector2(t, 0), Vector2(0, t), Vector2(-t, 0)])
|
||||
|
||||
const e := t - 1.0 / (2.0 * sqrt(3.0))
|
||||
if ts.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL:
|
||||
return PackedVector2Array([
|
||||
Vector2(0, -t),
|
||||
Vector2(t, -e),
|
||||
Vector2(t, e),
|
||||
Vector2(0, t),
|
||||
Vector2(-t, e),
|
||||
Vector2(-t, -e),
|
||||
])
|
||||
|
||||
return PackedVector2Array([
|
||||
Vector2(-t, 0),
|
||||
Vector2(-e, -t),
|
||||
Vector2(e, -t),
|
||||
Vector2(t, 0),
|
||||
Vector2(e, t),
|
||||
Vector2(-e, t),
|
||||
])
|
||||
|
||||
|
||||
## Returns an [Array] of coordinated that neighbor [code]coord[/code] based on [code]peering[/code]
|
||||
## [Array] of [enum TileSet.CellNeighbor] for a [TileSet].
|
||||
static func neighboring_coords(tm: TileMapLayer, coord: Vector2i, peerings: Array) -> Array:
|
||||
return peerings.map(func(p): return tm.get_neighbor_cell(coord, p))
|
||||
|
||||
|
||||
## Returns an [Array] of coordinates which neighbor the vertex describe by [code]corner[/code]
|
||||
## (which is of type [enum TileSet.CellNeighbor]) from [code]coord[/code] in [TileSet].
|
||||
static func associated_vertex_cells(tm: TileMapLayer, coord: Vector2i, corner: int) -> Array:
|
||||
# get array of associated peering bits
|
||||
if tm.tile_set.tile_shape in [TileSet.TILE_SHAPE_SQUARE, TileSet.TILE_SHAPE_ISOMETRIC]:
|
||||
match corner:
|
||||
# Square
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
|
||||
return neighboring_coords(tm, coord, [0, 3, 4])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
|
||||
return neighboring_coords(tm, coord, [4, 7, 8])
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER:
|
||||
return neighboring_coords(tm, coord, [8, 11, 12])
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER:
|
||||
return neighboring_coords(tm, coord, [12, 15, 0])
|
||||
# Isometric
|
||||
TileSet.CELL_NEIGHBOR_RIGHT_CORNER:
|
||||
return neighboring_coords(tm, coord, [14, 1, 2])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_CORNER:
|
||||
return neighboring_coords(tm, coord, [2, 5, 6])
|
||||
TileSet.CELL_NEIGHBOR_LEFT_CORNER:
|
||||
return neighboring_coords(tm, coord, [6, 9, 10])
|
||||
TileSet.CELL_NEIGHBOR_TOP_CORNER:
|
||||
return neighboring_coords(tm, coord, [10, 13, 14])
|
||||
|
||||
if tm.tile_set.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL:
|
||||
match corner:
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
|
||||
return neighboring_coords(tm, coord, [0, 2])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_CORNER:
|
||||
return neighboring_coords(tm, coord, [2, 6])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
|
||||
return neighboring_coords(tm, coord, [6, 8])
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER:
|
||||
return neighboring_coords(tm, coord, [8, 10])
|
||||
TileSet.CELL_NEIGHBOR_TOP_CORNER:
|
||||
return neighboring_coords(tm, coord, [10, 14])
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER:
|
||||
return neighboring_coords(tm, coord, [14, 0])
|
||||
|
||||
# TileSet.TILE_OFFSET_AXIS_VERTICAL
|
||||
match corner:
|
||||
TileSet.CELL_NEIGHBOR_RIGHT_CORNER:
|
||||
return neighboring_coords(tm, coord, [14, 2])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER:
|
||||
return neighboring_coords(tm, coord, [2, 4])
|
||||
TileSet.CELL_NEIGHBOR_BOTTOM_LEFT_CORNER:
|
||||
return neighboring_coords(tm, coord, [4, 6])
|
||||
TileSet.CELL_NEIGHBOR_LEFT_CORNER:
|
||||
return neighboring_coords(tm, coord, [6, 10])
|
||||
TileSet.CELL_NEIGHBOR_TOP_LEFT_CORNER:
|
||||
return neighboring_coords(tm, coord, [10, 12])
|
||||
TileSet.CELL_NEIGHBOR_TOP_RIGHT_CORNER:
|
||||
return neighboring_coords(tm, coord, [12, 14])
|
||||
|
||||
return []
|
||||
|
||||
|
||||
## Returns an [Array] of [enum TileSet.CellNeighbor] suitable for flood filling
|
||||
## an area in [TileSet].
|
||||
static func cells_adjacent_for_fill(ts: TileSet) -> Array[int]:
|
||||
if ts.tile_shape == TileSet.TILE_SHAPE_SQUARE:
|
||||
return [0, 4, 8, 12]
|
||||
if ts.tile_shape == TileSet.TILE_SHAPE_ISOMETRIC:
|
||||
return [2, 6, 10, 14]
|
||||
if ts.tile_offset_axis == TileSet.TILE_OFFSET_AXIS_HORIZONTAL:
|
||||
return _terrain_peering_horiztonal_tiles
|
||||
return _terrain_peering_vertical_tiles
|
||||
|
||||
|
||||
static func peering_bit_after_symmetry(bit: int, altflags: int) -> int:
|
||||
if altflags & TileSetAtlasSource.TRANSFORM_TRANSPOSE:
|
||||
bit = _terrain_peering_transpose[bit]
|
||||
if altflags & TileSetAtlasSource.TRANSFORM_FLIP_H:
|
||||
bit = _terrain_peering_hflip[bit]
|
||||
if altflags & TileSetAtlasSource.TRANSFORM_FLIP_V:
|
||||
bit = _terrain_peering_vflip[bit]
|
||||
return bit
|
||||
|
||||
|
||||
static func peering_bits_after_symmetry(dict: Dictionary, altflags: int) -> Dictionary:
|
||||
# rearrange dictionary keys based on altflags
|
||||
var result := {}
|
||||
for k in dict:
|
||||
result[peering_bit_after_symmetry(k, altflags)] = dict[k]
|
||||
return result
|
1
addons/better-terrain/BetterTerrainData.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://dns30obk1hpvd
|
72
addons/better-terrain/TerrainPlugin.gd
Executable file
@@ -0,0 +1,72 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const AUTOLOAD_NAME = "BetterTerrain"
|
||||
var dock : Control
|
||||
var button : Button
|
||||
|
||||
func _enter_tree() -> void:
|
||||
# Wait for autoloads to register
|
||||
await get_tree().process_frame
|
||||
|
||||
if !get_tree().root.get_node_or_null(^"BetterTerrain"):
|
||||
# Autoload wasn't present on plugin init, which means plugin won't have loaded correctly
|
||||
add_autoload_singleton(AUTOLOAD_NAME, "res://addons/better-terrain/BetterTerrain.gd")
|
||||
ProjectSettings.save()
|
||||
|
||||
var confirm = ConfirmationDialog.new()
|
||||
confirm.dialog_text = "The editor needs to be restarted for Better Terrain to load correctly. Restart now? Note: Unsaved changes will be lost."
|
||||
confirm.confirmed.connect(func():
|
||||
OS.set_restart_on_exit(true, ["-e"])
|
||||
get_tree().quit()
|
||||
)
|
||||
get_editor_interface().popup_dialog_centered(confirm)
|
||||
|
||||
dock = load("res://addons/better-terrain/editor/Dock.tscn").instantiate()
|
||||
dock.update_overlay.connect(self.update_overlays)
|
||||
get_editor_interface().get_editor_main_screen().mouse_exited.connect(dock.canvas_mouse_exit)
|
||||
dock.undo_manager = get_undo_redo()
|
||||
button = add_control_to_bottom_panel(dock, "Terrain")
|
||||
button.toggled.connect(dock.about_to_be_visible)
|
||||
dock.force_show_terrains.connect(button.toggled.emit.bind(true))
|
||||
button.visible = false
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
remove_control_from_bottom_panel(dock)
|
||||
dock.queue_free()
|
||||
|
||||
|
||||
func _handles(object) -> bool:
|
||||
return object is TileMapLayer or object is TileSet
|
||||
|
||||
|
||||
func _make_visible(visible) -> void:
|
||||
button.visible = visible
|
||||
|
||||
|
||||
func _edit(object) -> void:
|
||||
var new_tileset : TileSet = null
|
||||
|
||||
if object is TileMapLayer:
|
||||
dock.tilemap = object
|
||||
new_tileset = object.tile_set
|
||||
if object is TileSet:
|
||||
new_tileset = object
|
||||
|
||||
if dock.tileset != new_tileset:
|
||||
dock.tiles_about_to_change()
|
||||
dock.tileset = new_tileset
|
||||
dock.tiles_changed()
|
||||
|
||||
|
||||
func _forward_canvas_draw_over_viewport(overlay: Control) -> void:
|
||||
if dock.visible:
|
||||
dock.canvas_draw(overlay)
|
||||
|
||||
|
||||
func _forward_canvas_gui_input(event: InputEvent) -> bool:
|
||||
if !dock.visible:
|
||||
return false
|
||||
|
||||
return dock.canvas_input(event)
|
1
addons/better-terrain/TerrainPlugin.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://bj2cm2q4tdgno
|
20
addons/better-terrain/Watcher.gd
Executable file
@@ -0,0 +1,20 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
signal trigger
|
||||
var complete := false
|
||||
var tileset : TileSet
|
||||
|
||||
func tidy() -> bool:
|
||||
if complete:
|
||||
return false
|
||||
|
||||
complete = true
|
||||
queue_free()
|
||||
return true
|
||||
|
||||
|
||||
func activate():
|
||||
if tidy():
|
||||
trigger.emit()
|
||||
|
1
addons/better-terrain/Watcher.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://d3rqd2eagpoe0
|
939
addons/better-terrain/editor/Dock.gd
Executable file
@@ -0,0 +1,939 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
signal update_overlay
|
||||
signal force_show_terrains
|
||||
|
||||
# The maximum individual tiles the overlay will draw before shortcutting the display
|
||||
# To prevent editor lag when drawing large rectangles or filling large areas
|
||||
const MAX_CANVAS_RENDER_TILES = 1500
|
||||
const TERRAIN_PROPERTIES_SCENE := preload("res://addons/better-terrain/editor/TerrainProperties.tscn")
|
||||
const TERRAIN_ENTRY_SCENE := preload("res://addons/better-terrain/editor/TerrainEntry.tscn")
|
||||
const MIN_ZOOM_SETTING := "editor/better_terrain/min_zoom_amount"
|
||||
const MAX_ZOOM_SETTING := "editor/better_terrain/max_zoom_amount"
|
||||
|
||||
|
||||
# Buttons
|
||||
@onready var draw_button: Button = $VBox/Toolbar/Draw
|
||||
@onready var line_button: Button = $VBox/Toolbar/Line
|
||||
@onready var rectangle_button: Button = $VBox/Toolbar/Rectangle
|
||||
@onready var fill_button: Button = $VBox/Toolbar/Fill
|
||||
@onready var replace_button: Button = $VBox/Toolbar/Replace
|
||||
|
||||
@onready var paint_type: Button = $VBox/Toolbar/PaintType
|
||||
@onready var paint_terrain: Button = $VBox/Toolbar/PaintTerrain
|
||||
@onready var select_tiles: Button = $VBox/Toolbar/SelectTiles
|
||||
|
||||
@onready var paint_symmetry: Button = $VBox/Toolbar/PaintSymmetry
|
||||
@onready var symmetry_options: OptionButton = $VBox/Toolbar/SymmetryOptions
|
||||
|
||||
@onready var shuffle_random: Button = $VBox/Toolbar/ShuffleRandom
|
||||
@onready var zoom_slider_container: VBoxContainer = $VBox/Toolbar/ZoomContainer
|
||||
|
||||
@onready var source_selector: MenuBar = $VBox/Toolbar/Sources
|
||||
@onready var source_selector_popup: PopupMenu = $VBox/Toolbar/Sources/Sources
|
||||
|
||||
@onready var clean_button: Button = $VBox/Toolbar/Clean
|
||||
@onready var layer_up: Button = $VBox/Toolbar/LayerUp
|
||||
@onready var layer_down: Button = $VBox/Toolbar/LayerDown
|
||||
@onready var layer_highlight: Button = $VBox/Toolbar/LayerHighlight
|
||||
@onready var layer_grid: Button = $VBox/Toolbar/LayerGrid
|
||||
|
||||
@onready var grid_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/GridMode
|
||||
@onready var quick_mode_button: Button = $VBox/HSplit/Terrains/LowerToolbar/QuickMode
|
||||
|
||||
@onready var edit_tool_buttons: HBoxContainer = $VBox/HSplit/Terrains/LowerToolbar/EditTools
|
||||
@onready var add_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/AddTerrain
|
||||
@onready var edit_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/EditTerrain
|
||||
@onready var pick_icon_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon
|
||||
@onready var move_up_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveUp
|
||||
@onready var move_down_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveDown
|
||||
@onready var remove_terrain_button: Button = $VBox/HSplit/Terrains/LowerToolbar/EditTools/RemoveTerrain
|
||||
|
||||
@onready var scroll_container: ScrollContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer
|
||||
@onready var terrain_list: HFlowContainer = $VBox/HSplit/Terrains/Panel/ScrollContainer/TerrainList
|
||||
@onready var tile_view: Control = $VBox/HSplit/Panel/ScrollArea/TileView
|
||||
|
||||
|
||||
var selected_entry := -2
|
||||
|
||||
var tilemap : TileMapLayer
|
||||
var tileset : TileSet
|
||||
|
||||
var undo_manager : EditorUndoRedoManager
|
||||
var terrain_undo
|
||||
|
||||
var draw_overlay := false
|
||||
var initial_click : Vector2i
|
||||
var prev_position : Vector2i
|
||||
var current_position : Vector2i
|
||||
var tileset_dirty := false
|
||||
var zoom_slider : HSlider
|
||||
|
||||
enum PaintMode {
|
||||
NO_PAINT,
|
||||
PAINT,
|
||||
ERASE
|
||||
}
|
||||
|
||||
enum PaintAction {
|
||||
NO_ACTION,
|
||||
LINE,
|
||||
RECT
|
||||
}
|
||||
|
||||
enum SourceSelectors {
|
||||
ALL = 1000000,
|
||||
NONE = 1000001,
|
||||
}
|
||||
|
||||
var paint_mode := PaintMode.NO_PAINT
|
||||
|
||||
var paint_action := PaintAction.NO_ACTION
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
draw_button.icon = get_theme_icon("Edit", "EditorIcons")
|
||||
line_button.icon = get_theme_icon("Line", "EditorIcons")
|
||||
rectangle_button.icon = get_theme_icon("Rectangle", "EditorIcons")
|
||||
fill_button.icon = get_theme_icon("Bucket", "EditorIcons")
|
||||
select_tiles.icon = get_theme_icon("ToolSelect", "EditorIcons")
|
||||
add_terrain_button.icon = get_theme_icon("Add", "EditorIcons")
|
||||
edit_terrain_button.icon = get_theme_icon("Tools", "EditorIcons")
|
||||
pick_icon_button.icon = get_theme_icon("ColorPick", "EditorIcons")
|
||||
move_up_button.icon = get_theme_icon("ArrowUp", "EditorIcons")
|
||||
move_down_button.icon = get_theme_icon("ArrowDown", "EditorIcons")
|
||||
remove_terrain_button.icon = get_theme_icon("Remove", "EditorIcons")
|
||||
grid_mode_button.icon = get_theme_icon("FileThumbnail", "EditorIcons")
|
||||
quick_mode_button.icon = get_theme_icon("GuiVisibilityVisible", "EditorIcons")
|
||||
layer_up.icon = get_theme_icon("MoveUp", "EditorIcons")
|
||||
layer_down.icon = get_theme_icon("MoveDown", "EditorIcons")
|
||||
layer_highlight.icon = get_theme_icon("TileMapHighlightSelected", "EditorIcons")
|
||||
layer_grid.icon = get_theme_icon("Grid", "EditorIcons")
|
||||
|
||||
select_tiles.button_group.pressed.connect(_on_bit_button_pressed)
|
||||
|
||||
terrain_undo = load("res://addons/better-terrain/editor/TerrainUndo.gd").new()
|
||||
add_child(terrain_undo)
|
||||
tile_view.undo_manager = undo_manager
|
||||
tile_view.terrain_undo = terrain_undo
|
||||
|
||||
tile_view.paste_occurred.connect(_on_paste_occurred)
|
||||
tile_view.change_zoom_level.connect(_on_change_zoom_level)
|
||||
tile_view.terrain_updated.connect(_on_terrain_updated)
|
||||
|
||||
# Zoom slider is manipulated by settings, make it at runtime
|
||||
zoom_slider = HSlider.new()
|
||||
zoom_slider.custom_minimum_size = Vector2(100, 0)
|
||||
zoom_slider.value_changed.connect(tile_view._on_zoom_value_changed)
|
||||
zoom_slider_container.add_child(zoom_slider)
|
||||
|
||||
# Init settings if needed
|
||||
if !ProjectSettings.has_setting(MIN_ZOOM_SETTING):
|
||||
ProjectSettings.set(MIN_ZOOM_SETTING, 1.0)
|
||||
ProjectSettings.add_property_info({
|
||||
"name": MIN_ZOOM_SETTING,
|
||||
"type": TYPE_FLOAT,
|
||||
"hint": PROPERTY_HINT_RANGE,
|
||||
"hint_string": "0.1,1.0,0.1"
|
||||
})
|
||||
ProjectSettings.set_initial_value(MIN_ZOOM_SETTING, 1.0)
|
||||
ProjectSettings.set_as_basic(MIN_ZOOM_SETTING, true)
|
||||
|
||||
if !ProjectSettings.has_setting(MAX_ZOOM_SETTING):
|
||||
ProjectSettings.set(MAX_ZOOM_SETTING, 8.0)
|
||||
ProjectSettings.add_property_info({
|
||||
"name": MAX_ZOOM_SETTING,
|
||||
"type": TYPE_FLOAT,
|
||||
"hint": PROPERTY_HINT_RANGE,
|
||||
"hint_string": "2.0,32.0,1.0"
|
||||
})
|
||||
ProjectSettings.set_initial_value(MAX_ZOOM_SETTING, 8.0)
|
||||
ProjectSettings.set_as_basic(MAX_ZOOM_SETTING, true)
|
||||
ProjectSettings.set_order(MAX_ZOOM_SETTING, ProjectSettings.get_order(MIN_ZOOM_SETTING) + 1)
|
||||
|
||||
ProjectSettings.settings_changed.connect(_on_adjust_settings)
|
||||
_on_adjust_settings()
|
||||
zoom_slider.value = 1.0
|
||||
|
||||
|
||||
func _process(delta):
|
||||
scroll_container.scroll_horizontal = 0
|
||||
|
||||
|
||||
func _on_adjust_settings():
|
||||
zoom_slider.min_value = ProjectSettings.get_setting(MIN_ZOOM_SETTING, 1.0)
|
||||
zoom_slider.max_value = ProjectSettings.get_setting(MAX_ZOOM_SETTING, 8.0)
|
||||
zoom_slider.step = (zoom_slider.max_value - zoom_slider.min_value) / 100.0
|
||||
|
||||
|
||||
func _get_fill_cells(target: Vector2i) -> Array:
|
||||
var pick := BetterTerrain.get_cell(tilemap, target)
|
||||
var bounds := tilemap.get_used_rect()
|
||||
var neighbors = BetterTerrain.data.cells_adjacent_for_fill(tileset)
|
||||
|
||||
# No sets yet, so use a dictionary
|
||||
var checked := {}
|
||||
var pending := [target]
|
||||
var goal := []
|
||||
|
||||
while !pending.is_empty():
|
||||
var p = pending.pop_front()
|
||||
if checked.has(p):
|
||||
continue
|
||||
checked[p] = true
|
||||
if !bounds.has_point(p) or BetterTerrain.get_cell(tilemap, p) != pick:
|
||||
continue
|
||||
|
||||
goal.append(p)
|
||||
pending.append_array(BetterTerrain.data.neighboring_coords(tilemap, p, neighbors))
|
||||
|
||||
return goal
|
||||
|
||||
|
||||
func tiles_about_to_change() -> void:
|
||||
if tileset and tileset.changed.is_connected(queue_tiles_changed):
|
||||
tileset.changed.disconnect(queue_tiles_changed)
|
||||
|
||||
|
||||
func tiles_changed() -> void:
|
||||
# ensure up to date
|
||||
BetterTerrain._update_terrain_data(tileset)
|
||||
|
||||
# clear terrains
|
||||
for c in terrain_list.get_children():
|
||||
terrain_list.remove_child(c)
|
||||
c.queue_free()
|
||||
|
||||
# load terrains from tileset
|
||||
var terrain_count := BetterTerrain.terrain_count(tileset)
|
||||
var item_count = terrain_count + 1
|
||||
for i in terrain_count:
|
||||
var terrain := BetterTerrain.get_terrain(tileset, i)
|
||||
if i >= terrain_list.get_child_count():
|
||||
add_terrain_entry(terrain, i)
|
||||
|
||||
if item_count > terrain_list.get_child_count():
|
||||
var terrain := BetterTerrain.get_terrain(tileset, BetterTerrain.TileCategory.EMPTY)
|
||||
if terrain.valid:
|
||||
add_terrain_entry(terrain, item_count - 1)
|
||||
|
||||
while item_count < terrain_list.get_child_count():
|
||||
var child = terrain_list.get_child(terrain_list.get_child_count() - 1)
|
||||
terrain_list.remove_child(child)
|
||||
child.free()
|
||||
|
||||
source_selector_popup.clear()
|
||||
source_selector_popup.add_item("All", SourceSelectors.ALL)
|
||||
source_selector_popup.add_item("None", SourceSelectors.NONE)
|
||||
var source_count = tileset.get_source_count() if tileset else 0
|
||||
for s in source_count:
|
||||
var source_id = tileset.get_source_id(s)
|
||||
var source := tileset.get_source(source_id)
|
||||
if !(source is TileSetAtlasSource):
|
||||
continue
|
||||
|
||||
var name := source.resource_name
|
||||
if name.is_empty():
|
||||
var texture := (source as TileSetAtlasSource).texture
|
||||
var texture_name := texture.resource_name if texture else ""
|
||||
if !texture_name.is_empty():
|
||||
name = texture_name
|
||||
else:
|
||||
var texture_path := texture.resource_path if texture else ""
|
||||
if !texture_path.is_empty():
|
||||
name = texture_path.get_file()
|
||||
|
||||
if !name.is_empty():
|
||||
name += " "
|
||||
name += " (ID: %d)" % source_id
|
||||
|
||||
source_selector_popup.add_check_item(name, source_id)
|
||||
source_selector_popup.set_item_checked(source_selector_popup.get_item_index(source_id), true)
|
||||
source_selector.visible = source_selector_popup.item_count > 3 # All, None and more than one source
|
||||
|
||||
update_tile_view_paint()
|
||||
tile_view.refresh_tileset(tileset)
|
||||
|
||||
if tileset and !tileset.changed.is_connected(queue_tiles_changed):
|
||||
tileset.changed.connect(queue_tiles_changed)
|
||||
|
||||
clean_button.visible = BetterTerrain._has_invalid_peering_types(tileset)
|
||||
|
||||
tileset_dirty = false
|
||||
_on_grid_mode_pressed()
|
||||
_on_quick_mode_pressed()
|
||||
|
||||
|
||||
func about_to_be_visible(visible: bool) -> void:
|
||||
if !visible:
|
||||
return
|
||||
|
||||
if tileset != tilemap.tile_set:
|
||||
tiles_about_to_change()
|
||||
tileset = tilemap.tile_set
|
||||
tiles_changed()
|
||||
|
||||
var settings := EditorInterface.get_editor_settings()
|
||||
layer_highlight.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/highlight_selected_layer"))
|
||||
layer_grid.set_pressed_no_signal(settings.get_setting("editors/tiles_editor/display_grid"))
|
||||
|
||||
|
||||
func queue_tiles_changed() -> void:
|
||||
# Bring terrain data up to date with complex tileset changes
|
||||
if !tileset or tileset_dirty:
|
||||
return
|
||||
|
||||
tileset_dirty = true
|
||||
tiles_changed.call_deferred()
|
||||
|
||||
|
||||
func _on_entry_select(index:int):
|
||||
selected_entry = index
|
||||
if selected_entry >= BetterTerrain.terrain_count(tileset):
|
||||
selected_entry = BetterTerrain.TileCategory.EMPTY
|
||||
for i in range(terrain_list.get_child_count()):
|
||||
if i != index:
|
||||
terrain_list.get_child(i).set_selected(false)
|
||||
update_tile_view_paint()
|
||||
|
||||
|
||||
func _on_clean_pressed() -> void:
|
||||
var confirmed := [false]
|
||||
var popup := ConfirmationDialog.new()
|
||||
popup.dialog_text = tr("Tile set changes have caused terrain to become invalid. Remove invalid terrain data?")
|
||||
popup.dialog_hide_on_ok = false
|
||||
popup.confirmed.connect(func():
|
||||
confirmed[0] = true
|
||||
popup.hide()
|
||||
)
|
||||
EditorInterface.popup_dialog_centered(popup)
|
||||
await popup.visibility_changed
|
||||
popup.queue_free()
|
||||
|
||||
if confirmed[0]:
|
||||
undo_manager.create_action("Clean invalid terrain peering data", UndoRedo.MERGE_DISABLE, tileset)
|
||||
undo_manager.add_do_method(BetterTerrain, &"_clear_invalid_peering_types", tileset)
|
||||
undo_manager.add_do_method(self, &"tiles_changed")
|
||||
terrain_undo.create_peering_restore_point(undo_manager, tileset)
|
||||
undo_manager.add_undo_method(self, &"tiles_changed")
|
||||
undo_manager.commit_action()
|
||||
|
||||
|
||||
func _on_grid_mode_pressed() -> void:
|
||||
for c in terrain_list.get_children():
|
||||
c.grid_mode = grid_mode_button.button_pressed
|
||||
c.update_style()
|
||||
|
||||
|
||||
func _on_quick_mode_pressed() -> void:
|
||||
edit_tool_buttons.visible = !quick_mode_button.button_pressed
|
||||
for c in terrain_list.get_children():
|
||||
c.visible = !quick_mode_button.button_pressed or c.terrain.type in [BetterTerrain.TerrainType.MATCH_TILES, BetterTerrain.TerrainType.MATCH_VERTICES]
|
||||
|
||||
|
||||
func update_tile_view_paint() -> void:
|
||||
tile_view.paint = selected_entry
|
||||
tile_view.queue_redraw()
|
||||
|
||||
var editable = tile_view.paint != BetterTerrain.TileCategory.EMPTY
|
||||
edit_terrain_button.disabled = !editable
|
||||
move_up_button.disabled = !editable or tile_view.paint == 0
|
||||
move_down_button.disabled = !editable or tile_view.paint == BetterTerrain.terrain_count(tileset) - 1
|
||||
remove_terrain_button.disabled = !editable
|
||||
pick_icon_button.disabled = !editable
|
||||
|
||||
|
||||
func _on_add_terrain_pressed() -> void:
|
||||
if !tileset:
|
||||
return
|
||||
|
||||
var popup := TERRAIN_PROPERTIES_SCENE.instantiate()
|
||||
popup.set_category_data(BetterTerrain.get_terrain_categories(tileset))
|
||||
popup.terrain_name = "New terrain"
|
||||
popup.terrain_color = Color.from_hsv(randf(), 0.3 + 0.7 * randf(), 0.6 + 0.4 * randf())
|
||||
popup.terrain_icon = ""
|
||||
popup.terrain_type = 0
|
||||
EditorInterface.popup_dialog_centered(popup)
|
||||
await popup.visibility_changed
|
||||
if popup.accepted:
|
||||
undo_manager.create_action("Add terrain type", UndoRedo.MERGE_DISABLE, tileset)
|
||||
undo_manager.add_do_method(self, &"perform_add_terrain", popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon})
|
||||
undo_manager.add_undo_method(self, &"perform_remove_terrain", terrain_list.get_child_count() - 1)
|
||||
undo_manager.commit_action()
|
||||
popup.queue_free()
|
||||
|
||||
|
||||
func _on_edit_terrain_pressed() -> void:
|
||||
if !tileset:
|
||||
return
|
||||
|
||||
if selected_entry < 0:
|
||||
return
|
||||
|
||||
var t := BetterTerrain.get_terrain(tileset, selected_entry)
|
||||
var categories = BetterTerrain.get_terrain_categories(tileset)
|
||||
categories = categories.filter(func(x): return x.id != selected_entry)
|
||||
|
||||
var popup := TERRAIN_PROPERTIES_SCENE.instantiate()
|
||||
popup.set_category_data(categories)
|
||||
|
||||
t.icon = t.icon.duplicate()
|
||||
|
||||
popup.terrain_name = t.name
|
||||
popup.terrain_type = t.type
|
||||
popup.terrain_color = t.color
|
||||
if t.has("icon") and t.icon.has("path"):
|
||||
popup.terrain_icon = t.icon.path
|
||||
popup.terrain_categories = t.categories
|
||||
EditorInterface.popup_dialog_centered(popup)
|
||||
await popup.visibility_changed
|
||||
if popup.accepted:
|
||||
undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset)
|
||||
undo_manager.add_do_method(self, &"perform_edit_terrain", selected_entry, popup.terrain_name, popup.terrain_color, popup.terrain_type, popup.terrain_categories, {path = popup.terrain_icon})
|
||||
undo_manager.add_undo_method(self, &"perform_edit_terrain", selected_entry, t.name, t.color, t.type, t.categories, t.icon)
|
||||
if t.type != popup.terrain_type:
|
||||
terrain_undo.create_terrain_type_restore_point(undo_manager, tileset)
|
||||
terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry)
|
||||
undo_manager.commit_action()
|
||||
popup.queue_free()
|
||||
|
||||
|
||||
func _on_pick_icon_pressed():
|
||||
if selected_entry < 0:
|
||||
return
|
||||
tile_view.pick_icon_terrain = selected_entry
|
||||
|
||||
|
||||
func _on_pick_icon_focus_exited():
|
||||
tile_view.pick_icon_terrain_cancel = true
|
||||
pick_icon_button.button_pressed = false
|
||||
|
||||
|
||||
func _on_move_pressed(down: bool) -> void:
|
||||
if !tileset:
|
||||
return
|
||||
|
||||
if selected_entry < 0:
|
||||
return
|
||||
|
||||
var index1 = selected_entry
|
||||
var index2 = index1 + (1 if down else -1)
|
||||
if index2 < 0 or index2 >= terrain_list.get_child_count():
|
||||
return
|
||||
|
||||
undo_manager.create_action("Reorder terrains", UndoRedo.MERGE_DISABLE, tileset)
|
||||
undo_manager.add_do_method(self, &"perform_swap_terrain", index1, index2)
|
||||
undo_manager.add_undo_method(self, &"perform_swap_terrain", index1, index2)
|
||||
undo_manager.commit_action()
|
||||
|
||||
|
||||
func _on_remove_terrain_pressed() -> void:
|
||||
if !tileset:
|
||||
return
|
||||
|
||||
if selected_entry < 0:
|
||||
return
|
||||
|
||||
# store confirmation in array to pass by ref
|
||||
var t := BetterTerrain.get_terrain(tileset, selected_entry)
|
||||
var confirmed := [false]
|
||||
var popup := ConfirmationDialog.new()
|
||||
popup.dialog_text = tr("Are you sure you want to remove {0}?").format([t.name])
|
||||
popup.dialog_hide_on_ok = false
|
||||
popup.confirmed.connect(func():
|
||||
confirmed[0] = true
|
||||
popup.hide()
|
||||
)
|
||||
EditorInterface.popup_dialog_centered(popup)
|
||||
await popup.visibility_changed
|
||||
popup.queue_free()
|
||||
|
||||
if confirmed[0]:
|
||||
undo_manager.create_action("Remove terrain type", UndoRedo.MERGE_DISABLE, tileset)
|
||||
undo_manager.add_do_method(self, &"perform_remove_terrain", selected_entry)
|
||||
undo_manager.add_undo_method(self, &"perform_add_terrain", t.name, t.color, t.type, t.categories, t.icon)
|
||||
for n in range(terrain_list.get_child_count() - 2, selected_entry, -1):
|
||||
undo_manager.add_undo_method(self, &"perform_swap_terrain", n, n - 1)
|
||||
if t.type == BetterTerrain.TerrainType.CATEGORY:
|
||||
terrain_undo.create_terrain_type_restore_point(undo_manager, tileset)
|
||||
terrain_undo.create_peering_restore_point_specific(undo_manager, tileset, selected_entry)
|
||||
undo_manager.commit_action()
|
||||
|
||||
|
||||
func add_terrain_entry(terrain:Dictionary, index:int = -1):
|
||||
if index < 0:
|
||||
index = terrain_list.get_child_count()
|
||||
|
||||
var entry = TERRAIN_ENTRY_SCENE.instantiate()
|
||||
entry.tileset = tileset
|
||||
entry.terrain = terrain
|
||||
entry.grid_mode = grid_mode_button.button_pressed
|
||||
entry.select.connect(_on_entry_select)
|
||||
|
||||
terrain_list.add_child(entry)
|
||||
terrain_list.move_child(entry, index)
|
||||
|
||||
|
||||
func remove_terrain_entry(index: int):
|
||||
terrain_list.get_child(index).free()
|
||||
for i in range(index, terrain_list.get_child_count()):
|
||||
var child = terrain_list.get_child(i)
|
||||
child.terrain = BetterTerrain.get_terrain(tileset, i)
|
||||
child.update()
|
||||
|
||||
|
||||
func perform_add_terrain(name: String, color: Color, type: int, categories: Array, icon:Dictionary = {}) -> void:
|
||||
if BetterTerrain.add_terrain(tileset, name, color, type, categories, icon):
|
||||
var index = BetterTerrain.terrain_count(tileset) - 1
|
||||
var terrain = BetterTerrain.get_terrain(tileset, index)
|
||||
add_terrain_entry(terrain, index)
|
||||
|
||||
|
||||
func perform_remove_terrain(index: int) -> void:
|
||||
if index >= BetterTerrain.terrain_count(tileset):
|
||||
return
|
||||
if BetterTerrain.remove_terrain(tileset, index):
|
||||
remove_terrain_entry(index)
|
||||
update_tile_view_paint()
|
||||
|
||||
|
||||
func perform_swap_terrain(index1: int, index2: int) -> void:
|
||||
var lower := min(index1, index2)
|
||||
var higher := max(index1, index2)
|
||||
if lower >= terrain_list.get_child_count() or higher >= terrain_list.get_child_count():
|
||||
return
|
||||
var item1 = terrain_list.get_child(lower)
|
||||
var item2 = terrain_list.get_child(higher)
|
||||
if BetterTerrain.swap_terrains(tileset, lower, higher):
|
||||
terrain_list.move_child(item1, higher)
|
||||
item1.terrain = BetterTerrain.get_terrain(tileset, higher)
|
||||
item1.update()
|
||||
item2.terrain = BetterTerrain.get_terrain(tileset, lower)
|
||||
item2.update()
|
||||
selected_entry = index2
|
||||
terrain_list.get_child(index2).set_selected(true)
|
||||
update_tile_view_paint()
|
||||
|
||||
|
||||
func perform_edit_terrain(index: int, name: String, color: Color, type: int, categories: Array, icon: Dictionary = {}) -> void:
|
||||
if index >= terrain_list.get_child_count():
|
||||
return
|
||||
var entry = terrain_list.get_child(index)
|
||||
# don't overwrite empty icon
|
||||
var valid_icon = icon
|
||||
if icon.has("path") and icon.path.is_empty():
|
||||
var terrain = BetterTerrain.get_terrain(tileset, index)
|
||||
valid_icon = terrain.icon
|
||||
if BetterTerrain.set_terrain(tileset, index, name, color, type, categories, valid_icon):
|
||||
entry.terrain = BetterTerrain.get_terrain(tileset, index)
|
||||
entry.update()
|
||||
tile_view.queue_redraw()
|
||||
|
||||
|
||||
func _on_shuffle_random_pressed():
|
||||
BetterTerrain.use_seed = !shuffle_random.button_pressed
|
||||
|
||||
|
||||
func _on_bit_button_pressed(button: BaseButton) -> void:
|
||||
match select_tiles.button_group.get_pressed_button():
|
||||
select_tiles: tile_view.paint_mode = tile_view.PaintMode.SELECT
|
||||
paint_type: tile_view.paint_mode = tile_view.PaintMode.PAINT_TYPE
|
||||
paint_terrain: tile_view.paint_mode = tile_view.PaintMode.PAINT_PEERING
|
||||
paint_symmetry: tile_view.paint_mode = tile_view.PaintMode.PAINT_SYMMETRY
|
||||
_: tile_view.paint_mode = tile_view.PaintMode.NO_PAINT
|
||||
tile_view.queue_redraw()
|
||||
|
||||
symmetry_options.visible = paint_symmetry.button_pressed
|
||||
|
||||
|
||||
func _on_symmetry_selected(index):
|
||||
tile_view.paint_symmetry = index
|
||||
|
||||
|
||||
func _on_paste_occurred():
|
||||
select_tiles.button_pressed = true
|
||||
|
||||
|
||||
func _on_change_zoom_level(value):
|
||||
zoom_slider.value = value
|
||||
|
||||
|
||||
func _on_terrain_updated(index):
|
||||
var entry = terrain_list.get_child(index)
|
||||
entry.terrain = BetterTerrain.get_terrain(tileset, index)
|
||||
entry.update()
|
||||
|
||||
|
||||
func canvas_tilemap_transform() -> Transform2D:
|
||||
var transform := tilemap.get_viewport_transform() * tilemap.global_transform
|
||||
|
||||
# Handle subviewport
|
||||
var editor_viewport := EditorInterface.get_editor_viewport_2d()
|
||||
if tilemap.get_viewport() != editor_viewport:
|
||||
var container = tilemap.get_viewport().get_parent() as SubViewportContainer
|
||||
if container:
|
||||
transform = editor_viewport.global_canvas_transform * container.get_transform() * transform
|
||||
|
||||
return transform
|
||||
|
||||
|
||||
func canvas_draw(overlay: Control) -> void:
|
||||
if !draw_overlay:
|
||||
return
|
||||
|
||||
if selected_entry < 0:
|
||||
return
|
||||
|
||||
var type = selected_entry
|
||||
var terrain := BetterTerrain.get_terrain(tileset, type)
|
||||
if !terrain.valid:
|
||||
return
|
||||
|
||||
var tiles := []
|
||||
var transform := canvas_tilemap_transform()
|
||||
|
||||
if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT:
|
||||
var area := Rect2i(initial_click, current_position - initial_click).abs()
|
||||
|
||||
# Shortcut fill for large areas
|
||||
if area.size.x > 1 and area.size.y > 1 and area.size.x * area.size.y > MAX_CANVAS_RENDER_TILES:
|
||||
var shortcut := PackedVector2Array([
|
||||
tilemap.map_to_local(area.position),
|
||||
tilemap.map_to_local(Vector2i(area.end.x, area.position.y)),
|
||||
tilemap.map_to_local(area.end),
|
||||
tilemap.map_to_local(Vector2i(area.position.x, area.end.y))
|
||||
])
|
||||
overlay.draw_colored_polygon(transform * shortcut, Color(terrain.color, 0.5))
|
||||
return
|
||||
|
||||
for y in range(area.position.y, area.end.y + 1):
|
||||
for x in range(area.position.x, area.end.x + 1):
|
||||
tiles.append(Vector2i(x, y))
|
||||
elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT:
|
||||
var cells := _get_tileset_line(initial_click, current_position, tileset)
|
||||
var shape = BetterTerrain.data.cell_polygon(tileset)
|
||||
for c in cells:
|
||||
var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(c))
|
||||
overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5))
|
||||
elif fill_button.button_pressed:
|
||||
tiles = _get_fill_cells(current_position)
|
||||
if tiles.size() > MAX_CANVAS_RENDER_TILES:
|
||||
tiles.resize(MAX_CANVAS_RENDER_TILES)
|
||||
else:
|
||||
tiles.append(current_position)
|
||||
|
||||
var shape = BetterTerrain.data.cell_polygon(tileset)
|
||||
for t in tiles:
|
||||
var tile_transform := Transform2D(0.0, tilemap.tile_set.tile_size, 0.0, tilemap.map_to_local(t))
|
||||
overlay.draw_colored_polygon(transform * tile_transform * shape, Color(terrain.color, 0.5))
|
||||
|
||||
|
||||
func canvas_input(event: InputEvent) -> bool:
|
||||
if selected_entry < 0:
|
||||
return false
|
||||
|
||||
draw_overlay = true
|
||||
if event is InputEventMouseMotion:
|
||||
var tr := canvas_tilemap_transform()
|
||||
var pos := tr.affine_inverse() * Vector2(event.position)
|
||||
var event_position := tilemap.local_to_map(pos)
|
||||
prev_position = current_position
|
||||
if event_position == current_position:
|
||||
return false
|
||||
current_position = event_position
|
||||
update_overlay.emit()
|
||||
|
||||
var replace_mode = replace_button.button_pressed
|
||||
|
||||
var released : bool = event is InputEventMouseButton and !event.pressed
|
||||
if released:
|
||||
terrain_undo.finish_action()
|
||||
var type = selected_entry
|
||||
if paint_action == PaintAction.RECT and paint_mode != PaintMode.NO_PAINT:
|
||||
var area := Rect2i(initial_click, current_position - initial_click).abs()
|
||||
# Fill from initial_target to target
|
||||
undo_manager.create_action(tr("Draw terrain rectangle"), UndoRedo.MERGE_DISABLE, tilemap)
|
||||
for y in range(area.position.y, area.end.y + 1):
|
||||
for x in range(area.position.x, area.end.x + 1):
|
||||
var coord := Vector2i(x, y)
|
||||
if paint_mode == PaintMode.PAINT:
|
||||
if replace_mode:
|
||||
undo_manager.add_do_method(BetterTerrain, &"replace_cell", tilemap, coord, type)
|
||||
else:
|
||||
undo_manager.add_do_method(BetterTerrain, &"set_cell", tilemap, coord, type)
|
||||
else:
|
||||
undo_manager.add_do_method(tilemap, &"erase_cell", coord)
|
||||
|
||||
undo_manager.add_do_method(BetterTerrain, &"update_terrain_area", tilemap, area)
|
||||
terrain_undo.create_tile_restore_point_area(undo_manager, tilemap, area)
|
||||
undo_manager.commit_action()
|
||||
update_overlay.emit()
|
||||
elif paint_action == PaintAction.LINE and paint_mode != PaintMode.NO_PAINT:
|
||||
undo_manager.create_action(tr("Draw terrain line"), UndoRedo.MERGE_DISABLE, tilemap)
|
||||
var cells := _get_tileset_line(initial_click, current_position, tileset)
|
||||
if paint_mode == PaintMode.PAINT:
|
||||
if replace_mode:
|
||||
undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type)
|
||||
else:
|
||||
undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type)
|
||||
elif paint_mode == PaintMode.ERASE:
|
||||
for c in cells:
|
||||
undo_manager.add_do_method(tilemap, &"erase_cell", c)
|
||||
undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells)
|
||||
terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
|
||||
undo_manager.commit_action()
|
||||
update_overlay.emit()
|
||||
|
||||
paint_mode = PaintMode.NO_PAINT
|
||||
return true
|
||||
|
||||
var clicked : bool = event is InputEventMouseButton and event.pressed
|
||||
if clicked:
|
||||
paint_mode = PaintMode.NO_PAINT
|
||||
|
||||
if (event.is_command_or_control_pressed() and !event.shift_pressed):
|
||||
var pick = BetterTerrain.get_cell(tilemap, current_position)
|
||||
if pick >= 0:
|
||||
terrain_list.get_children()[pick]._on_focus_entered()
|
||||
#_on_entry_select(pick)
|
||||
return true
|
||||
|
||||
paint_action = PaintAction.NO_ACTION
|
||||
if rectangle_button.button_pressed:
|
||||
paint_action = PaintAction.RECT
|
||||
elif line_button.button_pressed:
|
||||
paint_action = PaintAction.LINE
|
||||
elif draw_button.button_pressed:
|
||||
if event.shift_pressed:
|
||||
paint_action = PaintAction.LINE
|
||||
if event.is_command_or_control_pressed():
|
||||
paint_action = PaintAction.RECT
|
||||
|
||||
if event.button_index == MOUSE_BUTTON_LEFT:
|
||||
paint_mode = PaintMode.PAINT
|
||||
elif event.button_index == MOUSE_BUTTON_RIGHT:
|
||||
paint_mode = PaintMode.ERASE
|
||||
else:
|
||||
return false
|
||||
|
||||
if (clicked or event is InputEventMouseMotion) and paint_mode != PaintMode.NO_PAINT:
|
||||
if clicked:
|
||||
initial_click = current_position
|
||||
terrain_undo.action_index += 1
|
||||
terrain_undo.action_count = 0
|
||||
var type = selected_entry
|
||||
|
||||
if paint_action == PaintAction.LINE or paint_action == PaintAction.RECT:
|
||||
# if painting as line, execution happens on release.
|
||||
# prevent other painting actions from running.
|
||||
pass
|
||||
elif draw_button.button_pressed:
|
||||
undo_manager.create_action(tr("Draw terrain") + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tilemap, true)
|
||||
var cells := _get_tileset_line(prev_position, current_position, tileset)
|
||||
if paint_mode == PaintMode.PAINT:
|
||||
if replace_mode:
|
||||
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"replace_cells", [tilemap, cells, type])
|
||||
else:
|
||||
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_cells", [tilemap, cells, type])
|
||||
elif paint_mode == PaintMode.ERASE:
|
||||
for c in cells:
|
||||
terrain_undo.add_do_method(undo_manager, tilemap, &"erase_cell", [c])
|
||||
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"update_terrain_cells", [tilemap, cells])
|
||||
terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
|
||||
undo_manager.commit_action()
|
||||
terrain_undo.action_count += 1
|
||||
elif fill_button.button_pressed:
|
||||
var cells := _get_fill_cells(current_position)
|
||||
undo_manager.create_action(tr("Fill terrain"), UndoRedo.MERGE_DISABLE, tilemap)
|
||||
if paint_mode == PaintMode.PAINT:
|
||||
if replace_mode:
|
||||
undo_manager.add_do_method(BetterTerrain, &"replace_cells", tilemap, cells, type)
|
||||
else:
|
||||
undo_manager.add_do_method(BetterTerrain, &"set_cells", tilemap, cells, type)
|
||||
elif paint_mode == PaintMode.ERASE:
|
||||
for c in cells:
|
||||
undo_manager.add_do_method(tilemap, &"erase_cell", c)
|
||||
undo_manager.add_do_method(BetterTerrain, &"update_terrain_cells", tilemap, cells)
|
||||
terrain_undo.create_tile_restore_point(undo_manager, tilemap, cells)
|
||||
undo_manager.commit_action()
|
||||
|
||||
update_overlay.emit()
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
|
||||
func canvas_mouse_exit() -> void:
|
||||
draw_overlay = false
|
||||
update_overlay.emit()
|
||||
|
||||
|
||||
func _shortcut_input(event) -> void:
|
||||
if event is InputEventKey:
|
||||
if event.keycode == KEY_C and (event.is_command_or_control_pressed() and not event.echo):
|
||||
get_viewport().set_input_as_handled()
|
||||
tile_view.copy_selection()
|
||||
if event.keycode == KEY_V and (event.is_command_or_control_pressed() and not event.echo):
|
||||
get_viewport().set_input_as_handled()
|
||||
tile_view.paste_selection()
|
||||
|
||||
|
||||
## bresenham alg ported from Geometry2D::bresenham_line()
|
||||
func _get_line(from:Vector2i, to:Vector2i) -> Array[Vector2i]:
|
||||
if from == to:
|
||||
return [to]
|
||||
|
||||
var points:Array[Vector2i] = []
|
||||
var delta := (to - from).abs() * 2
|
||||
var step := (to - from).sign()
|
||||
var current := from
|
||||
|
||||
if delta.x > delta.y:
|
||||
var err:int = delta.x / 2
|
||||
while current.x != to.x:
|
||||
points.push_back(current);
|
||||
err -= delta.y
|
||||
if err < 0:
|
||||
current.y += step.y
|
||||
err += delta.x
|
||||
current.x += step.x
|
||||
else:
|
||||
var err:int = delta.y / 2
|
||||
while current.y != to.y:
|
||||
points.push_back(current)
|
||||
err -= delta.x
|
||||
if err < 0:
|
||||
current.x += step.x
|
||||
err += delta.y
|
||||
current.y += step.y
|
||||
|
||||
points.push_back(current);
|
||||
return points;
|
||||
|
||||
|
||||
## half-offset bresenham alg ported from TileMapEditor::get_line
|
||||
func _get_tileset_line(from:Vector2i, to:Vector2i, tileset:TileSet) -> Array[Vector2i]:
|
||||
if tileset.tile_shape == TileSet.TILE_SHAPE_SQUARE:
|
||||
return _get_line(from, to)
|
||||
|
||||
var points:Array[Vector2i] = []
|
||||
|
||||
var transposed := tileset.get_tile_offset_axis() == TileSet.TILE_OFFSET_AXIS_VERTICAL
|
||||
if transposed:
|
||||
from = Vector2i(from.y, from.x)
|
||||
to = Vector2i(to.y, to.x)
|
||||
|
||||
var delta:Vector2i = to - from
|
||||
delta = Vector2i(2 * delta.x + abs(posmod(to.y, 2)) - abs(posmod(from.y, 2)), delta.y)
|
||||
var sign:Vector2i = delta.sign()
|
||||
|
||||
var current := from;
|
||||
points.push_back(Vector2i(current.y, current.x) if transposed else current)
|
||||
|
||||
var err := 0
|
||||
if abs(delta.y) < abs(delta.x):
|
||||
var err_step:Vector2i = 3 * delta.abs()
|
||||
while current != to:
|
||||
err += err_step.y
|
||||
if err > abs(delta.x):
|
||||
if sign.x == 0:
|
||||
current += Vector2i(sign.y, 0)
|
||||
else:
|
||||
current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y)
|
||||
err -= err_step.x
|
||||
else:
|
||||
current += Vector2i(sign.x, 0)
|
||||
err += err_step.y
|
||||
points.push_back(Vector2i(current.y, current.x) if transposed else current)
|
||||
else:
|
||||
var err_step:Vector2i = delta.abs()
|
||||
while current != to:
|
||||
err += err_step.x
|
||||
if err > 0:
|
||||
if sign.x == 0:
|
||||
current += Vector2i(0, sign.y)
|
||||
else:
|
||||
current += Vector2i(sign.x if bool(current.y % 2) != (sign.x < 0) else 0, sign.y)
|
||||
err -= err_step.y;
|
||||
else:
|
||||
if sign.x == 0:
|
||||
current += Vector2i(0, sign.y)
|
||||
else:
|
||||
current += Vector2i(-sign.x if bool(current.y % 2) != (sign.x > 0) else 0, sign.y)
|
||||
err += err_step.y
|
||||
points.push_back(Vector2i(current.y, current.x) if transposed else current)
|
||||
|
||||
return points
|
||||
|
||||
|
||||
func _on_terrain_enable_id_pressed(id):
|
||||
if id in [SourceSelectors.ALL, SourceSelectors.NONE]:
|
||||
for i in source_selector_popup.item_count:
|
||||
if source_selector_popup.is_item_checkable(i):
|
||||
source_selector_popup.set_item_checked(i, id == SourceSelectors.ALL)
|
||||
else:
|
||||
var index = source_selector_popup.get_item_index(id)
|
||||
var checked = source_selector_popup.is_item_checked(index)
|
||||
source_selector_popup.set_item_checked(index, !checked)
|
||||
|
||||
var disabled_sources : Array[int]
|
||||
for i in source_selector_popup.item_count:
|
||||
if source_selector_popup.is_item_checkable(i) and !source_selector_popup.is_item_checked(i):
|
||||
disabled_sources.append(source_selector_popup.get_item_id(i))
|
||||
tile_view.disabled_sources = disabled_sources
|
||||
|
||||
|
||||
func corresponding_tilemap_editor_button(similar: Button) -> Button:
|
||||
var editors = EditorInterface.get_base_control().find_children("*", "TileMapLayerEditor", true, false)
|
||||
var tile_map_layer_editor = editors[0]
|
||||
var buttons = tile_map_layer_editor.find_children("*", "Button", true, false)
|
||||
for button: Button in buttons:
|
||||
if button.icon == similar.icon:
|
||||
return button
|
||||
return null
|
||||
|
||||
|
||||
func _on_layer_up_or_down_pressed(button: Button) -> void:
|
||||
var matching_button = corresponding_tilemap_editor_button(button)
|
||||
if !matching_button:
|
||||
return
|
||||
|
||||
# Major hack, to reduce flicker hide the tileset editor briefly
|
||||
var editors = EditorInterface.get_base_control().find_children("*", "TileSetEditor", true, false)
|
||||
var tile_set_editor = editors[0]
|
||||
|
||||
matching_button.pressed.emit()
|
||||
tile_set_editor.modulate = Color.TRANSPARENT
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
force_show_terrains.emit()
|
||||
tile_set_editor.modulate = Color.WHITE
|
||||
|
||||
|
||||
|
||||
func _on_layer_up_pressed() -> void:
|
||||
_on_layer_up_or_down_pressed(layer_up)
|
||||
|
||||
|
||||
func _on_layer_down_pressed() -> void:
|
||||
_on_layer_up_or_down_pressed(layer_down)
|
||||
|
||||
|
||||
func _on_layer_highlight_toggled(toggled: bool) -> void:
|
||||
var settings = EditorInterface.get_editor_settings()
|
||||
settings.set_setting("editors/tiles_editor/highlight_selected_layer", toggled)
|
||||
|
||||
var highlight = corresponding_tilemap_editor_button(layer_highlight)
|
||||
if highlight:
|
||||
highlight.toggled.emit(toggled)
|
||||
|
||||
|
||||
func _on_layer_grid_toggled(toggled: bool) -> void:
|
||||
var settings = EditorInterface.get_editor_settings()
|
||||
settings.set_setting("editors/tiles_editor/display_grid", toggled)
|
||||
|
||||
var grid = corresponding_tilemap_editor_button(layer_grid)
|
||||
if grid:
|
||||
grid.toggled.emit(toggled)
|
1
addons/better-terrain/editor/Dock.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://ynajlxcomlkc
|
399
addons/better-terrain/editor/Dock.tscn
Executable file
@@ -0,0 +1,399 @@
|
||||
[gd_scene load_steps=32 format=3 uid="uid://de8b6h6ieal7r"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://ynajlxcomlkc" path="res://addons/better-terrain/editor/Dock.gd" id="1_raoha"]
|
||||
[ext_resource type="Texture2D" uid="uid://c6lxq2y7mpb18" path="res://addons/better-terrain/icons/EditType.svg" id="2_cpm2t"]
|
||||
[ext_resource type="Texture2D" uid="uid://y3xy6qdckht6" path="res://addons/better-terrain/icons/Replace.svg" id="2_fvmt6"]
|
||||
[ext_resource type="Texture2D" uid="uid://bo2cjv08jkvf8" path="res://addons/better-terrain/icons/EditTerrain.svg" id="3_pqb1p"]
|
||||
[ext_resource type="Texture2D" uid="uid://b0es228gfcykd" path="res://addons/better-terrain/icons/Warning.svg" id="4_6ahwe"]
|
||||
[ext_resource type="Script" uid="uid://cpm7dq6r0n0sn" path="res://addons/better-terrain/editor/TileView.gd" id="4_nqppq"]
|
||||
[ext_resource type="Texture2D" uid="uid://co6gwwmog0pjy" path="res://addons/better-terrain/icons/EditSymmetry.svg" id="5_kfjwu"]
|
||||
[ext_resource type="Texture2D" uid="uid://cs4mdmluiydj6" path="res://addons/better-terrain/icons/ShuffleRandom.svg" id="5_n3owo"]
|
||||
[ext_resource type="Texture2D" uid="uid://5hm3bfj3dvej" path="res://addons/better-terrain/icons/SymmetryMirror.svg" id="6_mofuh"]
|
||||
[ext_resource type="Texture2D" uid="uid://dqmc1jp56or8m" path="res://addons/better-terrain/icons/SymmetryFlip.svg" id="7_ojxs0"]
|
||||
[ext_resource type="Texture2D" uid="uid://cxoewno1cefua" path="res://addons/better-terrain/icons/SymmetryReflect.svg" id="8_8dhyg"]
|
||||
[ext_resource type="Texture2D" uid="uid://baxhjy28r1iqj" path="res://addons/better-terrain/icons/SymmetryRotateClockwise.svg" id="9_tq76a"]
|
||||
[ext_resource type="Texture2D" uid="uid://csbwdkr6bc2db" path="res://addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg" id="10_o5h1f"]
|
||||
[ext_resource type="Texture2D" uid="uid://8mcycyl3e66r" path="res://addons/better-terrain/icons/SymmetryRotate180.svg" id="11_m6syp"]
|
||||
[ext_resource type="Texture2D" uid="uid://b7fx4mk18lmls" path="res://addons/better-terrain/icons/SymmetryRotateAll.svg" id="12_11vru"]
|
||||
[ext_resource type="Texture2D" uid="uid://cyjra4g05dwh" path="res://addons/better-terrain/icons/SymmetryAll.svg" id="13_lp5m2"]
|
||||
|
||||
[sub_resource type="ButtonGroup" id="ButtonGroup_aon7c"]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_saph6"]
|
||||
device = -1
|
||||
keycode = 68
|
||||
unicode = 100
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_3k2al"]
|
||||
events = [SubResource("InputEventKey_saph6")]
|
||||
|
||||
[sub_resource type="SVGTexture" id="SVGTexture_nkf6h"]
|
||||
_source = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\"><path fill=\"#ff5d5d\" d=\"M2 1v8.586l1.293-1.293a1 1 0 0 1 1.414 0L7 10.587l2.293-2.293a1 1 0 0 1 1.414 0L13 10.586l1-1V6H9V1H2zm8 0v4h4zm-6 9.414-2 2V15h12v-2.586l-.293.293a1 1 0 0 1-1.414 0L10 10.414l-2.293 2.293a1 1 0 0 1-1.414 0L4 10.414z\"/></svg>
|
||||
"
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_q1v0d"]
|
||||
device = -1
|
||||
keycode = 76
|
||||
unicode = 108
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_wc6bu"]
|
||||
events = [SubResource("InputEventKey_q1v0d")]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_68n3h"]
|
||||
device = -1
|
||||
keycode = 82
|
||||
unicode = 114
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_qcu1e"]
|
||||
device = -1
|
||||
keycode = 67
|
||||
unicode = 99
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_tcjet"]
|
||||
events = [SubResource("InputEventKey_68n3h"), SubResource("InputEventKey_qcu1e")]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_grxy4"]
|
||||
device = -1
|
||||
keycode = 66
|
||||
unicode = 98
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_46fac"]
|
||||
events = [SubResource("InputEventKey_grxy4")]
|
||||
|
||||
[sub_resource type="InputEventKey" id="InputEventKey_xd61m"]
|
||||
device = -1
|
||||
keycode = 80
|
||||
unicode = 112
|
||||
|
||||
[sub_resource type="Shortcut" id="Shortcut_uwwa1"]
|
||||
events = [SubResource("InputEventKey_xd61m")]
|
||||
|
||||
[sub_resource type="ButtonGroup" id="ButtonGroup_3wrxn"]
|
||||
allow_unpress = true
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_mpeb7"]
|
||||
bg_color = Color(0, 0, 0, 0.4)
|
||||
|
||||
[node name="Dock" type="Control" node_paths=PackedStringArray("shortcut_context")]
|
||||
custom_minimum_size = Vector2(0, 100)
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
focus_mode = 2
|
||||
shortcut_context = NodePath(".")
|
||||
script = ExtResource("1_raoha")
|
||||
|
||||
[node name="VBox" type="VBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Toolbar" type="HBoxContainer" parent="VBox"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Draw" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Draw terrain
|
||||
Shift: Draw line.
|
||||
Ctrl/Cmd+Shift: Draw rectangle."
|
||||
toggle_mode = true
|
||||
button_pressed = true
|
||||
button_group = SubResource("ButtonGroup_aon7c")
|
||||
shortcut = SubResource("Shortcut_3k2al")
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="Line" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Draw line"
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_aon7c")
|
||||
shortcut = SubResource("Shortcut_wc6bu")
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="Rectangle" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Fill a rectangle of terrain"
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_aon7c")
|
||||
shortcut = SubResource("Shortcut_tcjet")
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="Fill" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Bucket fill terrain"
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_aon7c")
|
||||
shortcut = SubResource("Shortcut_46fac")
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="Replace" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Toggle replace mode"
|
||||
toggle_mode = true
|
||||
shortcut = SubResource("Shortcut_uwwa1")
|
||||
icon = ExtResource("2_fvmt6")
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SelectTiles" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Select"
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_3wrxn")
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="PaintType" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Paint terrain types"
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_3wrxn")
|
||||
icon = ExtResource("2_cpm2t")
|
||||
flat = true
|
||||
|
||||
[node name="PaintTerrain" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Paint terrain connecting types"
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_3wrxn")
|
||||
icon = ExtResource("3_pqb1p")
|
||||
flat = true
|
||||
|
||||
[node name="PaintSymmetry" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Paint tile symmetry"
|
||||
toggle_mode = true
|
||||
button_group = SubResource("ButtonGroup_3wrxn")
|
||||
icon = ExtResource("5_kfjwu")
|
||||
flat = true
|
||||
|
||||
[node name="SymmetryOptions" type="OptionButton" parent="VBox/Toolbar"]
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(100, 0)
|
||||
layout_mode = 2
|
||||
selected = 0
|
||||
item_count = 9
|
||||
popup/item_0/text = "No symmetry"
|
||||
popup/item_0/id = 8
|
||||
popup/item_1/text = "Mirror"
|
||||
popup/item_1/icon = ExtResource("6_mofuh")
|
||||
popup/item_1/id = 1
|
||||
popup/item_2/text = "Flip"
|
||||
popup/item_2/icon = ExtResource("7_ojxs0")
|
||||
popup/item_2/id = 1
|
||||
popup/item_3/text = "Reflect"
|
||||
popup/item_3/icon = ExtResource("8_8dhyg")
|
||||
popup/item_3/id = 2
|
||||
popup/item_4/text = "Rotate clockwise"
|
||||
popup/item_4/icon = ExtResource("9_tq76a")
|
||||
popup/item_4/id = 3
|
||||
popup/item_5/text = "Rotate counter-clockwise"
|
||||
popup/item_5/icon = ExtResource("10_o5h1f")
|
||||
popup/item_5/id = 4
|
||||
popup/item_6/text = "Rotate 180"
|
||||
popup/item_6/icon = ExtResource("11_m6syp")
|
||||
popup/item_6/id = 5
|
||||
popup/item_7/text = "All rotations"
|
||||
popup/item_7/icon = ExtResource("12_11vru")
|
||||
popup/item_7/id = 6
|
||||
popup/item_8/text = "All reflections & rotations"
|
||||
popup/item_8/icon = ExtResource("13_lp5m2")
|
||||
popup/item_8/id = 7
|
||||
|
||||
[node name="VSeparator3" type="VSeparator" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ZoomContainer" type="VBoxContainer" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
|
||||
[node name="Sources" type="MenuBar" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Sources" type="PopupMenu" parent="VBox/Toolbar/Sources"]
|
||||
auto_translate_mode = 2
|
||||
auto_translate = false
|
||||
hide_on_item_selection = false
|
||||
hide_on_checkable_item_selection = false
|
||||
|
||||
[node name="Spacer" type="Control" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="ShuffleRandom" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Shuffle random tiles each update"
|
||||
toggle_mode = true
|
||||
icon = ExtResource("5_n3owo")
|
||||
flat = true
|
||||
|
||||
[node name="Clean" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
text = "Clean tile data"
|
||||
icon = ExtResource("4_6ahwe")
|
||||
|
||||
[node name="VSeparator2" type="VSeparator" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LayerUp" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Select previous layer"
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="LayerDown" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Select next layer"
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="LayerHighlight" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Highlight selected layer"
|
||||
toggle_mode = true
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="LayerGrid" type="Button" parent="VBox/Toolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Toggle grid visibility"
|
||||
toggle_mode = true
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="HSplit" type="HSplitContainer" parent="VBox"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
split_offset = 325
|
||||
|
||||
[node name="Terrains" type="VBoxContainer" parent="VBox/HSplit"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="PanelContainer" parent="VBox/HSplit/Terrains"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_mpeb7")
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="VBox/HSplit/Terrains/Panel"]
|
||||
layout_mode = 2
|
||||
horizontal_scroll_mode = 3
|
||||
|
||||
[node name="TerrainList" type="HFlowContainer" parent="VBox/HSplit/Terrains/Panel/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="LowerToolbar" type="HBoxContainer" parent="VBox/HSplit/Terrains"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="GridMode" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Toggle grid view"
|
||||
toggle_mode = true
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="QuickMode" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar"]
|
||||
auto_translate_mode = 1
|
||||
layout_mode = 2
|
||||
tooltip_text = "Toggle quick mode. Only shows paintable terrain types."
|
||||
toggle_mode = true
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="VSeparator" type="VSeparator" parent="VBox/HSplit/Terrains/LowerToolbar"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="EditTools" type="HBoxContainer" parent="VBox/HSplit/Terrains/LowerToolbar"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
alignment = 2
|
||||
|
||||
[node name="AddTerrain" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Add terrain type"
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="EditTerrain" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Edit terrain type"
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="PickIcon" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Pick terrain icon from tileset"
|
||||
toggle_mode = true
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="MoveUp" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Move selected terrain up"
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="MoveDown" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Move selected terrain down"
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="RemoveTerrain" type="Button" parent="VBox/HSplit/Terrains/LowerToolbar/EditTools"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Remove selected terrain type(s)"
|
||||
icon = SubResource("SVGTexture_nkf6h")
|
||||
flat = true
|
||||
|
||||
[node name="Panel" type="Panel" parent="VBox/HSplit"]
|
||||
custom_minimum_size = Vector2(0, 80)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ScrollArea" type="ScrollContainer" parent="VBox/HSplit/Panel"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="TileView" type="Control" parent="VBox/HSplit/Panel/ScrollArea"]
|
||||
texture_filter = 1
|
||||
texture_repeat = 1
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
focus_mode = 2
|
||||
script = ExtResource("4_nqppq")
|
||||
|
||||
[connection signal="item_selected" from="VBox/Toolbar/SymmetryOptions" to="." method="_on_symmetry_selected"]
|
||||
[connection signal="id_pressed" from="VBox/Toolbar/Sources/Sources" to="." method="_on_terrain_enable_id_pressed"]
|
||||
[connection signal="pressed" from="VBox/Toolbar/ShuffleRandom" to="." method="_on_shuffle_random_pressed"]
|
||||
[connection signal="pressed" from="VBox/Toolbar/Clean" to="." method="_on_clean_pressed"]
|
||||
[connection signal="pressed" from="VBox/Toolbar/LayerUp" to="." method="_on_layer_up_pressed"]
|
||||
[connection signal="pressed" from="VBox/Toolbar/LayerDown" to="." method="_on_layer_down_pressed"]
|
||||
[connection signal="toggled" from="VBox/Toolbar/LayerHighlight" to="." method="_on_layer_highlight_toggled"]
|
||||
[connection signal="toggled" from="VBox/Toolbar/LayerGrid" to="." method="_on_layer_grid_toggled"]
|
||||
[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/GridMode" to="." method="_on_grid_mode_pressed"]
|
||||
[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/QuickMode" to="." method="_on_quick_mode_pressed"]
|
||||
[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/AddTerrain" to="." method="_on_add_terrain_pressed"]
|
||||
[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/EditTerrain" to="." method="_on_edit_terrain_pressed"]
|
||||
[connection signal="focus_exited" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon" to="." method="_on_pick_icon_focus_exited"]
|
||||
[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/PickIcon" to="." method="_on_pick_icon_pressed"]
|
||||
[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveUp" to="." method="_on_move_pressed" binds= [false]]
|
||||
[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/MoveDown" to="." method="_on_move_pressed" binds= [true]]
|
||||
[connection signal="pressed" from="VBox/HSplit/Terrains/LowerToolbar/EditTools/RemoveTerrain" to="." method="_on_remove_terrain_pressed"]
|
||||
[connection signal="mouse_exited" from="VBox/HSplit/Panel/ScrollArea/TileView" to="VBox/HSplit/Panel/ScrollArea/TileView" method="clear_highlighted_tile"]
|
185
addons/better-terrain/editor/TerrainEntry.gd
Executable file
@@ -0,0 +1,185 @@
|
||||
@tool
|
||||
extends PanelContainer
|
||||
|
||||
signal select(index)
|
||||
|
||||
@onready var color_panel := %Color
|
||||
@onready var terrain_icon_slot := %TerrainIcon
|
||||
@onready var type_icon_slot := %TypeIcon
|
||||
@onready var type_icon_panel := %TerrainIconPanel
|
||||
@onready var name_label := %Name
|
||||
@onready var layout_container := %Layout
|
||||
@onready var icon_layout_container := %IconLayout
|
||||
|
||||
var selected := false
|
||||
|
||||
var tileset:TileSet
|
||||
var terrain:Dictionary
|
||||
|
||||
var grid_mode := false
|
||||
var color_style_list:StyleBoxFlat
|
||||
var color_style_grid:StyleBoxFlat
|
||||
var color_style_decoration:StyleBoxFlat
|
||||
|
||||
var _terrain_texture:Texture2D
|
||||
var _terrain_texture_rect:Rect2i
|
||||
var _icon_draw_connected := false
|
||||
|
||||
|
||||
func _ready():
|
||||
update()
|
||||
|
||||
func update():
|
||||
if !terrain or !terrain.valid:
|
||||
return
|
||||
if !tileset:
|
||||
return
|
||||
|
||||
name_label.text = terrain.name
|
||||
tooltip_text = "%s (%d)" % [terrain.name, terrain.id]
|
||||
|
||||
color_style_list = color_panel.get_theme_stylebox("panel").duplicate()
|
||||
color_style_grid = color_panel.get_theme_stylebox("panel").duplicate()
|
||||
color_style_decoration = color_panel.get_theme_stylebox("panel").duplicate()
|
||||
|
||||
color_style_list.bg_color = terrain.color
|
||||
color_style_list.corner_radius_top_left = 8
|
||||
color_style_list.corner_radius_bottom_left = 8
|
||||
color_style_list.corner_radius_top_right = 0
|
||||
color_style_list.corner_radius_bottom_right = 0
|
||||
color_style_list.content_margin_left = -1
|
||||
color_style_list.content_margin_right = -1
|
||||
color_style_list.border_width_left = 0
|
||||
color_style_list.border_width_right = 0
|
||||
color_style_list.border_width_top = 0
|
||||
color_style_list.border_width_bottom = 0
|
||||
|
||||
color_style_grid.bg_color = terrain.color
|
||||
color_style_grid.corner_radius_top_left = 6
|
||||
color_style_grid.corner_radius_bottom_left = 6
|
||||
color_style_grid.corner_radius_top_right = 6
|
||||
color_style_grid.corner_radius_bottom_right = 6
|
||||
color_style_grid.content_margin_left = -1
|
||||
color_style_grid.content_margin_right = -1
|
||||
color_style_grid.border_width_left = 0
|
||||
color_style_grid.border_width_right = 0
|
||||
color_style_grid.border_width_top = 0
|
||||
color_style_grid.border_width_bottom = 0
|
||||
|
||||
color_style_decoration.bg_color = terrain.color
|
||||
color_style_decoration.corner_radius_top_left = 8
|
||||
color_style_decoration.corner_radius_bottom_left = 8
|
||||
color_style_decoration.corner_radius_top_right = 8
|
||||
color_style_decoration.corner_radius_bottom_right = 8
|
||||
color_style_decoration.content_margin_left = -1
|
||||
color_style_decoration.content_margin_right = -1
|
||||
color_style_decoration.border_width_left = 4
|
||||
color_style_decoration.border_width_right = 4
|
||||
color_style_decoration.border_width_top = 4
|
||||
color_style_decoration.border_width_bottom = 4
|
||||
|
||||
match terrain.type:
|
||||
BetterTerrain.TerrainType.MATCH_TILES:
|
||||
type_icon_slot.texture = load("res://addons/better-terrain/icons/MatchTiles.svg")
|
||||
BetterTerrain.TerrainType.MATCH_VERTICES:
|
||||
type_icon_slot.texture = load("res://addons/better-terrain/icons/MatchVertices.svg")
|
||||
BetterTerrain.TerrainType.CATEGORY:
|
||||
type_icon_slot.texture = load("res://addons/better-terrain/icons/NonModifying.svg")
|
||||
BetterTerrain.TerrainType.DECORATION:
|
||||
type_icon_slot.texture = load("res://addons/better-terrain/icons/Decoration.svg")
|
||||
|
||||
var has_icon = false
|
||||
if terrain.has("icon"):
|
||||
if terrain.icon.has("path") and not terrain.icon.path.is_empty():
|
||||
terrain_icon_slot.texture = load(terrain.icon.path)
|
||||
_terrain_texture = null
|
||||
terrain_icon_slot.queue_redraw()
|
||||
has_icon = true
|
||||
elif terrain.icon.has("source_id") and tileset.has_source(terrain.icon.source_id):
|
||||
var source := tileset.get_source(terrain.icon.source_id) as TileSetAtlasSource
|
||||
var coord := terrain.icon.coord as Vector2i
|
||||
var rect := source.get_tile_texture_region(coord, 0)
|
||||
_terrain_texture = source.texture
|
||||
_terrain_texture_rect = rect
|
||||
terrain_icon_slot.queue_redraw()
|
||||
has_icon = true
|
||||
|
||||
if not has_icon:
|
||||
var tiles = BetterTerrain.get_tile_sources_in_terrain(tileset, get_index())
|
||||
if tiles.size() > 0:
|
||||
var source := tiles[0].source as TileSetAtlasSource
|
||||
var coord := tiles[0].coord as Vector2i
|
||||
var rect := source.get_tile_texture_region(coord, 0)
|
||||
_terrain_texture = source.texture
|
||||
_terrain_texture_rect = rect
|
||||
terrain_icon_slot.queue_redraw()
|
||||
|
||||
if _terrain_texture:
|
||||
terrain_icon_slot.texture = null
|
||||
|
||||
if not _icon_draw_connected:
|
||||
terrain_icon_slot.connect("draw", func():
|
||||
if _terrain_texture:
|
||||
terrain_icon_slot.draw_texture_rect_region(_terrain_texture, Rect2i(0,0, 44, 44), _terrain_texture_rect)
|
||||
)
|
||||
_icon_draw_connected = true
|
||||
|
||||
update_style()
|
||||
|
||||
|
||||
func update_style():
|
||||
if terrain.type == BetterTerrain.TerrainType.DECORATION:
|
||||
type_icon_panel.visible = false
|
||||
color_panel.custom_minimum_size = Vector2i(52,52)
|
||||
else:
|
||||
type_icon_panel.visible = true
|
||||
color_panel.custom_minimum_size = Vector2i(24,24)
|
||||
|
||||
if grid_mode:
|
||||
if terrain.type == BetterTerrain.TerrainType.DECORATION:
|
||||
color_panel.add_theme_stylebox_override("panel", color_style_decoration)
|
||||
color_panel.size_flags_vertical = Control.SIZE_FILL
|
||||
icon_layout_container.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
else:
|
||||
color_panel.add_theme_stylebox_override("panel", color_style_grid)
|
||||
color_panel.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
|
||||
icon_layout_container.size_flags_vertical = Control.SIZE_FILL
|
||||
custom_minimum_size = Vector2(0, 60)
|
||||
size_flags_horizontal = Control.SIZE_FILL
|
||||
layout_container.vertical = true
|
||||
name_label.visible = false
|
||||
icon_layout_container.add_theme_constant_override("separation", -24)
|
||||
else:
|
||||
if terrain.type == BetterTerrain.TerrainType.DECORATION:
|
||||
color_panel.add_theme_stylebox_override("panel", color_style_decoration)
|
||||
else:
|
||||
color_panel.add_theme_stylebox_override("panel", color_style_list)
|
||||
icon_layout_container.size_flags_vertical = Control.SIZE_FILL
|
||||
custom_minimum_size = Vector2(2000, 60)
|
||||
size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
layout_container.vertical = false
|
||||
name_label.visible = true
|
||||
color_panel.size_flags_vertical = Control.SIZE_FILL
|
||||
icon_layout_container.add_theme_constant_override("separation", 4)
|
||||
|
||||
|
||||
func set_selected(value:bool = true):
|
||||
selected = value
|
||||
if value:
|
||||
select.emit(get_index())
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _draw():
|
||||
if selected:
|
||||
draw_rect(Rect2(Vector2.ZERO, get_rect().size), Color(0.15, 0.70, 1, 0.3))
|
||||
|
||||
|
||||
func _on_focus_entered():
|
||||
queue_redraw()
|
||||
selected = true
|
||||
select.emit(get_index())
|
||||
|
||||
|
||||
func _on_focus_exited():
|
||||
queue_redraw()
|
1
addons/better-terrain/editor/TerrainEntry.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://c2qfovpuj58b7
|
114
addons/better-terrain/editor/TerrainEntry.tscn
Executable file
@@ -0,0 +1,114 @@
|
||||
[gd_scene load_steps=8 format=3 uid="uid://u2y444hj182c"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://c2qfovpuj58b7" path="res://addons/better-terrain/editor/TerrainEntry.gd" id="1_o2na3"]
|
||||
[ext_resource type="Texture2D" uid="uid://kmypxsqhynyv" path="res://addons/better-terrain/icons/Decoration.svg" id="2_ossyj"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_3pdcc"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
draw_center = false
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_dqhir"]
|
||||
bg_color = Color(0.243, 0.816, 0.518, 1)
|
||||
border_color = Color(0, 0, 0, 0.439216)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_bottom_left = 8
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_rohyw"]
|
||||
content_margin_left = 2.0
|
||||
content_margin_top = 2.0
|
||||
content_margin_right = 2.0
|
||||
content_margin_bottom = 2.0
|
||||
bg_color = Color(0, 0, 0, 0.439216)
|
||||
corner_radius_top_left = 4
|
||||
corner_radius_top_right = 4
|
||||
corner_radius_bottom_right = 4
|
||||
corner_radius_bottom_left = 4
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xa0fl"]
|
||||
content_margin_left = 4.0
|
||||
content_margin_top = 4.0
|
||||
content_margin_right = 4.0
|
||||
content_margin_bottom = 4.0
|
||||
bg_color = Color(0, 0, 0, 0.439216)
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_b4rkm"]
|
||||
content_margin_left = 3.0
|
||||
bg_color = Color(0, 0, 0, 0.439216)
|
||||
draw_center = false
|
||||
|
||||
[node name="TerrainEntry" type="PanelContainer"]
|
||||
custom_minimum_size = Vector2(60, 60)
|
||||
offset_right = 200.0
|
||||
offset_bottom = 60.0
|
||||
size_flags_vertical = 3
|
||||
focus_mode = 2
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_3pdcc")
|
||||
script = ExtResource("1_o2na3")
|
||||
|
||||
[node name="Layout" type="BoxContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 4
|
||||
|
||||
[node name="IconLayout" type="HBoxContainer" parent="Layout"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
|
||||
[node name="Color" type="PanelContainer" parent="Layout/IconLayout"]
|
||||
unique_name_in_owner = true
|
||||
z_index = 1
|
||||
custom_minimum_size = Vector2(24, 24)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
mouse_filter = 1
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_dqhir")
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="Layout/IconLayout/Color"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
mouse_filter = 1
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_rohyw")
|
||||
|
||||
[node name="TypeIcon" type="TextureRect" parent="Layout/IconLayout/Color/PanelContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
texture = ExtResource("2_ossyj")
|
||||
|
||||
[node name="TerrainIconPanel" type="PanelContainer" parent="Layout/IconLayout"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(52, 52)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 4
|
||||
size_flags_vertical = 4
|
||||
mouse_filter = 1
|
||||
theme_override_styles/panel = SubResource("StyleBoxFlat_xa0fl")
|
||||
|
||||
[node name="TerrainIcon" type="TextureRect" parent="Layout/IconLayout/TerrainIconPanel"]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
custom_minimum_size = Vector2(40, 40)
|
||||
layout_mode = 2
|
||||
expand_mode = 4
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="Name" type="Label" parent="Layout"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 1
|
||||
theme_override_colors/font_outline_color = Color(0, 0, 0, 1)
|
||||
theme_override_constants/outline_size = 0
|
||||
theme_override_styles/normal = SubResource("StyleBoxFlat_b4rkm")
|
||||
text = "New Terrain"
|
||||
vertical_alignment = 1
|
||||
text_overrun_behavior = 3
|
||||
|
||||
[connection signal="focus_entered" from="." to="." method="_on_focus_entered"]
|
||||
[connection signal="focus_exited" from="." to="." method="_on_focus_exited"]
|
85
addons/better-terrain/editor/TerrainProperties.gd
Executable file
@@ -0,0 +1,85 @@
|
||||
@tool
|
||||
extends ConfirmationDialog
|
||||
|
||||
var category_icon := load("res://addons/better-terrain/icons/NonModifying.svg")
|
||||
|
||||
const CATEGORY_CHECK_ID = &"category_check_id"
|
||||
|
||||
var accepted := false
|
||||
|
||||
var terrain_name : String:
|
||||
set(value): %NameEdit.text = value
|
||||
get: return %NameEdit.text
|
||||
|
||||
var terrain_color : Color:
|
||||
set(value): %ColorPicker.color = value
|
||||
get: return %ColorPicker.color
|
||||
|
||||
var terrain_icon : String:
|
||||
set(value): %IconEdit.text = value
|
||||
get: return %IconEdit.text
|
||||
|
||||
var terrain_type : int:
|
||||
set(value):
|
||||
%TypeOption.selected = value
|
||||
_on_type_option_item_selected(value)
|
||||
get: return %TypeOption.selected
|
||||
|
||||
var terrain_categories : Array: set = set_categories, get = get_categories
|
||||
|
||||
|
||||
# category is name, color, id
|
||||
func set_category_data(options: Array) -> void:
|
||||
if !options.is_empty():
|
||||
%CategoryLabel.show()
|
||||
%CategoryContainer.show()
|
||||
|
||||
for o in options:
|
||||
var c = CheckBox.new()
|
||||
c.text = o.name
|
||||
c.icon = category_icon
|
||||
c.add_theme_color_override(&"icon_normal_color", o.color)
|
||||
c.add_theme_color_override(&"icon_disabled_color", Color(o.color, 0.4))
|
||||
c.add_theme_color_override(&"icon_focus_color", o.color)
|
||||
c.add_theme_color_override(&"icon_hover_color", o.color)
|
||||
c.add_theme_color_override(&"icon_hover_pressed_color", o.color)
|
||||
c.add_theme_color_override(&"icon_normal_color", o.color)
|
||||
c.add_theme_color_override(&"icon_pressed_color", o.color)
|
||||
|
||||
c.set_meta(CATEGORY_CHECK_ID, o.id)
|
||||
%CategoryLayout.add_child(c)
|
||||
|
||||
|
||||
func set_categories(ids : Array):
|
||||
for c in %CategoryLayout.get_children():
|
||||
c.button_pressed = c.get_meta(CATEGORY_CHECK_ID) in ids
|
||||
|
||||
|
||||
func get_categories() -> Array:
|
||||
var result := []
|
||||
if terrain_type == BetterTerrain.TerrainType.CATEGORY:
|
||||
return result
|
||||
for c in %CategoryLayout.get_children():
|
||||
if c.button_pressed:
|
||||
result.push_back(c.get_meta(CATEGORY_CHECK_ID))
|
||||
return result
|
||||
|
||||
|
||||
func _on_confirmed() -> void:
|
||||
# confirm valid name
|
||||
if terrain_name.is_empty():
|
||||
var dialog := AcceptDialog.new()
|
||||
dialog.dialog_text = "Name cannot be empty"
|
||||
EditorInterface.popup_dialog_centered(dialog)
|
||||
await dialog.visibility_changed
|
||||
dialog.queue_free()
|
||||
return
|
||||
|
||||
accepted = true
|
||||
hide()
|
||||
|
||||
|
||||
func _on_type_option_item_selected(index: int) -> void:
|
||||
var categories_available = (index != BetterTerrain.TerrainType.CATEGORY)
|
||||
for c in %CategoryLayout.get_children():
|
||||
c.disabled = !categories_available
|
1
addons/better-terrain/editor/TerrainProperties.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://j81f0xo4p36y
|
96
addons/better-terrain/editor/TerrainProperties.tscn
Normal file
@@ -0,0 +1,96 @@
|
||||
[gd_scene load_steps=5 format=3 uid="uid://fdjybw6e7whr"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://j81f0xo4p36y" path="res://addons/better-terrain/editor/TerrainProperties.gd" id="1_52nx8"]
|
||||
[ext_resource type="Texture2D" uid="uid://d1h1p7pcwdnjk" path="res://addons/better-terrain/icons/MatchTiles.svg" id="2_ncc5p"]
|
||||
[ext_resource type="Texture2D" uid="uid://dfemy1g6okwlv" path="res://addons/better-terrain/icons/MatchVertices.svg" id="3_0nvmi"]
|
||||
[ext_resource type="Texture2D" uid="uid://1yr6yruwl63u" path="res://addons/better-terrain/icons/NonModifying.svg" id="5_awp83"]
|
||||
|
||||
[node name="TerrainProperties" type="ConfirmationDialog"]
|
||||
auto_translate_mode = 2
|
||||
oversampling_override = 1.0
|
||||
title = "Edit terrain properties"
|
||||
initial_position = 2
|
||||
size = Vector2i(317, 257)
|
||||
visible = true
|
||||
dialog_hide_on_ok = false
|
||||
script = ExtResource("1_52nx8")
|
||||
|
||||
[node name="GridContainer" type="GridContainer" parent="."]
|
||||
offset_left = 8.0
|
||||
offset_top = 8.0
|
||||
offset_right = 309.0
|
||||
offset_bottom = 212.0
|
||||
columns = 2
|
||||
|
||||
[node name="NameLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
text = "Name"
|
||||
|
||||
[node name="NameEdit" type="LineEdit" parent="GridContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Terrain name"
|
||||
|
||||
[node name="ColorLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
text = "Color"
|
||||
|
||||
[node name="ColorPicker" type="ColorPickerButton" parent="GridContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
color = Color(1, 0.262745, 0.498039, 1)
|
||||
edit_alpha = false
|
||||
|
||||
[node name="IconLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
text = "Icon"
|
||||
|
||||
[node name="IconEdit" type="LineEdit" parent="GridContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
placeholder_text = "Icon path (optional)"
|
||||
|
||||
[node name="TypeLabel" type="Label" parent="GridContainer"]
|
||||
layout_mode = 2
|
||||
text = "Mode"
|
||||
|
||||
[node name="TypeOption" type="OptionButton" parent="GridContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
item_count = 3
|
||||
popup/item_0/text = "Match tiles"
|
||||
popup/item_0/icon = ExtResource("2_ncc5p")
|
||||
popup/item_0/id = 0
|
||||
popup/item_1/text = "Match vertices"
|
||||
popup/item_1/icon = ExtResource("3_0nvmi")
|
||||
popup/item_1/id = 1
|
||||
popup/item_2/text = "Category"
|
||||
popup/item_2/icon = ExtResource("5_awp83")
|
||||
popup/item_2/id = 2
|
||||
|
||||
[node name="CategoryLabel" type="Label" parent="GridContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 1
|
||||
text = "Categories"
|
||||
|
||||
[node name="CategoryContainer" type="ScrollContainer" parent="GridContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(0, 100)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="CategoryLayout" type="VBoxContainer" parent="GridContainer/CategoryContainer"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 100)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[connection signal="confirmed" from="." to="." method="_on_confirmed"]
|
||||
[connection signal="item_selected" from="GridContainer/TypeOption" to="." method="_on_type_option_item_selected"]
|
190
addons/better-terrain/editor/TerrainUndo.gd
Executable file
@@ -0,0 +1,190 @@
|
||||
@tool
|
||||
extends Node
|
||||
|
||||
var action_index := 0
|
||||
var action_count := 0
|
||||
var _current_action_index := 0
|
||||
var _current_action_count := 0
|
||||
|
||||
func create_tile_restore_point(undo_manager: EditorUndoRedoManager, tm: TileMapLayer, cells: Array, and_surrounding_cells: bool = true) -> void:
|
||||
if and_surrounding_cells:
|
||||
cells = BetterTerrain._widen(tm, cells)
|
||||
|
||||
var restore := []
|
||||
for c in cells:
|
||||
restore.append([
|
||||
c,
|
||||
tm.get_cell_source_id(c),
|
||||
tm.get_cell_atlas_coords(c),
|
||||
tm.get_cell_alternative_tile(c)
|
||||
])
|
||||
|
||||
undo_manager.add_undo_method(self, &"restore_tiles", tm, restore)
|
||||
|
||||
|
||||
func create_tile_restore_point_area(undo_manager: EditorUndoRedoManager, tm: TileMapLayer, area: Rect2i, and_surrounding_cells: bool = true) -> void:
|
||||
area.end += Vector2i.ONE
|
||||
|
||||
var restore := []
|
||||
for y in range(area.position.y, area.end.y):
|
||||
for x in range(area.position.x, area.end.x):
|
||||
var c := Vector2i(x, y)
|
||||
restore.append([
|
||||
c,
|
||||
tm.get_cell_source_id(c),
|
||||
tm.get_cell_atlas_coords(c),
|
||||
tm.get_cell_alternative_tile(c)
|
||||
])
|
||||
|
||||
undo_manager.add_undo_method(self, &"restore_tiles", tm, restore)
|
||||
|
||||
if !and_surrounding_cells:
|
||||
return
|
||||
|
||||
var edges := []
|
||||
for x in range(area.position.x, area.end.x):
|
||||
edges.append(Vector2i(x, area.position.y))
|
||||
edges.append(Vector2i(x, area.end.y))
|
||||
for y in range(area.position.y + 1, area.end.y - 1):
|
||||
edges.append(Vector2i(area.position.x, y))
|
||||
edges.append(Vector2i(area.end.x, y))
|
||||
|
||||
edges = BetterTerrain._widen_with_exclusion(tm, edges, area)
|
||||
create_tile_restore_point(undo_manager, tm, edges, false)
|
||||
|
||||
|
||||
func restore_tiles(tm: TileMapLayer, restore: Array) -> void:
|
||||
for r in restore:
|
||||
tm.set_cell(r[0], r[1], r[2], r[3])
|
||||
|
||||
|
||||
func create_peering_restore_point(undo_manager: EditorUndoRedoManager, ts: TileSet) -> void:
|
||||
var restore := []
|
||||
|
||||
for s in ts.get_source_count():
|
||||
var source_id := ts.get_source_id(s)
|
||||
var source := ts.get_source(source_id) as TileSetAtlasSource
|
||||
if !source:
|
||||
continue
|
||||
|
||||
for t in source.get_tiles_count():
|
||||
var coord := source.get_tile_id(t)
|
||||
for a in source.get_alternative_tiles_count(coord):
|
||||
var alternate := source.get_alternative_tile_id(coord, a)
|
||||
|
||||
var td := source.get_tile_data(coord, alternate)
|
||||
var tile_type := BetterTerrain.get_tile_terrain_type(td)
|
||||
if tile_type == BetterTerrain.TileCategory.NON_TERRAIN:
|
||||
continue
|
||||
|
||||
var peering_dict := {}
|
||||
for c in BetterTerrain.tile_peering_keys(td):
|
||||
peering_dict[c] = BetterTerrain.tile_peering_types(td, c)
|
||||
var symmetry = BetterTerrain.get_tile_symmetry_type(td)
|
||||
restore.append([source_id, coord, alternate, tile_type, peering_dict, symmetry])
|
||||
|
||||
undo_manager.add_undo_method(self, &"restore_peering", ts, restore)
|
||||
|
||||
|
||||
func create_peering_restore_point_specific(undo_manager: EditorUndoRedoManager, ts: TileSet, protect: int) -> void:
|
||||
var restore := []
|
||||
|
||||
for s in ts.get_source_count():
|
||||
var source_id := ts.get_source_id(s)
|
||||
var source := ts.get_source(source_id) as TileSetAtlasSource
|
||||
if !source:
|
||||
continue
|
||||
|
||||
for t in source.get_tiles_count():
|
||||
var coord := source.get_tile_id(t)
|
||||
for a in source.get_alternative_tiles_count(coord):
|
||||
var alternate := source.get_alternative_tile_id(coord, a)
|
||||
|
||||
var td := source.get_tile_data(coord, alternate)
|
||||
var tile_type := BetterTerrain.get_tile_terrain_type(td)
|
||||
if tile_type == BetterTerrain.TileCategory.NON_TERRAIN:
|
||||
continue
|
||||
|
||||
var to_restore : bool = tile_type == protect
|
||||
|
||||
var terrain := BetterTerrain.get_terrain(ts, tile_type)
|
||||
var cells = BetterTerrain.data.get_terrain_peering_cells(ts, terrain.type)
|
||||
for c in cells:
|
||||
if protect in BetterTerrain.tile_peering_types(td, c):
|
||||
to_restore = true
|
||||
break
|
||||
|
||||
if !to_restore:
|
||||
continue
|
||||
|
||||
var peering_dict := {}
|
||||
for c in cells:
|
||||
peering_dict[c] = BetterTerrain.tile_peering_types(td, c)
|
||||
var symmetry = BetterTerrain.get_tile_symmetry_type(td)
|
||||
restore.append([source_id, coord, alternate, tile_type, peering_dict, symmetry])
|
||||
|
||||
undo_manager.add_undo_method(self, &"restore_peering", ts, restore)
|
||||
|
||||
|
||||
func create_peering_restore_point_tile(undo_manager: EditorUndoRedoManager, ts: TileSet, source_id: int, coord: Vector2i, alternate: int) -> void:
|
||||
var source := ts.get_source(source_id) as TileSetAtlasSource
|
||||
var td := source.get_tile_data(coord, alternate)
|
||||
var tile_type := BetterTerrain.get_tile_terrain_type(td)
|
||||
|
||||
var restore := []
|
||||
var peering_dict := {}
|
||||
for c in BetterTerrain.tile_peering_keys(td):
|
||||
peering_dict[c] = BetterTerrain.tile_peering_types(td, c)
|
||||
var symmetry = BetterTerrain.get_tile_symmetry_type(td)
|
||||
restore.append([source_id, coord, alternate, tile_type, peering_dict, symmetry])
|
||||
|
||||
undo_manager.add_undo_method(self, &"restore_peering", ts, restore)
|
||||
|
||||
|
||||
func restore_peering(ts: TileSet, restore: Array) -> void:
|
||||
for r in restore:
|
||||
var source := ts.get_source(r[0]) as TileSetAtlasSource
|
||||
var td := source.get_tile_data(r[1], r[2])
|
||||
BetterTerrain.set_tile_terrain_type(ts, td, r[3])
|
||||
var peering_types = r[4]
|
||||
for peering in peering_types:
|
||||
var types := BetterTerrain.tile_peering_types(td, peering)
|
||||
for t in types:
|
||||
BetterTerrain.remove_tile_peering_type(ts, td, peering, t)
|
||||
for t in peering_types[peering]:
|
||||
BetterTerrain.add_tile_peering_type(ts, td, peering, t)
|
||||
var symmetry = r[5]
|
||||
BetterTerrain.set_tile_symmetry_type(ts, td, symmetry)
|
||||
|
||||
|
||||
func create_terrain_type_restore_point(undo_manager: EditorUndoRedoManager, ts: TileSet) -> void:
|
||||
var count = BetterTerrain.terrain_count(ts)
|
||||
var restore = []
|
||||
for i in count:
|
||||
restore.push_back(BetterTerrain.get_terrain(ts, i))
|
||||
|
||||
undo_manager.add_undo_method(self, &"restore_terrain", ts, restore)
|
||||
|
||||
|
||||
func restore_terrain(ts: TileSet, restore: Array) -> void:
|
||||
for i in restore.size():
|
||||
var r = restore[i]
|
||||
BetterTerrain.set_terrain(ts, i, r.name, r.color, r.type, r.categories, r.icon)
|
||||
|
||||
|
||||
func add_do_method(undo_manager: EditorUndoRedoManager, object:Object, method:StringName, args:Array):
|
||||
if action_index > _current_action_index:
|
||||
_current_action_index = action_index
|
||||
_current_action_count = action_count
|
||||
if action_count > _current_action_count:
|
||||
_current_action_count = action_count
|
||||
undo_manager.add_do_method(self, "_do_method", object, method, args, action_count)
|
||||
|
||||
|
||||
func _do_method(object:Object, method:StringName, args:Array, this_action_count:int):
|
||||
if this_action_count >= _current_action_count:
|
||||
object.callv(method, args)
|
||||
|
||||
|
||||
func finish_action():
|
||||
_current_action_count = 0
|
1
addons/better-terrain/editor/TerrainUndo.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://ds1kcnrnbywo6
|
896
addons/better-terrain/editor/TileView.gd
Executable file
@@ -0,0 +1,896 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
signal paste_occurred
|
||||
signal change_zoom_level(value)
|
||||
signal terrain_updated(index)
|
||||
|
||||
@onready var checkerboard := get_theme_icon("Checkerboard", "EditorIcons")
|
||||
|
||||
@onready var paint_symmetry_icons := [
|
||||
null,
|
||||
preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg"),
|
||||
preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg"),
|
||||
preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg"),
|
||||
preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg"),
|
||||
preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg"),
|
||||
preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg"),
|
||||
preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg"),
|
||||
preload("res://addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg"),
|
||||
]
|
||||
|
||||
# Draw checkerboard and tiles with specific materials in
|
||||
# individual canvas items via rendering server
|
||||
var _canvas_item_map = {}
|
||||
var _canvas_item_background : RID
|
||||
|
||||
var tileset: TileSet
|
||||
var disabled_sources: Array[int] = []: set = set_disabled_sources
|
||||
|
||||
var paint := BetterTerrain.TileCategory.NON_TERRAIN
|
||||
var paint_symmetry := BetterTerrain.SymmetryType.NONE
|
||||
var highlighted_tile_part := { valid = false }
|
||||
var zoom_level := 1.0
|
||||
|
||||
var tiles_size : Vector2
|
||||
var tile_size : Vector2i
|
||||
var tile_part_size : Vector2
|
||||
var alternate_size : Vector2
|
||||
var alternate_lookup := []
|
||||
var initial_click : Vector2i
|
||||
var prev_position : Vector2i
|
||||
var current_position : Vector2i
|
||||
|
||||
var selection_start : Vector2i
|
||||
var selection_end : Vector2i
|
||||
var selection_rect : Rect2i
|
||||
var selected_tile_states : Array[Dictionary] = []
|
||||
var copied_tile_states : Array[Dictionary] = []
|
||||
var staged_paste_tile_states : Array[Dictionary] = []
|
||||
|
||||
var pick_icon_terrain : int = -1
|
||||
var pick_icon_terrain_cancel := false
|
||||
|
||||
var undo_manager : EditorUndoRedoManager
|
||||
var terrain_undo
|
||||
|
||||
# Modes for painting
|
||||
enum PaintMode {
|
||||
NO_PAINT,
|
||||
PAINT_TYPE,
|
||||
PAINT_PEERING,
|
||||
PAINT_SYMMETRY,
|
||||
SELECT,
|
||||
PASTE
|
||||
}
|
||||
|
||||
var paint_mode := PaintMode.NO_PAINT
|
||||
|
||||
# Actual interactions for painting
|
||||
enum PaintAction {
|
||||
NO_ACTION,
|
||||
DRAW_TYPE,
|
||||
ERASE_TYPE,
|
||||
DRAW_PEERING,
|
||||
ERASE_PEERING,
|
||||
DRAW_SYMMETRY,
|
||||
ERASE_SYMMETRY,
|
||||
SELECT,
|
||||
PASTE
|
||||
}
|
||||
|
||||
var paint_action := PaintAction.NO_ACTION
|
||||
|
||||
const ALTERNATE_TILE_MARGIN := 18
|
||||
|
||||
func _enter_tree() -> void:
|
||||
_canvas_item_background = RenderingServer.canvas_item_create()
|
||||
RenderingServer.canvas_item_set_parent(_canvas_item_background, get_canvas_item())
|
||||
RenderingServer.canvas_item_set_draw_behind_parent(_canvas_item_background, true)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
RenderingServer.free_rid(_canvas_item_background)
|
||||
for p in _canvas_item_map:
|
||||
RenderingServer.free_rid(_canvas_item_map[p])
|
||||
_canvas_item_map.clear()
|
||||
|
||||
|
||||
func refresh_tileset(ts: TileSet) -> void:
|
||||
tileset = ts
|
||||
|
||||
tiles_size = Vector2.ZERO
|
||||
alternate_size = Vector2.ZERO
|
||||
alternate_lookup = []
|
||||
disabled_sources = []
|
||||
|
||||
if !tileset:
|
||||
return
|
||||
|
||||
for s in tileset.get_source_count():
|
||||
var source_id := tileset.get_source_id(s)
|
||||
var source := tileset.get_source(source_id) as TileSetAtlasSource
|
||||
if !source or !source.texture:
|
||||
continue
|
||||
|
||||
tiles_size.x = max(tiles_size.x, source.texture.get_width())
|
||||
tiles_size.y += source.texture.get_height()
|
||||
|
||||
tile_size = source.texture_region_size
|
||||
tile_part_size = Vector2(tile_size) / 3.0
|
||||
|
||||
for t in source.get_tiles_count():
|
||||
var coord := source.get_tile_id(t)
|
||||
var alt_count := source.get_alternative_tiles_count(coord)
|
||||
if alt_count <= 1:
|
||||
continue
|
||||
|
||||
var rect := source.get_tile_texture_region(coord, 0)
|
||||
alternate_lookup.append([rect.size, source_id, coord])
|
||||
alternate_size.x = max(alternate_size.x, rect.size.x * (alt_count - 1))
|
||||
alternate_size.y += rect.size.y
|
||||
|
||||
_on_zoom_value_changed(zoom_level)
|
||||
|
||||
|
||||
func is_tile_in_source(source: TileSetAtlasSource, coord: Vector2i) -> bool:
|
||||
var origin := source.get_tile_at_coords(coord)
|
||||
if origin == Vector2i(-1, -1):
|
||||
return false
|
||||
|
||||
# Animation frames are not needed
|
||||
var size := source.get_tile_size_in_atlas(origin)
|
||||
return coord.x < origin.x + size.x and coord.y < origin.y + size.y
|
||||
|
||||
|
||||
func _build_tile_part_from_position(result: Dictionary, position: Vector2i, rect: Rect2) -> void:
|
||||
result.rect = rect
|
||||
var type := BetterTerrain.get_tile_terrain_type(result.data)
|
||||
if type == BetterTerrain.TileCategory.NON_TERRAIN:
|
||||
return
|
||||
result.terrain_type = type
|
||||
|
||||
var normalize_position := (Vector2(position) - rect.position) / rect.size
|
||||
|
||||
var terrain := BetterTerrain.get_terrain(tileset, type)
|
||||
if !terrain.valid:
|
||||
return
|
||||
for p in BetterTerrain.data.get_terrain_peering_cells(tileset, terrain.type):
|
||||
var side_polygon = BetterTerrain.data.peering_polygon(tileset, terrain.type, p)
|
||||
if Geometry2D.is_point_in_polygon(normalize_position, side_polygon):
|
||||
result.peering = p
|
||||
result.polygon = side_polygon
|
||||
break
|
||||
|
||||
|
||||
func tile_part_from_position(position: Vector2i) -> Dictionary:
|
||||
if !tileset:
|
||||
return { valid = false }
|
||||
|
||||
var offset := Vector2.ZERO
|
||||
var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
|
||||
if Rect2(alt_offset, zoom_level * alternate_size).has_point(position):
|
||||
for a in alternate_lookup:
|
||||
if a[1] in disabled_sources:
|
||||
continue
|
||||
var next_offset_y = alt_offset.y + zoom_level * a[0].y
|
||||
if position.y > next_offset_y:
|
||||
alt_offset.y = next_offset_y
|
||||
continue
|
||||
|
||||
var source := tileset.get_source(a[1]) as TileSetAtlasSource
|
||||
if !source:
|
||||
break
|
||||
|
||||
var count := source.get_alternative_tiles_count(a[2])
|
||||
var index := int((position.x - alt_offset.x) / (zoom_level * a[0].x)) + 1
|
||||
|
||||
if index < count:
|
||||
var alt_id := source.get_alternative_tile_id(a[2], index)
|
||||
var target_rect := Rect2(
|
||||
alt_offset + Vector2.RIGHT * (index - 1) * zoom_level * a[0].x,
|
||||
zoom_level * a[0]
|
||||
)
|
||||
|
||||
var result := {
|
||||
valid = true,
|
||||
source_id = a[1],
|
||||
coord = a[2],
|
||||
alternate = alt_id,
|
||||
data = source.get_tile_data(a[2], alt_id)
|
||||
}
|
||||
_build_tile_part_from_position(result, position, target_rect)
|
||||
return result
|
||||
|
||||
else:
|
||||
for s in tileset.get_source_count():
|
||||
var source_id := tileset.get_source_id(s)
|
||||
if source_id in disabled_sources:
|
||||
continue
|
||||
var source := tileset.get_source(source_id) as TileSetAtlasSource
|
||||
if !source || !source.texture:
|
||||
continue
|
||||
for t in source.get_tiles_count():
|
||||
var coord := source.get_tile_id(t)
|
||||
var rect := source.get_tile_texture_region(coord, 0)
|
||||
var target_rect := Rect2(offset + zoom_level * rect.position, zoom_level * rect.size)
|
||||
if !target_rect.has_point(position):
|
||||
continue
|
||||
|
||||
var result := {
|
||||
valid = true,
|
||||
source_id = source_id,
|
||||
coord = coord,
|
||||
alternate = 0,
|
||||
data = source.get_tile_data(coord, 0)
|
||||
}
|
||||
_build_tile_part_from_position(result, position, target_rect)
|
||||
return result
|
||||
|
||||
offset.y += zoom_level * source.texture.get_height()
|
||||
|
||||
return { valid = false }
|
||||
|
||||
|
||||
func tile_rect_from_position(position: Vector2i) -> Rect2:
|
||||
if !tileset:
|
||||
return Rect2(-1,-1,0,0)
|
||||
|
||||
var offset := Vector2.ZERO
|
||||
var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
|
||||
if Rect2(alt_offset, zoom_level * alternate_size).has_point(position):
|
||||
for a in alternate_lookup:
|
||||
if a[1] in disabled_sources:
|
||||
continue
|
||||
var next_offset_y = alt_offset.y + zoom_level * a[0].y
|
||||
if position.y > next_offset_y:
|
||||
alt_offset.y = next_offset_y
|
||||
continue
|
||||
|
||||
var source := tileset.get_source(a[1]) as TileSetAtlasSource
|
||||
if !source:
|
||||
break
|
||||
|
||||
var count := source.get_alternative_tiles_count(a[2])
|
||||
var index := int((position.x - alt_offset.x) / (zoom_level * a[0].x)) + 1
|
||||
|
||||
if index < count:
|
||||
var target_rect := Rect2(
|
||||
alt_offset + Vector2.RIGHT * (index - 1) * zoom_level * a[0].x,
|
||||
zoom_level * a[0]
|
||||
)
|
||||
return target_rect
|
||||
|
||||
else:
|
||||
for s in tileset.get_source_count():
|
||||
var source_id := tileset.get_source_id(s)
|
||||
if source_id in disabled_sources:
|
||||
continue
|
||||
var source := tileset.get_source(source_id) as TileSetAtlasSource
|
||||
if !source:
|
||||
continue
|
||||
for t in source.get_tiles_count():
|
||||
var coord := source.get_tile_id(t)
|
||||
var rect := source.get_tile_texture_region(coord, 0)
|
||||
var target_rect := Rect2(offset + zoom_level * rect.position, zoom_level * rect.size)
|
||||
if target_rect.has_point(position):
|
||||
return target_rect
|
||||
|
||||
offset.y += zoom_level * source.texture.get_height()
|
||||
|
||||
return Rect2(-1,-1,0,0)
|
||||
|
||||
|
||||
func tile_parts_from_rect(rect:Rect2) -> Array[Dictionary]:
|
||||
if !tileset:
|
||||
return []
|
||||
|
||||
var tiles:Array[Dictionary] = []
|
||||
|
||||
var offset := Vector2.ZERO
|
||||
var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
|
||||
for s in tileset.get_source_count():
|
||||
var source_id := tileset.get_source_id(s)
|
||||
if source_id in disabled_sources:
|
||||
continue
|
||||
var source := tileset.get_source(source_id) as TileSetAtlasSource
|
||||
if !source:
|
||||
continue
|
||||
for t in source.get_tiles_count():
|
||||
var coord := source.get_tile_id(t)
|
||||
var tile_rect := source.get_tile_texture_region(coord, 0)
|
||||
var target_rect := Rect2(offset + zoom_level * tile_rect.position, zoom_level * tile_rect.size)
|
||||
if target_rect.intersects(rect):
|
||||
var result := {
|
||||
valid = true,
|
||||
source_id = source_id,
|
||||
coord = coord,
|
||||
alternate = 0,
|
||||
data = source.get_tile_data(coord, 0)
|
||||
}
|
||||
var pos = target_rect.position + target_rect.size/2
|
||||
_build_tile_part_from_position(result, pos, target_rect)
|
||||
tiles.push_back(result)
|
||||
var alt_count := source.get_alternative_tiles_count(coord)
|
||||
for a in alt_count:
|
||||
var alt_id := 0
|
||||
if a == 0:
|
||||
continue
|
||||
|
||||
target_rect = Rect2(alt_offset + zoom_level * (a - 1) * tile_rect.size.x * Vector2.RIGHT, zoom_level * tile_rect.size)
|
||||
alt_id = source.get_alternative_tile_id(coord, a)
|
||||
if target_rect.intersects(rect):
|
||||
var td := source.get_tile_data(coord, alt_id)
|
||||
var result := {
|
||||
valid = true,
|
||||
source_id = source_id,
|
||||
coord = coord,
|
||||
alternate = alt_id,
|
||||
data = td
|
||||
}
|
||||
var pos = target_rect.position + target_rect.size/2
|
||||
_build_tile_part_from_position(result, pos, target_rect)
|
||||
tiles.push_back(result)
|
||||
if alt_count > 1:
|
||||
alt_offset.y += zoom_level * tile_rect.size.y
|
||||
|
||||
offset.y += zoom_level * source.texture.get_height()
|
||||
|
||||
return tiles
|
||||
|
||||
|
||||
func _get_canvas_item(td: TileData) -> RID:
|
||||
if !td.material:
|
||||
return self.get_canvas_item()
|
||||
if _canvas_item_map.has(td.material):
|
||||
return _canvas_item_map[td.material]
|
||||
|
||||
var rid = RenderingServer.canvas_item_create()
|
||||
RenderingServer.canvas_item_set_material(rid, td.material.get_rid())
|
||||
RenderingServer.canvas_item_set_parent(rid, get_canvas_item())
|
||||
RenderingServer.canvas_item_set_draw_behind_parent(rid, true)
|
||||
RenderingServer.canvas_item_set_default_texture_filter(rid, RenderingServer.CANVAS_ITEM_TEXTURE_FILTER_NEAREST)
|
||||
_canvas_item_map[td.material] = rid
|
||||
return rid
|
||||
|
||||
|
||||
func _draw_tile_data(texture: Texture2D, rect: Rect2, src_rect: Rect2, td: TileData, draw_sides: bool = true) -> void:
|
||||
var flipped_rect := rect
|
||||
if td.flip_h:
|
||||
flipped_rect.size.x = -rect.size.x
|
||||
if td.flip_v:
|
||||
flipped_rect.size.y = -rect.size.y
|
||||
|
||||
RenderingServer.canvas_item_add_texture_rect_region(
|
||||
_get_canvas_item(td),
|
||||
flipped_rect,
|
||||
texture.get_rid(),
|
||||
src_rect,
|
||||
td.modulate,
|
||||
td.transpose
|
||||
)
|
||||
|
||||
var type := BetterTerrain.get_tile_terrain_type(td)
|
||||
if type == BetterTerrain.TileCategory.NON_TERRAIN:
|
||||
draw_rect(rect, Color(0.1, 0.1, 0.1, 0.5), true)
|
||||
return
|
||||
|
||||
var terrain := BetterTerrain.get_terrain(tileset, type)
|
||||
if !terrain.valid:
|
||||
return
|
||||
|
||||
var transform := Transform2D(0.0, rect.size, 0.0, rect.position)
|
||||
var center_polygon = transform * BetterTerrain.data.peering_polygon(tileset, terrain.type, -1)
|
||||
draw_colored_polygon(center_polygon, Color(terrain.color, 0.6))
|
||||
if terrain.type == BetterTerrain.TerrainType.DECORATION:
|
||||
center_polygon.append(center_polygon[0])
|
||||
draw_polyline(center_polygon, Color.BLACK)
|
||||
|
||||
if paint < BetterTerrain.TileCategory.EMPTY or paint >= BetterTerrain.terrain_count(tileset):
|
||||
return
|
||||
|
||||
if not draw_sides:
|
||||
return
|
||||
|
||||
var paint_terrain := BetterTerrain.get_terrain(tileset, paint)
|
||||
for p in BetterTerrain.data.get_terrain_peering_cells(tileset, terrain.type):
|
||||
if paint in BetterTerrain.tile_peering_types(td, p):
|
||||
var side_polygon = transform * BetterTerrain.data.peering_polygon(tileset, terrain.type, p)
|
||||
draw_colored_polygon(side_polygon, Color(paint_terrain.color, 0.6))
|
||||
if paint_terrain.type == BetterTerrain.TerrainType.DECORATION:
|
||||
side_polygon.append(side_polygon[0])
|
||||
draw_polyline(side_polygon, Color.BLACK)
|
||||
|
||||
|
||||
func _draw_tile_symmetry(texture: Texture2D, rect: Rect2, src_rect: Rect2, td: TileData, draw_icon: bool = true) -> void:
|
||||
var flipped_rect := rect
|
||||
if td.flip_h:
|
||||
flipped_rect.size.x = -rect.size.x
|
||||
if td.flip_v:
|
||||
flipped_rect.size.y = -rect.size.y
|
||||
|
||||
RenderingServer.canvas_item_add_texture_rect_region(
|
||||
_get_canvas_item(td),
|
||||
flipped_rect,
|
||||
texture.get_rid(),
|
||||
src_rect,
|
||||
td.modulate,
|
||||
td.transpose
|
||||
)
|
||||
|
||||
if not draw_icon:
|
||||
return
|
||||
|
||||
var symmetry_type = BetterTerrain.get_tile_symmetry_type(td)
|
||||
if symmetry_type == 0:
|
||||
return
|
||||
var symmetry_icon = paint_symmetry_icons[symmetry_type]
|
||||
|
||||
RenderingServer.canvas_item_add_texture_rect_region(
|
||||
_get_canvas_item(td),
|
||||
rect,
|
||||
symmetry_icon.get_rid(),
|
||||
Rect2(Vector2.ZERO, symmetry_icon.get_size()),
|
||||
Color(1,1,1,0.5)
|
||||
)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if !tileset:
|
||||
return
|
||||
|
||||
# Clear material-based render targets
|
||||
RenderingServer.canvas_item_clear(_canvas_item_background)
|
||||
for p in _canvas_item_map:
|
||||
RenderingServer.canvas_item_clear(_canvas_item_map[p])
|
||||
|
||||
var offset := Vector2.ZERO
|
||||
var alt_offset := Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
|
||||
|
||||
RenderingServer.canvas_item_add_texture_rect(
|
||||
_canvas_item_background,
|
||||
Rect2(alt_offset, zoom_level * alternate_size),
|
||||
checkerboard.get_rid(),
|
||||
true
|
||||
)
|
||||
|
||||
for s in tileset.get_source_count():
|
||||
var source_id := tileset.get_source_id(s)
|
||||
if source_id in disabled_sources:
|
||||
continue
|
||||
var source := tileset.get_source(source_id) as TileSetAtlasSource
|
||||
if !source or !source.texture:
|
||||
continue
|
||||
|
||||
RenderingServer.canvas_item_add_texture_rect(
|
||||
_canvas_item_background,
|
||||
Rect2(offset, zoom_level * source.texture.get_size()),
|
||||
checkerboard.get_rid(),
|
||||
true
|
||||
)
|
||||
for t in source.get_tiles_count():
|
||||
var coord := source.get_tile_id(t)
|
||||
var rect := source.get_tile_texture_region(coord, 0)
|
||||
var alt_count := source.get_alternative_tiles_count(coord)
|
||||
var target_rect : Rect2
|
||||
for a in alt_count:
|
||||
var alt_id := 0
|
||||
if a == 0:
|
||||
target_rect = Rect2(offset + zoom_level * rect.position, zoom_level * rect.size)
|
||||
else:
|
||||
target_rect = Rect2(alt_offset + zoom_level * (a - 1) * rect.size.x * Vector2.RIGHT, zoom_level * rect.size)
|
||||
alt_id = source.get_alternative_tile_id(coord, a)
|
||||
|
||||
var td := source.get_tile_data(coord, alt_id)
|
||||
var drawing_current = BetterTerrain.get_tile_terrain_type(td) == paint
|
||||
if paint_mode == PaintMode.PAINT_SYMMETRY:
|
||||
_draw_tile_symmetry(source.texture, target_rect, rect, td, drawing_current)
|
||||
else:
|
||||
_draw_tile_data(source.texture, target_rect, rect, td)
|
||||
|
||||
if drawing_current:
|
||||
draw_rect(target_rect.grow(-1), Color(0,0,0, 0.75), false, 1)
|
||||
draw_rect(target_rect, Color(1,1,1, 0.75), false, 1)
|
||||
|
||||
if paint_mode == PaintMode.SELECT:
|
||||
if selected_tile_states.any(func(v):
|
||||
return v.part.data == td
|
||||
):
|
||||
draw_rect(target_rect.grow(-1), Color.DEEP_SKY_BLUE, false, 2)
|
||||
|
||||
if alt_count > 1:
|
||||
alt_offset.y += zoom_level * rect.size.y
|
||||
|
||||
# Blank out unused or uninteresting tiles
|
||||
var size := source.get_atlas_grid_size()
|
||||
for y in size.y:
|
||||
for x in size.x:
|
||||
var pos := Vector2i(x, y)
|
||||
if !is_tile_in_source(source, pos):
|
||||
var atlas_pos := source.margins + pos * (source.separation + source.texture_region_size)
|
||||
draw_rect(Rect2(offset + zoom_level * atlas_pos, zoom_level * source.texture_region_size), Color(0.0, 0.0, 0.0, 0.8), true)
|
||||
|
||||
offset.y += zoom_level * source.texture.get_height()
|
||||
|
||||
# Blank out unused alternate tile sections
|
||||
alt_offset = Vector2.RIGHT * (zoom_level * tiles_size.x + ALTERNATE_TILE_MARGIN)
|
||||
for a in alternate_lookup:
|
||||
if a[1] in disabled_sources:
|
||||
continue
|
||||
var source := tileset.get_source(a[1]) as TileSetAtlasSource
|
||||
if source:
|
||||
var count := source.get_alternative_tiles_count(a[2]) - 1
|
||||
var occupied_width = count * zoom_level * a[0].x
|
||||
var area := Rect2(
|
||||
alt_offset.x + occupied_width,
|
||||
alt_offset.y,
|
||||
zoom_level * alternate_size.x - occupied_width,
|
||||
zoom_level * a[0].y
|
||||
)
|
||||
draw_rect(area, Color(0.0, 0.0, 0.0, 0.8), true)
|
||||
alt_offset.y += zoom_level * a[0].y
|
||||
|
||||
if highlighted_tile_part.valid:
|
||||
if paint_mode == PaintMode.PAINT_PEERING and highlighted_tile_part.has("polygon"):
|
||||
var transform := Transform2D(0.0, highlighted_tile_part.rect.size - 2 * Vector2.ONE, 0.0, highlighted_tile_part.rect.position + Vector2.ONE)
|
||||
draw_colored_polygon(transform * highlighted_tile_part.polygon, Color(Color.WHITE, 0.2))
|
||||
if paint_mode != PaintMode.NO_PAINT:
|
||||
var inner_rect := Rect2(highlighted_tile_part.rect.position + Vector2.ONE, highlighted_tile_part.rect.size - 2 * Vector2.ONE)
|
||||
draw_rect(inner_rect, Color.WHITE, false)
|
||||
if paint_mode == PaintMode.PAINT_SYMMETRY:
|
||||
if paint_symmetry > 0:
|
||||
var symmetry_icon = paint_symmetry_icons[paint_symmetry]
|
||||
draw_texture_rect(symmetry_icon, highlighted_tile_part.rect, false, Color(0.5,0.75,1,0.5))
|
||||
|
||||
if paint_mode == PaintMode.SELECT:
|
||||
draw_rect(selection_rect, Color.WHITE, false)
|
||||
|
||||
if paint_mode == PaintMode.PASTE:
|
||||
if staged_paste_tile_states.size() > 0:
|
||||
var base_rect = staged_paste_tile_states[0].base_rect
|
||||
var paint_terrain := BetterTerrain.get_terrain(tileset, paint)
|
||||
var paint_terrain_type = paint_terrain.type
|
||||
if paint_terrain_type == BetterTerrain.TerrainType.CATEGORY:
|
||||
paint_terrain_type = 0
|
||||
for state in staged_paste_tile_states:
|
||||
var staged_rect:Rect2 = state.base_rect
|
||||
staged_rect.position -= base_rect.position + base_rect.size / 2
|
||||
|
||||
staged_rect.position *= zoom_level
|
||||
staged_rect.size *= zoom_level
|
||||
|
||||
staged_rect.position += Vector2(current_position)
|
||||
|
||||
var real_rect = tile_rect_from_position(staged_rect.get_center())
|
||||
if real_rect.position.x >= 0:
|
||||
draw_rect(real_rect, Color(0,0,0, 0.3), true)
|
||||
var transform := Transform2D(0.0, real_rect.size, 0.0, real_rect.position)
|
||||
var tile_sides = BetterTerrain.data.get_terrain_peering_cells(tileset, paint_terrain_type)
|
||||
for p in tile_sides:
|
||||
if state.paint in BetterTerrain.tile_peering_types(state.part.data, p):
|
||||
var side_polygon = BetterTerrain.data.peering_polygon(tileset, paint_terrain_type, p)
|
||||
var color = Color(paint_terrain.color, 0.6)
|
||||
draw_colored_polygon(transform * side_polygon, color)
|
||||
|
||||
draw_rect(staged_rect, Color.DEEP_PINK, false)
|
||||
|
||||
|
||||
|
||||
func delete_selection():
|
||||
undo_manager.create_action("Delete tile terrain peering types", UndoRedo.MERGE_DISABLE, tileset)
|
||||
for t in selected_tile_states:
|
||||
for side in range(16):
|
||||
var old_peering = BetterTerrain.tile_peering_types(t.part.data, side)
|
||||
if old_peering.has(paint):
|
||||
undo_manager.add_do_method(BetterTerrain, &"remove_tile_peering_type", tileset, t.part.data, side, paint)
|
||||
undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, t.part.data, side, paint)
|
||||
|
||||
undo_manager.add_do_method(self, &"queue_redraw")
|
||||
undo_manager.add_undo_method(self, &"queue_redraw")
|
||||
undo_manager.commit_action()
|
||||
|
||||
|
||||
func toggle_selection():
|
||||
undo_manager.create_action("Toggle tile terrain", UndoRedo.MERGE_DISABLE, tileset, true)
|
||||
for t in selected_tile_states:
|
||||
var type := BetterTerrain.get_tile_terrain_type(t.part.data)
|
||||
var goal := paint if paint != type else BetterTerrain.TileCategory.NON_TERRAIN
|
||||
|
||||
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_terrain_type", [tileset, t.part.data, goal])
|
||||
if goal == BetterTerrain.TileCategory.NON_TERRAIN:
|
||||
terrain_undo.create_peering_restore_point_tile(
|
||||
undo_manager,
|
||||
tileset,
|
||||
t.part.source_id,
|
||||
t.part.coord,
|
||||
t.part.alternate
|
||||
)
|
||||
else:
|
||||
undo_manager.add_undo_method(BetterTerrain, &"set_tile_terrain_type", tileset, t.part.data, type)
|
||||
|
||||
terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
|
||||
undo_manager.add_undo_method(self, &"queue_redraw")
|
||||
undo_manager.commit_action()
|
||||
terrain_undo.action_count += 1
|
||||
|
||||
|
||||
func copy_selection():
|
||||
copied_tile_states = selected_tile_states
|
||||
|
||||
|
||||
func paste_selection():
|
||||
staged_paste_tile_states = copied_tile_states
|
||||
selected_tile_states = []
|
||||
paint_mode = PaintMode.PASTE
|
||||
paint_action = PaintAction.PASTE
|
||||
paste_occurred.emit()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func set_disabled_sources(list):
|
||||
disabled_sources = list
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func emit_terrain_updated(index):
|
||||
terrain_updated.emit(index)
|
||||
|
||||
|
||||
func _gui_input(event) -> void:
|
||||
if event is InputEventKey and event.is_pressed():
|
||||
if event.keycode == KEY_DELETE and not event.echo:
|
||||
accept_event()
|
||||
delete_selection()
|
||||
if event.keycode == KEY_ENTER and not event.echo:
|
||||
accept_event()
|
||||
toggle_selection()
|
||||
if event.keycode == KEY_ESCAPE and not event.echo:
|
||||
accept_event()
|
||||
if paint_action == PaintAction.PASTE:
|
||||
staged_paste_tile_states = []
|
||||
paint_mode = PaintMode.SELECT
|
||||
paint_action = PaintAction.NO_ACTION
|
||||
selection_start = Vector2i(-1,-1)
|
||||
if event.keycode == KEY_C and (event.ctrl_pressed or event.meta_pressed) and not event.echo:
|
||||
accept_event()
|
||||
copy_selection()
|
||||
if event.keycode == KEY_X and (event.ctrl_pressed or event.meta_pressed) and not event.echo:
|
||||
accept_event()
|
||||
copy_selection()
|
||||
delete_selection()
|
||||
if event.keycode == KEY_V and (event.ctrl_pressed or event.meta_pressed) and not event.echo:
|
||||
accept_event()
|
||||
paste_selection()
|
||||
if event is InputEventMouseButton:
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_UP and (event.ctrl_pressed or event.meta_pressed):
|
||||
accept_event()
|
||||
change_zoom_level.emit(zoom_level * 1.1)
|
||||
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN and (event.ctrl_pressed or event.meta_pressed):
|
||||
accept_event()
|
||||
change_zoom_level.emit(zoom_level / 1.1)
|
||||
|
||||
var released : bool = event is InputEventMouseButton and (not event.pressed and (event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT))
|
||||
if released:
|
||||
paint_action = PaintAction.NO_ACTION
|
||||
|
||||
if event is InputEventMouseMotion:
|
||||
prev_position = current_position
|
||||
current_position = event.position
|
||||
var tile := tile_part_from_position(event.position)
|
||||
if tile.valid != highlighted_tile_part.valid or\
|
||||
(tile.valid and tile.data != highlighted_tile_part.data) or\
|
||||
(tile.valid and tile.get("peering") != highlighted_tile_part.get("peering")) or\
|
||||
event.button_mask & MOUSE_BUTTON_LEFT and paint_action == PaintAction.SELECT:
|
||||
queue_redraw()
|
||||
highlighted_tile_part = tile
|
||||
|
||||
var clicked : bool = event is InputEventMouseButton and (event.pressed and (event.button_index == MOUSE_BUTTON_LEFT or event.button_index == MOUSE_BUTTON_RIGHT))
|
||||
if clicked:
|
||||
initial_click = current_position
|
||||
selection_start = Vector2i(-1,-1)
|
||||
terrain_undo.action_index += 1
|
||||
terrain_undo.action_count = 0
|
||||
if released:
|
||||
terrain_undo.finish_action()
|
||||
selection_rect = Rect2i(0,0,0,0)
|
||||
queue_redraw()
|
||||
|
||||
if paint_action == PaintAction.PASTE:
|
||||
if event is InputEventMouseMotion:
|
||||
queue_redraw()
|
||||
|
||||
if clicked:
|
||||
if event.button_index == MOUSE_BUTTON_LEFT and staged_paste_tile_states.size() > 0:
|
||||
undo_manager.create_action("Paste tile terrain peering types", UndoRedo.MERGE_DISABLE, tileset)
|
||||
var base_rect = staged_paste_tile_states[0].base_rect
|
||||
for p in staged_paste_tile_states:
|
||||
var staged_rect:Rect2 = p.base_rect
|
||||
staged_rect.position -= base_rect.position + base_rect.size / 2
|
||||
|
||||
staged_rect.position *= zoom_level
|
||||
staged_rect.size *= zoom_level
|
||||
|
||||
staged_rect.position += Vector2(current_position)
|
||||
|
||||
var old_tile_part = tile_part_from_position(staged_rect.get_center())
|
||||
var new_tile_state = p
|
||||
if (not old_tile_part.valid) or (not new_tile_state.part.valid):
|
||||
continue
|
||||
|
||||
for side in range(16):
|
||||
var old_peering = BetterTerrain.tile_peering_types(old_tile_part.data, side)
|
||||
var new_sides = new_tile_state.sides
|
||||
if new_sides.has(side) and not old_peering.has(paint):
|
||||
undo_manager.add_do_method(BetterTerrain, &"add_tile_peering_type", tileset, old_tile_part.data, side, paint)
|
||||
undo_manager.add_undo_method(BetterTerrain, &"remove_tile_peering_type", tileset, old_tile_part.data, side, paint)
|
||||
elif old_peering.has(paint) and not new_sides.has(side):
|
||||
undo_manager.add_do_method(BetterTerrain, &"remove_tile_peering_type", tileset, old_tile_part.data, side, paint)
|
||||
undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, old_tile_part.data, side, paint)
|
||||
|
||||
var old_symmetry = BetterTerrain.get_tile_symmetry_type(old_tile_part.data)
|
||||
var new_symmetry = new_tile_state.symmetry
|
||||
if new_symmetry != old_symmetry:
|
||||
undo_manager.add_do_method(BetterTerrain, &"set_tile_symmetry_type", tileset, old_tile_part.data, new_symmetry)
|
||||
undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, old_tile_part.data, old_symmetry)
|
||||
|
||||
undo_manager.add_do_method(self, &"queue_redraw")
|
||||
undo_manager.add_undo_method(self, &"queue_redraw")
|
||||
undo_manager.commit_action()
|
||||
|
||||
staged_paste_tile_states = []
|
||||
paint_mode = PaintMode.SELECT
|
||||
paint_action = PaintAction.SELECT
|
||||
return
|
||||
|
||||
if clicked and pick_icon_terrain >= 0:
|
||||
highlighted_tile_part = tile_part_from_position(current_position)
|
||||
if !highlighted_tile_part.valid:
|
||||
return
|
||||
|
||||
var t = BetterTerrain.get_terrain(tileset, paint)
|
||||
var prev_icon = t.icon.duplicate()
|
||||
var icon = {
|
||||
source_id = highlighted_tile_part.source_id,
|
||||
coord = highlighted_tile_part.coord
|
||||
}
|
||||
undo_manager.create_action("Edit terrain details", UndoRedo.MERGE_DISABLE, tileset)
|
||||
undo_manager.add_do_method(BetterTerrain, &"set_terrain", tileset, paint, t.name, t.color, t.type, t.categories, icon)
|
||||
undo_manager.add_do_method(self, &"emit_terrain_updated", paint)
|
||||
undo_manager.add_undo_method(BetterTerrain, &"set_terrain", tileset, paint, t.name, t.color, t.type, t.categories, prev_icon)
|
||||
undo_manager.add_undo_method(self, &"emit_terrain_updated", paint)
|
||||
undo_manager.commit_action()
|
||||
pick_icon_terrain = -1
|
||||
return
|
||||
|
||||
if pick_icon_terrain_cancel:
|
||||
pick_icon_terrain = -1
|
||||
pick_icon_terrain_cancel = false
|
||||
|
||||
if paint != BetterTerrain.TileCategory.NON_TERRAIN and clicked:
|
||||
paint_action = PaintAction.NO_ACTION
|
||||
if highlighted_tile_part.valid:
|
||||
match [paint_mode, event.button_index]:
|
||||
[PaintMode.PAINT_TYPE, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_TYPE
|
||||
[PaintMode.PAINT_TYPE, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_TYPE
|
||||
[PaintMode.PAINT_PEERING, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_PEERING
|
||||
[PaintMode.PAINT_PEERING, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_PEERING
|
||||
[PaintMode.PAINT_SYMMETRY, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.DRAW_SYMMETRY
|
||||
[PaintMode.PAINT_SYMMETRY, MOUSE_BUTTON_RIGHT]: paint_action = PaintAction.ERASE_SYMMETRY
|
||||
[PaintMode.SELECT, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.SELECT
|
||||
else:
|
||||
match [paint_mode, event.button_index]:
|
||||
[PaintMode.SELECT, MOUSE_BUTTON_LEFT]: paint_action = PaintAction.SELECT
|
||||
|
||||
if (clicked or event is InputEventMouseMotion) and paint_action != PaintAction.NO_ACTION:
|
||||
|
||||
if paint_action == PaintAction.SELECT:
|
||||
if clicked:
|
||||
selection_start = Vector2i(-1,-1)
|
||||
queue_redraw()
|
||||
if selection_start.x < 0:
|
||||
selection_start = current_position
|
||||
selection_end = current_position
|
||||
|
||||
selection_rect = Rect2i(selection_start, selection_end - selection_start).abs()
|
||||
var selected_tile_parts = tile_parts_from_rect(selection_rect)
|
||||
selected_tile_states = []
|
||||
for t in selected_tile_parts:
|
||||
var state := {
|
||||
part = t,
|
||||
base_rect = Rect2(t.rect.position / zoom_level, t.rect.size / zoom_level),
|
||||
paint = paint,
|
||||
sides = BetterTerrain.tile_peering_for_type(t.data, paint),
|
||||
symmetry = BetterTerrain.get_tile_symmetry_type(t.data)
|
||||
}
|
||||
selected_tile_states.push_back(state)
|
||||
else:
|
||||
if !highlighted_tile_part.valid:
|
||||
return
|
||||
#slightly crude and non-optimal but way simpler than the "correct" solution
|
||||
var current_position_vec2 = Vector2(current_position)
|
||||
var prev_position_vec2 = Vector2(prev_position)
|
||||
var mouse_dist = current_position_vec2.distance_to(prev_position_vec2)
|
||||
var step_size = (tile_part_size.x * zoom_level)
|
||||
var steps = ceil(mouse_dist / step_size) + 1
|
||||
for i in range(steps):
|
||||
var t = float(i) / steps
|
||||
var check_position = prev_position_vec2.lerp(current_position_vec2, t)
|
||||
highlighted_tile_part = tile_part_from_position(check_position)
|
||||
|
||||
if !highlighted_tile_part.valid:
|
||||
continue
|
||||
|
||||
if paint_action == PaintAction.DRAW_TYPE or paint_action == PaintAction.ERASE_TYPE:
|
||||
var type := BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data)
|
||||
var goal := paint if paint_action == PaintAction.DRAW_TYPE else BetterTerrain.TileCategory.NON_TERRAIN
|
||||
if type != goal:
|
||||
undo_manager.create_action("Set tile terrain type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
|
||||
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_terrain_type", [tileset, highlighted_tile_part.data, goal])
|
||||
terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
|
||||
if goal == BetterTerrain.TileCategory.NON_TERRAIN:
|
||||
terrain_undo.create_peering_restore_point_tile(
|
||||
undo_manager,
|
||||
tileset,
|
||||
highlighted_tile_part.source_id,
|
||||
highlighted_tile_part.coord,
|
||||
highlighted_tile_part.alternate
|
||||
)
|
||||
else:
|
||||
undo_manager.add_undo_method(BetterTerrain, &"set_tile_terrain_type", tileset, highlighted_tile_part.data, type)
|
||||
undo_manager.add_undo_method(self, &"queue_redraw")
|
||||
undo_manager.commit_action()
|
||||
terrain_undo.action_count += 1
|
||||
elif paint_action == PaintAction.DRAW_PEERING:
|
||||
if highlighted_tile_part.has("peering"):
|
||||
if !(paint in BetterTerrain.tile_peering_types(highlighted_tile_part.data, highlighted_tile_part.peering)):
|
||||
undo_manager.create_action("Set tile terrain peering type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
|
||||
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"add_tile_peering_type", [tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint])
|
||||
terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
|
||||
undo_manager.add_undo_method(BetterTerrain, &"remove_tile_peering_type", tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint)
|
||||
undo_manager.add_undo_method(self, &"queue_redraw")
|
||||
undo_manager.commit_action()
|
||||
terrain_undo.action_count += 1
|
||||
elif paint_action == PaintAction.ERASE_PEERING:
|
||||
if highlighted_tile_part.has("peering"):
|
||||
if paint in BetterTerrain.tile_peering_types(highlighted_tile_part.data, highlighted_tile_part.peering):
|
||||
undo_manager.create_action("Remove tile terrain peering type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
|
||||
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"remove_tile_peering_type", [tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint])
|
||||
terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
|
||||
undo_manager.add_undo_method(BetterTerrain, &"add_tile_peering_type", tileset, highlighted_tile_part.data, highlighted_tile_part.peering, paint)
|
||||
undo_manager.add_undo_method(self, &"queue_redraw")
|
||||
undo_manager.commit_action()
|
||||
terrain_undo.action_count += 1
|
||||
elif paint_action == PaintAction.DRAW_SYMMETRY:
|
||||
if paint == BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data):
|
||||
undo_manager.create_action("Set tile symmetry type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
|
||||
var old_symmetry = BetterTerrain.get_tile_symmetry_type(highlighted_tile_part.data)
|
||||
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_symmetry_type", [tileset, highlighted_tile_part.data, paint_symmetry])
|
||||
terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
|
||||
undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, highlighted_tile_part.data, old_symmetry)
|
||||
undo_manager.add_undo_method(self, &"queue_redraw")
|
||||
undo_manager.commit_action()
|
||||
terrain_undo.action_count += 1
|
||||
elif paint_action == PaintAction.ERASE_SYMMETRY:
|
||||
if paint == BetterTerrain.get_tile_terrain_type(highlighted_tile_part.data):
|
||||
undo_manager.create_action("Remove tile symmetry type " + str(terrain_undo.action_index), UndoRedo.MERGE_ALL, tileset, true)
|
||||
var old_symmetry = BetterTerrain.get_tile_symmetry_type(highlighted_tile_part.data)
|
||||
terrain_undo.add_do_method(undo_manager, BetterTerrain, &"set_tile_symmetry_type", [tileset, highlighted_tile_part.data, BetterTerrain.SymmetryType.NONE])
|
||||
terrain_undo.add_do_method(undo_manager, self, &"queue_redraw", [])
|
||||
undo_manager.add_undo_method(BetterTerrain, &"set_tile_symmetry_type", tileset, highlighted_tile_part.data, old_symmetry)
|
||||
undo_manager.add_undo_method(self, &"queue_redraw")
|
||||
undo_manager.commit_action()
|
||||
terrain_undo.action_count += 1
|
||||
|
||||
|
||||
func _on_zoom_value_changed(value) -> void:
|
||||
zoom_level = value
|
||||
custom_minimum_size.x = zoom_level * tiles_size.x
|
||||
if alternate_size.x > 0:
|
||||
custom_minimum_size.x += ALTERNATE_TILE_MARGIN + zoom_level * alternate_size.x
|
||||
custom_minimum_size.y = zoom_level * max(tiles_size.y, alternate_size.y)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func clear_highlighted_tile() -> void:
|
||||
highlighted_tile_part = { valid = false }
|
||||
queue_redraw()
|
1
addons/better-terrain/editor/TileView.gd.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://cpm7dq6r0n0sn
|
1
addons/better-terrain/icon.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg height="128" width="128" xmlns="http://www.w3.org/2000/svg"><path d="m83.636719 67.794922a20 20 0 0 1 -15.636719 15.800781v44.404297h40c11.08 0 20-8.92 20-20v-40.205078z" fill="#169318"/><path d="m0 67.794922v40.205078c0 11.08 8.92 20 20 20h40v-44.404297a20 20 0 0 1 -15.636719-15.800781z" fill="#993d16"/><path d="m68 0v44.404297a20 20 0 0 1 15.552734 15.390625h44.447266v-39.794922c0-11.08-8.92-20-20-20z" fill="#993d16"/><path d="m20 0c-11.08 0-20 8.92-20 20v39.794922h44.447266a20 20 0 0 1 15.552734-15.390625v-44.404297z" fill="#169318"/></svg>
|
After Width: | Height: | Size: 554 B |
43
addons/better-terrain/icon.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c66nal373iwgd"
|
||||
path="res://.godot/imported/icon.svg-7d4870855c0daec5051feb4adbea0091.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icon.svg"
|
||||
dest_files=["res://.godot/imported/icon.svg-7d4870855c0daec5051feb4adbea0091.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
7
addons/better-terrain/icons/Decoration.svg
Executable file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="6" y="6" width="4" height="4" fill="white"/>
|
||||
<path d="M4.5 6.5L1.5 9.5M1.5 6.5L4.5 9.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.5 6.5L11.5 9.5M11.5 6.5L14.5 9.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 1.5L6.5 4.5M6.5 1.5L9.5 4.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.5 11.5L6.5 14.5M6.5 11.5L9.5 14.5" stroke="white" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 593 B |
44
addons/better-terrain/icons/Decoration.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://kmypxsqhynyv"
|
||||
path="res://.godot/imported/Decoration.svg-03773e83cc849c7744ecf3d36eee0072.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/Decoration.svg"
|
||||
dest_files=["res://.godot/imported/Decoration.svg-03773e83cc849c7744ecf3d36eee0072.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
12
addons/better-terrain/icons/EditSymmetry.svg
Executable file
@@ -0,0 +1,12 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_952_3274)">
|
||||
<path d="M8.68988 11.1648L12.7626 15.2375C13.446 15.9209 14.554 15.9209 15.2374 15.2375C15.9208 14.5541 15.9208 13.446 15.2374 12.7626L11.1648 8.68994C10.5815 9.72354 9.72348 10.5815 8.68988 11.1648Z" fill="#E0E0E0"/>
|
||||
<path d="M11 1C11 0.447715 10.5523 0 10 0H7C6.44772 0 6 0.447715 6 1V4C6 4.55228 6.44772 5 7 5C7.55228 5 8 4.55228 8 4V3.05033C8.61889 3.68203 9 4.54703 9 5.50004C9 7.26328 7.69615 8.72198 6 8.96459V10.9776C8.80325 10.725 11 8.36906 11 5.50004C11 4.16979 10.5279 2.95059 9.74266 2H10C10.5523 2 11 1.55228 11 1Z" fill="#E0E0E0"/>
|
||||
<path d="M0 10C0 10.5523 0.447715 11 1 11H4C4.55228 11 5 10.5523 5 10V7C5 6.44772 4.55228 6 4 6C3.44772 6 3 6.44772 3 7V7.94975C2.38111 7.31805 2 6.45305 2 5.50004C2 3.7368 3.30385 2.2781 5 2.03548V0.0224609C2.19675 0.275075 0 2.63102 0 5.50004C0 6.83026 0.472062 8.04943 1.25727 9H1C0.447715 9 0 9.44771 0 10Z" fill="#E0E0E0"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_952_3274">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
44
addons/better-terrain/icons/EditSymmetry.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://co6gwwmog0pjy"
|
||||
path="res://.godot/imported/EditSymmetry.svg-794172208a8d86bb609531b82199f095.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/EditSymmetry.svg"
|
||||
dest_files=["res://.godot/imported/EditSymmetry.svg-794172208a8d86bb609531b82199f095.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/better-terrain/icons/EditTerrain.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1 1v14h14v-14zm1 1h12v12h-12zm1 1v2.6660156h2.6660156v-2.6660156zm3.6679688 0v2.6660156h2.6640624v-2.6660156zm3.6660152 0v2.6660156h2.666016v-2.6660156zm-7.333984 3.6660156v2.6660156h2.6660156v-2.6660156zm7.333984 0v2.6660156h2.666016v-2.6660156zm-7.333984 3.6679684v2.666016h2.6660156v-2.666016zm3.6679688 0v2.666016h2.6640624v-2.666016zm3.6660152 0v2.666016h2.666016v-2.666016z" fill="#e0e0e0"/></svg>
|
After Width: | Height: | Size: 496 B |
44
addons/better-terrain/icons/EditTerrain.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bo2cjv08jkvf8"
|
||||
path="res://.godot/imported/EditTerrain.svg-f7ee950d68a391de33e4e8ddd76bf2ac.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/EditTerrain.svg"
|
||||
dest_files=["res://.godot/imported/EditTerrain.svg-f7ee950d68a391de33e4e8ddd76bf2ac.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/better-terrain/icons/EditType.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1 1v14h14v-14zm1 1h12v12h-12zm4.6679688 4.6660156v2.6660156h2.6640624v-2.6660156z" fill="#e0e0e0"/></svg>
|
After Width: | Height: | Size: 198 B |
44
addons/better-terrain/icons/EditType.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c6lxq2y7mpb18"
|
||||
path="res://.godot/imported/EditType.svg-e7b3005c6a8f21d5102295c55b564ad1.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/EditType.svg"
|
||||
dest_files=["res://.godot/imported/EditType.svg-e7b3005c6a8f21d5102295c55b564ad1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/better-terrain/icons/MatchTiles.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1 1v4h4v-4zm5 0v4h4v-4zm5 0v4h4v-4zm-10 5v4h4v-4zm5 0v4h4v-4zm5 0v4h4v-4zm-10 5v4h4v-4zm5 0v4h4v-4zm5 0v4h4v-4z" fill="#fff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7.55906"/></svg>
|
After Width: | Height: | Size: 295 B |
44
addons/better-terrain/icons/MatchTiles.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d1h1p7pcwdnjk"
|
||||
path="res://.godot/imported/MatchTiles.svg-38111e21a893bd8f161311f0d1968a40.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/MatchTiles.svg"
|
||||
dest_files=["res://.godot/imported/MatchTiles.svg-38111e21a893bd8f161311f0d1968a40.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/better-terrain/icons/MatchVertices.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m1 1v6l6-6zm8 0 6 6v-6zm-1 1-6 6 6 6 6-6zm-7 7v6h6zm14 0-6 6h6z" fill="#fff" stroke-width="3.77952"/></svg>
|
After Width: | Height: | Size: 199 B |
44
addons/better-terrain/icons/MatchVertices.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dfemy1g6okwlv"
|
||||
path="res://.godot/imported/MatchVertices.svg-288fe47ee1089920379407d6abf1a06c.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/MatchVertices.svg"
|
||||
dest_files=["res://.godot/imported/MatchVertices.svg-288fe47ee1089920379407d6abf1a06c.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/better-terrain/icons/NonModifying.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3 1-2 2v10l2 2h10l2-2v-10l-2-2zm2.1992188 3.171875a1 1 0 0 1 .6796874.2929688l2.1210938 2.1210937 2.121094-2.1210937a1 1 0 0 1 1.414062 0 1 1 0 0 1 0 1.4140624l-2.1210935 2.1210938 2.1210935 2.121094a1 1 0 0 1 0 1.414062 1 1 0 0 1 -1.414062 0l-2.121094-2.1210935-2.1210938 2.1210935a1 1 0 0 1 -1.4140624 0 1 1 0 0 1 0-1.414062l2.1210937-2.121094-2.1210937-2.1210938a1 1 0 0 1 0-1.4140624 1 1 0 0 1 .734375-.2929688z" fill="#fffaff"/></svg>
|
After Width: | Height: | Size: 532 B |
44
addons/better-terrain/icons/NonModifying.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://1yr6yruwl63u"
|
||||
path="res://.godot/imported/NonModifying.svg-4d16d471be4a8f1d3ba0c013ff629ee1.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/NonModifying.svg"
|
||||
dest_files=["res://.godot/imported/NonModifying.svg-4d16d471be4a8f1d3ba0c013ff629ee1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
8
addons/better-terrain/icons/Replace.svg
Executable file
@@ -0,0 +1,8 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="10.5" y="1.5" width="4" height="4" stroke="#D9D9D9"/>
|
||||
<rect x="1" y="10" width="5" height="5" fill="#D9D9D9"/>
|
||||
<path d="M7.5 3.5H5.5C4.39543 3.5 3.5 4.39543 3.5 5.5V6.5" stroke="#E0E0E0" stroke-linecap="square"/>
|
||||
<path d="M8.5 12.5H10.5C11.6046 12.5 12.5 11.6046 12.5 10.5V9.5" stroke="#E0E0E0" stroke-linecap="square"/>
|
||||
<path d="M10 10L12.5 7.5L15 10H10Z" fill="#E0E0E0"/>
|
||||
<path d="M6 6L3.5 8.5L1 6H6Z" fill="#E0E0E0"/>
|
||||
</svg>
|
After Width: | Height: | Size: 533 B |
44
addons/better-terrain/icons/Replace.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://y3xy6qdckht6"
|
||||
path="res://.godot/imported/Replace.svg-7654df79fd42fc27133e4d3f81a4d56b.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/Replace.svg"
|
||||
dest_files=["res://.godot/imported/Replace.svg-7654df79fd42fc27133e4d3f81a4d56b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
12
addons/better-terrain/icons/ShuffleRandom.svg
Executable file
@@ -0,0 +1,12 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_513_3269)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.58578 3.4144C7.24015 2.76003 8.23514 2.6539 9 3.09599V4C9 4.80892 9.48728 5.5382 10.2346 5.84776C10.7544 6.06305 11.3288 6.04585 11.8228 5.82299L12.5858 6.58597C13.3668 7.36701 13.3668 8.63334 12.5858 9.41439L9.41421 12.586C8.75985 13.2403 7.76486 13.3465 7 12.9044V12C7 11.1911 6.51272 10.4618 5.76537 10.1522C5.24551 9.93691 4.67104 9.95416 4.17695 10.1771L3.41422 9.41439C2.63317 8.63334 2.63317 7.36701 3.41422 6.58597L6.58578 3.4144ZM8 9.00018C8.55229 9.00018 9 8.55246 9 8.00018C9 7.44789 8.55229 7.00018 8 7.00018C7.44772 7.00018 7 7.44789 7 8.00018C7 8.55246 7.44772 9.00018 8 9.00018ZM6 8.00018C6 8.55246 5.55229 9.00018 5 9.00018C4.44772 9.00018 4 8.55246 4 8.00018C4 7.44789 4.44772 7.00018 5 7.00018C5.55229 7.00018 6 7.44789 6 8.00018ZM11 9.00018C11.5523 9.00018 12 8.55246 12 8.00018C12 7.44789 11.5523 7.00018 11 7.00018C10.4477 7.00018 10 7.44789 10 8.00018C10 8.55246 10.4477 9.00018 11 9.00018Z" fill="white"/>
|
||||
<path d="M5.42909 2.57732C5.92795 2.34034 6.14024 1.74383 5.90326 1.24497C5.66628 0.746113 5.06976 0.53382 4.57091 0.770801C1.87043 2.05366 0 4.80762 0 8.00043C0 10.1046 0.812938 12.0189 2.13978 13.4462L1.29289 14.2931C1.0069 14.5791 0.92134 15.0092 1.07612 15.3829C1.2309 15.7566 1.59554 16.0002 2 16.0002H5C5.55228 16.0002 6 15.5525 6 15.0002V12.0002C6 11.5958 5.75636 11.2311 5.38268 11.0764C5.00901 10.9216 4.57889 11.0071 4.29289 11.2931L3.55511 12.0309C2.58795 10.9651 2 9.5514 2 8.00043C2 5.60845 3.39967 3.5414 5.42909 2.57732Z" fill="white"/>
|
||||
<path d="M11 0C10.4477 0 9.99999 0.447715 9.99999 1V4C9.99999 4.40446 10.2436 4.7691 10.6173 4.92388C10.991 5.07866 11.4211 4.9931 11.7071 4.70711L12.4448 3.96939C13.4119 5.03511 14 6.44912 14 8C14 10.392 12.6003 12.459 10.5709 13.4231C10.072 13.6601 9.85975 14.2566 10.0967 14.7555C10.3337 15.2543 10.9302 15.4666 11.4291 15.2296C14.1296 13.9468 16 11.1928 16 8C16 5.8956 15.1871 3.98156 13.8601 2.55412L14.7071 1.70711C14.9931 1.42111 15.0787 0.990991 14.9239 0.617317C14.7691 0.243642 14.4045 0 14 0H11Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_513_3269">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
44
addons/better-terrain/icons/ShuffleRandom.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cs4mdmluiydj6"
|
||||
path="res://.godot/imported/ShuffleRandom.svg-15ee49f7a06c55a1e95e1ed056732dc5.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/ShuffleRandom.svg"
|
||||
dest_files=["res://.godot/imported/ShuffleRandom.svg-15ee49f7a06c55a1e95e1ed056732dc5.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
4
addons/better-terrain/icons/SymmetryAll.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="8" cy="8" r="6" stroke="#E0E0E0" stroke-width="2"/>
|
||||
<circle cx="8" cy="8" r="3" fill="#E0E0E0"/>
|
||||
</svg>
|
After Width: | Height: | Size: 212 B |
44
addons/better-terrain/icons/SymmetryAll.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cyjra4g05dwh"
|
||||
path="res://.godot/imported/SymmetryAll.svg-cd6a02766f60c09344aa97e0325457c1.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/SymmetryAll.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryAll.svg-cd6a02766f60c09344aa97e0325457c1.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
3
addons/better-terrain/icons/SymmetryFlip.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 14L10 12M8 14L8 2M8 14L6 12M8 2L10 4M8 2L6 4" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 244 B |
44
addons/better-terrain/icons/SymmetryFlip.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dqmc1jp56or8m"
|
||||
path="res://.godot/imported/SymmetryFlip.svg-ea11c1010d0643843f115093c045dc42.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/SymmetryFlip.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryFlip.svg-ea11c1010d0643843f115093c045dc42.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
3
addons/better-terrain/icons/SymmetryMirror.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 8L4 10M2 8H14M2 8L4 6M14 8L12 10M14 8L12 6" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 242 B |
44
addons/better-terrain/icons/SymmetryMirror.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://5hm3bfj3dvej"
|
||||
path="res://.godot/imported/SymmetryMirror.svg-0bf9d259572cc33d41c783e35586310a.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/SymmetryMirror.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryMirror.svg-0bf9d259572cc33d41c783e35586310a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
11
addons/better-terrain/icons/SymmetryReflect.svg
Executable file
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_953_3297)">
|
||||
<path d="M8 15L10 13M8 15L8 1M8 15L6 13M8 1L10 3M8 1L6 3" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1 8L3 10M1 8H15M1 8L3 6M15 8L13 10M15 8L13 6" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_953_3297">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 527 B |
44
addons/better-terrain/icons/SymmetryReflect.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cxoewno1cefua"
|
||||
path="res://.godot/imported/SymmetryReflect.svg-39f88a51808c88d6cb37005ed1ddd254.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/SymmetryReflect.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryReflect.svg-39f88a51808c88d6cb37005ed1ddd254.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
6
addons/better-terrain/icons/SymmetryRotate180.svg
Executable file
@@ -0,0 +1,6 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.87869 5.87857H3.05026V3.05014" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.46447 4.46424C6.41709 2.51162 9.58291 2.51162 11.5355 4.46424C12.0384 4.96707 12.4117 5.55035 12.6556 6.17265" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M10.1213 10.1214H12.9497V12.9499" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.5355 11.5358C9.58291 13.4884 6.41709 13.4884 4.46447 11.5358C3.96164 11.0329 3.5883 10.4496 3.34444 9.82735" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 718 B |
44
addons/better-terrain/icons/SymmetryRotate180.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://8mcycyl3e66r"
|
||||
path="res://.godot/imported/SymmetryRotate180.svg-805113e1c31c7195ed5fec5febf455b9.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/SymmetryRotate180.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryRotate180.svg-805113e1c31c7195ed5fec5febf455b9.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
7
addons/better-terrain/icons/SymmetryRotateAll.svg
Executable file
@@ -0,0 +1,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.87869 5.87857H2.05026V3.05014" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5.87869 11.1211L5.87869 13.9496L3.05026 13.9496" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10.1213 4.87887L10.1213 2.05044L12.9497 2.05044" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11.1213 10.1214H13.9497V12.9499" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14 8C14 5.57851 12.5655 3.49205 10.5 2.54404M8 14C10.4215 14 12.508 12.5655 13.456 10.5M2 8C2 10.4215 3.43447 12.508 5.5 13.456M8 2C5.54028 2 3.42626 3.48012 2.5 5.59829" stroke="#E0E0E0" stroke-width="2"/>
|
||||
</svg>
|
After Width: | Height: | Size: 856 B |
44
addons/better-terrain/icons/SymmetryRotateAll.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b7fx4mk18lmls"
|
||||
path="res://.godot/imported/SymmetryRotateAll.svg-959ef9f7a9c5b12d37b3a1c9ddcf2432.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/SymmetryRotateAll.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryRotateAll.svg-959ef9f7a9c5b12d37b3a1c9ddcf2432.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
4
addons/better-terrain/icons/SymmetryRotateClockwise.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 9L12 11L14 9" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 13C4.23858 13 2 10.7614 2 8C2 5.23858 4.23858 3 7 3C9.76142 3 12 5.23858 12 8V10" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 366 B |
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://baxhjy28r1iqj"
|
||||
path="res://.godot/imported/SymmetryRotateClockwise.svg-9d1254877c31fcd2b5fd3dd58555e624.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/SymmetryRotateClockwise.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryRotateClockwise.svg-9d1254877c31fcd2b5fd3dd58555e624.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
4
addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg
Executable file
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 9L4 11L2 9" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 13C11.7614 13 14 10.7614 14 8C14 5.23858 11.7614 3 9 3C6.23858 3 4 5.23858 4 8V10" stroke="#E0E0E0" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 364 B |
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://csbwdkr6bc2db"
|
||||
path="res://.godot/imported/SymmetryRotateCounterClockwise.svg-ba4f86a741d97c0ebfc0ae19d3460f6f.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/SymmetryRotateCounterClockwise.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryRotateCounterClockwise.svg-ba4f86a741d97c0ebfc0ae19d3460f6f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
1
addons/better-terrain/icons/Warning.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3 1c-1.1046 0-2 .89543-2 2v10c0 1.1046.89543 2 2 2h10c1.1046 0 2-.89543 2-2v-10c0-1.1046-.89543-2-2-2zm4 2h2v6h-2zm0 8h2v2h-2z" fill="#ffdd65"/></svg>
|
After Width: | Height: | Size: 243 B |
44
addons/better-terrain/icons/Warning.svg.import
Normal file
@@ -0,0 +1,44 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b0es228gfcykd"
|
||||
path="res://.godot/imported/Warning.svg-7bb0ec60ff2da2c7ebdba79b0dcdd006.ctex"
|
||||
metadata={
|
||||
"has_editor_variant": true,
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/Warning.svg"
|
||||
dest_files=["res://.godot/imported/Warning.svg-7bb0ec60ff2da2c7ebdba79b0dcdd006.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=true
|
||||
editor/convert_colors_with_editor_theme=false
|
3
addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.5 9C4.5 6.51472 6.51472 4.5 9 4.5C11.4853 4.5 13.5 6.51472 13.5 9C13.5 11.4853 11.4853 13.5 9 13.5C6.51472 13.5 4.5 11.4853 4.5 9ZM9 1.5C4.85786 1.5 1.5 4.85786 1.5 9C1.5 13.1421 4.85786 16.5 9 16.5C13.1421 16.5 16.5 13.1421 16.5 9C16.5 4.85786 13.1421 1.5 9 1.5ZM9 11.5C10.3807 11.5 11.5 10.3807 11.5 9C11.5 7.61929 10.3807 6.5 9 6.5C7.61929 6.5 6.5 7.61929 6.5 9C6.5 10.3807 7.61929 11.5 9 11.5Z" fill="white" stroke="black"/>
|
||||
</svg>
|
After Width: | Height: | Size: 544 B |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://iid5buh1t5j5"
|
||||
path="res://.godot/imported/SymmetryAll.svg-c2902d14b54ee9a54b7986a2ea5e47a7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryAll.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryAll.svg-c2902d14b54ee9a54b7986a2ea5e47a7.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
3
addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.5 11.5854L10.5 6.41458C11.0277 6.60067 11.6386 6.4827 12.0607 6.06066C12.6464 5.47487 12.6464 4.52513 12.0607 3.93934L10.0607 1.93934C9.47487 1.35355 8.52513 1.35355 7.93934 1.93934L5.93934 3.93934C5.35355 4.52513 5.35355 5.47487 5.93934 6.06066C6.36138 6.4827 6.97234 6.60067 7.5 6.41458L7.5 11.5854C6.97234 11.3993 6.36138 11.5173 5.93934 11.9393C5.35355 12.5251 5.35355 13.4749 5.93934 14.0607L7.93934 16.0607C8.52513 16.6464 9.47487 16.6464 10.0607 16.0607L12.0607 14.0607C12.6464 13.4749 12.6464 12.5251 12.0607 11.9393C11.6386 11.5173 11.0277 11.3993 10.5 11.5854Z" fill="white" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 765 B |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://brro1lqnf3r5y"
|
||||
path="res://.godot/imported/SymmetryFlip.svg-0de1b384a4706cad746bcf7b3b7f0c2d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryFlip.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryFlip.svg-0de1b384a4706cad746bcf7b3b7f0c2d.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
3
addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.41458 10.5H11.5854C11.3993 11.0277 11.5173 11.6386 11.9393 12.0607C12.5251 12.6464 13.4749 12.6464 14.0607 12.0607L16.0607 10.0607C16.6464 9.47487 16.6464 8.52513 16.0607 7.93934L14.0607 5.93934C13.4749 5.35355 12.5251 5.35355 11.9393 5.93934C11.5173 6.36138 11.3993 6.97234 11.5854 7.5H6.41458C6.60067 6.97234 6.4827 6.36138 6.06066 5.93934C5.47487 5.35355 4.52513 5.35355 3.93934 5.93934L1.93934 7.93934C1.35355 8.52513 1.35355 9.47487 1.93934 10.0607L3.93934 12.0607C4.52513 12.6464 5.47487 12.6464 6.06066 12.0607C6.4827 11.6386 6.60067 11.0277 6.41458 10.5Z" fill="white" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 756 B |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dpf5p8xxn52cb"
|
||||
path="res://.godot/imported/SymmetryMirror.svg-2ba85612b4c15f1a7eab344dc47f9a9a.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryMirror.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryMirror.svg-2ba85612b4c15f1a7eab344dc47f9a9a.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
3
addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.35441 11.6456C6.52581 11.286 6.54587 10.8723 6.41458 10.5H7.5V11.5854C7.12774 11.4541 6.71401 11.4742 6.35441 11.6456ZM11.6456 11.6456C11.286 11.4742 10.8723 11.4541 10.5 11.5854V10.5H11.5854C11.4541 10.8723 11.4742 11.286 11.6456 11.6456ZM11.6456 6.35441C11.4742 6.71401 11.4541 7.12774 11.5854 7.5H10.5V6.41458C10.8723 6.54587 11.286 6.52581 11.6456 6.35441ZM6.35442 6.35442C6.71401 6.52582 7.12774 6.54587 7.5 6.41458V7.5H6.41458C6.54587 7.12774 6.52581 6.71401 6.35442 6.35442ZM5.64558 5.64558C5.08822 5.37993 4.40083 5.47785 3.93934 5.93934L1.93934 7.93934C1.35355 8.52513 1.35355 9.47487 1.93934 10.0607L3.93934 12.0607C4.40084 12.5222 5.08823 12.6201 5.64558 12.3544C5.37992 12.9118 5.47784 13.5992 5.93934 14.0607L7.93934 16.0607C8.52513 16.6464 9.47487 16.6464 10.0607 16.0607L12.0607 14.0607C12.5222 13.5992 12.6201 12.9118 12.3544 12.3544C12.9118 12.6201 13.5992 12.5222 14.0607 12.0607L16.0607 10.0607C16.6464 9.47487 16.6464 8.52513 16.0607 7.93934L14.0607 5.93934C13.5992 5.47784 12.9118 5.37992 12.3544 5.64559C12.6201 5.08823 12.5222 4.40084 12.0607 3.93934L10.0607 1.93934C9.77935 1.65804 9.39782 1.5 9 1.5C8.60217 1.5 8.22064 1.65804 7.93934 1.93934L5.93934 3.93934C5.47784 4.40083 5.37993 5.08823 5.64558 5.64558Z" fill="white" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://d251v4pxpwsre"
|
||||
path="res://.godot/imported/SymmetryReflect.svg-de65ca99c884ea9239bb60e11b7c0ca4.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryReflect.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryReflect.svg-de65ca99c884ea9239bb60e11b7c0ca4.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
3
addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.81404 5.70606C9.04953 5.26223 10.4851 5.53533 11.4749 6.52513C11.8288 6.87906 12.0891 7.28666 12.259 7.72015C12.5612 8.49147 13.4315 8.87172 14.2028 8.56946C14.9742 8.26721 15.3544 7.39691 15.0522 6.62559C14.7343 5.81449 14.2479 5.05553 13.5962 4.40381C11.3866 2.19423 7.98242 1.90803 5.46303 3.54507C5.25562 2.96524 4.70139 2.55036 4.05026 2.55036C3.22184 2.55036 2.55026 3.22194 2.55026 4.05036V6.87879C2.55026 7.70722 3.22184 8.37879 4.05026 8.37879H6.87869C7.70712 8.37879 8.37869 7.70722 8.37869 6.87879C8.37869 6.4041 8.1582 5.98092 7.81404 5.70606ZM10.186 12.2944C8.95048 12.7382 7.51492 12.4651 6.52513 11.4753C6.17119 11.1214 5.91091 10.7138 5.74104 10.2803C5.43878 9.50898 4.56848 9.12872 3.79716 9.43098C3.02584 9.73323 2.64559 10.6035 2.94784 11.3749C3.26569 12.186 3.75209 12.9449 4.40381 13.5966C6.61338 15.8062 10.0176 16.0924 12.537 14.4554C12.7444 15.0352 13.2986 15.4501 13.9497 15.4501C14.7782 15.4501 15.4497 14.7785 15.4497 13.9501V11.1217C15.4497 10.2932 14.7782 9.62165 13.9497 9.62165H11.1213C10.2929 9.62165 9.62131 10.2932 9.62131 11.1217C9.62131 11.5963 9.84181 12.0195 10.186 12.2944Z" fill="white" stroke="black" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c1bmbyb3ig0mx"
|
||||
path="res://.godot/imported/SymmetryRotate180.svg-ff244f85658bd621d56af3cf4f7c7ebe.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotate180.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryRotate180.svg-ff244f85658bd621d56af3cf4f7c7ebe.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
3
addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.8216 4.27106C15.2019 3.99897 15.4497 3.55358 15.4497 3.05029C15.4497 2.22187 14.7782 1.55029 13.9497 1.55029H11.1213C10.2929 1.55029 9.62131 2.22187 9.62131 3.05029V5.87872C9.62131 6.70715 10.2929 7.37872 11.1213 7.37872C11.8172 7.37872 12.4024 6.90487 12.5718 6.26224C13.0101 6.83299 13.314 7.51086 13.4377 8.24808C13.5748 9.06509 14.3482 9.61629 15.1652 9.47922C15.9822 9.34216 16.5334 8.56872 16.3964 7.75172C16.1771 6.44496 15.6211 5.25423 14.8216 4.27106ZM6.26246 5.42796C6.83316 4.98981 7.51098 4.68586 8.24817 4.56218C9.06518 4.42511 9.61638 3.65168 9.47932 2.83467C9.34225 2.01766 8.56882 1.46646 7.75181 1.60353C6.44508 1.82276 5.2543 2.37868 4.27106 3.17821C3.99897 2.79787 3.55356 2.54999 3.05026 2.54999C2.22184 2.54999 1.55026 3.22156 1.55026 4.04999V6.87842C1.55026 7.70684 2.22184 8.37842 3.05026 8.37842H5.87869C6.70712 8.37842 7.37869 7.70685 7.37869 6.87842C7.37869 6.18264 6.90496 5.59751 6.26246 5.42796ZM6.87869 10.621C6.18279 10.621 5.59757 11.0949 5.42814 11.7376C4.98992 11.1668 4.68596 10.4889 4.56227 9.75172C4.42521 8.93471 3.65177 8.3835 2.83476 8.52057C2.01776 8.65764 1.46655 9.43107 1.60362 10.2481C1.82285 11.5548 2.37891 12.7455 3.17838 13.7287C2.7981 14.0008 2.55027 14.4461 2.55027 14.9494C2.55027 15.7778 3.22184 16.4494 4.05027 16.4494H6.87869C7.70712 16.4494 8.37869 15.7778 8.37869 14.9494L8.37869 12.121C8.37869 11.2926 7.70712 10.621 6.87869 10.621ZM11.7376 12.5718C11.1669 13.01 10.489 13.3139 9.75181 13.4376C8.9348 13.5747 8.3836 14.3481 8.52066 15.1651C8.65773 15.9821 9.43117 16.5333 10.2482 16.3963C11.555 16.177 12.7457 15.621 13.7289 14.8215C14.001 15.2018 14.4464 15.4497 14.9497 15.4497C15.7782 15.4497 16.4497 14.7781 16.4497 13.9497V11.1213C16.4497 10.2929 15.7782 9.62128 14.9497 9.62128H12.1213C11.2929 9.62128 10.6213 10.2929 10.6213 11.1213C10.6213 11.8171 11.0951 12.4022 11.7376 12.5718Z" fill="white" stroke="black" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bcky1dfn4umac"
|
||||
path="res://.godot/imported/SymmetryRotateAll.svg-795a9b37a8f5df7e7376c9f762121b21.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateAll.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryRotateAll.svg-795a9b37a8f5df7e7376c9f762121b21.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
3
addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg
Executable file
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 5.5C9.78974 5.5 11.2657 6.84334 11.4747 8.57674C10.9529 8.40317 10.3546 8.52404 9.93934 8.93934C9.35355 9.52513 9.35355 10.4749 9.93934 11.0607L11.9393 13.0607C12.5251 13.6464 13.4749 13.6464 14.0607 13.0607L16.0607 11.0607C16.6464 10.4749 16.6464 9.52513 16.0607 8.93934C15.6352 8.5139 15.0178 8.39745 14.4873 8.58997C14.2757 5.19115 11.4521 2.5 8 2.5C4.41015 2.5 1.5 5.41015 1.5 9C1.5 12.5899 4.41015 15.5 8 15.5C8.82843 15.5 9.5 14.8284 9.5 14C9.5 13.1716 8.82843 12.5 8 12.5C6.067 12.5 4.5 10.933 4.5 9C4.5 7.067 6.067 5.5 8 5.5Z" fill="white" stroke="black" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 703 B |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://def0fcqsn6s6x"
|
||||
path="res://.godot/imported/SymmetryRotateClockwise.svg-e133d151dd3970411596d18bb133aece.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateClockwise.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryRotateClockwise.svg-e133d151dd3970411596d18bb133aece.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
@@ -0,0 +1,3 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.51272 8.58997C2.98219 8.39745 2.36478 8.5139 1.93934 8.93934C1.35355 9.52513 1.35355 10.4749 1.93934 11.0607L3.93934 13.0607C4.52513 13.6464 5.47487 13.6464 6.06066 13.0607L8.06066 11.0607C8.64645 10.4749 8.64645 9.52513 8.06066 8.93934C7.64536 8.52404 7.04712 8.40317 6.52533 8.57674C6.73428 6.84334 8.21026 5.5 10 5.5C11.933 5.5 13.5 7.067 13.5 9C13.5 10.933 11.933 12.5 10 12.5C9.17157 12.5 8.5 13.1716 8.5 14C8.5 14.8284 9.17157 15.5 10 15.5C13.5899 15.5 16.5 12.5899 16.5 9C16.5 5.41015 13.5899 2.5 10 2.5C6.54787 2.5 3.72429 5.19115 3.51272 8.58997Z" fill="white" stroke="black" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 725 B |
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ngej4qhkypb2"
|
||||
path="res://.godot/imported/SymmetryRotateCounterClockwise.svg-b603f534dc5383de58f7e26cdf86fe8b.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/better-terrain/icons/paint-symmetry/SymmetryRotateCounterClockwise.svg"
|
||||
dest_files=["res://.godot/imported/SymmetryRotateCounterClockwise.svg-b603f534dc5383de58f7e26cdf86fe8b.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=4.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
7
addons/better-terrain/plugin.cfg
Executable file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="BetterTerrain"
|
||||
description="This is a drop-in replacement for Godot 4's tilemap terrain system, offering more versatile and straightforward autotiling. It can be used with any existing TileMap or TileSet, either through the editor plugin, or directly via code."
|
||||
author="Portponky"
|
||||
version=""
|
||||
script="TerrainPlugin.gd"
|
44
addons/commitreminder/Reminder.gd
Normal file
@@ -0,0 +1,44 @@
|
||||
@tool
|
||||
extends PanelContainer
|
||||
|
||||
var timer_active := false
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if timer_active:
|
||||
var time_left = $Timer.time_left
|
||||
$VBoxContainer/TimerCountdown.text = gen_time_string(format_time(time_left)) + " Left..."
|
||||
|
||||
func format_time(time_time := 0.0) -> Dictionary:
|
||||
var mils = abs(fmod(time_time, 1) * 100)
|
||||
var secs = abs(fmod(time_time, 60))
|
||||
var mins = abs(time_time / 60)
|
||||
return {"mils": int(mils), "secs": int(secs), "mins": int(mins)}
|
||||
|
||||
func gen_time_string(timer_dict := {}) -> String:
|
||||
return str(int(timer_dict["mins"])).pad_zeros(2) + ":" + str(int(timer_dict["secs"])).pad_zeros(2) + ":" + str(int(timer_dict["mils"])).pad_zeros(2)
|
||||
|
||||
func timer_finished() -> void:
|
||||
for i in 3:
|
||||
$AudioStreamPlayer.play()
|
||||
await get_tree().create_timer(1).timeout
|
||||
start_timer()
|
||||
|
||||
func start_timer() -> void:
|
||||
print("ahh")
|
||||
if not timer_active:
|
||||
$Timer.wait_time = $VBoxContainer/HBoxContainer/SpinBox.value * 60
|
||||
$Timer.start()
|
||||
$VBoxContainer/Inactive.hide()
|
||||
$VBoxContainer/TimerCountdown.show()
|
||||
timer_active = true
|
||||
else:
|
||||
timer_active = false
|
||||
$Timer.stop()
|
||||
$VBoxContainer/Inactive.show()
|
||||
$VBoxContainer/TimerCountdown.hide()
|
||||
$VBoxContainer/HBoxContainer/Start.text = "Start" if not timer_active else "Stop"
|
||||
|
||||
|
||||
func on_pressed() -> void:
|
||||
print("FUCK")
|
1
addons/commitreminder/Reminder.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://vbyesiqqwfur
|
16
addons/commitreminder/commitreminder.gd
Normal file
@@ -0,0 +1,16 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const REMINDER = preload("uid://hbpket74t6f7")
|
||||
var reminder
|
||||
|
||||
func _enter_tree() -> void:
|
||||
# Initialization of the plugin goes here.
|
||||
reminder = REMINDER.instantiate()
|
||||
add_control_to_dock(EditorPlugin.DOCK_SLOT_LEFT_BR, reminder)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
# Clean-up of the plugin goes here.
|
||||
remove_control_from_docks(reminder)
|
||||
reminder.free()
|
1
addons/commitreminder/commitreminder.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bwllafh4hjbdq
|
7
addons/commitreminder/plugin.cfg
Normal file
@@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="CommitReminder"
|
||||
description=""
|
||||
author="JoeMama"
|
||||
version=""
|
||||
script="commitreminder.gd"
|
89
addons/commitreminder/reminder.tscn
Normal file
@@ -0,0 +1,89 @@
|
||||
[gd_scene load_steps=3 format=3 uid="uid://hbpket74t6f7"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://vbyesiqqwfur" path="res://addons/commitreminder/Reminder.gd" id="1_dxju6"]
|
||||
[ext_resource type="AudioStream" uid="uid://o8wqcv7gjfkn" path="res://Assets/Audio/SFX/Pause.wav" id="2_kjwcf"]
|
||||
|
||||
[node name="Reminder" type="PanelContainer"]
|
||||
process_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_dxju6")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SpinBox" type="SpinBox" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
step = 0.0
|
||||
value = 2.5
|
||||
suffix = "Minutes"
|
||||
|
||||
[node name="Start" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Start"
|
||||
|
||||
[node name="Pause" type="Button" parent="VBoxContainer/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Pause"
|
||||
|
||||
[node name="Inactive" type="Label" parent="VBoxContainer"]
|
||||
modulate = Color(0.41215247, 0.41215247, 0.41215247, 1)
|
||||
layout_mode = 2
|
||||
text = "Timer Inactive."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="TimerCountdown" type="Label" parent="VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
text = "00:00:00 Left."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Timer" type="Timer" parent="."]
|
||||
one_shot = true
|
||||
|
||||
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="."]
|
||||
stream = ExtResource("2_kjwcf")
|
||||
pitch_scale = 1.5
|
||||
|
||||
[node name="Window" type="Window" parent="."]
|
||||
oversampling_override = 1.0
|
||||
title = "COMMIT"
|
||||
initial_position = 4
|
||||
size = Vector2i(256, 100)
|
||||
visible = false
|
||||
exclusive = true
|
||||
always_on_top = true
|
||||
|
||||
[node name="Control" type="Control" parent="Window"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Label" type="Label" parent="Window/Control"]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
text = "REMEMBER TO COMMIT YOUR CHANGES YOU CUNT"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Start" to="." method="start_timer"]
|
||||
[connection signal="pressed" from="VBoxContainer/HBoxContainer/Pause" to="." method="on_pressed"]
|
||||
[connection signal="timeout" from="Timer" to="." method="timer_finished"]
|
||||
[connection signal="timeout" from="Timer" to="Window" method="show"]
|
||||
[connection signal="close_requested" from="Window" to="." method="start_timer"]
|
||||
[connection signal="close_requested" from="Window" to="Window" method="hide"]
|
10
addons/discord-rpc-gd/Debug.svg
Executable file
@@ -0,0 +1,10 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_110_26)">
|
||||
<path d="M13.3334 5.33333H11.4601C11.1594 4.81182 10.7446 4.36512 10.2467 4.02667L11.3334 2.94L10.3934 2L8.94675 3.44667C8.64008 3.37333 8.32675 3.33333 8.00008 3.33333C7.67341 3.33333 7.36008 3.37333 7.06008 3.44667L5.60675 2L4.66675 2.94L5.74675 4.02667C5.25341 4.36667 4.84008 4.81333 4.54008 5.33333H2.66675V6.66667H4.06008C4.02675 6.88667 4.00008 7.10667 4.00008 7.33333V8H2.66675V9.33333H4.00008V10C4.00008 10.2267 4.02675 10.4467 4.06008 10.6667H2.66675V12H4.54008C5.23341 13.1933 6.52008 14 8.00008 14C9.48008 14 10.7667 13.1933 11.4601 12H13.3334V10.6667H11.9401C11.9734 10.4467 12.0001 10.2267 12.0001 10V9.33333H13.3334V8H12.0001V7.33333C12.0001 7.10667 11.9734 6.88667 11.9401 6.66667H13.3334V5.33333ZM9.33341 10.6667H6.66675V9.33333H9.33341V10.6667ZM9.33341 8H6.66675V6.66667H9.33341V8Z" fill="#5865F2"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_110_26">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.0 KiB |
43
addons/discord-rpc-gd/Debug.svg.import
Normal file
@@ -0,0 +1,43 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ufh1hha1ehui"
|
||||
path="res://.godot/imported/Debug.svg-d4cb8599fa7926b76a2d6e40d2efd949.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/discord-rpc-gd/Debug.svg"
|
||||
dest_files=["res://.godot/imported/Debug.svg-d4cb8599fa7926b76a2d6e40d2efd949.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
svg/scale=1.0
|
||||
editor/scale_with_editor_scale=false
|
||||
editor/convert_colors_with_editor_theme=false
|
BIN
addons/discord-rpc-gd/Logo_V2_No_Bg.png
Executable file
After Width: | Height: | Size: 3.9 KiB |
40
addons/discord-rpc-gd/Logo_V2_No_Bg.png.import
Normal file
@@ -0,0 +1,40 @@
|
||||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://csl0e2px0cwc1"
|
||||
path="res://.godot/imported/Logo_V2_No_Bg.png-ed667fb599fe1e17ebcfc361ff7c9c93.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://addons/discord-rpc-gd/Logo_V2_No_Bg.png"
|
||||
dest_files=["res://.godot/imported/Logo_V2_No_Bg.png-ed667fb599fe1e17ebcfc361ff7c9c93.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
7
addons/discord-rpc-gd/READ_ME_PLEASE.txt
Executable file
@@ -0,0 +1,7 @@
|
||||
MINIMUM GODOT VERSION: 4.2
|
||||
|
||||
PLEASE ACTIVATE THE PLUGIN UNDER Project -> Project Settings... -> Plugins -> DiscordRPC -> Status
|
||||
IGNORE THE RED ERRORS ON THE FIRST 2 RESTARTS
|
||||
READ THE TUTORIAL LINKED IN THE WINDOW THAT WILL OPEN ON PLUGIN ENABLE
|
||||
|
||||
If nothing works, enable the plugin and delete /addons/discord-rpc-gd/bin/.gdignore
|
29
addons/discord-rpc-gd/bin/discord-rpc-gd.gdextension
Executable file
@@ -0,0 +1,29 @@
|
||||
[configuration]
|
||||
|
||||
entry_symbol = "discordrpcgd_library_init"
|
||||
compatibility_minimum = 4.1
|
||||
|
||||
[libraries]
|
||||
|
||||
macos.debug = "macos/libdiscord_game_sdk_binding_debug.dylib"
|
||||
macos.release = "macos/libdiscord_game_sdk_binding.dylib"
|
||||
windows.debug.x86_64 = "windows/discord_game_sdk_binding_debug.dll"
|
||||
windows.release.x86_64 = "windows/discord_game_sdk_binding.dll"
|
||||
linux.debug.x86_64 = "linux/libdiscord_game_sdk_binding_debug.so"
|
||||
linux.release.x86_64 = "linux/libdiscord_game_sdk_binding.so"
|
||||
linux.debug.arm64 = "linux/libdiscord_game_sdk_binding_debug.so"
|
||||
linux.release.arm64 = "linux/libdiscord_game_sdk_binding.so"
|
||||
linux.debug.rv64 = "linux/libdiscord_game_sdk_binding_debug.so"
|
||||
linux.release.rv64 = "linux/libdiscord_game_sdk_binding.so"
|
||||
|
||||
[dependencies]
|
||||
|
||||
macos = { "macos/libdiscord_game_sdk.dylib": "" }
|
||||
windows.debug.x86_64 = { "windows/discord_game_sdk.dll": "" }
|
||||
windows.release.x86_64 = { "windows/discord_game_sdk.dll": "" }
|
||||
linux.debug.x86_64 = { "linux/libdiscord_game_sdk.so": "" }
|
||||
linux.release.x86_64 = { "linux/libdiscord_game_sdk.so": "" }
|
||||
linux.debug.arm64 = { "linux/libdiscord_game_sdk.so": "" }
|
||||
linux.release.arm64 = { "linux/libdiscord_game_sdk.so": "" }
|
||||
linux.debug.rv64 = { "linux/libdiscord_game_sdk.so": "" }
|
||||
linux.release.rv64 = { "linux/libdiscord_game_sdk.so": "" }
|
1
addons/discord-rpc-gd/bin/discord-rpc-gd.gdextension.uid
Executable file
@@ -0,0 +1 @@
|
||||
uid://bt2cujv8apbju
|