summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/core_bind.cpp67
-rw-r--r--core/core_constants.cpp13
-rw-r--r--core/debugger/remote_debugger.cpp7
-rw-r--r--core/doc_data.h13
-rw-r--r--core/input/input_map.cpp2
-rw-r--r--core/io/file_access.cpp37
-rw-r--r--core/io/ip.cpp63
-rw-r--r--core/io/marshalls.cpp7
-rw-r--r--core/io/multiplayer_api.cpp8
-rw-r--r--core/io/multiplayer_api.h2
-rw-r--r--core/io/multiplayer_peer.cpp3
-rw-r--r--core/io/multiplayer_peer.h2
-rw-r--r--core/io/resource.cpp2
-rw-r--r--core/io/resource_format_binary.cpp11
-rw-r--r--core/io/resource_format_binary.h2
-rw-r--r--core/math/a_star.cpp10
-rw-r--r--core/math/math_defs.h20
-rw-r--r--core/math/math_funcs.cpp10
-rw-r--r--core/math/math_funcs.h1
-rw-r--r--core/math/transform_3d.h81
-rw-r--r--core/object/script_language.h1
-rw-r--r--core/os/os.cpp10
-rw-r--r--core/os/os.h4
-rw-r--r--core/string/translation.cpp260
-rw-r--r--core/string/translation.h27
-rw-r--r--core/variant/binder_common.h1
-rw-r--r--core/variant/variant_utility.cpp5
27 files changed, 545 insertions, 124 deletions
diff --git a/core/core_bind.cpp b/core/core_bind.cpp
index 9e96d4b079..05fc309a28 100644
--- a/core/core_bind.cpp
+++ b/core/core_bind.cpp
@@ -356,7 +356,7 @@ void _OS::print_all_textures_by_size() {
ResourceCache::get_cached_resources(&rsrc);
for (Ref<Resource> &res : rsrc) {
- if (!res->is_class("ImageTexture")) {
+ if (!res->is_class("Texture")) {
continue;
}
@@ -376,14 +376,30 @@ void _OS::print_all_textures_by_size() {
imgs.sort();
- for (_OSCoreBindImg &E : imgs) {
- total -= E.vram;
+ if (imgs.size() == 0) {
+ print_line("No textures seem used in this project.");
+ } else {
+ print_line("Textures currently in use, sorted by VRAM usage:\n"
+ "Path - VRAM usage (Dimensions)");
+ }
+
+ for (const _OSCoreBindImg &img : imgs) {
+ print_line(vformat("%s - %s %s",
+ img.path,
+ String::humanize_size(img.vram),
+ img.size));
}
+
+ print_line(vformat("Total VRAM usage: %s.", String::humanize_size(total)));
}
void _OS::print_resources_by_type(const Vector<String> &p_types) {
- Map<String, int> type_count;
+ ERR_FAIL_COND_MSG(p_types.size() == 0,
+ "At least one type should be provided to print resources by type.");
+
+ print_line(vformat("Resources currently in use for the following types: %s", p_types));
+ Map<String, int> type_count;
List<Ref<Resource>> resources;
ResourceCache::get_cached_resources(&resources);
@@ -404,6 +420,18 @@ void _OS::print_resources_by_type(const Vector<String> &p_types) {
}
type_count[r->get_class()]++;
+
+ print_line(vformat("%s: %s", r->get_class(), r->get_path()));
+
+ List<StringName> metas;
+ r->get_meta_list(&metas);
+ for (const StringName &meta : metas) {
+ print_line(vformat(" %s: %s", meta, r->get_meta(meta)));
+ }
+ }
+
+ for (const KeyValue<String, int> &E : type_count) {
+ print_line(vformat("%s count: %d", E.key, E.value));
}
}
@@ -1733,7 +1761,36 @@ void _Thread::_start_func(void *ud) {
memdelete(tud);
Callable::CallError ce;
const Variant *arg[1] = { &t->userdata };
- int argc = (int)(arg[0]->get_type() != Variant::NIL);
+ int argc = 0;
+ if (arg[0]->get_type() != Variant::NIL) {
+ // Just pass to the target function whatever came as user data
+ argc = 1;
+ } else {
+ // There are two cases of null user data:
+ // a) The target function has zero parameters and the caller is just honoring that.
+ // b) The target function has at least one parameter with no default and the caller is
+ // leveraging the fact that user data defaults to null in Thread.start().
+ // We care about the case of more than one parameter because, even if a thread
+ // function can have one at most, out mindset here is to do our best with the
+ // only/first one and let the call handle any other error conditions, like too
+ // much arguments.
+ // We must check if we are in case b).
+ int target_param_count = 0;
+ int target_default_arg_count = 0;
+ Ref<Script> script = t->target_instance->get_script();
+ if (script.is_valid()) {
+ MethodInfo mi = script->get_method_info(t->target_method);
+ target_param_count = mi.arguments.size();
+ target_default_arg_count = mi.default_arguments.size();
+ } else {
+ MethodBind *method = ClassDB::get_method(t->target_instance->get_class_name(), t->target_method);
+ target_param_count = method->get_argument_count();
+ target_default_arg_count = method->get_default_argument_count();
+ }
+ if (target_param_count >= 1 && target_default_arg_count < target_param_count) {
+ argc = 1;
+ }
+ }
Thread::set_name(t->target_method);
diff --git a/core/core_constants.cpp b/core/core_constants.cpp
index de15cfd14a..cd8096c610 100644
--- a/core/core_constants.cpp
+++ b/core/core_constants.cpp
@@ -133,6 +133,19 @@ void register_global_constants() {
BIND_CORE_ENUM_CONSTANT(VALIGN_CENTER);
BIND_CORE_ENUM_CONSTANT(VALIGN_BOTTOM);
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TOP_TO);
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_CENTER_TO);
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_BOTTOM_TO);
+
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TO_TOP);
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TO_CENTER);
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TO_BASELINE);
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TO_BOTTOM);
+
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_TOP);
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_CENTER);
+ BIND_CORE_ENUM_CONSTANT(INLINE_ALIGN_BOTTOM);
+
// huge list of keys
BIND_CORE_CONSTANT(SPKEY);
diff --git a/core/debugger/remote_debugger.cpp b/core/debugger/remote_debugger.cpp
index 0add12ff3d..62d5126e57 100644
--- a/core/debugger/remote_debugger.cpp
+++ b/core/debugger/remote_debugger.cpp
@@ -706,6 +706,8 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
Array msg;
msg.push_back(p_can_continue);
msg.push_back(error_str);
+ ERR_FAIL_COND(!script_lang);
+ msg.push_back(script_lang->debug_get_stack_level_count() > 0);
send_message("debug_enter", msg);
servers_profiler->skip_profile_frame = true; // Avoid frame time spike in debug.
@@ -754,7 +756,6 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
break;
} else if (command == "get_stack_dump") {
- ERR_FAIL_COND(!script_lang);
DebuggerMarshalls::ScriptStackDump dump;
int slc = script_lang->debug_get_stack_level_count();
for (int i = 0; i < slc; i++) {
@@ -790,7 +791,9 @@ void RemoteDebugger::debug(bool p_can_continue, bool p_is_error_breakpoint) {
script_lang->debug_get_globals(&globals, &globals_vals);
ERR_FAIL_COND(globals.size() != globals_vals.size());
- send_message("stack_frame_vars", Array());
+ Array var_size;
+ var_size.push_back(local_vals.size() + member_vals.size() + globals_vals.size());
+ send_message("stack_frame_vars", var_size);
_send_stack_vars(locals, local_vals, 0);
_send_stack_vars(members, member_vals, 1);
_send_stack_vars(globals, globals_vals, 2);
diff --git a/core/doc_data.h b/core/doc_data.h
index 46ab697768..a3011fe275 100644
--- a/core/doc_data.h
+++ b/core/doc_data.h
@@ -116,6 +116,17 @@ public:
}
};
+ struct ThemeItemDoc {
+ String name;
+ String type;
+ String data_type;
+ String description;
+ String default_value;
+ bool operator<(const ThemeItemDoc &p_theme_item) const {
+ return name < p_theme_item.name;
+ }
+ };
+
struct TutorialDoc {
String link;
String title;
@@ -133,7 +144,7 @@ public:
Vector<ConstantDoc> constants;
Map<String, String> enums;
Vector<PropertyDoc> properties;
- Vector<PropertyDoc> theme_properties;
+ Vector<ThemeItemDoc> theme_properties;
bool is_script_doc = false;
String script_path;
bool operator<(const ClassDoc &p_class) const {
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp
index 6714705bb5..15be0f1e36 100644
--- a/core/input/input_map.cpp
+++ b/core/input/input_map.cpp
@@ -196,7 +196,7 @@ Array InputMap::_action_get_events(const StringName &p_action) {
const List<Ref<InputEvent>> *al = action_get_events(p_action);
if (al) {
for (const List<Ref<InputEvent>>::Element *E = al->front(); E; E = E->next()) {
- ret.push_back(E);
+ ret.push_back(E->get());
}
}
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index d21c0bd9a2..e6e79dff8a 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -316,52 +316,53 @@ String FileAccess::get_line() const {
}
Vector<String> FileAccess::get_csv_line(const String &p_delim) const {
- ERR_FAIL_COND_V(p_delim.length() != 1, Vector<String>());
+ ERR_FAIL_COND_V_MSG(p_delim.length() != 1, Vector<String>(), "Only single character delimiters are supported to parse CSV lines.");
+ ERR_FAIL_COND_V_MSG(p_delim[0] == '"', Vector<String>(), "The double quotation mark character (\") is not supported as a delimiter for CSV lines.");
- String l;
+ String line;
+
+ // CSV can support entries with line breaks as long as they are enclosed
+ // in double quotes. So our "line" might be more than a single line in the
+ // text file.
int qc = 0;
do {
if (eof_reached()) {
break;
}
-
- l += get_line() + "\n";
+ line += get_line() + "\n";
qc = 0;
- for (int i = 0; i < l.length(); i++) {
- if (l[i] == '"') {
+ for (int i = 0; i < line.length(); i++) {
+ if (line[i] == '"') {
qc++;
}
}
-
} while (qc % 2);
- l = l.substr(0, l.length() - 1);
+ // Remove the extraneous newline we've added above.
+ line = line.substr(0, line.length() - 1);
Vector<String> strings;
bool in_quote = false;
String current;
- for (int i = 0; i < l.length(); i++) {
- char32_t c = l[i];
- char32_t s[2] = { 0, 0 };
-
+ for (int i = 0; i < line.length(); i++) {
+ char32_t c = line[i];
+ // A delimiter ends the current entry, unless it's in a quoted string.
if (!in_quote && c == p_delim[0]) {
strings.push_back(current);
current = String();
} else if (c == '"') {
- if (l[i + 1] == '"' && in_quote) {
- s[0] = '"';
- current += s;
+ // Doubled quotes are escapes for intentional quotes in the string.
+ if (line[i + 1] == '"' && in_quote) {
+ current += '"';
i++;
} else {
in_quote = !in_quote;
}
} else {
- s[0] = c;
- current += s;
+ current += c;
}
}
-
strings.push_back(current);
return strings;
diff --git a/core/io/ip.cpp b/core/io/ip.cpp
index cd1b6d1994..e3102508a3 100644
--- a/core/io/ip.cpp
+++ b/core/io/ip.cpp
@@ -83,8 +83,23 @@ struct _IP_ResolverPrivate {
continue;
}
- IP::get_singleton()->_resolve_hostname(queue[i].response, queue[i].hostname, queue[i].type);
- queue[i].status.set(queue[i].response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE);
+ mutex.lock();
+ List<IPAddress> response;
+ String hostname = queue[i].hostname;
+ IP::Type type = queue[i].type;
+ mutex.unlock();
+
+ // We should not lock while resolving the hostname,
+ // only when modifying the queue.
+ IP::get_singleton()->_resolve_hostname(response, hostname, type);
+
+ MutexLock lock(mutex);
+ // Could have been completed by another function, or deleted.
+ if (queue[i].status.get() != IP::RESOLVER_STATUS_WAITING) {
+ continue;
+ }
+ queue[i].response = response;
+ queue[i].status.set(response.is_empty() ? IP::RESOLVER_STATUS_ERROR : IP::RESOLVER_STATUS_DONE);
}
}
@@ -93,8 +108,6 @@ struct _IP_ResolverPrivate {
while (!ipr->thread_abort) {
ipr->sem.wait();
-
- MutexLock lock(ipr->mutex);
ipr->resolve_queues();
}
}
@@ -107,17 +120,23 @@ struct _IP_ResolverPrivate {
};
IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) {
- MutexLock lock(resolver->mutex);
-
List<IPAddress> res;
-
String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type);
+
+ resolver->mutex.lock();
if (resolver->cache.has(key)) {
res = resolver->cache[key];
} else {
+ // This should be run unlocked so the resolver thread can keep
+ // resolving other requests.
+ resolver->mutex.unlock();
_resolve_hostname(res, p_hostname, p_type);
+ resolver->mutex.lock();
+ // We might be overriding another result, but we don't care (they are the
+ // same hostname).
resolver->cache[key] = res;
}
+ resolver->mutex.unlock();
for (int i = 0; i < res.size(); ++i) {
if (res[i].is_valid()) {
@@ -128,14 +147,23 @@ IPAddress IP::resolve_hostname(const String &p_hostname, IP::Type p_type) {
}
Array IP::resolve_hostname_addresses(const String &p_hostname, Type p_type) {
- MutexLock lock(resolver->mutex);
-
+ List<IPAddress> res;
String key = _IP_ResolverPrivate::get_cache_key(p_hostname, p_type);
- if (!resolver->cache.has(key)) {
- _resolve_hostname(resolver->cache[key], p_hostname, p_type);
- }
- List<IPAddress> res = resolver->cache[key];
+ resolver->mutex.lock();
+ if (resolver->cache.has(key)) {
+ res = resolver->cache[key];
+ } else {
+ // This should be run unlocked so the resolver thread can keep resolving
+ // other requests.
+ resolver->mutex.unlock();
+ _resolve_hostname(res, p_hostname, p_type);
+ resolver->mutex.lock();
+ // We might be overriding another result, but we don't care (they are the
+ // same hostname).
+ resolver->cache[key] = res;
+ }
+ resolver->mutex.unlock();
Array result;
for (int i = 0; i < res.size(); ++i) {
@@ -178,13 +206,12 @@ IP::ResolverID IP::resolve_hostname_queue_item(const String &p_hostname, IP::Typ
IP::ResolverStatus IP::get_resolve_item_status(ResolverID p_id) const {
ERR_FAIL_INDEX_V(p_id, IP::RESOLVER_MAX_QUERIES, IP::RESOLVER_STATUS_NONE);
- MutexLock lock(resolver->mutex);
-
- if (resolver->queue[p_id].status.get() == IP::RESOLVER_STATUS_NONE) {
+ IP::ResolverStatus res = resolver->queue[p_id].status.get();
+ if (res == IP::RESOLVER_STATUS_NONE) {
ERR_PRINT("Condition status == IP::RESOLVER_STATUS_NONE");
return IP::RESOLVER_STATUS_NONE;
}
- return resolver->queue[p_id].status.get();
+ return res;
}
IPAddress IP::get_resolve_item_address(ResolverID p_id) const {
@@ -230,8 +257,6 @@ Array IP::get_resolve_item_addresses(ResolverID p_id) const {
void IP::erase_resolve_item(ResolverID p_id) {
ERR_FAIL_INDEX(p_id, IP::RESOLVER_MAX_QUERIES);
- MutexLock lock(resolver->mutex);
-
resolver->queue[p_id].status.set(IP::RESOLVER_STATUS_NONE);
}
diff --git a/core/io/marshalls.cpp b/core/io/marshalls.cpp
index 4f85eced93..e7d5b78d14 100644
--- a/core/io/marshalls.cpp
+++ b/core/io/marshalls.cpp
@@ -746,7 +746,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::PACKED_INT64_ARRAY: {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
- int64_t count = decode_uint64(buf);
+ int32_t count = decode_uint32(buf);
buf += 4;
len -= 4;
ERR_FAIL_MUL_OF(count, 8, ERR_INVALID_DATA);
@@ -795,7 +795,7 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
} break;
case Variant::PACKED_FLOAT64_ARRAY: {
ERR_FAIL_COND_V(len < 4, ERR_INVALID_DATA);
- int64_t count = decode_uint64(buf);
+ int32_t count = decode_uint32(buf);
buf += 4;
len -= 4;
ERR_FAIL_MUL_OF(count, 8, ERR_INVALID_DATA);
@@ -804,7 +804,6 @@ Error decode_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int
Vector<double> data;
if (count) {
- //const double*rbuf=(const double*)buf;
data.resize(count);
double *w = data.ptrw();
for (int64_t i = 0; i < count; i++) {
@@ -1519,7 +1518,7 @@ Error encode_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bo
int datasize = sizeof(int64_t);
if (buf) {
- encode_uint64(datalen, buf);
+ encode_uint32(datalen, buf);
buf += 4;
const int64_t *r = data.ptr();
for (int64_t i = 0; i < datalen; i++) {
diff --git a/core/io/multiplayer_api.cpp b/core/io/multiplayer_api.cpp
index d4f09b2135..1c3f231170 100644
--- a/core/io/multiplayer_api.cpp
+++ b/core/io/multiplayer_api.cpp
@@ -478,6 +478,7 @@ void MultiplayerAPI::_process_simplify_path(int p_from, const uint8_t *p_packet,
packet.write[1] = valid_rpc_checksum;
encode_cstring(pname.get_data(), &packet.write[2]);
+ network_peer->set_transfer_channel(0);
network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
network_peer->set_target_peer(p_from);
network_peer->put_packet(packet.ptr(), packet.size());
@@ -557,6 +558,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC
for (int &E : peers_to_add) {
network_peer->set_target_peer(E); // To all of you.
+ network_peer->set_transfer_channel(0);
network_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
network_peer->put_packet(packet.ptr(), packet.size());
@@ -858,6 +860,7 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const
#endif
// Take chance and set transfer mode, since all send methods will use it.
+ network_peer->set_transfer_channel(p_config.channel);
network_peer->set_transfer_mode(p_config.transfer_mode);
if (has_all_peers) {
@@ -996,7 +999,7 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const
ERR_FAIL_COND_MSG(p_peer_id == node_id && !config.sync, "RPC '" + p_method + "' on yourself is not allowed by selected mode.");
}
-Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode) {
+Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) {
ERR_FAIL_COND_V_MSG(p_data.size() < 1, ERR_INVALID_DATA, "Trying to send an empty raw packet.");
ERR_FAIL_COND_V_MSG(!network_peer.is_valid(), ERR_UNCONFIGURED, "Trying to send a raw packet while no network peer is active.");
ERR_FAIL_COND_V_MSG(network_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED, "Trying to send a raw packet via a network peer which is not connected.");
@@ -1007,6 +1010,7 @@ Error MultiplayerAPI::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPe
memcpy(&packet_cache.write[1], &r[0], p_data.size());
network_peer->set_target_peer(p_to);
+ network_peer->set_transfer_channel(p_channel);
network_peer->set_transfer_mode(p_mode);
return network_peer->put_packet(packet_cache.ptr(), p_data.size() + 1);
@@ -1066,7 +1070,7 @@ bool MultiplayerAPI::is_object_decoding_allowed() const {
void MultiplayerAPI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node);
ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node);
- ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE));
+ ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &MultiplayerAPI::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
ClassDB::bind_method(D_METHOD("has_network_peer"), &MultiplayerAPI::has_network_peer);
ClassDB::bind_method(D_METHOD("get_network_peer"), &MultiplayerAPI::get_network_peer);
ClassDB::bind_method(D_METHOD("get_network_unique_id"), &MultiplayerAPI::get_network_unique_id);
diff --git a/core/io/multiplayer_api.h b/core/io/multiplayer_api.h
index cc994a9852..011bc3dde9 100644
--- a/core/io/multiplayer_api.h
+++ b/core/io/multiplayer_api.h
@@ -132,7 +132,7 @@ public:
Node *get_root_node();
void set_network_peer(const Ref<MultiplayerPeer> &p_peer);
Ref<MultiplayerPeer> get_network_peer() const;
- Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE);
+ Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0);
// Called by Node.rpc
void rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const StringName &p_method, const Variant **p_arg, int p_argcount);
diff --git a/core/io/multiplayer_peer.cpp b/core/io/multiplayer_peer.cpp
index 8b3b1eef8e..83cf24d7e3 100644
--- a/core/io/multiplayer_peer.cpp
+++ b/core/io/multiplayer_peer.cpp
@@ -54,6 +54,8 @@ uint32_t MultiplayerPeer::generate_unique_id() const {
}
void MultiplayerPeer::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_transfer_channel", "channel"), &MultiplayerPeer::set_transfer_channel);
+ ClassDB::bind_method(D_METHOD("get_transfer_channel"), &MultiplayerPeer::get_transfer_channel);
ClassDB::bind_method(D_METHOD("set_transfer_mode", "mode"), &MultiplayerPeer::set_transfer_mode);
ClassDB::bind_method(D_METHOD("get_transfer_mode"), &MultiplayerPeer::get_transfer_mode);
ClassDB::bind_method(D_METHOD("set_target_peer", "id"), &MultiplayerPeer::set_target_peer);
@@ -71,6 +73,7 @@ void MultiplayerPeer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections");
ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_mode", PROPERTY_HINT_ENUM, "Unreliable,Unreliable Ordered,Reliable"), "set_transfer_mode", "get_transfer_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "transfer_channel", PROPERTY_HINT_RANGE, "0,255,1"), "set_transfer_channel", "get_transfer_channel");
BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE);
BIND_ENUM_CONSTANT(TRANSFER_MODE_UNRELIABLE_ORDERED);
diff --git a/core/io/multiplayer_peer.h b/core/io/multiplayer_peer.h
index 91a3ad7954..7ca4e7930b 100644
--- a/core/io/multiplayer_peer.h
+++ b/core/io/multiplayer_peer.h
@@ -56,6 +56,8 @@ public:
CONNECTION_CONNECTED,
};
+ virtual void set_transfer_channel(int p_channel) = 0;
+ virtual int get_transfer_channel() const = 0;
virtual void set_transfer_mode(TransferMode p_mode) = 0;
virtual TransferMode get_transfer_mode() const = 0;
virtual void set_target_peer(int p_peer_id) = 0;
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index 727611a573..0262655927 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -552,5 +552,7 @@ void ResourceCache::dump(const char *p_file, bool p_short) {
}
lock.read_unlock();
+#else
+ WARN_PRINT("ResourceCache::dump only with in debug builds.");
#endif
}
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index a3ebc32cc5..00d4d093da 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -903,10 +903,11 @@ void ResourceLoaderBinary::open(FileAccess *p_f, bool p_no_resources, bool p_kee
if (using_uids) {
uid = f->get_64();
} else {
+ f->get_64(); // skip over uid field
uid = ResourceUID::INVALID_ID;
}
- for (int i = 0; i < 5; i++) {
+ for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) {
f->get_32(); //skip a few reserved fields
}
@@ -1209,8 +1210,8 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
fw->store_32(flags);
fw->store_64(uid_data);
- for (int i = 0; i < 5; i++) {
- f->store_32(0); // reserved
+ for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) {
+ fw->store_32(0); // reserved
f->get_32();
}
@@ -1264,7 +1265,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons
if (using_uids) {
ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(full_path);
- f->store_64(uid);
+ fw->store_64(uid);
}
}
@@ -1902,7 +1903,7 @@ Error ResourceFormatSaverBinaryInstance::save(const String &p_path, const RES &p
f->store_32(FORMAT_FLAG_NAMED_SCENE_IDS | FORMAT_FLAG_UIDS);
ResourceUID::ID uid = ResourceSaver::get_resource_id_for_path(p_path, true);
f->store_64(uid);
- for (int i = 0; i < 5; i++) {
+ for (int i = 0; i < ResourceFormatSaverBinaryInstance::RESERVED_FIELDS; i++) {
f->store_32(0); // reserved
}
diff --git a/core/io/resource_format_binary.h b/core/io/resource_format_binary.h
index ac964d2053..a6e6d1848e 100644
--- a/core/io/resource_format_binary.h
+++ b/core/io/resource_format_binary.h
@@ -164,6 +164,8 @@ public:
enum {
FORMAT_FLAG_NAMED_SCENE_IDS = 1,
FORMAT_FLAG_UIDS = 2,
+ // Amount of reserved 32-bit fields in resource header
+ RESERVED_FIELDS = 11
};
Error save(const String &p_path, const RES &p_resource, uint32_t p_flags = 0);
static void write_variant(FileAccess *f, const Variant &p_property, Map<RES, int> &resource_map, Map<RES, int> &external_resources, Map<StringName, int> &string_map, const PropertyInfo &p_hint = PropertyInfo());
diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp
index 88e11a630c..322eb7ac61 100644
--- a/core/math/a_star.cpp
+++ b/core/math/a_star.cpp
@@ -35,18 +35,12 @@
#include "scene/scene_string_names.h"
int AStar::get_available_point_id() const {
- if (points.is_empty()) {
- return 1;
- }
-
- // calculate our new next available point id if bigger than before or next id already contained in set of points.
if (points.has(last_free_id)) {
- int cur_new_id = last_free_id;
+ int cur_new_id = last_free_id + 1;
while (points.has(cur_new_id)) {
cur_new_id++;
}
- int &non_const = const_cast<int &>(last_free_id);
- non_const = cur_new_id;
+ const_cast<int &>(last_free_id) = cur_new_id;
}
return last_free_id;
diff --git a/core/math/math_defs.h b/core/math/math_defs.h
index 7692e1be47..c3a8f910c0 100644
--- a/core/math/math_defs.h
+++ b/core/math/math_defs.h
@@ -81,6 +81,26 @@ enum VAlign {
VALIGN_BOTTOM
};
+enum InlineAlign {
+ // Image alignment points.
+ INLINE_ALIGN_TOP_TO = 0b0000,
+ INLINE_ALIGN_CENTER_TO = 0b0001,
+ INLINE_ALIGN_BOTTOM_TO = 0b0010,
+ INLINE_ALIGN_IMAGE_MASK = 0b0011,
+
+ // Text alignment points.
+ INLINE_ALIGN_TO_TOP = 0b0000,
+ INLINE_ALIGN_TO_CENTER = 0b0100,
+ INLINE_ALIGN_TO_BASELINE = 0b1000,
+ INLINE_ALIGN_TO_BOTTOM = 0b1100,
+ INLINE_ALIGN_TEXT_MASK = 0b1100,
+
+ // Presets.
+ INLINE_ALIGN_TOP = INLINE_ALIGN_TOP_TO | INLINE_ALIGN_TO_TOP,
+ INLINE_ALIGN_CENTER = INLINE_ALIGN_CENTER_TO | INLINE_ALIGN_TO_CENTER,
+ INLINE_ALIGN_BOTTOM = INLINE_ALIGN_BOTTOM_TO | INLINE_ALIGN_TO_BOTTOM
+};
+
enum Side {
SIDE_LEFT,
SIDE_TOP,
diff --git a/core/math/math_funcs.cpp b/core/math/math_funcs.cpp
index e92bb9f4aa..bbed257f60 100644
--- a/core/math/math_funcs.cpp
+++ b/core/math/math_funcs.cpp
@@ -88,16 +88,6 @@ int Math::range_step_decimals(double p_step) {
return step_decimals(p_step);
}
-double Math::dectime(double p_value, double p_amount, double p_step) {
- double sgn = p_value < 0 ? -1.0 : 1.0;
- double val = Math::abs(p_value);
- val -= p_amount * p_step;
- if (val < 0.0) {
- val = 0.0;
- }
- return val * sgn;
-}
-
double Math::ease(double p_x, double p_c) {
if (p_x < 0) {
p_x = 0;
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index 3389407e72..4e4f566517 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -296,7 +296,6 @@ public:
static int step_decimals(double p_step);
static int range_step_decimals(double p_step);
static double snapped(double p_value, double p_step);
- static double dectime(double p_value, double p_amount, double p_step);
static uint32_t larger_prime(uint32_t p_val);
diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h
index 3d8e70cec7..cadfdc13d1 100644
--- a/core/math/transform_3d.h
+++ b/core/math/transform_3d.h
@@ -75,16 +75,24 @@ public:
bool operator!=(const Transform3D &p_transform) const;
_FORCE_INLINE_ Vector3 xform(const Vector3 &p_vector) const;
+ _FORCE_INLINE_ AABB xform(const AABB &p_aabb) const;
+ _FORCE_INLINE_ Vector<Vector3> xform(const Vector<Vector3> &p_array) const;
+
+ // NOTE: These are UNSAFE with non-uniform scaling, and will produce incorrect results.
+ // They use the transpose.
+ // For safe inverse transforms, xform by the affine_inverse.
_FORCE_INLINE_ Vector3 xform_inv(const Vector3 &p_vector) const;
+ _FORCE_INLINE_ AABB xform_inv(const AABB &p_aabb) const;
+ _FORCE_INLINE_ Vector<Vector3> xform_inv(const Vector<Vector3> &p_array) const;
+ // Safe with non-uniform scaling (uses affine_inverse).
_FORCE_INLINE_ Plane xform(const Plane &p_plane) const;
_FORCE_INLINE_ Plane xform_inv(const Plane &p_plane) const;
- _FORCE_INLINE_ AABB xform(const AABB &p_aabb) const;
- _FORCE_INLINE_ AABB xform_inv(const AABB &p_aabb) const;
-
- _FORCE_INLINE_ Vector<Vector3> xform(const Vector<Vector3> &p_array) const;
- _FORCE_INLINE_ Vector<Vector3> xform_inv(const Vector<Vector3> &p_array) const;
+ // These fast versions use precomputed affine inverse, and should be used in bottleneck areas where
+ // multiple planes are to be transformed.
+ _FORCE_INLINE_ Plane xform_fast(const Plane &p_plane, const Basis &p_basis_inverse_transpose) const;
+ static _FORCE_INLINE_ Plane xform_inv_fast(const Plane &p_plane, const Transform3D &p_inverse, const Basis &p_basis_transpose);
void operator*=(const Transform3D &p_transform);
Transform3D operator*(const Transform3D &p_transform) const;
@@ -130,30 +138,20 @@ _FORCE_INLINE_ Vector3 Transform3D::xform_inv(const Vector3 &p_vector) const {
(basis.elements[0][2] * v.x) + (basis.elements[1][2] * v.y) + (basis.elements[2][2] * v.z));
}
+// Neither the plane regular xform or xform_inv are particularly efficient,
+// as they do a basis inverse. For xforming a large number
+// of planes it is better to pre-calculate the inverse transpose basis once
+// and reuse it for each plane, by using the 'fast' version of the functions.
_FORCE_INLINE_ Plane Transform3D::xform(const Plane &p_plane) const {
- Vector3 point = p_plane.normal * p_plane.d;
- Vector3 point_dir = point + p_plane.normal;
- point = xform(point);
- point_dir = xform(point_dir);
-
- Vector3 normal = point_dir - point;
- normal.normalize();
- real_t d = normal.dot(point);
-
- return Plane(normal, d);
+ Basis b = basis.inverse();
+ b.transpose();
+ return xform_fast(p_plane, b);
}
_FORCE_INLINE_ Plane Transform3D::xform_inv(const Plane &p_plane) const {
- Vector3 point = p_plane.normal * p_plane.d;
- Vector3 point_dir = point + p_plane.normal;
- point = xform_inv(point);
- point_dir = xform_inv(point_dir);
-
- Vector3 normal = point_dir - point;
- normal.normalize();
- real_t d = normal.dot(point);
-
- return Plane(normal, d);
+ Transform3D inv = affine_inverse();
+ Basis basis_transpose = basis.transposed();
+ return xform_inv_fast(p_plane, inv, basis_transpose);
}
_FORCE_INLINE_ AABB Transform3D::xform(const AABB &p_aabb) const {
@@ -231,4 +229,37 @@ Vector<Vector3> Transform3D::xform_inv(const Vector<Vector3> &p_array) const {
return array;
}
+_FORCE_INLINE_ Plane Transform3D::xform_fast(const Plane &p_plane, const Basis &p_basis_inverse_transpose) const {
+ // Transform a single point on the plane.
+ Vector3 point = p_plane.normal * p_plane.d;
+ point = xform(point);
+
+ // Use inverse transpose for correct normals with non-uniform scaling.
+ Vector3 normal = p_basis_inverse_transpose.xform(p_plane.normal);
+ normal.normalize();
+
+ real_t d = normal.dot(point);
+ return Plane(normal, d);
+}
+
+_FORCE_INLINE_ Plane Transform3D::xform_inv_fast(const Plane &p_plane, const Transform3D &p_inverse, const Basis &p_basis_transpose) {
+ // Transform a single point on the plane.
+ Vector3 point = p_plane.normal * p_plane.d;
+ point = p_inverse.xform(point);
+
+ // Note that instead of precalculating the transpose, an alternative
+ // would be to use the transpose for the basis transform.
+ // However that would be less SIMD friendly (requiring a swizzle).
+ // So the cost is one extra precalced value in the calling code.
+ // This is probably worth it, as this could be used in bottleneck areas. And
+ // where it is not a bottleneck, the non-fast method is fine.
+
+ // Use transpose for correct normals with non-uniform scaling.
+ Vector3 normal = p_basis_transpose.xform(p_plane.normal);
+ normal.normalize();
+
+ real_t d = normal.dot(point);
+ return Plane(normal, d);
+}
+
#endif // TRANSFORM_H
diff --git a/core/object/script_language.h b/core/object/script_language.h
index 2cbaa0f52e..385bf79c1a 100644
--- a/core/object/script_language.h
+++ b/core/object/script_language.h
@@ -310,6 +310,7 @@ public:
Ref<Script> script;
String class_name;
String class_member;
+ String class_path;
int location;
};
diff --git a/core/os/os.cpp b/core/os/os.cpp
index f7af74da3e..76a6da51e1 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -178,7 +178,7 @@ static void _OS_printres(Object *p_obj) {
return;
}
- String str = itos(res->get_instance_id()) + String(res->get_class()) + ":" + String(res->get_name()) + " - " + res->get_path();
+ String str = vformat("%s - %s - %s", res->to_string(), res->get_name(), res->get_path());
if (_OSPRF) {
_OSPRF->store_line(str);
} else {
@@ -215,14 +215,6 @@ void OS::dump_resources_to_file(const char *p_file) {
ResourceCache::dump(p_file);
}
-void OS::set_no_window_mode(bool p_enable) {
- _no_window = p_enable;
-}
-
-bool OS::is_no_window_mode_enabled() const {
- return _no_window;
-}
-
int OS::get_exit_code() const {
return _exit_code;
}
diff --git a/core/os/os.h b/core/os/os.h
index fcb195afe1..0466d94acd 100644
--- a/core/os/os.h
+++ b/core/os/os.h
@@ -53,7 +53,6 @@ class OS {
bool _verbose_stdout = false;
bool _debug_stdout = false;
String _local_clipboard;
- bool _no_window = false;
int _exit_code = EXIT_FAILURE; // unexpected exit is marked as failure
int _orientation;
bool _allow_hidpi = false;
@@ -269,9 +268,6 @@ public:
virtual Error move_to_trash(const String &p_path) { return FAILED; }
- virtual void set_no_window_mode(bool p_enable);
- virtual bool is_no_window_mode_enabled() const;
-
virtual void debug_break();
virtual int get_exit_code() const;
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 19d23fd375..cb7d924556 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -929,6 +929,66 @@ void Translation::_bind_methods() {
///////////////////////////////////////////////
+struct _character_accent_pair {
+ const char32_t character;
+ const char32_t *accented_character;
+};
+
+static _character_accent_pair _character_to_accented[] = {
+ { 'A', U"Å" },
+ { 'B', U"ß" },
+ { 'C', U"Ç" },
+ { 'D', U"Ð" },
+ { 'E', U"É" },
+ { 'F', U"F́" },
+ { 'G', U"Ĝ" },
+ { 'H', U"Ĥ" },
+ { 'I', U"Ĩ" },
+ { 'J', U"Ĵ" },
+ { 'K', U"ĸ" },
+ { 'L', U"Ł" },
+ { 'M', U"Ḿ" },
+ { 'N', U"й" },
+ { 'O', U"Ö" },
+ { 'P', U"Ṕ" },
+ { 'Q', U"Q́" },
+ { 'R', U"Ř" },
+ { 'S', U"Ŝ" },
+ { 'T', U"Ŧ" },
+ { 'U', U"Ũ" },
+ { 'V', U"Ṽ" },
+ { 'W', U"Ŵ" },
+ { 'X', U"X́" },
+ { 'Y', U"Ÿ" },
+ { 'Z', U"Ž" },
+ { 'a', U"á" },
+ { 'b', U"ḅ" },
+ { 'c', U"ć" },
+ { 'd', U"d́" },
+ { 'e', U"é" },
+ { 'f', U"f́" },
+ { 'g', U"ǵ" },
+ { 'h', U"h̀" },
+ { 'i', U"í" },
+ { 'j', U"ǰ" },
+ { 'k', U"ḱ" },
+ { 'l', U"ł" },
+ { 'm', U"m̀" },
+ { 'n', U"ή" },
+ { 'o', U"ô" },
+ { 'p', U"ṕ" },
+ { 'q', U"q́" },
+ { 'r', U"ŕ" },
+ { 's', U"š" },
+ { 't', U"ŧ" },
+ { 'u', U"ü" },
+ { 'v', U"ṽ" },
+ { 'w', U"ŵ" },
+ { 'x', U"x́" },
+ { 'y', U"ý" },
+ { 'z', U"ź" },
+};
+
bool TranslationServer::is_locale_valid(const String &p_locale) {
const char **ptr = locale_list;
@@ -1101,10 +1161,10 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin
}
if (!res) {
- return p_message;
+ return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
}
- return res;
+ return pseudolocalization_enabled ? pseudolocalize(res) : res;
}
StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@@ -1217,7 +1277,18 @@ void TranslationServer::setup() {
} else {
set_locale(OS::get_singleton()->get_locale());
}
+
fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
+ pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
+ pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
+ pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false);
+ pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false);
+ pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false);
+ expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
+ pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
+ pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
+
#ifdef TOOLS_ENABLED
{
String options = "";
@@ -1258,10 +1329,10 @@ StringName TranslationServer::tool_translate(const StringName &p_message, const
if (tool_translation.is_valid()) {
StringName r = tool_translation->get_message(p_message, p_context);
if (r) {
- return r;
+ return editor_pseudolocalization ? tool_pseudolocalize(r) : r;
}
}
- return p_message;
+ return editor_pseudolocalization ? tool_pseudolocalize(p_message) : p_message;
}
StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@@ -1306,6 +1377,181 @@ StringName TranslationServer::doc_translate_plural(const StringName &p_message,
return p_message_plural;
}
+bool TranslationServer::is_pseudolocalization_enabled() const {
+ return pseudolocalization_enabled;
+}
+
+void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
+ pseudolocalization_enabled = p_enabled;
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+ ResourceLoader::reload_translation_remaps();
+}
+
+void TranslationServer::set_editor_pseudolocalization(bool p_enabled) {
+ editor_pseudolocalization = p_enabled;
+}
+
+void TranslationServer::reload_pseudolocalization() {
+ pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents");
+ pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels");
+ pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi");
+ pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override");
+ expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
+ pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
+ pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+ ResourceLoader::reload_translation_remaps();
+}
+
+StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ int length = message.length();
+ if (pseudolocalization_override_enabled) {
+ message = get_override_string(message);
+ }
+
+ if (pseudolocalization_double_vowels_enabled) {
+ message = double_vowels(message);
+ }
+
+ if (pseudolocalization_accents_enabled) {
+ message = replace_with_accented_string(message);
+ }
+
+ if (pseudolocalization_fake_bidi_enabled) {
+ message = wrap_with_fakebidi_characters(message);
+ }
+
+ StringName res = add_padding(message, length);
+ return res;
+}
+
+StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ message = double_vowels(message);
+ message = replace_with_accented_string(message);
+ StringName res = "[!!! " + message + " !!!]";
+ return res;
+}
+
+String TranslationServer::get_override_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.size(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += '*';
+ }
+ return res;
+}
+
+String TranslationServer::double_vowels(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.size(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += p_message[i];
+ if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
+ p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
+ res += p_message[i];
+ }
+ }
+ return res;
+};
+
+String TranslationServer::replace_with_accented_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.size(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ const char32_t *accented = get_accented_version(p_message[i]);
+ if (accented) {
+ res += accented;
+ } else {
+ res += p_message[i];
+ }
+ }
+ return res;
+}
+
+String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
+ String res;
+ char32_t fakebidiprefix = U'\u202e';
+ char32_t fakebidisuffix = U'\u202c';
+ res += fakebidiprefix;
+ // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
+ for (int i = 0; i < p_message.size(); i++) {
+ if (p_message[i] == '\n') {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += fakebidiprefix;
+ } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += p_message[i + 1];
+ res += fakebidiprefix;
+ i++;
+ } else {
+ res += p_message[i];
+ }
+ }
+ res += fakebidisuffix;
+ return res;
+}
+
+String TranslationServer::add_padding(String &p_message, int p_length) const {
+ String res;
+ String prefix = pseudolocalization_prefix;
+ String suffix;
+ for (int i = 0; i < p_length * expansion_ratio / 2; i++) {
+ prefix += "_";
+ suffix += "_";
+ }
+ suffix += pseudolocalization_suffix;
+ res += prefix;
+ res += p_message;
+ res += suffix;
+ return res;
+}
+
+const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
+ if (!((p_character >= 'a' && p_character <= 'z') || (p_character >= 'A' && p_character <= 'Z'))) {
+ return nullptr;
+ }
+
+ for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
+ if (_character_to_accented[i].character == p_character) {
+ return _character_to_accented[i].accented_character;
+ }
+ }
+
+ return nullptr;
+}
+
+bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
+ return p_message[p_index] == '%' && p_index < p_message.size() - 1 &&
+ (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
+ p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
+}
+
void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
@@ -1322,6 +1568,12 @@ void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
+
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
+ ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
}
void TranslationServer::load_translations() {
diff --git a/core/string/translation.h b/core/string/translation.h
index 72a828227e..4f179ac0fe 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -77,6 +77,26 @@ class TranslationServer : public Object {
bool enabled = true;
+ bool pseudolocalization_enabled = false;
+ bool pseudolocalization_accents_enabled = false;
+ bool pseudolocalization_double_vowels_enabled = false;
+ bool pseudolocalization_fake_bidi_enabled = false;
+ bool pseudolocalization_override_enabled = false;
+ bool pseudolocalization_skip_placeholders_enabled = false;
+ bool editor_pseudolocalization = false;
+ float expansion_ratio = 0.0;
+ String pseudolocalization_prefix;
+ String pseudolocalization_suffix;
+
+ StringName tool_pseudolocalize(const StringName &p_message) const;
+ String get_override_string(String &p_message) const;
+ String double_vowels(String &p_message) const;
+ String replace_with_accented_string(String &p_message) const;
+ String wrap_with_fakebidi_characters(String &p_message) const;
+ String add_padding(String &p_message, int p_length) const;
+ const char32_t *get_accented_version(char32_t p_character) const;
+ bool is_placeholder(String &p_message, int p_index) const;
+
static TranslationServer *singleton;
bool _load_translations(const String &p_from);
@@ -104,6 +124,13 @@ public:
StringName translate(const StringName &p_message, const StringName &p_context = "") const;
StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ StringName pseudolocalize(const StringName &p_message) const;
+
+ bool is_pseudolocalization_enabled() const;
+ void set_pseudolocalization_enabled(bool p_enabled);
+ void set_editor_pseudolocalization(bool p_enabled);
+ void reload_pseudolocalization();
+
static Vector<String> get_all_locales();
static Vector<String> get_all_locale_names();
static bool is_locale_valid(const String &p_locale);
diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h
index 3cb2a6bfb5..001da3ddcb 100644
--- a/core/variant/binder_common.h
+++ b/core/variant/binder_common.h
@@ -101,6 +101,7 @@ VARIANT_ENUM_CAST(MouseButton);
VARIANT_ENUM_CAST(Orientation);
VARIANT_ENUM_CAST(HAlign);
VARIANT_ENUM_CAST(VAlign);
+VARIANT_ENUM_CAST(InlineAlign);
VARIANT_ENUM_CAST(PropertyHint);
VARIANT_ENUM_CAST(PropertyUsageFlags);
VARIANT_ENUM_CAST(Variant::Type);
diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp
index 64f07e075e..34dbf130b6 100644
--- a/core/variant/variant_utility.cpp
+++ b/core/variant/variant_utility.cpp
@@ -249,10 +249,6 @@ struct VariantUtilityFunctions {
return Math::move_toward(from, to, delta);
}
- static inline double dectime(double value, double amount, double step) {
- return Math::dectime(value, amount, step);
- }
-
static inline double deg2rad(double angle_deg) {
return Math::deg2rad(angle_deg);
}
@@ -1195,7 +1191,6 @@ void Variant::_register_variant_utility_functions() {
FUNCBINDR(smoothstep, sarray("from", "to", "x"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(move_toward, sarray("from", "to", "delta"), Variant::UTILITY_FUNC_TYPE_MATH);
- FUNCBINDR(dectime, sarray("value", "amount", "step"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(deg2rad, sarray("deg"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(rad2deg, sarray("rad"), Variant::UTILITY_FUNC_TYPE_MATH);