feat: replaced separate melee and ranged actions with use weapon and get in range

This commit is contained in:
Sara 2024-08-21 14:18:27 +02:00
parent c9c41ac2d7
commit 2a0c9a623e
19 changed files with 70 additions and 78 deletions

View file

@ -32,7 +32,7 @@ available_goals_inspector = [ExtResource("1_jwvis"), ExtResource("1_b1qo1")]
unique_name_in_owner = true unique_name_in_owner = true
[node name="Planner" type="Planner" parent="."] [node name="Planner" type="Planner" parent="."]
actions_inspector = [3, 2, 4, 5, 6] actions_inspector = [3, 2, 4, 5, 1]
unique_name_in_owner = true unique_name_in_owner = true
[node name="EntityHealth" type="EntityHealth" parent="."] [node name="EntityHealth" type="EntityHealth" parent="."]

View file

@ -27,7 +27,7 @@ collision_mask = 0
unique_name_in_owner = true unique_name_in_owner = true
[node name="Planner" type="Planner" parent="."] [node name="Planner" type="Planner" parent="."]
actions_inspector = [0, 1, 2] actions_inspector = [0, 1, 2, 3]
unique_name_in_owner = true unique_name_in_owner = true
[node name="EntityHealth" type="EntityHealth" parent="."] [node name="EntityHealth" type="EntityHealth" parent="."]

View file

@ -1,4 +1,5 @@
#include "character_world_state.hpp" #include "character_world_state.hpp"
#include "unit.hpp"
#include "utils/godot_macros.hpp" #include "utils/godot_macros.hpp"
void CharacterWorldState::_bind_methods() { void CharacterWorldState::_bind_methods() {
@ -10,7 +11,11 @@ void CharacterWorldState::_enter_tree() {
this->inventory = this->get_node<Inventory>("%Inventory"); this->inventory = this->get_node<Inventory>("%Inventory");
} }
bool CharacterWorldState::get_is_weapon_ranged() const { gd::String CharacterWorldState::get_weapon_animation() const {
return this->inventory->get_weapon()->has_capability(ItemCapability::WeaponRanged); return this->inventory->get_weapon()->get_animation();
} }
bool CharacterWorldState::get_is_in_range() const {
float const distance{this->parent_unit->get_global_position().distance_to(this->target_node->get_global_position())};
return distance <= inventory->get_stats().weapon_range;
}

View file

@ -9,7 +9,8 @@ class CharacterWorldState : public UnitWorldState {
static void _bind_methods(); static void _bind_methods();
public: public:
virtual void _enter_tree() override; virtual void _enter_tree() override;
virtual bool get_is_weapon_ranged() const override; virtual gd::String get_weapon_animation() const override;
virtual bool get_is_in_range() const override;
private: private:
Inventory *inventory{nullptr}; Inventory *inventory{nullptr};
}; };

View file

