diff --git a/CMakeLists.txt b/CMakeLists.txt index c30796c5..3315e975 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -630,6 +630,7 @@ set(GAME_SOURCES src/game/script/routines.cpp src/game/script/routines_actions.cpp src/game/script/routines_ai.cpp + src/game/script/routines_combat.cpp src/game/script/routines_common.cpp src/game/script/routines_effects.cpp src/game/script/routines_enginetypes.cpp diff --git a/src/game/actionexecutor.cpp b/src/game/actionexecutor.cpp index e310b359..7215ef90 100644 --- a/src/game/actionexecutor.cpp +++ b/src/game/actionexecutor.cpp @@ -185,15 +185,15 @@ void ActionExecutor::executeStartConversation(const shared_ptr &actor, S } void ActionExecutor::executeAttack(const shared_ptr &actor, AttackAction &action, float dt) { - auto target = action.target(); + shared_ptr target(action.target()); if (target->isDead()) { action.complete(); return; } - glm::vec3 dest(target->position()); auto creatureActor = static_pointer_cast(actor); - - navigateCreature(creatureActor, dest, true, action.range(), dt); + if (navigateCreature(creatureActor, target->position(), true, action.range(), dt)) { + // TODO: start a combat round + } } bool ActionExecutor::navigateCreature(const shared_ptr &creature, const glm::vec3 &dest, bool run, float distance, float dt) { diff --git a/src/game/object/creature.cpp b/src/game/object/creature.cpp index d410aaab..cc90203d 100644 --- a/src/game/object/creature.cpp +++ b/src/game/object/creature.cpp @@ -57,6 +57,13 @@ static constexpr int kStrRefRemains = 38151; static string g_headHookNode("headhook"); static string g_talkDummyNode("talkdummy"); +void Creature::Combat::reset() { + attackTarget.reset(); + attemptedAttackTarget.reset(); + spellTarget.reset(); + attemptedSpellTarget.reset(); +} + Creature::Creature( uint32_t id, ObjectFactory *objectFactory, diff --git a/src/game/object/creature.h b/src/game/object/creature.h index abc8f2f6..c4a51b1f 100644 --- a/src/game/object/creature.h +++ b/src/game/object/creature.h @@ -90,6 +90,15 @@ public: std::shared_ptr lastPerceived; }; + struct Combat { + std::shared_ptr attackTarget; + std::shared_ptr attemptedAttackTarget; + std::shared_ptr spellTarget; + std::shared_ptr attemptedSpellTarget; + + void reset(); + }; + Creature( uint32_t id, ObjectFactory *objectFactory, @@ -130,6 +139,7 @@ public: int xp() const { return _xp; } RacialType racialType() const { return _racialType; } NPCAIStyle aiStyle() const { return _aiStyle; } + Combat &combat() { return _combat; } void setMovementType(MovementType type); void setFaction(Faction faction); @@ -217,6 +227,7 @@ private: Perception _perception; RacialType _racialType { RacialType::Unknown }; NPCAIStyle _aiStyle { NPCAIStyle::MeleeAttack }; + Combat _combat; // Animation diff --git a/src/game/script/routines.h b/src/game/script/routines.h index 5c890c13..6df98ee0 100644 --- a/src/game/script/routines.h +++ b/src/game/script/routines.h @@ -505,6 +505,15 @@ private: // END Perception + // Combat + + script::Variable getAttackTarget(const VariablesList &args, script::ExecutionContext &ctx); + script::Variable getAttemptedAttackTarget(const VariablesList &args, script::ExecutionContext &ctx); + script::Variable getSpellTarget(const VariablesList &args, script::ExecutionContext &ctx); + script::Variable getAttemptedSpellTarget(const VariablesList &args, script::ExecutionContext &ctx); + + // END Combat + // AI script::Variable getNPCAIStyle(const VariablesList &args, script::ExecutionContext &ctx); diff --git a/src/game/script/routines_actions.cpp b/src/game/script/routines_actions.cpp index e7a96ddf..3ca8ada0 100644 --- a/src/game/script/routines_actions.cpp +++ b/src/game/script/routines_actions.cpp @@ -338,7 +338,7 @@ Variable Routines::actionAttack(const VariablesList &args, ExecutionContext &ctx warn("Script: actionAttack: attackee is invalid"); return Variable(); } - auto action = make_unique(attackee); + auto action = make_unique(attackee, caller->getAttackRange()); caller->actionQueue().add(move(action)); return Variable(); diff --git a/src/game/script/routines_combat.cpp b/src/game/script/routines_combat.cpp new file mode 100644 index 00000000..4f3f0c9f --- /dev/null +++ b/src/game/script/routines_combat.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020-2021 The reone project contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "routines.h" + +#include "../../common/log.h" + +#include "../object/creature.h" + +using namespace std; + +using namespace reone::script; + +namespace reone { + +namespace game { + +Variable Routines::getAttackTarget(const VariablesList &args, ExecutionContext &ctx) { + Variable result; + result.type = VariableType::Object; + + auto creature = getCreatureOrCaller(args, 0, ctx); + if (creature) { + result.object = creature->combat().attackTarget; + } else { + warn("Script: getAttackTarget: creature is invalid"); + } + + return move(result); +} + +Variable Routines::getAttemptedAttackTarget(const VariablesList &args, ExecutionContext &ctx) { + Variable result; + result.type = VariableType::Object; + + auto caller = getCallerAsCreature(ctx); + if (caller) { + result.object = caller->combat().attemptedAttackTarget; + } else { + warn("Script: getAttemptedAttackTarget: caller is invalid"); + } + + return move(result); +} + +Variable Routines::getSpellTarget(const VariablesList &args, ExecutionContext &ctx) { + Variable result; + result.type = VariableType::Object; + + auto creature = getCreatureOrCaller(args, 0, ctx); + if (creature) { + result.object = creature->combat().spellTarget; + } else { + warn("Script: getSpellTarget: creature is invalid"); + } + + return move(result); +} + +Variable Routines::getAttemptedSpellTarget(const VariablesList &args, ExecutionContext &ctx) { + Variable result; + result.type = VariableType::Object; + + auto caller = getCallerAsCreature(ctx); + if (caller) { + result.object = caller->combat().attemptedSpellTarget; + } else { + warn("Script: getAttemptedSpellTarget: caller is invalid"); + } + + return move(result); +} + +} // namespace game + +} // namespace reone diff --git a/src/game/script/routines_kotor.cpp b/src/game/script/routines_kotor.cpp index 5c4a6fbd..4046a103 100644 --- a/src/game/script/routines_kotor.cpp +++ b/src/game/script/routines_kotor.cpp @@ -354,7 +354,7 @@ void Routines::addKotorRoutines() { add("JumpToLocation", Void, { Location }, &Routines::jumpToLocation); add("EffectTemporaryHitpoints", Effect, { Int }, &Routines::effectTemporaryHitpoints); add("GetSkillRank", Int, { Int, Object }, &Routines::getSkillRank); - add("GetAttackTarget", Object, { Object }); + add("GetAttackTarget", Object, { Object }, &Routines::getAttackTarget); add("GetLastAttackType", Int, { Object }); add("GetLastAttackMode", Int, { Object }); add("GetDistanceBetween2D", Float, { Object, Object }, &Routines::getDistanceBetween2D); @@ -399,7 +399,7 @@ void Routines::addKotorRoutines() { add("GetGender", Int, { Object }, &Routines::getGender); add("GetIsTalentValid", Int, { Talent }); add("ActionMoveAwayFromLocation", Void, { Location, Int, Float }, &Routines::actionMoveAwayFromLocation); - add("GetAttemptedAttackTarget", Object, { }); + add("GetAttemptedAttackTarget", Object, { }, &Routines::getAttemptedAttackTarget); add("GetTypeFromTalent", Int, { Talent }); add("GetIdFromTalent", Int, { Talent }); add("PlayPazaak", Void, { Int, String, Int, Int, Object }); @@ -413,7 +413,7 @@ void Routines::addKotorRoutines() { add("EffectDamageForcePoints", Effect, { Int }, &Routines::effectDamageForcePoints); add("EffectHealForcePoints", Effect, { Int }, &Routines::effectHealForcePoints); add("SendMessageToPC", Void, { Object, String }); - add("GetAttemptedSpellTarget", Object, { }); + add("GetAttemptedSpellTarget", Object, { }, &Routines::getAttemptedSpellTarget); add("GetLastOpenedBy", Object, { }, &Routines::getLastOpenedBy); add("GetHasSpell", Int, { Int, Object }); add("OpenStore", Void, { Object, Object, Int, Int }); @@ -800,7 +800,7 @@ void Routines::addKotorRoutines() { add("ResetDialogState", Void, { }); add("SetGoodEvilValue", Void, { Object, Int }); add("GetIsPoisoned", Int, { Object }); - add("GetSpellTarget", Object, { Object }); + add("GetSpellTarget", Object, { Object }, &Routines::getSpellTarget); add("SetSoloMode", Void, { Int }); add("EffectCutSceneHorrified", Effect, { }, &Routines::effectCutSceneHorrified); add("EffectCutSceneParalyze", Effect, { }, &Routines::effectCutSceneParalyze); diff --git a/src/game/script/routines_tsl.cpp b/src/game/script/routines_tsl.cpp index 8abbca3a..5079010f 100644 --- a/src/game/script/routines_tsl.cpp +++ b/src/game/script/routines_tsl.cpp @@ -355,7 +355,7 @@ void Routines::addTslRoutines() { add("JumpToLocation", Void, { Location }, &Routines::jumpToLocation); add("EffectTemporaryHitpoints", Effect, { Int }, &Routines::effectTemporaryHitpoints); add("GetSkillRank", Int, { Int, Object }, &Routines::getSkillRank); - add("GetAttackTarget", Object, { Object }); + add("GetAttackTarget", Object, { Object }, &Routines::getAttackTarget); add("GetLastAttackType", Int, { Object }); add("GetLastAttackMode", Int, { Object }); add("GetDistanceBetween2D", Float, { Object, Object }, &Routines::getDistanceBetween2D); @@ -400,7 +400,7 @@ void Routines::addTslRoutines() { add("GetGender", Int, { Object }, &Routines::getGender); add("GetIsTalentValid", Int, { Talent }); add("ActionMoveAwayFromLocation", Void, { Location, Int, Float }, &Routines::actionMoveAwayFromLocation); - add("GetAttemptedAttackTarget", Object, { }); + add("GetAttemptedAttackTarget", Object, { }, &Routines::getAttemptedAttackTarget); add("GetTypeFromTalent", Int, { Talent }); add("GetIdFromTalent", Int, { Talent }); add("PlayPazaak", Void, { Int, String, Int, Int, Object }); @@ -414,7 +414,7 @@ void Routines::addTslRoutines() { add("EffectDamageForcePoints", Effect, { Int }, &Routines::effectDamageForcePoints); add("EffectHealForcePoints", Effect, { Int }, &Routines::effectHealForcePoints); add("SendMessageToPC", Void, { Object, String }); - add("GetAttemptedSpellTarget", Object, { }); + add("GetAttemptedSpellTarget", Object, { }, &Routines::getAttemptedSpellTarget); add("GetLastOpenedBy", Object, { }, &Routines::getLastOpenedBy); add("GetHasSpell", Int, { Int, Object }); add("OpenStore", Void, { Object, Object, Int, Int }); @@ -799,7 +799,7 @@ void Routines::addTslRoutines() { add("ResetDialogState", Void, { }); add("SetGoodEvilValue", Void, { Object, Int }); add("GetIsPoisoned", Int, { Object }); - add("GetSpellTarget", Object, { Object }); + add("GetSpellTarget", Object, { Object }, &Routines::getSpellTarget); add("SetSoloMode", Void, { Int }); add("EffectCutSceneHorrified", Effect, { }, &Routines::effectCutSceneHorrified); add("EffectCutSceneParalyze", Effect, { }, &Routines::effectCutSceneParalyze);