diff --git a/blackboard/blackboard_plan.cpp b/blackboard/blackboard_plan.cpp index c4bee42..0cdd385 100644 --- a/blackboard/blackboard_plan.cpp +++ b/blackboard/blackboard_plan.cpp @@ -31,24 +31,45 @@ bool BlackboardPlan::_set(const StringName &p_name, const Variant &p_value) { if (name_str.begins_with("mapping/")) { StringName mapped_var_name = name_str.get_slicec('/', 1); StringName value = p_value; - bool properties_changed = false; + bool prop_list_changed = false; if (value == StringName()) { if (parent_scope_mapping.has(mapped_var_name)) { - properties_changed = true; + prop_list_changed = true; parent_scope_mapping.erase(mapped_var_name); } } else { if (!parent_scope_mapping.has(mapped_var_name)) { - properties_changed = true; + prop_list_changed = true; } parent_scope_mapping[mapped_var_name] = value; } - if (properties_changed) { + if (prop_list_changed) { notify_property_list_changed(); } return true; } + // * Binding + if (name_str.begins_with("binding/")) { + StringName bound_var = name_str.get_slicec('/', 1); + NodePath value = p_value; + bool prop_list_changed = false; + if (value.is_empty()) { + if (property_bindings.has(bound_var)) { + prop_list_changed = true; + property_bindings.erase(bound_var); + } + } else { + if (!property_bindings.has(bound_var)) { + prop_list_changed = true; + } + property_bindings[bound_var] = value; + } + if (prop_list_changed) { + notify_property_list_changed(); + } + } + // * Storage if (name_str.begins_with("var/")) { StringName var_name = name_str.get_slicec('/', 1); @@ -66,6 +87,8 @@ bool BlackboardPlan::_set(const StringName &p_name, const Variant &p_value) { var_map[var_name].set_hint((PropertyHint)(int)p_value); } else if (what == "hint_string") { var_map[var_name].set_hint_string(p_value); + } else if (what == "property_binding") { + property_bindings[var_name] = NodePath(p_value); } else { return false; } @@ -82,6 +105,8 @@ bool BlackboardPlan::_get(const StringName &p_name, Variant &r_ret) const { if (var_map.has(p_name)) { if (has_mapping(p_name)) { r_ret = "Mapped to " + LimboUtility::get_singleton()->decorate_var(parent_scope_mapping[p_name]); + } else if (has_property_binding(p_name)) { + r_ret = "Bound to " + property_bindings[p_name]; } else { r_ret = var_map[p_name].get_value(); } @@ -92,11 +117,15 @@ bool BlackboardPlan::_get(const StringName &p_name, Variant &r_ret) const { if (name_str.begins_with("mapping/")) { StringName mapped_var_name = name_str.get_slicec('/', 1); ERR_FAIL_COND_V(mapped_var_name == StringName(), false); - if (parent_scope_mapping.has(mapped_var_name)) { - r_ret = parent_scope_mapping[mapped_var_name]; - } else { - r_ret = StringName(); - } + r_ret = parent_scope_mapping.has(mapped_var_name) ? parent_scope_mapping[mapped_var_name] : StringName(); + return true; + } + + // * Binding + if (name_str.begins_with("binding/")) { + StringName bound_var = name_str.get_slicec('/', 1); + ERR_FAIL_COND_V(bound_var == StringName(), false); + r_ret = property_bindings.has(bound_var) ? property_bindings[bound_var] : NodePath(); return true; } @@ -129,7 +158,7 @@ void BlackboardPlan::_get_property_list(List *p_list) const { // * Editor if (var.get_type() != Variant::NIL && (!is_derived() || !var_name.begins_with("_"))) { - if (has_mapping(var_name)) { + if (has_mapping(var_name) || has_property_binding(var_name)) { p_list->push_back(PropertyInfo(Variant::STRING, var_name, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_READ_ONLY)); } else { p_list->push_back(PropertyInfo(var.get_type(), var_name, var.get_hint(), var.get_hint_string(), PROPERTY_USAGE_EDITOR)); @@ -158,6 +187,15 @@ void BlackboardPlan::_get_property_list(List *p_list) const { p_list->push_back(PropertyInfo(Variant::STRING_NAME, "mapping/" + p.first, PROPERTY_HINT_NONE, "", usage)); } } + + // * Binding + p_list->push_back(PropertyInfo(Variant::NIL, "Binding", PROPERTY_HINT_NONE, "binding/", PROPERTY_USAGE_GROUP)); + for (const Pair &p : var_list) { + PropertyUsageFlags usage = has_property_binding(p.first) ? PROPERTY_USAGE_DEFAULT : PROPERTY_USAGE_EDITOR; + // PROPERTY_HINT_LINK is used to signal that NodePath should point to a property. + // Our inspector plugin will know how to handle it. + p_list->push_back(PropertyInfo(Variant::NODE_PATH, "binding/" + p.first, PROPERTY_HINT_LINK, "", usage)); + } } bool BlackboardPlan::_property_can_revert(const StringName &p_name) const { @@ -199,6 +237,11 @@ bool BlackboardPlan::has_mapping(const StringName &p_name) const { return is_mapping_enabled() && parent_scope_mapping.has(p_name) && parent_scope_mapping[p_name] != StringName(); } +void BlackboardPlan::set_property_binding(const StringName &p_name, const NodePath &p_path) { + property_bindings[p_name] = p_path; + emit_changed(); +} + void BlackboardPlan::set_prefetch_nodepath_vars(bool p_enable) { prefetch_nodepath_vars = p_enable; emit_changed(); diff --git a/blackboard/blackboard_plan.h b/blackboard/blackboard_plan.h index 804f325..be22018 100644 --- a/blackboard/blackboard_plan.h +++ b/blackboard/blackboard_plan.h @@ -34,15 +34,20 @@ private: // When base is not null, the plan is considered to be derived from the base plan. // A derived plan can only have variables that exist in the base plan, // and only the values can be different in those variables. + // The derived plan is synced with the base plan to maintain consistency. Ref base; // Mapping between variables in this plan and their parent scope names. // Used for linking variables to their parent scope counterparts upon Blackboard creation/population. HashMap parent_scope_mapping; - // Fetcher function for the parent scope plan. Funtion should return a Ref. - // Used in the inspector. When set, mapping feature becomes available. + // Fetcher function for the parent scope plan. Function should return a Ref. + // Used in the inspector: enables mapping feature when set. Callable parent_scope_plan_provider; + // Bindings to properties in the scene to which this plan belongs. + HashMap property_bindings; + bool property_binding_enabled = false; + // If true, NodePath variables will be prefetched, so that the vars will contain node pointers instead (upon BB creation/population). bool prefetch_nodepath_vars = true; @@ -69,6 +74,10 @@ public: bool is_mapping_enabled() const { return parent_scope_plan_provider.is_valid() && (parent_scope_plan_provider.call() != Ref()); } bool has_mapping(const StringName &p_name) const; + bool has_property_binding(const StringName &p_name) const { return property_bindings.has(p_name); } + void set_property_binding(const StringName &p_name, const NodePath &p_path); + NodePath get_property_binding(const StringName &p_name) const { return property_bindings.has(p_name) ? property_bindings[p_name] : NodePath(); } + void set_prefetch_nodepath_vars(bool p_enable); bool is_prefetching_nodepath_vars() const;