@ -17,7 +17,12 @@ void Inventory::_ready() {
} }
Stats Inventory::get_stats() const { Stats Inventory::get_stats() const {
return this->weapon->get_stats() & this->armour->get_stats() & this->helmet->get_stats() & this->utility->get_stats(); Stats stats{};
if(this->weapon != nullptr) this->weapon->apply_stats(stats);
if(this->utility != nullptr) this->utility->apply_stats(stats);
if(this->armour != nullptr) this->armour->apply_stats(stats);
if(this->helmet != nullptr) this->helmet->apply_stats(stats);
return stats;
} }
void Inventory::add_item(const Item *item, unsigned num) { void Inventory::add_item(const Item *item, unsigned num) {

View file

@ -36,9 +36,9 @@ public:
gd::Dictionary get_inventory_inspector() const; gd::Dictionary get_inventory_inspector() const;
private: private:
Item const *weapon{nullptr}; Item const *weapon{nullptr};
Item const *utility{nullptr};
Item const *armour{nullptr}; Item const *armour{nullptr};
Item const *helmet{nullptr}; Item const *helmet{nullptr};
Item const *utility{nullptr};
gd::HashMap<Item const *, unsigned> inventory{}; gd::HashMap<Item const *, unsigned> inventory{};
Unit *parent_unit{nullptr}; Unit *parent_unit{nullptr};

View file

@ -22,14 +22,17 @@ void Item::use_on(Unit *used_by, gd::Object *used_on) const {
gd::UtilityFunctions::push_warning(used_by->get_path(), " tried to use default Item::use_on implementation on object of type ", used_on->get_class()); gd::UtilityFunctions::push_warning(used_by->get_path(), " tried to use default Item::use_on implementation on object of type ", used_on->get_class());
} }
Stats Item::get_stats() const { void Item::apply_stats(Stats &stats) const {
return {};
} }
bool Item::has_capability(ItemCapability const &capability) const { bool Item::has_capability(ItemCapability const &capability) const {
return this->capabilities.has(capability); return this->capabilities.has(capability);
} }
gd::StringName const &Item::get_animation() const {
return this->animation;
}
ItemID Item::get_id() const { ItemID Item::get_id() const {
return this->id; return this->id;
} }

View file

@ -48,18 +48,21 @@ public:
public: public:
virtual bool can_use_on(Unit *used_by, gd::Object *used_on) const; virtual bool can_use_on(Unit *used_by, gd::Object *used_on) const;
bool try_use_on(Unit *used_by, gd::Object *used_on) const; bool try_use_on(Unit *used_by, gd::Object *used_on) const;
virtual Stats get_stats() const; virtual void apply_stats(Stats &stats) const;
bool has_capability(ItemCapability const &capability) const; bool has_capability(ItemCapability const &capability) const;
gd::StringName const &get_animation() const;
ItemID get_id() const; ItemID get_id() const;
protected: protected:
virtual void use_on(Unit *used_by, gd::Object *used_on) const; virtual void use_on(Unit *used_by, gd::Object *used_on) const;
protected: protected:
gd::StringName animation{"RESET"};
gd::HashSet<uint32_t> capabilities{}; gd::HashSet<uint32_t> capabilities{};
private: private:
ItemID id{-1}; ItemID id{-1};
}; };
#define ITEM_CLASS(Class_, DisplayName_, Description_)\ #define ITEM_CLASS(Class_, DisplayName_, Description_)\
friend class ItemDB;\
public: \ public: \
_FORCE_INLINE_ static godot::StringName get_static_class() { return #Class_; }\ _FORCE_INLINE_ static godot::StringName get_static_class() { return #Class_; }\
_FORCE_INLINE_ virtual godot::StringName get_class() const override { return #Class_; }\ _FORCE_INLINE_ virtual godot::StringName get_class() const override { return #Class_; }\

View file

@ -39,10 +39,9 @@ void initialize_gdextension_types(gd::ModuleInitializationLevel p_level)
// always register actions before classes, // always register actions before classes,
// so that ActionDB::get_enum_hint is complete before _bind_methods. // so that ActionDB::get_enum_hint is complete before _bind_methods.
goap::ActionDB::register_action<MoveToTarget>(); goap::ActionDB::register_action<MoveToTarget>();
goap::ActionDB::register_action<FireAtTarget>(); goap::ActionDB::register_action<UseWeapon>();
goap::ActionDB::register_action<FindTarget>(); goap::ActionDB::register_action<FindTarget>();
goap::ActionDB::register_action<GetInMeleeRange>(); goap::ActionDB::register_action<GetInRange>();
goap::ActionDB::register_action<MeleeAttack>();
goap::ActionDB::register_action<TankSelfHeal>(); goap::ActionDB::register_action<TankSelfHeal>();
goap::ActionDB::register_action<TakeCover>(); goap::ActionDB::register_action<TakeCover>();

View file

@ -26,16 +26,16 @@ goap::State *MoveToTarget::get_apply_state(goap::ActorWorldState *context) const
} }
} }
FireAtTarget::FireAtTarget() UseWeapon::UseWeapon()
: Action() { : Action() {
this->required.insert("can_see_target", true); this->required.insert("can_see_target", true);
this->required.insert("is_weapon_ranged", true); this->required.insert("is_in_range", true);
this->effects.insert("is_target_dead", true); this->effects.insert("is_target_dead", true);
} }
goap::State *FireAtTarget::get_apply_state(goap::ActorWorldState *) const { goap::State *UseWeapon::get_apply_state(goap::ActorWorldState *context) const {
Animate *state{this->create_state<Animate>()}; Animate *state{this->create_state<Animate>()};
state->animation = "fire_weapon"; state->animation = context->get_world_property("weapon_animation");
return state; return state;
} }
@ -52,33 +52,20 @@ goap::State *FindTarget::get_apply_state(goap::ActorWorldState *context) const {
return state; return state;
} }
GetInMeleeRange::GetInMeleeRange() GetInRange::GetInRange()
: Action() { : Action() {
this->require_state_complete = false; this->require_state_complete = false;
this->required.insert("can_see_target", true); this->required.insert("can_see_target", true);
this->effects.insert("is_in_melee_range", true); this->effects.insert("is_in_range", true);
} }
goap::State *GetInMeleeRange::get_apply_state(goap::ActorWorldState *context) const { goap::State *GetInRange::get_apply_state(goap::ActorWorldState *context) const {
gd::Node3D *target{gd::Object::cast_to<gd::Node3D>(context->get_world_property("target_node"))}; gd::Node3D *target{gd::Object::cast_to<gd::Node3D>(context->get_world_property("target_node"))};
MoveTo *state{this->create_state<MoveTo>()}; MoveTo *state{this->create_state<MoveTo>()};
state->target_node = target; state->target_node = target;
return state; return state;
} }
MeleeAttack::MeleeAttack()
: Action() {
this->required.insert("can_see_target", true);
this->required.insert("is_in_melee_range", true);
this->effects.insert("is_target_dead", true);
}
goap::State *MeleeAttack::get_apply_state(goap::ActorWorldState *) const {
Animate *state{this->create_state<Animate>()};
state->animation = "melee_attack";
return state;
}
TankSelfHeal::TankSelfHeal() TankSelfHeal::TankSelfHeal()
: Action() { : Action() {
this->required.insert("can_see_target", false); this->required.insert("can_see_target", false);

View file

@ -11,11 +11,11 @@ public:
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
}; };
class FireAtTarget : public goap::Action { class UseWeapon : public goap::Action {
GOAP_ACTION(FireAtTarget); GOAP_ACTION(UseWeapon);
public: public:
FireAtTarget(); UseWeapon();
virtual goap::State *get_apply_state(goap::ActorWorldState *) const override; virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
}; };
class FindTarget : public goap::Action { class FindTarget : public goap::Action {
@ -25,20 +25,13 @@ public:
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
}; };
class GetInMeleeRange : public goap::Action { class GetInRange : public goap::Action {
GOAP_ACTION(GetInMeleeRange); GOAP_ACTION(GetInRange);
public: public:
GetInMeleeRange(); GetInRange();
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override; virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
}; };
class MeleeAttack : public goap::Action {
GOAP_ACTION(MeleeAttack);
public:
MeleeAttack();
virtual goap::State *get_apply_state(goap::ActorWorldState *) const override;
};
class TankSelfHeal : public goap::Action { class TankSelfHeal : public goap::Action {
GOAP_ACTION(TankSelfHeal); GOAP_ACTION(TankSelfHeal);
public: public:

View file

@ -19,6 +19,7 @@ void Medkit::use_on(Unit *used_by, gd::Object *used_on) const {
Handgun::Handgun() Handgun::Handgun()
: Item() { : Item() {
this->animation = "fire_weapon";
this->capabilities.insert(ItemCapability::WeaponEquip); this->capabilities.insert(ItemCapability::WeaponEquip);
this->capabilities.insert(ItemCapability::WeaponRanged); this->capabilities.insert(ItemCapability::WeaponRanged);
} }
@ -28,6 +29,10 @@ bool Handgun::can_use_on(Unit *used_by, gd::Object *used_on) const {
&& used_by->get_world_state()->get_can_see_target(); && used_by->get_world_state()->get_can_see_target();
} }
void Handgun::apply_stats(Stats &stats) const {
stats.weapon_range = 10.f;
}
void Handgun::use_on(Unit* used_by, gd::Object* used_on) const { void Handgun::use_on(Unit* used_by, gd::Object* used_on) const {
Unit *target{gd::Object::cast_to<Unit>(used_on)}; Unit *target{gd::Object::cast_to<Unit>(used_on)};
used_by->aim_at(target); used_by->aim_at(target);

View file

@ -6,8 +6,9 @@
class Medkit : public Item { class Medkit : public Item {
ITEM_CLASS(Medkit, "Medkit", ITEM_CLASS(Medkit, "Medkit",
"Standard emergency home medical kit. Use to manage wounds and stabilize a person."); "Standard emergency home medical kit. Use to manage wounds and stabilize a person.");
public: private:
Medkit(); Medkit();
public:
virtual bool can_use_on(Unit *used_by, gd::Object *used_on) const override; virtual bool can_use_on(Unit *used_by, gd::Object *used_on) const override;
protected: protected:
virtual void use_on(Unit *used_by, gd::Object *used_on) const override; virtual void use_on(Unit *used_by, gd::Object *used_on) const override;
@ -16,9 +17,11 @@ protected:
class Handgun : public Item { class Handgun : public Item {
ITEM_CLASS(Handgun, "9mm handgun.", ITEM_CLASS(Handgun, "9mm handgun.",
"A standard issue police firearm."); "A standard issue police firearm.");
public: private:
Handgun(); Handgun();
public:
virtual bool can_use_on(Unit *used_by, gd::Object *used_on) const override; virtual bool can_use_on(Unit *used_by, gd::Object *used_on) const override;
virtual void apply_stats(Stats &stats) const override;
protected: protected:
virtual void use_on(Unit *used_by, gd::Object *used_on) const override; virtual void use_on(Unit *used_by, gd::Object *used_on) const override;
}; };
@ -26,8 +29,9 @@ protected:
class Lasercutter : public Item { class Lasercutter : public Item {
ITEM_CLASS(Lasercutter, "Lasercutter", ITEM_CLASS(Lasercutter, "Lasercutter",
"A laser cutter, use to clear metal obstacles."); "A laser cutter, use to clear metal obstacles.");
public: private:
Lasercutter(); Lasercutter();
public:
// virtual bool can_use_on(Unit *used_by, gd::Object *object) const override; // virtual bool can_use_on(Unit *used_by, gd::Object *object) const override;
protected: protected:
// virtual void use_on(Unit *used_by, gd::Object *object) const override; // virtual void use_on(Unit *used_by, gd::Object *object) const override;
@ -36,8 +40,9 @@ protected:
class Welder : public Item { class Welder : public Item {
ITEM_CLASS(Welder, "Welder", ITEM_CLASS(Welder, "Welder",
"A welder with tanks intended to be carried on a person's back. Use to repair broken metal parts."); "A welder with tanks intended to be carried on a person's back. Use to repair broken metal parts.");
public: private:
Welder(); Welder();
public:
// virtual bool can_use_on(Unit *used_by, gd::Object *object) const override; // virtual bool can_use_on(Unit *used_by, gd::Object *object) const override;
protected: protected:
// virtual void use_on(Unit *used_by, gd::Object *object) const override; // virtual void use_on(Unit *used_by, gd::Object *object) const override;

View file

@ -2,14 +2,3 @@
#include <godot_cpp/core/math.hpp> #include <godot_cpp/core/math.hpp>
namespace gd = godot; namespace gd = godot;
Stats operator &(Stats const &lhs, Stats const &rhs) {
Stats rval{};
rval.damage_absorb = gd::Math::min(lhs.damage_absorb + rhs.damage_absorb, 1.f);
rval.hazmat_level = gd::Math::max(lhs.hazmat_level, rhs.hazmat_level);
return rval;
}
Stats &operator <<(Stats &lhs, Stats const &rhs) {
return (lhs = lhs & rhs);
}

View file

@ -7,11 +7,10 @@
namespace gd = godot; namespace gd = godot;
struct Stats { struct Stats {
float damage_absorb{0.f}; float weapon_range{1.f};
int hazmat_level{0};
float armour_damage_absorb{0.f};
int armour_hazmat_level{0};
}; };
Stats operator &(Stats const &lhs, Stats const &rhs);
Stats &operator <<(Stats &lhs, Stats const &rhs);
#endif // !STATS_HPP #endif // !STATS_HPP

View file

@ -76,7 +76,7 @@ void Unit::begin_goal(gd::Ref<goap::Goal> goal) {
void Unit::use_weapon() { void Unit::use_weapon() {
Unit *target_unit{gd::Object::cast_to<Unit>(this->world_state->get_target_node())}; 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()) if(target_unit != nullptr && this->world_state->get_can_see_target() && this->world_state->get_is_in_range())
target_unit->get_entity_health()->damaged_by(1, this); target_unit->get_entity_health()->damaged_by(1, this);
} }

View file

@ -32,7 +32,6 @@ public:
void begin_marker_temporary(GoalMarker *marker); void begin_marker_temporary(GoalMarker *marker);
void begin_goal(gd::Ref<goap::Goal> goal); void begin_goal(gd::Ref<goap::Goal> goal);
void set_target_goal(gd::Node3D *target, gd::Ref<goap::Goal> goal); void set_target_goal(gd::Node3D *target, gd::Ref<goap::Goal> goal);
virtual void use_weapon(); virtual void use_weapon();
void aim_at(gd::Node3D *node); void aim_at(gd::Node3D *node);
void on_unconscious(Unit *damage_source); void on_unconscious(Unit *damage_source);

View file

@ -12,14 +12,14 @@
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_weapon_animation);
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);
GDFUNCTION(get_has_target); GDFUNCTION(get_has_target);
GDFUNCTION(get_target_node); GDFUNCTION(get_target_node);
GDFUNCTION(get_is_target_enemy); GDFUNCTION(get_is_target_enemy);
GDFUNCTION(get_is_in_melee_range); GDFUNCTION(get_is_in_range);
GDFUNCTION(get_is_health_safe); GDFUNCTION(get_is_health_safe);
GDFUNCTION(get_parent_global_position); GDFUNCTION(get_parent_global_position);
GDFUNCTION(get_target_global_position); GDFUNCTION(get_target_global_position);
@ -42,8 +42,8 @@ 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 { gd::String UnitWorldState::get_weapon_animation() const {
return false; return "melee_attack";
} }
bool UnitWorldState::get_can_see_target() { bool UnitWorldState::get_can_see_target() {
@ -105,10 +105,9 @@ bool UnitWorldState::get_is_unit_enemy(Unit *unit) const {
&& unit->get_team() != this->parent_unit->get_team(); && unit->get_team() != this->parent_unit->get_team();
} }
bool UnitWorldState::get_is_in_melee_range() const { bool UnitWorldState::get_is_in_range() const {
return this->target_node != nullptr return this->target_node != nullptr
&& this->target_node->get_global_position() && this->target_node->get_global_position().distance_squared_to(this->parent_unit->get_global_position()) <= 2.f * 2.f;
.distance_squared_to(this->parent_unit->get_global_position()) <= 2.f * 2.f;
} }
bool UnitWorldState::get_is_health_safe() const { bool UnitWorldState::get_is_health_safe() const {

View file

@ -23,7 +23,7 @@ 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; virtual gd::String get_weapon_animation() 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;
@ -32,7 +32,7 @@ public:
bool get_is_target_unit() const; bool get_is_target_unit() const;
bool get_is_target_enemy() const; bool get_is_target_enemy() const;
bool get_is_unit_enemy(Unit *unit) const; bool get_is_unit_enemy(Unit *unit) const;
bool get_is_in_melee_range() const; virtual bool get_is_in_range() const;
bool get_is_health_safe() const; bool get_is_health_safe() const;
gd::Vector3 get_parent_global_position() const; gd::Vector3 get_parent_global_position() const;
gd::Vector3 get_target_global_position() const; gd::Vector3 get_target_global_position() const;