OpenGothic
Open source reimplementation of Gothic I and II
Loading...
Searching...
No Matches
fightalgo.cpp
Go to the documentation of this file.
1#include "fightalgo.h"
2
3#include <Tempest/Log>
4
6#include "world/objects/npc.h"
8#include "gothic.h"
9#include "serialize.h"
10
11// According to Gothic1 scripts:
12// W - Weapon Range (FIGHT_RANGE_FIST * 3)
13// G - Walking range (3 * W). Buffer for ranged fighters in which they should switch to a melee weapon.
14// FK - Long-range combat range (30m)
15
18
20 fin.read(reinterpret_cast<int32_t&>(queueId));
21 for(int i=0;i<MV_MAX;++i)
22 fin.read(reinterpret_cast<uint8_t&>(tr[i]));
23 fin.read(hitFlg);
24 }
25
27 fout.write(int32_t(queueId));
28 for(int i=0;i<MV_MAX;++i)
29 fout.write(uint8_t(tr[i]));
30 fout.write(hitFlg);
31 }
32
33void FightAlgo::fillQueue(Npc &npc, Npc &tg, GameScript& owner) {
34 auto& ai = Gothic::fai()[size_t(npc.handle().fight_tactic)];
35 auto ws = npc.weaponState();
36
37 if(hitFlg) {
38 hitFlg = false;
39 if(fillQueue(owner,ai.my_w_strafe))
40 return;
41 }
42
43 const bool focus = isInFocusAngle(npc,tg);
44
45 if(tg.isPrehit() && isInWRange(tg,npc,owner) && isInFocusAngle(tg,npc) && focus){
46 if(tg.bodyStateMasked()==BS_RUN)
47 if(fillQueue(owner,ai.enemy_stormprehit))
48 return;
49 if(fillQueue(owner,ai.enemy_prehit))
50 return;
51 }
52
54 if(isInWRange(npc,tg,owner)) {
55 if(focus && npc.bodyStateMasked()==BS_RUN)
56 if(fillQueue(owner,ai.my_w_runto))
57 return;
58 if(focus && npc.bodyStateMasked()!=BS_RUN)
59 if(fillQueue(owner,ai.my_w_focus))
60 return;
61 if(fillQueue(owner,ai.my_w_nofocus))
62 return;
63 }
64
65 if(isInGRange(npc,tg,owner)) {
66 if(focus && npc.bodyStateMasked()==BS_RUN)
67 if(fillQueue(owner,ai.my_g_runto))
68 return;
69 if(focus && npc.bodyStateMasked()!=BS_RUN)
70 if(fillQueue(owner,ai.my_g_focus))
71 return;
72 }
73 }
74
75 if(ws==WeaponState::Mage) {
76 if(isInWRange(npc,tg,owner))
77 if(fillQueue(owner,ai.my_fk_focus_mag))
78 return;
79 if(fillQueue(owner,ai.my_fk_nofocus_mag))
80 return;
81 }
82
83 if(isInWRange(npc,tg,owner) && focus)
84 if(fillQueue(owner,ai.my_fk_focus_far))
85 return;
86 if(fillQueue(owner,ai.my_fk_nofocus_far))
87 return;
88
89 fillQueue(owner,ai.my_w_nofocus);
90 }
91
92bool FightAlgo::fillQueue(GameScript& owner, const zenkit::IFightAi& src) {
93 uint32_t sz=0;
94 for(size_t i=0; i<zenkit::IFightAi::move_count; ++i){
95 if(src.move[i]==zenkit::FightAiMove::NOP)
96 break;
97 sz++;
98 }
99 if(sz==0)
100 return false;
101 queueId = zenkit::FightAiMove(src.move[owner.rand(sz)]);
102 return queueId!=zenkit::FightAiMove::NOP;
103 }
104
106 using zenkit::FightAiMove;
107
108 if(tr[0]==MV_NULL) {
109 switch(queueId) {
110 case FightAiMove::TURN:
111 if(!isInGRange(npc,tg,owner))
112 tr[0] = MV_TURNG; else
113 tr[0] = MV_TURNA;
114 break;
115 case FightAiMove::RUN:{
116 if(!isInGRange(npc,tg,owner))
117 tr[0] = MV_MOVEG; else
118 tr[0] = MV_MOVEA;
119 break;
120 }
121 case FightAiMove::RUN_BACK:{
122 tr[0] = MV_NULL; //TODO
123 break;
124 }
125 case FightAiMove::JUMP_BACK:{
126 tr[0] = MV_JUMPBACK;
127 break;
128 }
129 case FightAiMove::STRAFE:{
130 tr[0] = owner.rand(2) ? MV_STRAFEL : MV_STRAFER;
132 break;
133 }
134 case FightAiMove::ATTACK:{
135 tr[0] = MV_ATTACK;
136 break;
137 }
138 case FightAiMove::ATTACK_SIDE:{
139 tr[0] = MV_ATTACKL;
140 tr[1] = MV_ATTACKR;
141 break;
142 }
143 case FightAiMove::ATTACK_FRONT:{
144 tr[0] = owner.rand(2) ? MV_ATTACKL : MV_ATTACKR;
145 tr[1] = MV_ATTACK;
146 break;
147 }
148 case FightAiMove::ATTACK_TRIPLE:{
149 if(owner.rand(2)){
150 tr[0] = MV_ATTACK;
151 tr[1] = MV_ATTACKR;
152 tr[2] = MV_ATTACKL;
153 } else {
154 tr[0] = MV_ATTACKL;
155 tr[1] = MV_ATTACKR;
156 tr[2] = MV_ATTACK;
157 }
158 break;
159 }
160 case FightAiMove::ATTACK_WHIRL:{
161 tr[0] = MV_ATTACKL;
162 tr[1] = MV_ATTACKR;
163 tr[2] = MV_ATTACKL;
164 tr[3] = MV_ATTACKR;
165 break;
166 }
167 case FightAiMove::ATTACK_MASTER:{
168 tr[0] = MV_ATTACKL;
169 tr[1] = MV_ATTACKR;
170 tr[2] = MV_ATTACK;
171 tr[3] = MV_ATTACK;
172 tr[4] = MV_ATTACK;
173 tr[5] = MV_ATTACK;
174 break;
175 }
176 case FightAiMove::TURN_TO_HIT:{
177 tr[0] = MV_TURN2HIT;
178 break;
179 }
180 case FightAiMove::PARRY:{
181 tr[0] = MV_BLOCK;
182 break;
183 }
184 case FightAiMove::STAND_UP:{
185 break;
186 }
187 case FightAiMove::WAIT:
188 case FightAiMove::WAIT_EXT:{
189 tr[0] = MV_WAIT;
190 break;
191 }
192 case FightAiMove::WAIT_LONGER:{
193 tr[0] = MV_WAITLONG;
194 break;
195 }
196 default: {
197 static std::set<FightAiMove> inst;
198 if(inst.find(queueId)==inst.end()) {
199 Tempest::Log::d("unrecognized FAI instruction: ", int(queueId));
200 inst.insert(queueId);
201 }
202 }
203 }
204 queueId = FightAiMove::NOP;
205 }
206 return tr[0];
207 }
208
210 return tr[0]!=MV_NULL;
211 }
212
214 fillQueue(npc,tg,owner);
215 if(queueId==zenkit::FightAiMove::NOP)
216 return false;
217 nextFromQueue(npc,tg,owner);
218 return true;
219 }
220
222 for(size_t i=1;i<MV_MAX;++i)
223 tr[i-1]=tr[i];
224 tr[MV_MAX-1]=MV_NULL;
225 }
226
228 queueId = zenkit::FightAiMove::NOP;
229 tr[0] = MV_NULL;
230 }
231
233 hitFlg = true;
234 for(auto& i:tr)
235 i = MV_NULL;
236 }
237
238float FightAlgo::baseDistance(const Npc& npc, const Npc& tg, GameScript &owner) const {
239 auto& gv = owner.guildVal();
240 float baseTg = float(gv.fight_range_base[tg .guild()]);
241 float baseNpc = float(gv.fight_range_base[npc.guild()]);
242 return baseTg + baseNpc;
243 }
244
245float FightAlgo::prefferedAttackDistance(const Npc& npc, const Npc& tg, GameScript &owner) const {
246 auto& gv = owner.guildVal();
247 float baseTg = float(gv.fight_range_base[tg .guild()]);
248 float baseNpc = float(gv.fight_range_base[npc.guild()]);
249 return baseTg + baseNpc + weaponRange(owner,npc);
250 }
251
252float FightAlgo::prefferedGDistance(const Npc& npc, const Npc& tg, GameScript &owner) const {
253 auto gl = npc.guild();
254 auto& gv = owner.guildVal();
255 float baseTg = float(gv.fight_range_base[tg .guild()]);
256 float baseNpc = float(gv.fight_range_base[npc.guild()]);
257 return float(baseTg + baseNpc + float(gv.fight_range_g[gl])) + weaponRange(owner,npc);
258 }
259
261 float NPC_ATTACK_FINISH_DISTANCE = 180;
262 if(auto var = owner.findSymbol("NPC_ATTACK_FINISH_DISTANCE")) {
263 if(var->type()==zenkit::DaedalusDataType::INT)
264 NPC_ATTACK_FINISH_DISTANCE = float(var->get_int());
265 else if(var->type()==zenkit::DaedalusDataType::FLOAT)
266 NPC_ATTACK_FINISH_DISTANCE = var->get_float();
267 }
268 return NPC_ATTACK_FINISH_DISTANCE;
269 }
270
271bool FightAlgo::isInAttackRange(const Npc &npc, const Npc &tg, GameScript &owner) const {
272 // tested in vanilla on Bloofly's:
273 // 60 weapon range (Spiked club) is not enough to hit
274 // 70 weapon range (Rusty Sword) is good to hit
275 auto dist = npc.qDistTo(tg);
276 auto pd = prefferedAttackDistance(npc,tg,owner);
277 static float padding = 0;
278 if(npc.hasState(BS_RUN))
279 pd += padding; // padding, for wolf
280 return (dist<=pd*pd);
281 }
282
283bool FightAlgo::isInFinishRange(const Npc& npc, const Npc& tg, GameScript& owner) const {
284 auto dist = npc.qDistTo(tg);
285 auto pd = attackFinishDistance(owner);
286 return (dist<=pd*pd);
287 }
288
289bool FightAlgo::isInWRange(const Npc& npc, const Npc& tg, GameScript& owner) const {
290 auto dist = npc.qDistTo(tg);
291 auto pd = prefferedAttackDistance(npc,tg,owner);
292 return (dist<=pd*pd);
293 }
294
295bool FightAlgo::isInGRange(const Npc &npc, const Npc &tg, GameScript &owner) const {
296 auto dist = npc.qDistTo(tg);
297 auto pd = prefferedGDistance(npc,tg,owner);
298 return (dist<=pd*pd);
299 }
300
301bool FightAlgo::isInFocusAngle(const Npc &npc, const Npc &tg) const {
302 static const float maxAngle = std::cos(float(30.0*M_PI/180.0));
303
304 const auto dpos = npc.centerPosition()-tg.centerPosition();
305 const float plAng = npc.rotationRad()+float(M_PI/2);
306
307 const float da = plAng-std::atan2(dpos.z,dpos.x);
308 const float c = std::cos(da);
309
310 if(c<maxAngle)
311 return false;
312 return true;
313 }
314
315float FightAlgo::weaponRange(GameScript &owner, const Npc &npc) {
316 auto gl = npc.guild();
317 auto& gv = owner.guildVal();
318 auto w = npc.inventory().activeWeapon();
319 int add = w ? w->swordLength() : 0;
321
322 switch(npc.weaponState()) {
323 case WeaponState::W1H:
324 return float(gv.fight_range_1ha[gl] + add);
325 case WeaponState::W2H:
326 return float(gv.fight_range_2ha[gl] + add);
329 return float(gv.fight_range_fist[gl]);
330 case WeaponState::Bow:
332 return float(bR);
334 return float(MaxMagRange);
335 }
336 return 0;
337 }
338
void load(Serialize &fin)
Definition fightalgo.cpp:19
Action nextFromQueue(Npc &npc, Npc &tg, GameScript &owner)
bool isInWRange(const Npc &npc, const Npc &tg, GameScript &owner) const
float prefferedGDistance(const Npc &npc, const Npc &tg, GameScript &owner) const
float prefferedAttackDistance(const Npc &npc, const Npc &tg, GameScript &owner) const
bool hasInstructions() const
float baseDistance(const Npc &npc, const Npc &tg, GameScript &owner) const
void save(Serialize &fout)
Definition fightalgo.cpp:26
@ MV_WAITLONG
Definition fightalgo.h:30
@ MV_JUMPBACK
Definition fightalgo.h:22
@ MV_TURN2HIT
Definition fightalgo.h:31
@ MV_STRAFE_E
Definition fightalgo.h:34
bool fetchInstructions(Npc &npc, Npc &tg, GameScript &owner)
float attackFinishDistance(GameScript &owner) const
void onClearTarget()
bool isInAttackRange(const Npc &npc, const Npc &tg, GameScript &owner) const
void onTakeHit()
bool isInFinishRange(const Npc &npc, const Npc &tg, GameScript &owner) const
bool isInGRange(const Npc &npc, const Npc &tg, GameScript &owner) const
void consumeAction()
bool isInFocusAngle(const Npc &npc, const Npc &tg) const
zenkit::DaedalusSymbol * findSymbol(std::string_view s)
const zenkit::IGuildValues & guildVal() const
Definition gamescript.h:107
uint32_t rand(uint32_t max)
static Gothic & inst()
Definition gothic.cpp:249
static const FightAi & fai()
Definition gothic.cpp:651
auto version() const -> const VersionInfo &
Definition gothic.cpp:263
Definition npc.h:25
float qDistTo(const Tempest::Vec3 pos) const
auto weaponState() const -> WeaponState
Definition npc.cpp:3621
bool isPrehit() const
Definition npc.cpp:3990
uint32_t guild() const
Definition npc.cpp:1223
float rotationRad() const
Definition npc.cpp:662
bool hasState(BodyState s) const
Definition npc.cpp:3166
auto inventory() const -> const Inventory &
Definition npc.h:330
auto centerPosition() const -> Tempest::Vec3
Definition npc.cpp:684
zenkit::INpc & handle()
Definition npc.h:327
BodyState bodyStateMasked() const
Definition npc.cpp:3161
void write(const Arg &... a)
Definition serialize.h:76
void read(Arg &... a)
Definition serialize.h:81
@ ReferenceBowRangeG2
Definition constants.h:134
@ MaxMagRange
Definition constants.h:136
@ ReferenceBowRangeG1
Definition constants.h:133
@ BS_RUN
Definition constants.h:157