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"]
|
[sub_resource type="BoxMesh" id="BoxMesh_p8wvo"]
|
||||||
size = Vector3(0.2, 0.2, 0.5)
|
size = Vector3(0.2, 0.2, 0.5)
|
||||||
|
|
||||||
[node name="Tank" type="Unit"]
|
[node name="Tank" type="TankUnit"]
|
||||||
configure_team = 3
|
configure_team = 3
|
||||||
movement_speed = 1.5
|
movement_speed = 1.5
|
||||||
collision_layer = 6
|
collision_layer = 6
|
||||||
|
|
|
@ -18,12 +18,12 @@ rings = 3
|
||||||
[sub_resource type="BoxMesh" id="BoxMesh_p8wvo"]
|
[sub_resource type="BoxMesh" id="BoxMesh_p8wvo"]
|
||||||
size = Vector3(0.2, 0.2, 0.5)
|
size = Vector3(0.2, 0.2, 0.5)
|
||||||
|
|
||||||
[node name="Player" type="Unit"]
|
[node name="Player" type="CharacterUnit"]
|
||||||
configure_team = 1
|
configure_team = 1
|
||||||
collision_layer = 6
|
collision_layer = 6
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
|
|
||||||
[node name="ActorWorldState" type="UnitWorldState" parent="."]
|
[node name="ActorWorldState" type="CharacterWorldState" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
||||||
[node name="Planner" type="Planner" parent="."]
|
[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
|
#define GOAP_STATE_HPP
|
||||||
|
|
||||||
#include "actor_world_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/node.hpp>
|
||||||
#include <godot_cpp/classes/node3d.hpp>
|
#include <godot_cpp/classes/node3d.hpp>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#include "register_types.h"
|
#include "register_types.h"
|
||||||
|
#include "character_unit.hpp"
|
||||||
|
#include "character_world_state.hpp"
|
||||||
#include "enemy_world_state.hpp"
|
#include "enemy_world_state.hpp"
|
||||||
#include "entity_health.hpp"
|
#include "entity_health.hpp"
|
||||||
#include "inventory.hpp"
|
#include "inventory.hpp"
|
||||||
|
@ -10,6 +12,7 @@
|
||||||
#include "rts_items.hpp"
|
#include "rts_items.hpp"
|
||||||
#include "rts_player.hpp"
|
#include "rts_player.hpp"
|
||||||
#include "rts_states.hpp"
|
#include "rts_states.hpp"
|
||||||
|
#include "tank_unit.hpp"
|
||||||
#include "unit.hpp"
|
#include "unit.hpp"
|
||||||
#include "unit_world_state.hpp"
|
#include "unit_world_state.hpp"
|
||||||
#include "goap/action.hpp"
|
#include "goap/action.hpp"
|
||||||
|
@ -60,8 +63,11 @@ void initialize_gdextension_types(gd::ModuleInitializationLevel p_level)
|
||||||
GDREGISTER_RUNTIME_CLASS(Activate);
|
GDREGISTER_RUNTIME_CLASS(Activate);
|
||||||
GDREGISTER_RUNTIME_CLASS(UnitWorldState);
|
GDREGISTER_RUNTIME_CLASS(UnitWorldState);
|
||||||
GDREGISTER_RUNTIME_CLASS(EnemyWorldState);
|
GDREGISTER_RUNTIME_CLASS(EnemyWorldState);
|
||||||
|
GDREGISTER_RUNTIME_CLASS(CharacterWorldState);
|
||||||
GDREGISTER_RUNTIME_CLASS(GoalMarker);
|
GDREGISTER_RUNTIME_CLASS(GoalMarker);
|
||||||
GDREGISTER_RUNTIME_CLASS(Unit);
|
GDREGISTER_RUNTIME_CLASS(Unit);
|
||||||
|
GDREGISTER_RUNTIME_CLASS(TankUnit)
|
||||||
|
GDREGISTER_RUNTIME_CLASS(CharacterUnit);
|
||||||
GDREGISTER_RUNTIME_CLASS(EntityHealth);
|
GDREGISTER_RUNTIME_CLASS(EntityHealth);
|
||||||
GDREGISTER_CLASS(NavMarker);
|
GDREGISTER_CLASS(NavMarker);
|
||||||
GDREGISTER_RUNTIME_CLASS(NavRoom);
|
GDREGISTER_RUNTIME_CLASS(NavRoom);
|
||||||
|
|
|
@ -29,6 +29,7 @@ goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const
|
||||||
FireAtTarget::FireAtTarget()
|
FireAtTarget::FireAtTarget()
|
||||||
: Action() {
|
: Action() {
|
||||||
this->required.insert("can_see_target", true);
|
this->required.insert("can_see_target", true);
|
||||||
|
this->required.insert("is_weapon_ranged", true);
|
||||||
this->effects.insert("is_target_dead", true);
|
this->effects.insert("is_target_dead", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ void Handgun::use_on(Unit* used_by, gd::Object* used_on) const {
|
||||||
used_by->aim_at(target);
|
used_by->aim_at(target);
|
||||||
EntityHealth *health{target->get_entity_health()};
|
EntityHealth *health{target->get_entity_health()};
|
||||||
health->damaged_by(4, used_by);
|
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()
|
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));
|
input->listen_to("_mouse_down", "_mouse_up", callable_mp(this, &RTSPlayer::on_mouse_vertical));
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
input->listen_to("DEBUG_toggle_debug", callable_mp(this, &RTSPlayer::DEBUG_enable_debug));
|
input->listen_to("DEBUG_toggle_debug", callable_mp(this, &RTSPlayer::DEBUG_enable_debug));
|
||||||
gd::UtilityFunctions::print("!!! DEBUG_ENABLED");
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
32
src/unit.cpp
32
src/unit.cpp
|
@ -29,7 +29,6 @@ void Unit::_enter_tree() {
|
||||||
this->anim_player = this->get_node<gd::AnimationPlayer>("%AnimationPlayer");
|
this->anim_player = this->get_node<gd::AnimationPlayer>("%AnimationPlayer");
|
||||||
this->health = this->get_node<EntityHealth>("%EntityHealth");
|
this->health = this->get_node<EntityHealth>("%EntityHealth");
|
||||||
this->health->connect("death", callable_mp(this, &Unit::on_death));
|
this->health->connect("death", callable_mp(this, &Unit::on_death));
|
||||||
this->inventory = this->get_node<Inventory>("%Inventory");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::_physics_process(double) {
|
void Unit::_physics_process(double) {
|
||||||
|
@ -46,9 +45,7 @@ void Unit::_physics_process(double) {
|
||||||
void Unit::stop_plan() {
|
void Unit::stop_plan() {
|
||||||
this->current_goal.unref();
|
this->current_goal.unref();
|
||||||
this->current_plan.clear();
|
this->current_plan.clear();
|
||||||
if(this->state && !this->state->is_queued_for_deletion())
|
|
||||||
this->destroy_state();
|
this->destroy_state();
|
||||||
this->state = nullptr;
|
|
||||||
this->call_deferred("emit_signal", "plan_failed");
|
this->call_deferred("emit_signal", "plan_failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,16 +75,9 @@ void Unit::begin_goal(gd::Ref<goap::Goal> goal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::use_weapon() {
|
void Unit::use_weapon() {
|
||||||
gd::Node3D *target{this->world_state->get_target_node()};
|
Unit *target_unit{gd::Object::cast_to<Unit>(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())
|
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);
|
target_unit->get_entity_health()->damaged_by(1, this);
|
||||||
} else {
|
|
||||||
this->inventory->get_weapon()->try_use_on(this, target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::aim_at(gd::Node3D *target) {
|
void Unit::aim_at(gd::Node3D *target) {
|
||||||
|
@ -119,8 +109,9 @@ void Unit::on_velocity_computed(gd::Vector3 vel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::destroy_state() {
|
void Unit::destroy_state() {
|
||||||
if(this->state == nullptr || this->state->is_queued_for_deletion() || !this->state->is_inside_tree())
|
if(this->state == nullptr)
|
||||||
return;
|
return;
|
||||||
|
if(!this->state->is_queued_for_deletion() && this->state->is_inside_tree())
|
||||||
this->state->interrupt_state();
|
this->state->interrupt_state();
|
||||||
this->state = nullptr;
|
this->state = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -132,30 +123,30 @@ void Unit::state_finished() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::next_action() {
|
void Unit::next_action() {
|
||||||
|
// destroy active state if relevant
|
||||||
|
this->destroy_state();
|
||||||
// cannot perform actions while dead
|
// cannot perform actions while dead
|
||||||
if(!this->health->is_conscious())
|
if(!this->health->is_conscious())
|
||||||
return;
|
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())
|
if(this->current_plan.is_empty())
|
||||||
return;
|
return;
|
||||||
goap::Action const *action{this->current_plan.get(0)};
|
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");
|
this->emit_signal("plan_interrupted");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// pop next action and apply state
|
// get next action and apply state
|
||||||
this->state = action->get_apply_state(this->world_state);
|
this->state = action->get_apply_state(this->world_state);
|
||||||
if(state == nullptr) {
|
if(state == nullptr) {
|
||||||
this->emit_signal("plan_interrupted");
|
this->emit_signal("plan_interrupted");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// pop action from plan
|
||||||
this->current_plan.remove_at(0);
|
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_finished", this->on_state_finished);
|
||||||
this->state->connect("state_failed", this->on_plan_failed);
|
this->state->connect("state_failed", this->on_plan_failed);
|
||||||
|
this->add_child(this->state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unit::replan_goal() {
|
void Unit::replan_goal() {
|
||||||
|
@ -168,10 +159,9 @@ void Unit::set_goal_and_plan(gd::Ref<goap::Goal> goal) {
|
||||||
this->current_plan.clear();
|
this->current_plan.clear();
|
||||||
} else {
|
} else {
|
||||||
this->current_plan = this->planner->plan_for_goal(goal);
|
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();
|
this->current_goal.unref();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UnitWorldState *Unit::get_world_state() const {
|
UnitWorldState *Unit::get_world_state() const {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#define RTS_UNIT_HPP
|
#define RTS_UNIT_HPP
|
||||||
|
|
||||||
#include "goal_marker.hpp"
|
#include "goal_marker.hpp"
|
||||||
#include "inventory.hpp"
|
|
||||||
#include "unit_world_state.hpp"
|
#include "unit_world_state.hpp"
|
||||||
#include "goap/goal.hpp"
|
#include "goap/goal.hpp"
|
||||||
#include "goap/planner.hpp"
|
#include "goap/planner.hpp"
|
||||||
|
@ -70,7 +69,6 @@ protected:
|
||||||
goap::Planner *planner{nullptr};
|
goap::Planner *planner{nullptr};
|
||||||
EntityHealth *health{nullptr};
|
EntityHealth *health{nullptr};
|
||||||
UnitWorldState *world_state{nullptr};
|
UnitWorldState *world_state{nullptr};
|
||||||
Inventory *inventory{nullptr};
|
|
||||||
#ifdef DEBUG_ENABLED
|
#ifdef DEBUG_ENABLED
|
||||||
public:
|
public:
|
||||||
gd::String DEBUG_print_debug_info();
|
gd::String DEBUG_print_debug_info();
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
void UnitWorldState::_bind_methods() {
|
void UnitWorldState::_bind_methods() {
|
||||||
#define CLASSNAME UnitWorldState
|
#define CLASSNAME UnitWorldState
|
||||||
GDSIGNAL("attention_changed");
|
GDSIGNAL("attention_changed");
|
||||||
|
GDFUNCTION(get_is_weapon_ranged);
|
||||||
GDFUNCTION(get_can_see_target);
|
GDFUNCTION(get_can_see_target);
|
||||||
GDFUNCTION(get_is_target_dead);
|
GDFUNCTION(get_is_target_dead);
|
||||||
GDFUNCTION(get_is_at_target);
|
GDFUNCTION(get_is_at_target);
|
||||||
|
@ -41,6 +42,10 @@ gd::Variant UnitWorldState::get_world_property(gd::String property) {
|
||||||
return ActorWorldState::get_world_property(property);
|
return ActorWorldState::get_world_property(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool UnitWorldState::get_is_weapon_ranged() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool UnitWorldState::get_can_see_target() {
|
bool UnitWorldState::get_can_see_target() {
|
||||||
bool value{this->get_can_see_node(this->target_node)};
|
bool value{this->get_can_see_node(this->target_node)};
|
||||||
this->cache_property("can_see_target", value, utils::time_seconds() + 0.5);
|
this->cache_property("can_see_target", value, utils::time_seconds() + 0.5);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define UNIT_WORLD_STATE_HPP
|
#define UNIT_WORLD_STATE_HPP
|
||||||
|
|
||||||
#include "entity_health.hpp"
|
#include "entity_health.hpp"
|
||||||
|
#include "inventory.hpp"
|
||||||
#include "goap/actor_world_state.hpp"
|
#include "goap/actor_world_state.hpp"
|
||||||
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
#include <godot_cpp/classes/navigation_agent3d.hpp>
|
||||||
#include <godot_cpp/classes/node3d.hpp>
|
#include <godot_cpp/classes/node3d.hpp>
|
||||||
|
@ -21,6 +22,8 @@ class UnitWorldState : public goap::ActorWorldState {
|
||||||
public:
|
public:
|
||||||
virtual void _enter_tree() override;
|
virtual void _enter_tree() override;
|
||||||
virtual gd::Variant get_world_property(gd::String property) 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_target();
|
||||||
bool get_can_see_node(gd::Node3D *node) const;
|
bool get_can_see_node(gd::Node3D *node) const;
|
||||||
bool get_is_at_target() const;
|
bool get_is_at_target() const;
|
||||||
|
|
Loading…
Reference in a new issue