1#include "Enemy/Togezo.h"
2
3#include "Library/Collision/Collider.h"
4#include "Library/Item/ItemUtil.h"
5#include "Library/LiveActor/ActorActionFunction.h"
6#include "Library/LiveActor/ActorAreaFunction.h"
7#include "Library/LiveActor/ActorClippingFunction.h"
8#include "Library/LiveActor/ActorCollisionFunction.h"
9#include "Library/LiveActor/ActorFlagFunction.h"
10#include "Library/LiveActor/ActorInitUtil.h"
11#include "Library/LiveActor/ActorMovementFunction.h"
12#include "Library/LiveActor/ActorPoseUtil.h"
13#include "Library/LiveActor/ActorSensorUtil.h"
14#include "Library/Math/MathUtil.h"
15#include "Library/Nature/NatureUtil.h"
16#include "Library/Nerve/NerveSetupUtil.h"
17#include "Library/Nerve/NerveUtil.h"
18#include "Library/Player/PlayerUtil.h"
19#include "Library/Stage/StageSwitchUtil.h"
20#include "Library/Thread/FunctorV0M.h"
21
22#include "Enemy/EnemyStateWander.h"
23#include "Util/ExternalForceKeeper.h"
24#include "Util/ItemUtil.h"
25#include "Util/SensorMsgFunction.h"
26
27namespace {
28NERVE_IMPL(Togezo, Wait)
29NERVE_IMPL(Togezo, Wander)
30NERVE_IMPL(Togezo, CapHit)
31NERVE_IMPL(Togezo, BlowDown)
32NERVE_IMPL(Togezo, Attack)
33NERVE_IMPL(Togezo, Turn)
34NERVE_IMPL(Togezo, Fall)
35NERVE_IMPL(Togezo, Find)
36NERVE_IMPL(Togezo, Chase)
37NERVE_IMPL(Togezo, Land)
38
39NERVES_MAKE_NOSTRUCT(Togezo, Land)
40NERVES_MAKE_STRUCT(Togezo, Wait, Wander, CapHit, BlowDown, Attack, Turn, Fall, Find, Chase)
41} // namespace
42
43Togezo::Togezo(const char* name) : al::LiveActor(name) {}
44
45void Togezo::init(const al::ActorInitInfo& info) {
46 using TogezoFunctor = al::FunctorV0M<Togezo*, void (Togezo::*)()>;
47
48 al::initActorWithArchiveName(actor: this, initInfo: info, archiveName: "Togezo", suffix: nullptr);
49
50 if (al::isValidStageSwitch(user: this, linkName: "SwitchStart"))
51 al::initNerve(actor: this, nerve: &NrvTogezo.Wait, maxStates: 1);
52 else
53 al::initNerve(actor: this, nerve: &NrvTogezo.Wander, maxStates: 1);
54
55 mStateWander = new EnemyStateWander(this, "Walk");
56 al::initNerveState(user: this, state: mStateWander, nerve: &NrvTogezo.Wander, hostName: "徘徊");
57
58 mForceKeeper = new ExternalForceKeeper();
59
60 if (al::listenStageSwitchOnAppear(user: this, action: TogezoFunctor(this, &Togezo::listenAppear)))
61 makeActorDead();
62 else
63 makeActorAlive();
64}
65
66void Togezo::listenAppear() {
67 appear();
68}
69
70bool Togezo::receiveMsg(const al::SensorMsg* message, al::HitSensor* other, al::HitSensor* self) {
71 if (rs::isMsgTargetMarkerPosition(message)) {
72 rs::setMsgTargetMarkerPosition(message,
73 al::getTrans(actor: this) + sead::Vector3f(0.0f, 180.0f, 0.0f));
74 return true;
75 }
76
77 if (rs::isMsgKillByShineGet(message)) {
78 kill();
79 return true;
80 }
81
82 if (rs::isMsgNpcScareByEnemy(message))
83 return true;
84
85 if (al::tryReceiveMsgPushAndAddVelocity(this, message, other, self, 1.0))
86 return true;
87
88 if (rs::isMsgCapReflect(message) && !al::isNerve(user: this, nerve: &NrvTogezo.BlowDown) &&
89 mCapHitCooldown <= 0) {
90 rs::requestHitReactionToAttacker(message, self, other);
91 al::setNerve(user: this, nerve: &NrvTogezo.CapHit);
92 mCapPos = al::getSensorPos(other);
93 mCapHitCooldown = 10;
94 return true;
95 }
96
97 if ((rs::isMsgBlowDown(message) || rs::isMsgDonsukeAttack(message)) &&
98 !al::isNerve(user: this, nerve: &NrvTogezo.BlowDown)) {
99 al::setVelocityBlowAttackAndTurnToTarget(actor: this, target: al::getActorTrans(other), speedH: 15.0f, speedV: 35.0f);
100 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
101 rs::requestHitReactionToAttacker(message, self, other);
102 al::setNerve(user: this, nerve: &NrvTogezo.BlowDown);
103 return true;
104 }
105
106 if ((rs::isMsgPechoSpot(message) || rs::isMsgDamageBallAttack(message) ||
107 al::isMsgPlayerFireBallAttack(msg: message)) &&
108 al::isSensorEnemyBody(self)) {
109 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
110 rs::requestHitReactionToAttacker(message, self, other);
111 al::startHitReaction(actor: this, name: "死亡");
112
113 kill();
114 return false;
115 }
116
117 return mForceKeeper->receiveMsg(message, other, self);
118}
119
120void Togezo::attackSensor(al::HitSensor* self, al::HitSensor* other) {
121 if (al::isNerve(user: this, nerve: &NrvTogezo.BlowDown))
122 return;
123
124 if (al::sendMsgEnemyAttack(receiver: other, sender: self)) {
125 al::setNerve(user: this, nerve: &NrvTogezo.Attack);
126 return;
127 }
128
129 rs::sendMsgPushToPlayer(source: other, target: self);
130
131 if (al::isNerve(user: this, nerve: &NrvTogezo.BlowDown))
132 return;
133
134 if (!al::isNerve(user: this, nerve: &NrvTogezo.BlowDown) && !al::isNerve(user: this, nerve: &NrvTogezo.Attack) &&
135 (al::sendMsgEnemyAttackNeedle(receiver: other, sender: self) || al::sendMsgEnemyAttack(receiver: other, sender: self)))
136 al::setNerve(user: this, nerve: &NrvTogezo.Attack);
137 else
138 al::sendMsgPushAndKillVelocityToTarget(this, self, other);
139}
140
141void Togezo::control() {
142 if (al::isInDeathArea(actor: this) || al::isCollidedFloorCode(this, "DamageFire") ||
143 al::isCollidedFloorCode(this, "Needle") || al::isCollidedFloorCode(this, "Poison") ||
144 al::isInWaterArea(actor: this)) {
145 al::startHitReaction(actor: this, name: "死亡");
146 al::tryAddRippleMiddle(this);
147 kill();
148
149 return;
150 }
151
152 if (mCapHitCooldown > 0)
153 mCapHitCooldown--;
154
155 sead::Vector3f calculatedForce = sead::Vector3f::zero;
156 mForceKeeper->calcForce(force: &calculatedForce);
157
158 mFuturePos += calculatedForce * 0.64f;
159 mFuturePos *= 0.955f;
160
161 mForceKeeper->reset();
162
163 if (!al::isNearZero(vec: calculatedForce)) {
164 mWanderCooldown = 180;
165 al::invalidateClipping(actor: this);
166 }
167
168 if (mWanderCooldown > 0) {
169 mWanderCooldown--;
170
171 if (mWanderCooldown == 0 && al::isNerve(user: this, nerve: &NrvTogezo.Wander))
172 al::validateClipping(actor: this);
173 }
174}
175
176void Togezo::updateCollider() {
177 const sead::Vector3f& velocity = al::getVelocity(actor: this);
178
179 if (al::isNoCollide(actor: this)) {
180 *al::getTransPtr(actor: this) += velocity;
181 al::getActorCollider(this)->onInvalidate();
182 } else if (al::isFallOrDamageCodeNextMove(actor: this, velocity: (velocity + mFuturePos) * 1.5f, gravity: 50.0f,
183 searchDist: 200.0f)) {
184 *al::getTransPtr(actor: this) +=
185 al::getActorCollider(this)->collide((velocity + mFuturePos) * 1.5f);
186 } else {
187 sead::Vector3f result = al::getActorCollider(this)->collide(velocity + mFuturePos);
188 *al::getTransPtr(actor: this) += result;
189 }
190}
191
192void Togezo::exeWait() {
193 if (al::isFirstStep(user: this)) {
194 al::startAction(actor: this, actionName: "Wait");
195 al::setVelocityZero(this);
196 }
197 if (al::isValidStageSwitch(user: this, linkName: "SwitchStart") && al::isOnStageSwitch(user: this, linkName: "SwitchStart"))
198 al::setNerve(user: this, nerve: &NrvTogezo.Wander);
199}
200
201void Togezo::exeWander() {
202 if (al::isFirstStep(user: this)) {
203 al::setVelocityZero(this);
204 al::startAction(actor: this, actionName: "Walk");
205 }
206
207 al::updateNerveState(user: this);
208
209 bool isGrounded = al::isOnGround(this, 0);
210 bool isNearPlayer = al::isNearPlayer(this, 1000.0f);
211
212 if (isGrounded && isNearPlayer) {
213 al::setNerve(user: this, nerve: &NrvTogezo.Turn);
214 } else if (isGrounded) {
215 mAirTime = 0;
216 mGroundNormal = al::getOnGroundNormal(this, 0);
217 } else {
218 if (mAirTime++ >= 4)
219 al::setNerve(user: this, nerve: &NrvTogezo.Fall);
220 }
221}
222
223void Togezo::exeTurn() {
224 if (al::isFirstStep(user: this)) {
225 al::setVelocityZero(this);
226 al::startAction(actor: this, actionName: "Turn");
227 }
228
229 sead::Vector3f frontDir = sead::Vector3f::zero;
230 al::calcFrontDir(front: &frontDir, actor: this);
231
232 al::LiveActor* player = al::tryFindNearestPlayerActor(this);
233 if (player) {
234 if (al::isFaceToTargetDegreeH(actor: this, target: al::getTrans(actor: player), face: frontDir, degH: 1.0f)) {
235 al::setNerve(user: this, nerve: &NrvTogezo.Find);
236 return;
237 }
238 al::turnToTarget(actor: this, target: al::getTrans(actor: player), deg: 3.5f);
239 }
240
241 if (!al::isNearPlayer(this, 1300.0f)) {
242 al::setNerve(user: this, nerve: &NrvTogezo.Wander);
243 return;
244 }
245
246 if (al::isOnGround(this, 0)) {
247 mAirTime = 0;
248 } else {
249 al::addVelocityToGravity(actor: this, force: 1.0f);
250 al::scaleVelocity(actor: this, factor: 0.98f);
251
252 if (mAirTime++ >= 4)
253 al::setNerve(user: this, nerve: &NrvTogezo.Fall);
254 }
255}
256
257void applyTogezoGroundVelocity(Togezo* self) {
258 if (al::isOnGround(self, 0)) {
259 const sead::Vector3f& normal = al::getOnGroundNormal(self, 0);
260 al::getVelocity(actor: self); // unused
261
262 if (al::isFallOrDamageCodeNextMove(actor: self, velocity: al::getVelocity(actor: self), gravity: 50.0f, searchDist: 200.0f)) {
263 f32 y = al::getVelocity(actor: self).y;
264 al::scaleVelocity(actor: self, factor: -1.0f);
265 al::getVelocityPtr(actor: self)->y = y;
266 } else {
267 al::addVelocity(actor: self, vel: -normal);
268 al::scaleVelocity(actor: self, factor: 0.95f);
269 }
270 } else {
271 al::addVelocityY(actor: self, y: -2.0f);
272 al::scaleVelocity(actor: self, factor: 0.98f);
273 }
274}
275
276void Togezo::exeFind() {
277 if (al::isFirstStep(user: this)) {
278 al::setVelocityZero(this);
279 al::startAction(actor: this, actionName: "Find");
280 mAirTime = 0;
281 al::invalidateClipping(actor: this);
282 }
283
284 if (!al::isOnGround(this, 0) && mAirTime++ >= 4) {
285 al::setNerve(user: this, nerve: &NrvTogezo.Fall);
286 } else {
287 applyTogezoGroundVelocity(self: this);
288 if (al::isActionEnd(actor: this))
289 al::setNerve(user: this, nerve: &NrvTogezo.Chase);
290 }
291}
292
293void Togezo::exeChase() {
294 if (al::isFirstStep(user: this)) {
295 al::startAction(actor: this, actionName: "Run");
296 al::invalidateClipping(actor: this);
297 }
298
299 if (al::isOnGround(this, 0)) {
300 sead::Vector3f normal = al::getOnGroundNormal(this, 0);
301 al::scaleVelocityDirection(actor: this, direction: normal, factor: 0);
302 mAirTime = 0;
303
304 al::LiveActor* player = al::tryFindNearestPlayerActor(this);
305 if (player) {
306 al::turnToTarget(actor: this, target: al::getTrans(actor: player), deg: 3.5f);
307
308 sead::Vector3f frontDir = sead::Vector3f::zero;
309 al::calcFrontDir(front: &frontDir, actor: this);
310
311 sead::Vector3f verticalVel = sead::Vector3f::zero;
312 sead::Vector3f horizontalVel = sead::Vector3f::zero;
313 al::separateVectorHV(&horizontalVel, &verticalVel, normal, frontDir);
314 al::tryNormalizeOrDirZ(out: &horizontalVel, vec: horizontalVel);
315
316 al::addVelocity(actor: this, vel: horizontalVel * 0.6f);
317 al::scaleVelocity(actor: this, factor: 0.95f);
318 }
319 if (!al::isNearPlayer(this, 1300.0f)) {
320 al::setNerve(user: this, nerve: &NrvTogezo.Wander);
321 return;
322 }
323 } else if (mAirTime++ >= 4) {
324 al::setNerve(user: this, nerve: &NrvTogezo.Fall);
325 return;
326 }
327
328 applyTogezoGroundVelocity(self: this);
329}
330
331void Togezo::exeFall() {
332 if (al::isFirstStep(user: this)) {
333 al::invalidateClipping(actor: this);
334 al::startAction(actor: this, actionName: "Fall");
335 }
336
337 applyTogezoGroundVelocity(self: this);
338
339 if (al::isOnGround(this, 0)) {
340 mAirTime = 0;
341 al::validateClipping(actor: this);
342 al::setNerve(user: this, nerve: &Land);
343 }
344}
345
346void Togezo::exeLand() {
347 if (al::isFirstStep(user: this)) {
348 al::setVelocityZero(this);
349 al::startAction(actor: this, actionName: "Land");
350 mAirTime = 0;
351 }
352
353 s32* airTimePtr = &mAirTime;
354
355 applyTogezoGroundVelocity(self: this);
356
357 if (!al::isOnGround(this, 0) && (*airTimePtr)++ >= 4)
358 al::setNerve(user: this, nerve: &NrvTogezo.Fall);
359 else if (al::isActionEnd(actor: this))
360 al::setNerve(user: this, nerve: &NrvTogezo.Wander);
361}
362
363void Togezo::exeAttack() {
364 if (al::isFirstStep(user: this)) {
365 al::startAction(actor: this, actionName: "AttackSuccess");
366 al::setVelocityZero(this);
367 }
368
369 applyTogezoGroundVelocity(self: this);
370
371 if (al::isActionEnd(actor: this))
372 al::setNerve(user: this, nerve: &NrvTogezo.Wander);
373}
374
375void Togezo::exeCapHit() {
376 if (al::isFirstStep(user: this)) {
377 al::startAction(actor: this, actionName: "CapHit");
378
379 sead::Vector3f capDirection = al::getTrans(actor: this) - mCapPos;
380 capDirection.y = 0.0f;
381
382 al::tryNormalizeOrDirZ(out: &capDirection, vec: capDirection);
383 al::setVelocity(actor: this, vel: capDirection * 20.0f);
384
385 sead::Quatf frontUp = sead::Quatf::unit;
386 al::makeQuatFrontUp(outQuat: &frontUp, front: capDirection, up: sead::Vector3f::ey);
387
388 mAirTime = 0;
389
390 al::invalidateClipping(actor: this);
391 }
392
393 if (al::isActionEnd(actor: this)) {
394 if (al::isNearPlayer(this, 1000.0f))
395 al::setNerve(user: this, nerve: &NrvTogezo.Find);
396 else
397 al::setNerve(user: this, nerve: &NrvTogezo.Wander);
398 } else if (al::isOnGround(this, 0)) {
399 mAirTime = 0;
400
401 al::addVelocityToGravity(actor: this, force: 1.0);
402 al::scaleVelocity(actor: this, factor: 0.95f);
403
404 sead::Vector3f velocity = al::getVelocity(actor: this);
405 f32 y = velocity.y;
406 velocity.y = 0.0f;
407
408 if (al::tryNormalizeOrZero(out: &velocity, vec: velocity)) {
409 if (al::isFallOrDamageCodeNextMove(actor: this, velocity: velocity * 10.0f, gravity: 50.0f, searchDist: 200.0f))
410 al::setVelocity(actor: this,
411 vel: sead::Vector3f(velocity * 5.0f + sead::Vector3f(0.0f, y, 0.0f)));
412 }
413
414 } else if (mAirTime++ >= 5) {
415 al::setNerve(user: this, nerve: &NrvTogezo.Fall);
416 } else {
417 al::addVelocityToGravity(actor: this, force: 1.0f);
418 al::scaleVelocity(actor: this, factor: 0.98f);
419 }
420}
421
422void Togezo::exeBlowDown() {
423 if (al::isFirstStep(user: this)) {
424 al::startAction(actor: this, actionName: "BlowDown");
425 al::invalidateClipping(actor: this);
426 }
427
428 al::addVelocityToGravity(actor: this, force: 2.0f);
429 al::scaleVelocity(actor: this, factor: 0.98f);
430
431 if (al::isActionEnd(actor: this)) {
432 al::startHitReaction(actor: this, name: "死亡");
433 al::appearItem(actor: this);
434 kill();
435 }
436}
437