1#include "Enemy/Mummy.h"
2
3#include "Library/Area/AreaObjUtil.h"
4#include "Library/Base/StringUtil.h"
5#include "Library/Collision/CollisionPartsKeeperUtil.h"
6#include "Library/Effect/EffectSystemInfo.h"
7#include "Library/Item/ItemUtil.h"
8#include "Library/Joint/JointControllerKeeper.h"
9#include "Library/Joint/JointSpringControllerHolder.h"
10#include "Library/LiveActor/ActorActionFunction.h"
11#include "Library/LiveActor/ActorAnimFunction.h"
12#include "Library/LiveActor/ActorAreaFunction.h"
13#include "Library/LiveActor/ActorClippingFunction.h"
14#include "Library/LiveActor/ActorCollisionFunction.h"
15#include "Library/LiveActor/ActorFlagFunction.h"
16#include "Library/LiveActor/ActorInitInfo.h"
17#include "Library/LiveActor/ActorInitUtil.h"
18#include "Library/LiveActor/ActorModelFunction.h"
19#include "Library/LiveActor/ActorMovementFunction.h"
20#include "Library/LiveActor/ActorPoseUtil.h"
21#include "Library/LiveActor/ActorSensorUtil.h"
22#include "Library/Math/MathUtil.h"
23#include "Library/Matrix/MatrixUtil.h"
24#include "Library/Nature/NatureUtil.h"
25#include "Library/Nerve/NerveSetupUtil.h"
26#include "Library/Nerve/NerveUtil.h"
27#include "Library/Player/PlayerUtil.h"
28#include "Library/Stage/StageSwitchUtil.h"
29#include "Library/Thread/FunctorV0M.h"
30
31#include "Util/ItemUtil.h"
32#include "Util/PlayerUtil.h"
33#include "Util/SensorMsgFunction.h"
34
35namespace {
36NERVE_IMPL(Mummy, Sleep)
37NERVE_IMPL(Mummy, SleepShineStop)
38NERVE_IMPL(Mummy, SleepShine)
39NERVE_IMPL(Mummy, Walk)
40NERVE_IMPL(Mummy, WalkStart)
41NERVE_IMPL(Mummy, BlowDown)
42NERVE_IMPL(Mummy, HalfReaction)
43NERVE_IMPL(Mummy, HeadLost)
44NERVE_IMPL(Mummy, Appear)
45NERVE_IMPL(Mummy, AppearAir)
46NERVE_IMPL(Mummy, HideStart)
47NERVE_IMPL(Mummy, Wait)
48NERVE_IMPL(Mummy, Hide)
49
50NERVES_MAKE_NOSTRUCT(Mummy, Appear)
51NERVES_MAKE_STRUCT(Mummy, Sleep, SleepShineStop, SleepShine, Walk, WalkStart, BlowDown,
52 HalfReaction, HeadLost, AppearAir, HideStart, Wait, Hide)
53} // namespace
54
55const f32 sWalkSpeedHeadLost = 0.43f;
56const f32 sWalkSpeedNormal = 0.76f;
57
58Mummy::Mummy(const char* name) : al::LiveActor(name) {}
59
60void Mummy::init(const al::ActorInitInfo& info) {
61 using MummyFunctor = al::FunctorV0M<Mummy*, void (Mummy::*)()>;
62
63 al::initActorWithArchiveName(actor: this, initInfo: info, archiveName: "Mummy", suffix: nullptr);
64 al::initNerve(actor: this, nerve: &NrvMummy.Sleep, maxStates: 0);
65 mShineActor = rs::tryInitLinkShine(info, name: "ShineActor", linkIndex: 0);
66 if (mShineActor) {
67 if (al::listenStageSwitchOnStart(user: this, action: MummyFunctor(this, &Mummy::startSleep)))
68 al::setNerve(user: this, nerve: &NrvMummy.SleepShineStop);
69 else
70 al::setNerve(user: this, nerve: &NrvMummy.SleepShine);
71 }
72
73 al::initJointControllerKeeper(this, 10);
74
75 mJointSpringControllerHolder = new al::JointSpringControllerHolder();
76 mJointSpringControllerHolder->init(this, "JointSpringControllerInfo");
77
78 mTimeLimit = al::getRandom(min: 0, max: 60) + 390;
79 makeActorAlive();
80}
81
82inline void Mummy::setupEffectMatrix() {
83 sead::Vector3f hitPos;
84 sead::Vector3f normal = sead::Vector3f::ey;
85 alCollisionUtil::getHitPosAndNormalOnArrow(
86 this, &hitPos, &normal, al::getTrans(actor: this) + sead::Vector3f(0.0f, 100.0f, 0.0f),
87 sead::Vector3f(0.0f, -200.0f, 0.0f), nullptr, nullptr);
88
89 al::makeMtxUpFrontPos(outMtx: &mEffectMatrix, up: normal, front: al::getFront(actor: this), pos: hitPos);
90}
91
92inline void Mummy::checkEffects() {
93 if (!mIsHeadLost) {
94 if (--mEffectTimer == 0) {
95 al::deleteEffect(this, "Core");
96 al::startVisAnim(this, "Normal");
97 }
98 }
99}
100
101inline void Mummy::adjustVelocity() {
102 bool isOnGround = al::isOnGround(this, 0);
103 al::scaleVelocity(actor: this, factor: 0.92f);
104 if (isOnGround)
105 al::addVelocityToGravityFittedGround(actor: this, force: 1.05f, maxAirTime: 0);
106 else
107 al::addVelocityToGravity(actor: this, force: 1.05f);
108}
109
110inline bool Mummy::isAsleep() const {
111 return al::isNerve(user: this, nerve: &NrvMummy.Sleep) || al::isNerve(user: this, nerve: &NrvMummy.SleepShine) ||
112 al::isNerve(user: this, nerve: &NrvMummy.SleepShineStop);
113}
114
115void Mummy::startSleep() {
116 al::setNerve(user: this, nerve: &NrvMummy.SleepShine);
117}
118
119void Mummy::attackSensor(al::HitSensor* self, al::HitSensor* other) {
120 if (isHide())
121 return;
122
123 if (al::isNerve(user: this, nerve: &NrvMummy.SleepShineStop))
124 return;
125
126 if (al::isSensorEnemyBody(self) && al::isSensorEnemyBody(other))
127 al::sendMsgPushAndKillVelocityToTarget(this, self, other);
128
129 if (al::isSensorEnemyAttack(self))
130 if (!(al::isNerve(user: this, nerve: &NrvMummy.Walk) || al::isNerve(user: this, nerve: &NrvMummy.WalkStart)) ||
131 !al::sendMsgEnemyAttack(receiver: other, sender: self))
132
133 rs::sendMsgPushToPlayer(source: other, target: self);
134}
135
136bool Mummy::isHide() {
137 return al::isNerve(user: this, nerve: &NrvMummy.Wait) || al::isNerve(user: this, nerve: &NrvMummy.Sleep) ||
138 (al::isNerve(user: this, nerve: &NrvMummy.Hide) && al::isGreaterEqualStep(user: this, step: 60)) ||
139 al::isNerve(user: this, nerve: &NrvMummy.BlowDown) || al::isDead(actor: this);
140}
141
142bool Mummy::receiveMsg(const al::SensorMsg* message, al::HitSensor* other, al::HitSensor* self) {
143 if (!isHide() && rs::isMsgNpcScareByEnemy(message))
144 return true;
145
146 if (isAsleep() && al::isMsgPlayerDisregard(msg: message))
147 return true;
148
149 if (isHide() && rs::isMsgPlayerDisregardHomingAttack(message))
150 return true;
151
152 if (isHide() || al::isNerve(user: this, nerve: &NrvMummy.SleepShineStop))
153 return false;
154
155 if (al::tryReceiveMsgPushAndAddVelocity(this, message, other, self, 1.4f))
156 return true;
157
158 if (rs::isMsgPressDown(message)) {
159 if (mIsNoHitStop)
160 al::startHitReactionHitEffect(actor: this, name: "ヒットマーク", other, self);
161 else
162 rs::requestHitReactionToAttacker(message, self, other);
163
164 if (mHasCoin)
165 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
166
167 al::tryDeleteEffect(this, "Core");
168 al::setNerve(user: this, nerve: &NrvMummy.BlowDown);
169 return false;
170 }
171
172 if (rs::isMsgSphinxRideAttack(message) || rs::isMsgBullAttack(message) ||
173 rs::isMsgGamaneBulletThrough(message) || rs::isMsgBlowDown(message) ||
174 al::isMsgExplosion(msg: message) || rs::isMsgBossKnuckleFallAttack(message) ||
175 rs::isMsgBossKnuckleIceFallToMummy(message)) {
176 if (mIsNoHitStop)
177 al::startHitReactionHitEffect(actor: this, name: "ヒットマーク", other, self);
178 else
179 rs::requestHitReactionToAttacker(message, self, other);
180
181 if (mHasCoin) {
182 if (rs::isMsgCactusNeedleAttack(message))
183 mHasCoin = false;
184 else
185 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
186 }
187
188 al::tryDeleteEffect(this, "Core");
189 al::setNerve(user: this, nerve: &NrvMummy.BlowDown);
190 return true;
191 }
192
193 if (rs::isMsgCapAttack(message) || rs::isMsgSeedAttack(message)) {
194 sead::Vector3f dir;
195 al::calcDirOnPlane(outVec: &dir, vecA: al::getTrans(actor: this), vecB: rs::getPlayerPos(this), plane: sead::Vector3f::ey);
196 sead::Vector3f vel = dir;
197 al::faceToDirection(actor: this, dir);
198
199 vel *= -35.0f;
200
201 if (mIsHeadLost) {
202 if (mInvulnerableTimer > 0)
203 return false;
204
205 if (mIsNoHitStop)
206 al::startHitReactionHitEffect(actor: this, name: "ヒットマーク", other, self);
207 else
208 rs::requestHitReactionToAttacker(message, self, other);
209
210 al::addVelocity(actor: this, vel);
211 al::setNerve(user: this, nerve: &NrvMummy.HalfReaction);
212 return false;
213 } else {
214 if (mIsNoHitStop)
215 al::startHitReactionHitEffect(actor: this, name: "ヒットマーク", other, self);
216 else
217 rs::requestHitReactionToAttacker(message, self, other);
218
219 al::addVelocity(actor: this, vel);
220 al::setNerve(user: this, nerve: &NrvMummy.HeadLost);
221 return false;
222 }
223 }
224
225 return false;
226}
227
228void Mummy::control() {
229 if (mInvulnerableTimer > 0)
230 --mInvulnerableTimer;
231
232 if (!isHide() && al::isCollidedFloorCode(this, "Needle")) {
233 al::tryDeleteEffect(this, "Core");
234 al::setNerve(user: this, nerve: &NrvMummy.BlowDown);
235 mHasCoin = false;
236 return;
237 }
238
239 if (al::isInDeathArea(areaUser: this, position: al::getTrans(actor: this)) || al::isCollidedFloorCode(this, "Poison") ||
240 al::isCollidedFloorCode(this, "DamageFire") || al::isInWater(this)) {
241 al::tryAddRippleLarge(this);
242 al::startHitReaction(actor: this, name: "死亡");
243 kill();
244 }
245
246 if (!isAsleep() && al::isOnGround(this, 0)) {
247 const char* collidedFloorMaterialCodeName = al::getCollidedFloorMaterialCodeName(this);
248 if (collidedFloorMaterialCodeName)
249 al::setMaterialCode(actor: this, materialCode: collidedFloorMaterialCodeName);
250 }
251}
252
253void Mummy::setKnuckleMode() {
254 mTimeLimit = al::getRandom(min: 0, max: 70) + 195;
255}
256
257void Mummy::appearWithTrans(const sead::Vector3f& trans) {
258 al::resetPosition(actor: this, trans);
259 al::setScale(actor: this, scale: sead::Vector3f::ones);
260 al::LiveActor* nearestPlayerActor = al::tryFindNearestPlayerActor(this);
261 if (nearestPlayerActor)
262 al::faceToTarget(actor: this, target: nearestPlayerActor);
263
264 mIsHeadLost = false;
265 mTimeLimit = 390;
266 al::showModelIfHide(actor: this);
267
268 setupEffect();
269
270 mEffectTimer = 0;
271 al::tryKillEmitterAndParticleAll(this);
272 al::startVisAnim(this, "Normal");
273 al::startAction(actor: this, actionName: "Appear");
274 al::setNerve(user: this, nerve: &Appear);
275 appear();
276}
277
278void Mummy::setupEffect() {
279 setupEffectMatrix();
280 al::setEffectFollowMtxPtr(this, "MummyAppear", &mEffectMatrix);
281 al::setEffectFollowMtxPtr(this, "MummyHide", &mEffectMatrix);
282}
283
284void Mummy::appearByTreasureBox(const sead::Vector3f& trans) {
285 al::resetPosition(actor: this, trans);
286 al::invalidateClipping(actor: this);
287
288 mIsHeadLost = false;
289 al::faceToTarget(actor: this, target: rs::getPlayerPos(this));
290 mTimeLimit = 390;
291 mHasCoin = false;
292 mIsTreasureBoxSpawned = true;
293
294 al::showModelIfHide(actor: this);
295
296 setupEffect();
297
298 mEffectTimer = 0;
299 al::startVisAnim(this, "Normal");
300 al::setNerve(user: this, nerve: &NrvMummy.AppearAir);
301 appear();
302}
303
304void Mummy::startWalkByTreasureBox() {
305 if (al::isNerve(user: this, nerve: &NrvMummy.AppearAir))
306 al::setNerve(user: this, nerve: &NrvMummy.Walk);
307}
308
309void Mummy::sleep() {
310 if (isHide()) {
311 Shine* shine = mShineActor;
312 al::validateClipping(actor: this);
313 if (shine)
314 al::setNerve(user: this, nerve: &NrvMummy.SleepShine);
315 else
316 al::setNerve(user: this, nerve: &NrvMummy.Sleep);
317 } else if (isOnSpecialGround()) {
318 al::validateClipping(actor: this);
319 al::setNerve(user: this, nerve: &NrvMummy.HideStart);
320 }
321}
322
323void Mummy::exeSleepShineStop() {
324 if (al::isFirstStep(user: this)) {
325 al::startAction(actor: this, actionName: "Hide");
326 al::setActionFrame(actor: this, frame: al::getActionFrameMax(actor: this, actionName: "Hide") - 1.0f);
327 }
328}
329
330void Mummy::exeSleepShine() {
331 al::validateClipping(actor: this);
332
333 if (al::isFirstStep(user: this)) {
334 setupEffect();
335 al::startAction(actor: this, actionName: "Hide");
336 al::startVisAnim(this, "Normal");
337 al::setActionFrame(actor: this, frame: al::getActionFrameMax(actor: this, actionName: "Hide") - 1.0f);
338 }
339
340 if (al::isGreaterEqualStep(user: this, step: 200)) {
341 if ((rs::getPlayerPos(this) - al::getTrans(actor: this)).length() < 1750.0f) {
342 al::invalidateClipping(actor: this);
343 mIsHeadLost = false;
344 al::faceToTarget(actor: this, target: rs::getPlayerPos(this));
345 mHasCoin = false;
346 mTimeLimit = 390;
347 al::setNerve(user: this, nerve: &Appear);
348 }
349 }
350}
351
352void Mummy::exeSleep() {
353 if (al::isFirstStep(user: this)) {
354 al::validateClipping(actor: this);
355 kill();
356 }
357}
358
359void Mummy::exeWait() {}
360
361void Mummy::exeAppear() {
362 if (al::isFirstStep(user: this)) {
363 al::invalidateClipping(actor: this);
364 al::tryStartActionIfNotPlaying(actor: this, actionName: "Appear");
365 if (mShineActor)
366 al::emitEffect(this, "ShineGlow", nullptr);
367 }
368
369 adjustVelocity();
370
371 if (al::isActionEnd(actor: this))
372 al::setNerve(user: this, nerve: &NrvMummy.WalkStart);
373}
374
375void Mummy::exeAppearAir() {
376 if (al::isFirstStep(user: this))
377 al::startAction(actor: this, actionName: "AppearAir");
378}
379
380void Mummy::exeWalkStart() {
381 if (al::isFirstStep(user: this))
382 al::startAction(actor: this, actionName: "WalkStart");
383
384 walk();
385
386 if (al::isActionEnd(actor: this))
387 al::setNerve(user: this, nerve: &NrvMummy.Walk);
388}
389
390void Mummy::walk() {
391 al::LiveActor* nearestPlayerActor = al::tryFindNearestPlayerActor(this);
392 if (!nearestPlayerActor || (al::calcDistanceH(actor: this, target: nearestPlayerActor) > 3500.0f &&
393 !mIsTreasureBoxSpawned && isOnSpecialGround())) {
394 al::setNerve(user: this, nerve: &NrvMummy.HideStart);
395 return;
396 }
397
398 sead::Vector3f dir;
399 al::calcDirToActor(dir: &dir, actor: this, target: nearestPlayerActor);
400 al::rotateVectorDegreeY(&dir, mWalkDirectionOffset);
401 al::turnToDirection(actor: this, dir, deg: 1.8f);
402 if (al::isNerve(user: this, nerve: &NrvMummy.WalkStart)) {
403 f32 nerveRate = al::calcNerveRate(user: this, max: al::getActionFrameMax(actor: this, actionName: "WalkStart"));
404 al::addVelocityToFront(actor: this, force: nerveRate * nerveRate * 0.76f);
405 } else {
406 al::addVelocityToFront(actor: this, force: mIsHeadLost ? sWalkSpeedHeadLost : sWalkSpeedNormal);
407 }
408
409 adjustVelocity();
410
411 if (--mWalkDirectionChangeTimer <= 0) {
412 mWalkDirectionOffset = al::getRandom(min: -25.0f, max: 25.0f);
413 mWalkDirectionChangeTimer = al::getRandom(min: -30.0f, max: 30.0f) + 60.0f;
414 }
415
416 al::reboundVelocityFromCollision(actor: this, reboundStrength: 0.0f, reboundMin: 0.0f, friction: 1.0f);
417}
418
419void Mummy::exeWalk() {
420 if (al::isFirstStep(user: this))
421 al::startAction(actor: this, actionName: "Walk");
422
423 walk();
424
425 if (mIsHeadLost) {
426 if (--mHeadRegenTimer <= 0) {
427 mIsHeadLost = false;
428 al::startHitReaction(actor: this, name: "頭復活");
429 mEffectTimer = 24;
430 }
431 } else
432 checkEffects();
433
434 if (--mTimeLimit <= 0 && isHideByTimeLimit())
435 al::setNerve(user: this, nerve: &NrvMummy.HideStart);
436}
437
438inline bool Mummy::isOnSpecialGround() const {
439 if (al::isOnGround(this, 0)) {
440 const char* materialName = al::getCollidedFloorMaterialCodeName(this);
441 if (!materialName)
442 return false;
443 if (al::isEqualSubString(str: materialName, subStr: "Sand") ||
444 al::isEqualString(str1: materialName, str2: "ExStarCube"))
445 return true;
446 }
447
448 return false;
449}
450
451bool Mummy::isHideByTimeLimit() const {
452 if (mTimeLimit > 0 || mIsTreasureBoxSpawned)
453 return false;
454
455 return isOnSpecialGround();
456}
457
458void Mummy::exeHeadLost() {
459 if (al::isFirstStep(user: this)) {
460 mIsHeadLost = true;
461 mHeadRegenTimer = 120;
462 al::startAction(actor: this, actionName: "Half");
463 al::startHitReaction(actor: this, name: "頭飛び");
464 al::emitEffect(this, "Core", nullptr);
465 mInvulnerableTimer = 25;
466 }
467
468 adjustVelocity();
469
470 if (al::isActionEnd(actor: this))
471 al::setNerve(user: this, nerve: &NrvMummy.Walk);
472}
473
474void Mummy::exeHalfReaction() {
475 if (al::isFirstStep(user: this)) {
476 mHeadRegenTimer = 120;
477 al::startAction(actor: this, actionName: "HalfReaction");
478 mInvulnerableTimer = 25;
479 }
480
481 checkEffects();
482 adjustVelocity();
483
484 if (al::isActionEnd(actor: this))
485 al::setNerve(user: this, nerve: &NrvMummy.Walk);
486}
487
488void Mummy::exeHideStart() {
489 if (al::isFirstStep(user: this))
490 al::startAction(actor: this, actionName: "HideStart");
491
492 adjustVelocity();
493
494 if (al::isActionEnd(actor: this))
495 al::setNerve(user: this, nerve: &NrvMummy.Hide);
496}
497
498void Mummy::exeHide() {
499 if (al::isFirstStep(user: this)) {
500 al::setVelocityZero(this);
501 setupEffectMatrix();
502 al::startAction(actor: this, actionName: "Hide");
503 }
504
505 checkEffects();
506 adjustVelocity();
507
508 if (al::isActionEnd(actor: this)) {
509 if (mShineActor) {
510 al::deleteEffect(this, "ShineGlow");
511 al::setNerve(user: this, nerve: &NrvMummy.SleepShine);
512 } else {
513 al::setNerve(user: this, nerve: &NrvMummy.Sleep);
514 }
515 }
516}
517
518void Mummy::exeBlowDown() {
519 if (al::isFirstStep(user: this)) {
520 al::setVelocityZero(this);
521 al::startAction(actor: this, actionName: "BlowDown");
522 al::hideModelIfShow(actor: this);
523 }
524
525 if (al::isStep(user: this, step: 25)) {
526 if (mShineActor) {
527 rs::appearPopupShine(shine: mShineActor, actor: this);
528 mShineActor = nullptr;
529 al::deleteEffect(this, "ShineGlow");
530 }
531 if (mHasCoin)
532 al::appearItem(actor: this);
533 }
534 if (al::isActionEnd(actor: this) && al::isGreaterEqualStep(user: this, step: 90))
535 al::setNerve(user: this, nerve: &NrvMummy.Sleep);
536}
537