diff --git a/modules/entity_spell_system/editor/ess_editor_plugin.cpp b/modules/entity_spell_system/editor/ess_editor_plugin.cpp index 5db55ff46..498fc7450 100644 --- a/modules/entity_spell_system/editor/ess_editor_plugin.cpp +++ b/modules/entity_spell_system/editor/ess_editor_plugin.cpp @@ -65,10 +65,12 @@ WorldSpawner3DSpatialGizmoPlugin::WorldSpawner3DSpatialGizmoPlugin() { const Color gizmo_outline_color = EDITOR_DEF("editors/ess/gizmo_colors/world_spawner_outline", Color(0.5, 0.5, 1)); const Color gizmo_text_color = EDITOR_DEF("editors/ess/gizmo_colors/world_spawner_text", Color(0.5, 0.5, 1)); const Color gizmo_area_outline_color = EDITOR_DEF("editors/ess/gizmo_colors/world_spawner_area_outline", Color(0.7, 0.3, 0.3)); + const Color gizmo_area_spawn_pos_color = EDITOR_DEF("editors/ess/gizmo_colors/world_spawner_area_spawn_position", Color(0.9, 0.5, 0.3)); create_material("outline_material", gizmo_outline_color); create_material("text_material", gizmo_text_color); create_material("area_outline_material", gizmo_area_outline_color); + create_material("area_spawn_pos_material", gizmo_area_spawn_pos_color); create_handle_material("handles"); } @@ -193,6 +195,28 @@ void WorldSpawner3DSpatialGizmoPlugin::redraw(EditorSpatialGizmo *p_gizmo) { p_gizmo->add_lines(lines, area_outline_material); p_gizmo->add_collision_segments(lines); p_gizmo->add_handles(handles, handles_material); + + // Spawn positions + + Vector spawn_positions = aws->get_spawn_positions(); + + if (spawn_positions.empty()) { + return; + } + + lines.clear(); + + const Ref area_spawn_pos_material = get_material("area_spawn_pos_material"); + + for (int i = 0; i < spawn_positions.size(); ++i) { + Vector2 p = spawn_positions[i]; + + lines.push_back(Vector3(p.x, -extents.z, p.y)); + lines.push_back(Vector3(p.x, extents.z, p.y)); + } + + p_gizmo->add_lines(lines, area_spawn_pos_material); + p_gizmo->add_collision_segments(lines); } String WorldSpawner3DSpatialGizmoPlugin::get_handle_name(const EditorSpatialGizmo *p_gizmo, int p_id, bool p_secondary) const { diff --git a/modules/entity_spell_system/world_spawners/ess_entity_world_spawner_3d_area.cpp b/modules/entity_spell_system/world_spawners/ess_entity_world_spawner_3d_area.cpp index 8116d8bf0..626de631e 100644 --- a/modules/entity_spell_system/world_spawners/ess_entity_world_spawner_3d_area.cpp +++ b/modules/entity_spell_system/world_spawners/ess_entity_world_spawner_3d_area.cpp @@ -48,27 +48,17 @@ Vector3 ESSEntityWorldSpawner3DArea::get_spawn_area_extents() const { } void ESSEntityWorldSpawner3DArea::set_spawn_area_extents(const Vector3 &p_extents) { _spawn_area_extents = p_extents; + + _spawn_slots_dirty = true; } -String ESSEntityWorldSpawner3DArea::get_entity_name() const { - return _entity_name; -} -void ESSEntityWorldSpawner3DArea::set_entity_name(const String &p_name) { - _entity_name = p_name; +int ESSEntityWorldSpawner3DArea::get_spawn_slot_count() const { + return _spawn_slot_count; } +void ESSEntityWorldSpawner3DArea::set_spawn_slot_count(const int p_spawn_slot_count) { + _spawn_slot_count = p_spawn_slot_count; -Ref ESSEntityWorldSpawner3DArea::get_entity_data() const { - return _entity_data; -} -void ESSEntityWorldSpawner3DArea::set_entity_data(const Ref &p_data) { - _entity_data = p_data; -} - -int ESSEntityWorldSpawner3DArea::get_level() const { - return _level; -} -void ESSEntityWorldSpawner3DArea::set_level(const int p_level) { - _level = p_level; + _spawn_slots_dirty = true; } float ESSEntityWorldSpawner3DArea::get_respawn_time_min() const { @@ -85,12 +75,89 @@ void ESSEntityWorldSpawner3DArea::set_respawn_time_max(const float p_respawn_tim _respawn_time_max = p_respawn_time; } -void ESSEntityWorldSpawner3DArea::_spawn() { - if (Engine::get_singleton()->is_editor_hint()) { - return; - } +String ESSEntityWorldSpawner3DArea::get_entity_name(const int p_index) const { + ERR_FAIL_INDEX_V(p_index, _spawn_entries.size(), String()); - if (!_entity_data.is_valid()) { + return _spawn_entries[p_index].entity_name; +} +void ESSEntityWorldSpawner3DArea::set_entity_name(const int p_index, const String &p_name) { + ERR_FAIL_INDEX(p_index, _spawn_entries.size()); + + _spawn_entries.write[p_index].entity_name = p_name; + + _spawn_entries_dirty = true; +} + +Ref ESSEntityWorldSpawner3DArea::get_entity_data(const int p_index) const { + ERR_FAIL_INDEX_V(p_index, _spawn_entries.size(), Ref()); + + return _spawn_entries[p_index].entity_data; +} +void ESSEntityWorldSpawner3DArea::set_entity_data(const int p_index, const Ref &p_data) { + ERR_FAIL_INDEX(p_index, _spawn_entries.size()); + + _spawn_entries.write[p_index].entity_data = p_data; + + _spawn_entries_dirty = true; +} + +Vector2i ESSEntityWorldSpawner3DArea::get_entity_level_range(const int p_index) const { + ERR_FAIL_INDEX_V(p_index, _spawn_entries.size(), Vector2i()); + + return _spawn_entries[p_index].level_range; +} +void ESSEntityWorldSpawner3DArea::set_entity_level_range(const int p_index, const Vector2i &p_level_range) { + ERR_FAIL_INDEX(p_index, _spawn_entries.size()); + + _spawn_entries.write[p_index].level_range = p_level_range; + + _spawn_entries_dirty = true; +} + +float ESSEntityWorldSpawner3DArea::get_entity_spawn_chance(const int p_index) const { + ERR_FAIL_INDEX_V(p_index, _spawn_entries.size(), 0); + + return _spawn_entries[p_index].spawn_chance; +} +void ESSEntityWorldSpawner3DArea::set_entity_spawn_chance(const int p_index, const float p_spawn_chance) { + ERR_FAIL_INDEX(p_index, _spawn_entries.size()); + + _spawn_entries.write[p_index].spawn_chance = p_spawn_chance; + + _spawn_entries_dirty = true; +} + +int ESSEntityWorldSpawner3DArea::get_spawn_entry_count() const { + return _spawn_entries.size(); +} +void ESSEntityWorldSpawner3DArea::clear_spawn_entries() { + _spawn_entries.clear(); + _spawn_entries_dirty = true; +} +void ESSEntityWorldSpawner3DArea::add_spawn_entry(const String &p_name, const Ref &p_data, const Vector2i &p_level_range, const float p_spawn_chance) { + SpawnEntry e; + + e.entity_name = p_name; + e.entity_data = p_data; + e.level_range = p_level_range; + e.spawn_chance = p_spawn_chance; + + _spawn_entries.push_back(e); + + _spawn_entries_dirty = true; +} +void ESSEntityWorldSpawner3DArea::remove_spawn_entry(const int p_index) { + ERR_FAIL_INDEX(p_index, _spawn_entries.size()); + + _spawn_entries.remove(p_index); + + _spawn_entries_dirty = true; +} + +void ESSEntityWorldSpawner3DArea::_spawn() { + // force spawns in all empty slots + + if (Engine::get_singleton()->is_editor_hint()) { return; } @@ -98,83 +165,218 @@ void ESSEntityWorldSpawner3DArea::_spawn() { return; } - Ref info; - info.instance(); - - if (!_entity_name.empty()) { - info->set_entity_name(_entity_name); - } else { - info->set_entity_name(get_name()); + if (_spawn_entries_dirty) { + _recalculate_valid_spawn_entries(); } - info->set_entity_controller(EntityEnums::ENITIY_CONTROLLER_AI); - info->set_level(_level); - info->set_transform(get_global_transform()); - info->set_entity_data(_entity_data); - info->set_species_instance(_entity_data->get_species_instance()); - ESS::get_singleton()->request_entity_spawn(info); - - Entity *created_entity = info->get_created_entity(); - - if (created_entity != NULL) { - created_entity->sets_spawner_object_id(get_instance_id()); - _entity = created_entity->get_instance_id(); - created_entity->connect("tree_exited", this, "_on_entity_tree_exited"); + if (_spawn_slots_dirty) { + _recalculate_slots(); } - emit_on_entity_spawned(info); + for (int i = 0; i < _spawn_slots.size(); ++i) { + const SpawnSlot &e = _spawn_slots.write[i]; + + if (e.entity == ObjectID()) { + _spawn_slot(i); + } + } } ESSEntityWorldSpawner3DArea::ESSEntityWorldSpawner3DArea() { - _level = 1; - _entity = ObjectID(); + _spawn_slot_count = 0; _respawn_time_min = 0; _respawn_time_max = 0; - _respawn_timer = 0; _spawn_area_extents = Vector3(2, 2, 2); + _spawn_slots_dirty = true; + _spawn_entries_dirty = true; + _spawn_entries_max_chance = 0; } ESSEntityWorldSpawner3DArea::~ESSEntityWorldSpawner3DArea() { } -void ESSEntityWorldSpawner3DArea::_on_entity_tree_exited() { - _entity = ObjectID(); +Vector ESSEntityWorldSpawner3DArea::get_spawn_positions() const { + Vector pos; - if (_respawn_time_min > CMP_EPSILON) { - if (_respawn_time_max > CMP_EPSILON) { - _respawn_timer = Math::random(_respawn_time_min, _respawn_time_max); - } else { - _respawn_timer = _respawn_time_min; - } + for (int i = 0; i < _spawn_slots.size(); ++i) { + const SpawnSlot &e = _spawn_slots[i]; - set_process_internal(true); + pos.push_back(e.position); } + + return pos; +} + +void ESSEntityWorldSpawner3DArea::_on_entity_tree_exited(const ObjectID p_entity_object_id) { + for (int i = 0; i < _spawn_slots.size(); ++i) { + SpawnSlot &e = _spawn_slots.write[i]; + + if (e.entity == p_entity_object_id) { + e.entity = ObjectID(); + + if (_respawn_time_min > CMP_EPSILON) { + if (_respawn_time_max > CMP_EPSILON) { + e.respawn_timer = Math::random(_respawn_time_min, _respawn_time_max); + } else { + e.respawn_timer = _respawn_time_min; + } + + set_process_internal(true); + } + + return; + } + } +} + +void ESSEntityWorldSpawner3DArea::_recalculate_slots() { + _spawn_slots.resize(_spawn_slot_count); + + for (int i = 0; i < _spawn_slots.size(); ++i) { + SpawnSlot &e = _spawn_slots.write[i]; + + e.position = Vector2(Math::random(-_spawn_area_extents.x, _spawn_area_extents.x), Math::random(-_spawn_area_extents.z, _spawn_area_extents.z)); + } + + _spawn_slots_dirty = false; +} + +void ESSEntityWorldSpawner3DArea::_recalculate_valid_spawn_entries() { + _spawn_entries_max_chance = 0; + _valid_spawn_entries.clear(); + + for (int i = 0; i < _spawn_entries.size(); ++i) { + const SpawnEntry &e = _spawn_entries[i]; + + if (e.entity_data.is_valid() && e.spawn_chance > CMP_EPSILON) { + _valid_spawn_entries.push_back(e); + _spawn_entries_max_chance += e.spawn_chance; + } + } + + _spawn_entries_dirty = false; +} + +void ESSEntityWorldSpawner3DArea::_spawn_slot(const int p_index) { + int spawn_index = generate_spawn_index(); + + if (spawn_index == -1) { + return; + } + + const SpawnEntry &e = _spawn_entries[spawn_index]; + + SpawnSlot &s = _spawn_slots.write[p_index]; + + Ref info; + info.instance(); + + if (!e.entity_name.empty()) { + info->set_entity_name(e.entity_name); + } else { + info->set_entity_name(get_name()); + } + + info->set_entity_controller(EntityEnums::ENITIY_CONTROLLER_AI); + + int level; + + if (e.level_range.y > 0) { + level = Math::randomi(e.level_range.x, e.level_range.y); + } else { + level = e.level_range.x; + } + + info->set_level(level); + + Transform offset; + offset.translate_local(Vector3(s.position.x, 0, s.position.y)); + + info->set_transform(get_global_transform() * offset); + + info->set_entity_data(e.entity_data); + info->set_species_instance(e.entity_data->get_species_instance()); + ESS::get_singleton()->request_entity_spawn(info); + + s.respawn_timer = 0; + + Entity *created_entity = info->get_created_entity(); + + if (created_entity != NULL) { + created_entity->sets_spawner_object_id(get_instance_id()); + s.entity = created_entity->get_instance_id(); + created_entity->connect("tree_exited", this, "_on_entity_tree_exited", varray(s.entity)); + } + + emit_on_entity_spawned(info); +} + +int ESSEntityWorldSpawner3DArea::generate_spawn_index() { + if (_spawn_entries_dirty) { + _recalculate_valid_spawn_entries(); + } + + float rnd = Math::random(0.0f, _spawn_entries_max_chance); + float current_spawn_chance = 0; + + for (int i = 0; i < _spawn_entries.size(); ++i) { + const SpawnEntry &e = _spawn_entries[i]; + + current_spawn_chance += e.spawn_chance; + + if (rnd < current_spawn_chance) { + return i; + } + } + + return _spawn_entries.size() - 1; } void ESSEntityWorldSpawner3DArea::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { call_deferred("spawn"); - break; - case NOTIFICATION_EXIT_TREE: - if (_entity != ObjectID()) { - Entity *ent = ObjectDB::get_instance(_entity); + } break; + case NOTIFICATION_EXIT_TREE: { + for (int i = 0; i < _spawn_slots.size(); ++i) { + SpawnSlot &s = _spawn_slots.write[i]; - if (ent) { - ent->sets_spawner_object_id(ObjectID()); + if (s.entity != ObjectID()) { + Entity *ent = ObjectDB::get_instance(s.entity); + + if (ent) { + ent->sets_spawner_object_id(ObjectID()); + } + + s.entity = ObjectID(); } } - break; - case NOTIFICATION_INTERNAL_PROCESS: - _respawn_timer -= get_tree()->get_idle_process_time(); + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + float delta = get_tree()->get_idle_process_time(); - if (_respawn_timer <= 0) { - _respawn_timer = 0; - set_process_internal(false); - call_deferred("spawn"); + bool has_active_timer = false; + + for (int i = 0; i < _spawn_slots.size(); ++i) { + SpawnSlot &s = _spawn_slots.write[i]; + + if (s.respawn_timer > CMP_EPSILON2) { + s.respawn_timer -= delta; + + if (s.respawn_timer <= 0) { + s.respawn_timer = 0; + + call_deferred("_spawn_slot", i); + } else { + has_active_timer = true; + } + } } - break; + if (!has_active_timer) { + set_process_internal(false); + } + } break; default: break; } @@ -185,25 +387,41 @@ void ESSEntityWorldSpawner3DArea::_bind_methods() { ClassDB::bind_method(D_METHOD("set_spawn_area_extents", "value"), &ESSEntityWorldSpawner3DArea::set_spawn_area_extents); ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "spawn_area_extents"), "set_spawn_area_extents", "get_spawn_area_extents"); - ClassDB::bind_method(D_METHOD("get_entity_name"), &ESSEntityWorldSpawner3DArea::get_entity_name); - ClassDB::bind_method(D_METHOD("set_entity_name", "value"), &ESSEntityWorldSpawner3DArea::set_entity_name); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "entity_name"), "set_entity_name", "get_entity_name"); - - ClassDB::bind_method(D_METHOD("get_entity_data"), &ESSEntityWorldSpawner3DArea::get_entity_data); - ClassDB::bind_method(D_METHOD("set_entity_data", "value"), &ESSEntityWorldSpawner3DArea::set_entity_data); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "entity_data", PROPERTY_HINT_RESOURCE_TYPE, "EntityData"), "set_entity_data", "get_entity_data"); - - ClassDB::bind_method(D_METHOD("get_level"), &ESSEntityWorldSpawner3DArea::get_level); - ClassDB::bind_method(D_METHOD("set_level", "value"), &ESSEntityWorldSpawner3DArea::set_level); - ADD_PROPERTY(PropertyInfo(Variant::INT, "level"), "set_level", "get_level"); + ClassDB::bind_method(D_METHOD("get_spawn_slot_count"), &ESSEntityWorldSpawner3DArea::get_spawn_slot_count); + ClassDB::bind_method(D_METHOD("set_spawn_slot_count", "respawn_time"), &ESSEntityWorldSpawner3DArea::set_spawn_slot_count); + ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_slot_count"), "set_spawn_slot_count", "get_spawn_slot_count"); ClassDB::bind_method(D_METHOD("get_respawn_time_min"), &ESSEntityWorldSpawner3DArea::get_respawn_time_min); ClassDB::bind_method(D_METHOD("set_respawn_time_min", "respawn_time"), &ESSEntityWorldSpawner3DArea::set_respawn_time_min); - ADD_PROPERTY(PropertyInfo(Variant::INT, "respawn_time_min"), "set_respawn_time_min", "get_respawn_time_min"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "respawn_time_min"), "set_respawn_time_min", "get_respawn_time_min"); ClassDB::bind_method(D_METHOD("get_respawn_time_max"), &ESSEntityWorldSpawner3DArea::get_respawn_time_max); ClassDB::bind_method(D_METHOD("set_respawn_time_max", "respawn_time"), &ESSEntityWorldSpawner3DArea::set_respawn_time_max); - ADD_PROPERTY(PropertyInfo(Variant::INT, "respawn_time_max"), "set_respawn_time_max", "get_respawn_time_max"); + ADD_PROPERTY(PropertyInfo(Variant::REAL, "respawn_time_max"), "set_respawn_time_max", "get_respawn_time_max"); + + ClassDB::bind_method(D_METHOD("get_entity_name", "index"), &ESSEntityWorldSpawner3DArea::get_entity_name); + ClassDB::bind_method(D_METHOD("set_entity_name", "index", "name"), &ESSEntityWorldSpawner3DArea::set_entity_name); + + ClassDB::bind_method(D_METHOD("get_entity_data", "index"), &ESSEntityWorldSpawner3DArea::get_entity_data); + ClassDB::bind_method(D_METHOD("set_entity_data", "index", "data"), &ESSEntityWorldSpawner3DArea::set_entity_data); + + ClassDB::bind_method(D_METHOD("get_entity_level_range", "index"), &ESSEntityWorldSpawner3DArea::get_entity_level_range); + ClassDB::bind_method(D_METHOD("set_entity_level_range", "index", "level_range"), &ESSEntityWorldSpawner3DArea::set_entity_level_range); + + ClassDB::bind_method(D_METHOD("get_entity_spawn_chance", "index"), &ESSEntityWorldSpawner3DArea::get_entity_spawn_chance); + ClassDB::bind_method(D_METHOD("set_entity_spawn_chance", "index", "spawn_chance"), &ESSEntityWorldSpawner3DArea::set_entity_spawn_chance); + + ClassDB::bind_method(D_METHOD("get_spawn_entry_count"), &ESSEntityWorldSpawner3DArea::get_spawn_entry_count); + ClassDB::bind_method(D_METHOD("clear_spawn_entries"), &ESSEntityWorldSpawner3DArea::clear_spawn_entries); + ClassDB::bind_method(D_METHOD("add_spawn_entry", "name", "data", "level_range", "spawn_chance"), &ESSEntityWorldSpawner3DArea::add_spawn_entry); + ClassDB::bind_method(D_METHOD("remove_spawn_entry", "index"), &ESSEntityWorldSpawner3DArea::remove_spawn_entry); ClassDB::bind_method(D_METHOD("_on_entity_tree_exited"), &ESSEntityWorldSpawner3DArea::_on_entity_tree_exited); + ClassDB::bind_method(D_METHOD("_spawn_slot"), &ESSEntityWorldSpawner3DArea::_spawn_slot); +} + +ESSEntityWorldSpawner3DArea::SpawnEntry::SpawnEntry() { + spawn_chance = 1; +} +ESSEntityWorldSpawner3DArea::SpawnEntry::~SpawnEntry() { } diff --git a/modules/entity_spell_system/world_spawners/ess_entity_world_spawner_3d_area.h b/modules/entity_spell_system/world_spawners/ess_entity_world_spawner_3d_area.h index 33b950ab9..e44411ec9 100644 --- a/modules/entity_spell_system/world_spawners/ess_entity_world_spawner_3d_area.h +++ b/modules/entity_spell_system/world_spawners/ess_entity_world_spawner_3d_area.h @@ -43,14 +43,8 @@ public: Vector3 get_spawn_area_extents() const; void set_spawn_area_extents(const Vector3 &p_extents); - String get_entity_name() const; - void set_entity_name(const String &p_name); - - Ref get_entity_data() const; - void set_entity_data(const Ref &p_data); - - int get_level() const; - void set_level(const int p_level); + int get_spawn_slot_count() const; + void set_spawn_slot_count(const int p_spawn_slot_count); float get_respawn_time_min() const; void set_respawn_time_min(const float p_respawn_time); @@ -58,27 +52,76 @@ public: float get_respawn_time_max() const; void set_respawn_time_max(const float p_respawn_time); + String get_entity_name(const int p_index) const; + void set_entity_name(const int p_index, const String &p_name); + + Ref get_entity_data(const int p_index) const; + void set_entity_data(const int p_index, const Ref &p_data); + + Vector2i get_entity_level_range(const int p_index) const; + void set_entity_level_range(const int p_index, const Vector2i &p_level_range); + + float get_entity_spawn_chance(const int p_index) const; + void set_entity_spawn_chance(const int p_index, const float p_spawn_chance); + + int get_spawn_entry_count() const; + void clear_spawn_entries(); + void add_spawn_entry(const String &p_name, const Ref &p_data, const Vector2i &p_level_range, const float p_spawn_chance); + void remove_spawn_entry(const int p_index); + virtual void _spawn(); ESSEntityWorldSpawner3DArea(); ~ESSEntityWorldSpawner3DArea(); + // For the gizmo + Vector get_spawn_positions() const; + protected: void _notification(int p_what); static void _bind_methods(); - void _on_entity_tree_exited(); + void _on_entity_tree_exited(const ObjectID p_entity_object_id); + void _recalculate_slots(); + void _recalculate_valid_spawn_entries(); + void _spawn_slot(const int p_index); + int generate_spawn_index(); Vector3 _spawn_area_extents; + int _spawn_slot_count; + + bool _spawn_slots_dirty; - String _entity_name; - Ref _entity_data; - int _level; float _respawn_time_min; float _respawn_time_max; - real_t _respawn_timer; - ObjectID _entity; + struct SpawnEntry { + String entity_name; + Ref entity_data; + Vector2i level_range; + float spawn_chance; + + SpawnEntry(); + ~SpawnEntry(); + }; + + Vector _spawn_entries; + Vector _valid_spawn_entries; + float _spawn_entries_max_chance; + bool _spawn_entries_dirty; + + struct SpawnSlot { + Vector2 position; + float respawn_timer; + ObjectID entity; + + SpawnSlot() { + respawn_timer = 0; + entity = ObjectID(); + } + }; + + Vector _spawn_slots; }; #endif