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
[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
[node name="EntityHealth" type="EntityHealth" parent="."]

View file

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

View file

@ -1,4 +1,5 @@
#include "character_world_state.hpp"
#include "unit.hpp"
#include "utils/godot_macros.hpp"
void CharacterWorldState::_bind_methods() {
@ -10,7 +11,11 @@ void CharacterWorldState::_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);
gd::String CharacterWorldState::get_weapon_animation() const {
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();
public:
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:
Inventory *inventory{nullptr};
};

View file

@ -17,7 +17,12 @@ void Inventory::_ready() {
}
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) {

View file

@ -36,9 +36,9 @@ public:
gd::Dictionary get_inventory_inspector() const;
private:
Item const *weapon{nullptr};
Item const *utility{nullptr};
Item const *armour{nullptr};
Item const *helmet{nullptr};
Item const *utility{nullptr};
gd::HashMap<Item const *, unsigned> inventory{};
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());
}
Stats Item::get_stats() const {
return {};
void Item::apply_stats(Stats &stats) const {
}
bool Item::has_capability(ItemCapability const &capability) const {
return this->capabilities.has(capability);
}
gd::StringName const &Item::get_animation() const {
return this->animation;
}
ItemID Item::get_id() const {
return this->id;
}

View file

@ -48,18 +48,21 @@ public:
public:
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;
virtual Stats get_stats() const;
virtual void apply_stats(Stats &stats) const;
bool has_capability(ItemCapability const &capability) const;
gd::StringName const &get_animation() const;
ItemID get_id() const;
protected:
virtual void use_on(Unit *used_by, gd::Object *used_on) const;
protected:
gd::StringName animation{"RESET"};
gd::HashSet<uint32_t> capabilities{};
private:
ItemID id{-1};
};
#define ITEM_CLASS(Class_, DisplayName_, Description_)\
friend class ItemDB;\
public: \
_FORCE_INLINE_ static godot::StringName get_static_class() { 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,
// so that ActionDB::get_enum_hint is complete before _bind_methods.
goap::ActionDB::register_action<MoveToTarget>();
goap::ActionDB::register_action<FireAtTarget>();
goap::ActionDB::register_action<UseWeapon>();
goap::ActionDB::register_action<FindTarget>();
goap::ActionDB::register_action<GetInMeleeRange>();
goap::ActionDB::register_action<MeleeAttack>();
goap::ActionDB::register_action<GetInRange>();
goap::ActionDB::register_action<TankSelfHeal>();
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() {
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);
}
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>()};
state->animation = "fire_weapon";
state->animation = context->get_world_property("weapon_animation");
return state;
}
@ -52,33 +52,20 @@ goap::State *FindTarget::get_apply_state(goap::ActorWorldState *context) const {
return state;
}
GetInMeleeRange::GetInMeleeRange()
GetInRange::GetInRange()
: Action() {
this->require_state_complete = false;
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"))};
MoveTo *state{this->create_state<MoveTo>()};
state->target_node = target;
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()
: Action() {
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;
};
class FireAtTarget : public goap::Action {
GOAP_ACTION(FireAtTarget);
class UseWeapon : public goap::Action {
GOAP_ACTION(UseWeapon);
public:
FireAtTarget();
virtual goap::State *get_apply_state(goap::ActorWorldState *) const override;
UseWeapon();
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
};
class FindTarget : public goap::Action {
@ -25,20 +25,13 @@ public:
virtual goap::State *get_apply_state(goap::ActorWorldState *context) const override;
};
class GetInMeleeRange : public goap::Action {
GOAP_ACTION(GetInMeleeRange);
class GetInRange : public goap::Action {
GOAP_ACTION(GetInRange);
public:
GetInMeleeRange();
GetInRange();
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 {
GOAP_ACTION(TankSelfHeal);
public:

View file

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

View file

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

View file

@ -2,14 +2,3 @@
#include <godot_cpp/core/math.hpp>
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;
struct Stats {
float damage_absorb{0.f};
int hazmat_level{0};
float weapon_range{1.f};
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

View file

@ -76,7 +76,7 @@ void Unit::begin_goal(gd::Ref<goap::Goal> goal) {
void Unit::use_weapon() {
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);
}

View file

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

View file

@ -12,14 +12,14 @@
void UnitWorldState::_bind_methods() {
#define CLASSNAME UnitWorldState
GDSIGNAL("attention_changed");
GDFUNCTION(get_is_weapon_ranged);
GDFUNCTION(get_weapon_animation);
GDFUNCTION(get_can_see_target);
GDFUNCTION(get_is_target_dead);
GDFUNCTION(get_is_at_target);
GDFUNCTION(get_has_target);
GDFUNCTION(get_target_node);
GDFUNCTION(get_is_target_enemy);
GDFUNCTION(get_is_in_melee_range);
GDFUNCTION(get_is_in_range);
GDFUNCTION(get_is_health_safe);
GDFUNCTION(get_parent_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);
}
bool UnitWorldState::get_is_weapon_ranged() const {
return false;
gd::String UnitWorldState::get_weapon_animation() const {
return "melee_attack";
}
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();
}
bool UnitWorldState::get_is_in_melee_range() const {
bool UnitWorldState::get_is_in_range() const {
return this->target_node != nullptr
&& this->target_node->get_global_position()
.distance_squared_to(this->parent_unit->get_global_position()) <= 2.f * 2.f;
&& this->target_node->get_global_position().distance_squared_to(this->parent_unit->get_global_position()) <= 2.f * 2.f;
}
bool UnitWorldState::get_is_health_safe() const {

View file

@ -23,7 +23,7 @@ public:
virtual void _enter_tree() 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_node(gd::Node3D *node) const;
bool get_is_at_target() const;
@ -32,7 +32,7 @@ public:
bool get_is_target_unit() const;
bool get_is_target_enemy() 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;
gd::Vector3 get_parent_global_position() const;
gd::Vector3 get_target_global_position() const;