feat: created character specializations of unit and world state
This commit is contained in:
parent
7312df3679
commit
c9c41ac2d7
|
@ -21,7 +21,7 @@ rings = 3
|
|||
[sub_resource type="BoxMesh" id="BoxMesh_p8wvo"]
|
||||
size = Vector3(0.2, 0.2, 0.5)
|
||||
|
||||
[node name="Tank" type="Unit"]
|
||||
[node name="Tank" type="TankUnit"]
|
||||
configure_team = 3
|
||||
movement_speed = 1.5
|
||||
collision_layer = 6
|
||||
|
|
|
@ -18,12 +18,12 @@ rings = 3
|
|||
[sub_resource type="BoxMesh" id="BoxMesh_p8wvo"]
|
||||
size = Vector3(0.2, 0.2, 0.5)
|
||||
|
||||
[node name="Player" type="Unit"]
|
||||
[node name="Player" type="CharacterUnit"]
|
||||
configure_team = 1
|
||||
collision_layer = 6
|
||||
collision_mask = 0
|
||||
|
||||
[node name="ActorWorldState" type="UnitWorldState" parent="."]
|
||||
[node name="ActorWorldState" type="CharacterWorldState" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
||||
[node name="Planner" type="Planner" parent="."]
|
||||
|
|
21
src/character_unit.cpp
Normal file
21
src/character_unit.cpp
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "character_unit.hpp"
|
||||
|
||||
void CharacterUnit::_bind_methods() {
|
||||
#define CLASSNAME CharacterUnit
|
||||
}
|
||||
|
||||
void CharacterUnit::_ready() {
|
||||
this->parent_type::_ready();
|
||||
this->inventory = this->get_node<Inventory>("%Inventory");
|
||||
}
|
||||
|
||||
void CharacterUnit::use_weapon() {
|
||||
gd::Node3D *target{this->world_state->get_target_node()};
|
||||
Item const *weapon{this->inventory->get_weapon()};
|
||||
if(weapon == nullptr) {
|
||||
this->parent_type::use_weapon(); // use the default unarmed attack instead
|
||||
} else {
|
||||
this->inventory->get_weapon()->try_use_on(this, target);
|
||||
}
|
||||
}
|
||||
|
17
src/character_unit.hpp
Normal file
17
src/character_unit.hpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#ifndef CHARACTER_UNIT_HPP
|
||||
#define CHARACTER_UNIT_HPP
|
||||
|
||||
#include "unit.hpp"
|
||||
#include "inventory.hpp"
|
||||
|
||||
class CharacterUnit : public Unit {
|
||||
GDCLASS(CharacterUnit, Unit);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _ready() override;
|
||||
virtual void use_weapon() override;
|
||||
private:
|
||||
Inventory *inventory{nullptr};
|
||||
};
|
||||
|
||||
#endif // !CHARACTER_UNIT_HPP
|
16
src/character_world_state.cpp
Normal file
16
src/character_world_state.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "character_world_state.hpp"
|
||||
#include "utils/godot_macros.hpp"
|
||||
|
||||
void CharacterWorldState::_bind_methods() {
|
||||
#define CLASSNAME CharacterWorldState
|
||||
}
|
||||
|
||||
void CharacterWorldState::_enter_tree() {
|
||||
this->parent_type::_enter_tree();
|
||||
this->inventory = this->get_node<Inventory>("%Inventory");
|
||||
}
|
||||
|
||||
bool CharacterWorldState::get_is_weapon_ranged() const {
|
||||
return this->inventory->get_weapon()->has_capability(ItemCapability::WeaponRanged);
|
||||
}
|
||||
|
17
src/character_world_state.hpp
Normal file
17
src/character_world_state.hpp
Normal file
|
@ -0,0 +1,17 @@
|
|||
#ifndef CHARACTER_WORLD_STATE_HPP
|
||||
#define CHARACTER_WORLD_STATE_HPP
|
||||
|
||||
#include "inventory.hpp"
|
||||
#include "unit_world_state.hpp"
|
||||
|
||||
class CharacterWorldState : public UnitWorldState {
|
||||
GDCLASS(CharacterWorldState, UnitWorldState);
|
||||
static void _bind_methods();
|
||||
public:
|
||||
virtual void _enter_tree() override;
|
||||
virtual bool get_is_weapon_ranged() const override;
|
||||
private:
|
||||
Inventory *inventory{nullptr};
|
||||
};
|
||||
|
||||
#endif // !CHARACTER_WORLD_STATE_HPP
|
|
@ -2,6 +2,8 @@
|
|||
#define GOAP_STATE_HPP
|
||||
|
||||
#include "actor_world_state.hpp"
|
||||
#include <godot_cpp/core/object.hpp>
|
||||
#include <godot_cpp/classes/object.hpp>
|
||||
#include <godot_cpp/classes/node.hpp>
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ void Inventory::_bind_methods() {
|
|||
}
|
||||
|
||||
void Inventory::_ready() {
|
||||
if(gd::Engine::get_singleton()->is_editor_hint())
|
||||
return;
|
||||
if(gd::Engine::get_singleton()->is_editor_hint())
|
||||
return;
|
||||
this->parent_unit = gd::Object::cast_to<Unit>(this->get_parent());
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ void NavMarker::_bind_methods() {
|
|||
|
||||
void NavMarker::_process(double) {
|
||||
#ifdef DEBUG_ENABLED
|
||||
// only run in editor while selected
|
||||
// only run in editor while selected
|
||||
if(gd::Engine::get_singleton()->is_editor_hint() && gd::EditorInterface::get_singleton()->get_selection()->get_selected_nodes().has(this)) {
|
||||
gd::RID const map_id{this->get_world_3d()->get_navigation_map()};
|
||||
gd::Vector3 const new_point{gd::NavigationServer3D::get_singleton()->map_get_closest_point(map_id, this->get_global_position())};
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include "register_types.h"
|
||||
#include "character_unit.hpp"
|
||||
#include "character_world_state.hpp"
|
||||
#include "enemy_world_state.hpp"
|
||||
#include "entity_health.hpp"
|
||||
#include "inventory.hpp"
|
||||
|
@ -10,6 +12,7 @@
|
|||
#include "rts_items.hpp"
|
||||
#include "rts_player.hpp"
|
||||
#include "rts_states.hpp"
|
||||
#include "tank_unit.hpp"
|
||||
#include "unit.hpp"
|
||||
#include "unit_world_state.hpp"
|
||||
#include "goap/action.hpp"
|
||||
|
@ -60,8 +63,11 @@ void initialize_gdextension_types(gd::ModuleInitializationLevel p_level)
|
|||
GDREGISTER_RUNTIME_CLASS(Activate);
|
||||
GDREGISTER_RUNTIME_CLASS(UnitWorldState);
|
||||
GDREGISTER_RUNTIME_CLASS(EnemyWorldState);
|
||||
GDREGISTER_RUNTIME_CLASS(CharacterWorldState);
|
||||
GDREGISTER_RUNTIME_CLASS(GoalMarker);
|
||||
GDREGISTER_RUNTIME_CLASS(Unit);
|
||||
GDREGISTER_RUNTIME_CLASS(TankUnit)
|
||||
GDREGISTER_RUNTIME_CLASS(CharacterUnit);
|
||||
GDREGISTER_RUNTIME_CLASS(EntityHealth);
|
||||
GDREGISTER_CLASS(NavMarker);
|
||||
GDREGISTER_RUNTIME_CLASS(NavRoom);
|
||||
|
|
|
@ -29,6 +29,7 @@ goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const
|
|||
FireAtTarget::FireAtTarget()
|
||||
: Action() {
|
||||
this->required.insert("can_see_target", true);
|
||||
this->required.insert("is_weapon_ranged", true);
|
||||
this->effects.insert("is_target_dead", true);
|
||||
}
|
||||
|
||||
|
@ -96,7 +97,7 @@ TakeCover::TakeCover()
|
|||
}
|
||||
|
||||
bool TakeCover::procedural_is_possible(goap::ActorWorldState *context) const {
|
||||
// positions of the context and the target
|
||||
// positions of the context and the target
|
||||
gd::Vector3 const context_position{context->get_world_property("parent_global_position")};
|
||||
gd::Vector3 const target_position{context->get_world_property("target_global_position")};
|
||||
// if there is no available navigation room, it is not possible to find a cover marker
|
||||
|
|
|
@ -33,6 +33,9 @@ void Handgun::use_on(Unit* used_by, gd::Object* used_on) const {
|
|||
used_by->aim_at(target);
|
||||
EntityHealth *health{target->get_entity_health()};
|
||||
health->damaged_by(4, used_by);
|
||||
gd::Vector3 const owner_position{used_by->get_global_position()};
|
||||
gd::Vector3 const target_position{target->get_global_position()};
|
||||
used_by->look_at(owner_position-(target_position-owner_position));
|
||||
}
|
||||
|
||||
Lasercutter::Lasercutter()
|
||||
|
|
|
@ -43,7 +43,6 @@ void RTSPlayer::setup_player_input(utils::PlayerInput *input) {
|
|||
input->listen_to("_mouse_down", "_mouse_up", callable_mp(this, &RTSPlayer::on_mouse_vertical));
|
||||
#ifdef DEBUG_ENABLED
|
||||
input->listen_to("DEBUG_toggle_debug", callable_mp(this, &RTSPlayer::DEBUG_enable_debug));
|
||||
gd::UtilityFunctions::print("!!! DEBUG_ENABLED");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
40
src/unit.cpp
40
src/unit.cpp
|
@ -29,7 +29,6 @@ void Unit::_enter_tree() {
|
|||
this->anim_player = this->get_node<gd::AnimationPlayer>("%AnimationPlayer");
|
||||
this->health = this->get_node<EntityHealth>("%EntityHealth");
|
||||
this->health->connect("death", callable_mp(this, &Unit::on_death));
|
||||
this->inventory = this->get_node<Inventory>("%Inventory");
|
||||
}
|
||||
|
||||
void Unit::_physics_process(double) {
|
||||
|
@ -46,9 +45,7 @@ void Unit::_physics_process(double) {
|
|||
void Unit::stop_plan() {
|
||||
this->current_goal.unref();
|
||||
this->current_plan.clear();
|
||||
if(this->state && !this->state->is_queued_for_deletion())
|
||||
this->destroy_state();
|
||||
this->state = nullptr;
|
||||
this->destroy_state();
|
||||
this->call_deferred("emit_signal", "plan_failed");
|
||||
}
|
||||
|
||||
|
@ -78,16 +75,9 @@ void Unit::begin_goal(gd::Ref<goap::Goal> goal) {
|
|||
}
|
||||
|
||||
void Unit::use_weapon() {
|
||||
gd::Node3D *target{this->world_state->get_target_node()};
|
||||
Item const *weapon{this->inventory->get_weapon()};
|
||||
if(weapon == nullptr) {
|
||||
// try and do a melee attack instead when no weapon is equipped
|
||||
Unit *target_unit{gd::Object::cast_to<Unit>(target)};
|
||||
if(target_unit != nullptr && this->world_state->get_can_see_target() && this->world_state->get_is_in_melee_range())
|
||||
target_unit->get_entity_health()->damaged_by(1, this);
|
||||
} else {
|
||||
this->inventory->get_weapon()->try_use_on(this, target);
|
||||
}
|
||||
Unit *target_unit{gd::Object::cast_to<Unit>(this->world_state->get_target_node())};
|
||||
if(target_unit != nullptr && this->world_state->get_can_see_target() && this->world_state->get_is_in_melee_range())
|
||||
target_unit->get_entity_health()->damaged_by(1, this);
|
||||
}
|
||||
|
||||
void Unit::aim_at(gd::Node3D *target) {
|
||||
|
@ -119,9 +109,10 @@ void Unit::on_velocity_computed(gd::Vector3 vel) {
|
|||
}
|
||||
|
||||
void Unit::destroy_state() {
|
||||
if(this->state == nullptr || this->state->is_queued_for_deletion() || !this->state->is_inside_tree())
|
||||
if(this->state == nullptr)
|
||||
return;
|
||||
this->state->interrupt_state();
|
||||
if(!this->state->is_queued_for_deletion() && this->state->is_inside_tree())
|
||||
this->state->interrupt_state();
|
||||
this->state = nullptr;
|
||||
}
|
||||
|
||||
|
@ -132,30 +123,30 @@ void Unit::state_finished() {
|
|||
}
|
||||
|
||||
void Unit::next_action() {
|
||||
// destroy active state if relevant
|
||||
this->destroy_state();
|
||||
// cannot perform actions while dead
|
||||
if(!this->health->is_conscious())
|
||||
return;
|
||||
// destroy active state if relevant
|
||||
if(this->state != nullptr && !this->state->is_queued_for_deletion())
|
||||
this->destroy_state();
|
||||
this->state = nullptr;
|
||||
if(this->current_plan.is_empty())
|
||||
return;
|
||||
goap::Action const *action{this->current_plan.get(0)};
|
||||
if(!action->is_possible(this->world_state)) {
|
||||
if(action == nullptr || !action->is_possible(this->world_state)) {
|
||||
this->emit_signal("plan_interrupted");
|
||||
return;
|
||||
}
|
||||
// pop next action and apply state
|
||||
// get next action and apply state
|
||||
this->state = action->get_apply_state(this->world_state);
|
||||
if(state == nullptr) {
|
||||
this->emit_signal("plan_interrupted");
|
||||
return;
|
||||
}
|
||||
// pop action from plan
|
||||
this->current_plan.remove_at(0);
|
||||
this->add_child(this->state);
|
||||
// register new state and start
|
||||
this->state->connect("state_finished", this->on_state_finished);
|
||||
this->state->connect("state_failed", this->on_plan_failed);
|
||||
this->add_child(this->state);
|
||||
}
|
||||
|
||||
void Unit::replan_goal() {
|
||||
|
@ -168,9 +159,8 @@ void Unit::set_goal_and_plan(gd::Ref<goap::Goal> goal) {
|
|||
this->current_plan.clear();
|
||||
} else {
|
||||
this->current_plan = this->planner->plan_for_goal(goal);
|
||||
if(this->current_plan.is_empty()) {
|
||||
if(this->current_plan.is_empty())
|
||||
this->current_goal.unref();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define RTS_UNIT_HPP
|
||||
|
||||
#include "goal_marker.hpp"
|
||||
#include "inventory.hpp"
|
||||
#include "unit_world_state.hpp"
|
||||
#include "goap/goal.hpp"
|
||||
#include "goap/planner.hpp"
|
||||
|
@ -70,7 +69,6 @@ protected:
|
|||
goap::Planner *planner{nullptr};
|
||||
EntityHealth *health{nullptr};
|
||||
UnitWorldState *world_state{nullptr};
|
||||
Inventory *inventory{nullptr};
|
||||
#ifdef DEBUG_ENABLED
|
||||
public:
|
||||
gd::String DEBUG_print_debug_info();
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
void UnitWorldState::_bind_methods() {
|
||||
#define CLASSNAME UnitWorldState
|
||||
GDSIGNAL("attention_changed");
|
||||
GDFUNCTION(get_is_weapon_ranged);
|
||||
GDFUNCTION(get_can_see_target);
|
||||
GDFUNCTION(get_is_target_dead);
|
||||
GDFUNCTION(get_is_at_target);
|
||||
|
@ -41,6 +42,10 @@ gd::Variant UnitWorldState::get_world_property(gd::String property) {
|
|||
return ActorWorldState::get_world_property(property);
|
||||
}
|
||||
|
||||
bool UnitWorldState::get_is_weapon_ranged() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UnitWorldState::get_can_see_target() {
|
||||
bool value{this->get_can_see_node(this->target_node)};
|
||||
this->cache_property("can_see_target", value, utils::time_seconds() + 0.5);
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#define UNIT_WORLD_STATE_HPP
|
||||
|
||||
#include "entity_health.hpp"
|
||||
#include "inventory.hpp"
|
||||
#include "goap/actor_world_state.hpp"
|
||||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
|
@ -21,6 +22,8 @@ class UnitWorldState : public goap::ActorWorldState {
|
|||
public:
|
||||
virtual void _enter_tree() override;
|
||||
virtual gd::Variant get_world_property(gd::String property) override;
|
||||
|
||||
virtual bool get_is_weapon_ranged() const;
|
||||
bool get_can_see_target();
|
||||
bool get_can_see_node(gd::Node3D *node) const;
|
||||
bool get_is_at_target() const;
|
||||
|
|
Loading…
Reference in a new issue