1#include "Item/Coin.h"
2
3#include "Library/Collision/CollisionPartsKeeperUtil.h"
4#include "Library/Collision/CollisionPartsTriangle.h"
5#include "Library/Collision/PartsConnector.h"
6#include "Library/Controller/PadRumbleFunction.h"
7#include "Library/Demo/DemoFunction.h"
8#include "Library/LiveActor/ActorActionFunction.h"
9#include "Library/LiveActor/ActorAreaFunction.h"
10#include "Library/LiveActor/ActorClippingFunction.h"
11#include "Library/LiveActor/ActorCollisionFunction.h"
12#include "Library/LiveActor/ActorFlagFunction.h"
13#include "Library/LiveActor/ActorInitInfo.h"
14#include "Library/LiveActor/ActorInitUtil.h"
15#include "Library/LiveActor/ActorModelFunction.h"
16#include "Library/LiveActor/ActorMovementFunction.h"
17#include "Library/LiveActor/ActorPoseUtil.h"
18#include "Library/LiveActor/ActorSensorUtil.h"
19#include "Library/Math/MathUtil.h"
20#include "Library/Nature/NatureUtil.h"
21#include "Library/Nerve/NerveSetupUtil.h"
22#include "Library/Nerve/NerveUtil.h"
23#include "Library/Placement/PlacementFunction.h"
24#include "Library/Se/SeFunction.h"
25#include "Library/Shadow/ActorShadowUtil.h"
26#include "Library/Stage/StageSwitchUtil.h"
27
28#include "Item/CoinRotateCalculator.h"
29#include "Item/CoinStateAppearRotate.h"
30#include "System/GameDataFunction.h"
31#include "System/GameDataUtil.h"
32#include "Util/AreaUtil.h"
33#include "Util/DemoUtil.h"
34#include "Util/ExternalForceKeeper.h"
35#include "Util/SensorMsgFunction.h"
36#include "Util/ShadowUtil.h"
37#include "Util/WaterSurfaceShadow.h"
38
39namespace {
40NERVE_IMPL_(Coin, WaitConnectMtx, Wait)
41NERVE_IMPL(Coin, Wait)
42NERVE_IMPL(Coin, Appear)
43NERVE_IMPL(Coin, AppearCoinLead)
44NERVE_IMPL_(Coin, WaitPlayerDead, Wait)
45NERVE_IMPL(Coin, Got)
46NERVE_IMPL_(Coin, WaitCircle, Wait)
47NERVE_IMPL_(Coin, PopUpNormal, PopUp)
48NERVE_IMPL(Coin, CountUp)
49NERVE_IMPL_(Coin, CountUpFive, CountUp)
50NERVE_IMPL(Coin, CountUpDelay)
51NERVE_IMPL_(Coin, WaitCoinRail, Wait)
52NERVE_IMPL_(Coin, PopUpPlayerDeadTimeBalloon, PopUp)
53NERVE_IMPL_(Coin, PopUpPlayerDead, PopUp)
54NERVE_IMPL_(Coin, AppearOnDemo, Appear)
55NERVE_IMPL_(Coin, WaitOnDemo, Wait)
56NERVE_IMPL_(Coin, GotNoCoin, Got)
57NERVE_IMPL(Coin, WaitOnDemoEnd)
58NERVE_IMPL(Coin, BlowUpDelay)
59
60NERVES_MAKE_NOSTRUCT(Coin, BlowUpDelay)
61NERVES_MAKE_STRUCT(Coin, WaitConnectMtx, Wait, Appear, AppearCoinLead, WaitPlayerDead, Got,
62 WaitCircle, PopUpNormal, CountUp, CountUpFive, CountUpDelay, WaitCoinRail,
63 PopUpPlayerDeadTimeBalloon, PopUpPlayerDead, AppearOnDemo, WaitOnDemo, GotNoCoin,
64 WaitOnDemoEnd)
65} // namespace
66
67const sead::Vector3f sAppearAboveVelocity(0.0f, 25.0f, 0.0f);
68
69Coin::Coin(const char* name, bool isDemo) : al::LiveActor(name), mIsDemo(isDemo) {}
70
71void Coin::init(const al::ActorInitInfo& initInfo) {
72 const char* modelName = "Coin";
73 if (al::isPlaced(initInfo)) {
74 mIsPlaced = true;
75 alPlacementFunction::tryGetModelName(modelName: &modelName, initInfo);
76 } else {
77 mIsPlaced = false;
78 }
79
80 const char* suffix = mIsDemo ? "MoveDemo" : nullptr;
81 al::initActorWithArchiveName(actor: this, initInfo, archiveName: modelName, suffix);
82
83 bool isConnectToCollision = false;
84 al::tryGetArg(arg: &isConnectToCollision, initInfo, key: "IsConnectToCollision");
85 if (!isConnectToCollision)
86 al::initNerve(actor: this, nerve: &NrvCoin.Wait, maxStates: 1);
87
88 else
89 al::initNerve(actor: this, nerve: &NrvCoin.WaitConnectMtx, maxStates: 1);
90
91 al::offCollide(actor: this);
92 mExternalForceKeeper = new ExternalForceKeeper();
93 mRotateCalculator = new CoinRotateCalculator(this);
94
95 mStartingQuat.set(al::getQuat(actor: this));
96 al::tryAddDisplayOffset(actor: this, initInfo);
97 al::tryGetDisplayOffset(offset: &mDisplayOffset, initInfo);
98 mMtxConnector = al::createMtxConnector(actor: this);
99
100 mShadowSize = rs::setShadowDropLength(this, initInfo, "本体");
101 al::expandClippingRadiusByShadowLength(actor: this, nullptr, radius: mShadowSize);
102 mWaterSurfaceShadow = rs::tryCreateWaterSurfaceCoinShadow(initInfo);
103
104 if (mMtxConnector != nullptr)
105 al::tryGetArg(arg: &mIsConnectToCollisionBack, initInfo, key: "IsConnectToCollisionBack");
106
107 if (al::trySyncStageSwitchAppear(actor: this))
108 al::setNerve(user: this, nerve: &NrvCoin.Appear);
109
110 bool isCoinSave = false;
111 al::tryGetArg(arg: &isCoinSave, initInfo, key: "IsCoinSave");
112 if (isCoinSave) {
113 mSaveObjInfo = rs::createSaveObjInfoNoWriteSaveDataInSameWorldResetMiniGame(actorInitInfo: initInfo);
114 if (rs::isOnSaveObjInfo(saveObjInfo: mSaveObjInfo)) {
115 kill();
116 return;
117 }
118 }
119
120 mStateAppearRotate = new CoinStateAppearRotate(this, nullptr, mDisplayOffset, "誘導コイン出現");
121 al::initNerveState(user: this, state: mStateAppearRotate, nerve: &NrvCoin.AppearCoinLead, hostName: "誘導コイン出現");
122 mPoseTrans = al::getTrans(actor: this);
123 mPoseQuat = al::getQuat(actor: this);
124 al::registActorToDemoInfo(actor: this, initInfo);
125}
126
127void Coin::initAfterPlacement() {
128 if (mMtxConnector != nullptr) {
129 if (!mIsConnectToCollisionBack) {
130 al::attachMtxConnectorToCollision(mtxConnector: mMtxConnector, actor: this, 50.0f, 400.0f);
131 } else {
132 sead::Vector3f frontDir = sead::Vector3f(0.0f, 0.0f, 0.0f);
133 al::calcFrontDir(front: &frontDir, actor: this);
134 al::attachMtxConnectorToCollision(
135 mtxConnector: mMtxConnector, actor: this, al::getTrans(actor: this) + frontDir * 50.0f, frontDir * -400.0f);
136 }
137 }
138
139 if (al::isNerve(user: this, nerve: &NrvCoin.WaitPlayerDead)) {
140 sead::Vector3f hitPos = sead::Vector3f::zero;
141 al::Triangle triangle;
142
143 if (alCollisionUtil::getFirstPolyOnArrow(
144 this, &hitPos, &triangle, al::getTrans(actor: this) + sead::Vector3f(0.0f, 20.0f, 0.0f),
145 sead::Vector3f::ey * -200.0f, nullptr, nullptr)) {
146 al::resetPosition(actor: this, trans: hitPos + 80.0f * sead::Vector3f::ey);
147 return;
148 }
149
150 kill();
151 }
152}
153
154void Coin::appear() {
155 mExternalForceKeeper->reset();
156 mRotateCalculator->reset();
157 if (al::isNerve(user: this, nerve: &NrvCoin.Got) && mIsPlaced)
158 return;
159 al::LiveActor::appear();
160 al::showModelIfHide(actor: this);
161}
162
163void Coin::makeActorAlive() {
164 al::LiveActor::makeActorAlive();
165 al::showModelIfHide(actor: this);
166}
167
168// https://decomp.me/scratch/5pT1P
169// NON_MATCHING: Different stack pointer order
170void Coin::control() {
171 sead::Vector3f force = sead::Vector3f::zero;
172 mExternalForceKeeper->calcForce(force: &force);
173 mExternalForceKeeper->reset();
174 bool checkWater;
175 if (!al::isNerve(user: this, nerve: &NrvCoin.Wait) && !al::isNerve(user: this, nerve: &NrvCoin.WaitPlayerDead))
176 checkWater = true;
177 else
178 checkWater = false;
179
180 mRotateCalculator->update(force, checkWater);
181
182 if (!rs::isActiveDemo(this) && mTimeLimit > 0) {
183 mTimeLimit--;
184 if (mTimeLimit <= 0) {
185 kill();
186 return;
187 }
188 if (!(mTimeLimit >= 201) && al::blinkModel(actor: this, mTimeLimit, 6, 0))
189 al::startSe(this, "PgBlink");
190 }
191
192 if (!al::isNerve(user: this, nerve: &NrvCoin.Got))
193 rs::tryUpdateWaterSurfaceCoinShadow(mWaterSurfaceShadow, this, mShadowSize);
194}
195
196void Coin::endClipped() {
197 mRotateCalculator->reset();
198 al::LiveActor::endClipped();
199}
200
201bool Coin::receiveMsg(const al::SensorMsg* message, al::HitSensor* other, al::HitSensor* self) {
202 if (rs::isMsgPlayerDisregardTargetMarker(message) ||
203 mExternalForceKeeper->receiveMsg(message, other, self))
204 return true;
205
206 if (al::isMsgRestart(msg: message)) {
207 if (al::isNerve(user: this, nerve: &NrvCoin.Got))
208 return false;
209 al::setNerve(user: this, nerve: &NrvCoin.AppearOnDemo);
210 appear();
211 return true;
212 }
213
214 if (rs::isMsgTimerAthleticDemoStart(message)) {
215 rs::addDemoActor(this, false);
216 return true;
217 }
218
219 if (rs::isMsgConductLightning(message) && isAnyWaitOrAppear() && isWait())
220 return true;
221
222 if (rs::isMsgItemGetAll(message) && isAnyWaitOrAppear()) {
223 al::invalidateClipping(actor: this);
224 al::startHitReaction(actor: this, name: "取得");
225 al::setNerve(user: this, nerve: &NrvCoin.Got);
226 return true;
227 }
228
229 if (rs::isMsgFishingItemGet(message) && isAnyWaitOrAppear()) {
230 appearCountUp();
231 return true;
232 }
233
234 if (rs::isMsgItemAmiiboKoopa(message) && isAnyWaitOrAppear()) {
235 sead::Vector3f direction = sead::Vector3f::zero;
236 al::calcDirBetweenSensorsH(&direction, other, self);
237 al::setVelocity(actor: this, vel: direction * 20.0f + 40.0f * sead::Vector3f::ey);
238 al::setNerve(user: this, nerve: &NrvCoin.PopUpNormal);
239 return true;
240 }
241
242 return false;
243}
244
245void Coin::tryCreateMtxConnector() {
246 if (mMtxConnector == nullptr)
247 mMtxConnector = al::createMtxConnector(actor: this);
248}
249
250void Coin::appearCirclePlacement() {
251 appear();
252 al::setNerve(user: this, nerve: &NrvCoin.WaitCircle);
253}
254
255void Coin::appearPopUpCommon(bool startHitReaction) {
256 al::validateHitSensors(this);
257 al::setNerve(user: this, nerve: &NrvCoin.PopUpNormal);
258 appear();
259 al::invalidateClipping(actor: this);
260 if (startHitReaction)
261 al::startHitReaction(actor: this, name: "飛出し出現");
262
263 sead::Vector3f frontDir = sead::Vector3f(0.0f, 0.0f, 0.0f);
264 sead::Vector3f upDir = sead::Vector3f(0.0f, 0.0f, 0.0f);
265 al::calcFrontDir(front: &frontDir, actor: this);
266 al::calcUpDir(up: &upDir, actor: this);
267 al::setVelocity(actor: this, vel: frontDir * 7.5f + upDir * 20.0f);
268}
269
270void Coin::appearPopUp() {
271 if (!al::isNerve(user: this, nerve: &NrvCoin.Got) || !mIsPlaced) {
272 appearPopUpCommon(startHitReaction: true);
273 mTimeLimit = 600;
274 }
275}
276
277void Coin::appearPopUpWithoutHitReaction() {
278 if (!al::isNerve(user: this, nerve: &NrvCoin.Got) || !mIsPlaced) {
279 appearPopUpCommon(startHitReaction: false);
280 mTimeLimit = 600;
281 }
282}
283
284void Coin::appearPopUpVelocity() {
285 appearPopUp();
286
287 sead::Vector3f frontDir = sead::Vector3f(0.0f, 0.0f, 0.0f);
288 sead::Vector3f upDir = sead::Vector3f(0.0f, 0.0f, 0.0f);
289 al::calcFrontDir(front: &frontDir, actor: this);
290 al::calcUpDir(up: &upDir, actor: this);
291 f32 frontOffset = al::getRandom(min: 10.0f, max: 15.0f);
292 f32 upOffset = al::getRandom(min: 20.0f, max: 30.0f);
293 al::setVelocity(actor: this, vel: frontOffset * frontDir + upOffset * upDir);
294}
295
296void Coin::appearAbove() {
297 appear();
298 al::invalidateClipping(actor: this);
299 al::setVelocity(actor: this, vel: sAppearAboveVelocity);
300 al::setNerve(user: this, nerve: &NrvCoin.PopUpNormal);
301 al::startHitReaction(actor: this, name: "真上に出現");
302 mTimeLimit = -1;
303}
304
305inline void appearCountUpReset(al::LiveActor* actor, ExternalForceKeeper* forceKeeper) {
306 al::offCollide(actor);
307 al::invalidateClipping(actor);
308 al::invalidateHitSensors(actor);
309 sead::Vector3f direction = sead::Vector3f(0.0f, 0.0f, 0.0f);
310 al::calcQuatUp(up: &direction, actor);
311 al::setVelocityToDirection(actor, dir: direction, speed: 16.0f);
312 forceKeeper->reset();
313 actor->appear();
314}
315
316void Coin::appearCountUpCommon(sead::Vector3f* velocity, s32 delay) {
317 al::setVelocityZero(this);
318 sead::Vector3f quatUp = sead::Vector3f::zero;
319 al::calcQuatUp(up: &quatUp, actor: this);
320 velocity->set(quatUp * 16.0f);
321
322 if (delay != 0) {
323 al::setNerve(user: this, nerve: &NrvCoin.CountUpDelay);
324 al::hideModelIfShow(actor: this);
325 return;
326 }
327
328 al::setNerve(user: this, nerve: &NrvCoin.CountUp);
329 al::setVelocity(actor: this, vel: *velocity);
330}
331
332void Coin::appearCountUp() {
333 al::setNerve(user: this, nerve: &NrvCoin.CountUp);
334 al::setQuat(actor: this, quat: sead::Quatf::unit);
335 appearCountUpReset(actor: this, forceKeeper: mExternalForceKeeper);
336 mTimeLimit = -1;
337}
338
339void Coin::appearCountUpFiveCount() {
340 al::setNerve(user: this, nerve: &NrvCoin.CountUpFive);
341 al::setQuat(actor: this, quat: sead::Quatf::unit);
342 appearCountUpReset(actor: this, forceKeeper: mExternalForceKeeper);
343 mTimeLimit = -1;
344}
345
346void Coin::appearCountUp3(s32 coinIndex) {
347 sead::Vector3f direction = sead::Vector3f::zero;
348
349 switch (coinIndex) {
350 case 0:
351 mCountUpDelay = 0;
352 direction = sead::Vector3f(0.0f, 0.0f, 50.0f);
353 break;
354 case 1:
355 mCountUpDelay = 5;
356 direction = sead::Vector3f(40.0f, 0.0f, -30.0f);
357 break;
358 case 2:
359 mCountUpDelay = 10;
360 direction = sead::Vector3f(-40.0f, 0.0f, -30.0f);
361 break;
362 default:
363 break;
364 }
365
366 appearCountUpReset(actor: this, forceKeeper: mExternalForceKeeper);
367 al::rotateVectorQuat(&direction, al::getQuat(actor: this));
368 al::getTransPtr(actor: this)->add(a: direction);
369 mTimeLimit = -1;
370 appearCountUpCommon(velocity: &mBlowVelocity, delay: mCountUpDelay);
371}
372
373void Coin::appearCountUp5(s32 coinIndex) {
374 sead::Vector3f direction = sead::Vector3f::zero;
375
376 switch (coinIndex) {
377 case 0:
378 mCountUpDelay = 0;
379 direction = sead::Vector3f(0.0f, 0.0f, 220.0f);
380 break;
381 case 1:
382 mCountUpDelay = 5;
383 direction = sead::Vector3f(-120.0f, 0.0f, -200.0f);
384 break;
385 case 2:
386 mCountUpDelay = 10;
387 direction = sead::Vector3f(240.0f, 0.0f, 50.0f);
388 break;
389 case 3:
390 mCountUpDelay = 15;
391 direction = sead::Vector3f(-240.0f, 0.0f, 50.0f);
392 break;
393 case 4:
394 mCountUpDelay = 20;
395 direction = sead::Vector3f(120.0f, 0.0f, -200.0f);
396 break;
397 default:
398 break;
399 }
400
401 appearCountUpReset(actor: this, forceKeeper: mExternalForceKeeper);
402 al::rotateVectorQuat(&direction, al::getQuat(actor: this));
403 al::setTrans(actor: this, trans: al::getTrans(actor: this) + direction);
404 mTimeLimit = -1;
405 appearCountUpCommon(velocity: &mBlowVelocity, delay: mCountUpDelay);
406}
407
408void Coin::appearCountUp10(s32 coinIndex) {
409 sead::Vector3f direction = sead::Vector3f(200.0f, 200.0f, 200.0f);
410 al::rotateVectorDegreeY(&direction, coinIndex * 36.0f);
411
412 // Weird optimization here. It's supposed to be a switch case.
413 if ((coinIndex == 5) || (coinIndex == 0))
414 mCountUpDelay = coinIndex == 0 ? 0 : 20;
415
416 else if ((coinIndex == 6) || (coinIndex == 1))
417 mCountUpDelay = coinIndex == 1 ? 5 : 25;
418
419 else if ((coinIndex == 7) || (coinIndex == 2))
420 mCountUpDelay = coinIndex == 2 ? 10 : 30;
421
422 else if ((coinIndex == 8) || (coinIndex == 3))
423 mCountUpDelay = coinIndex == 3 ? 15 : 35;
424
425 else if ((coinIndex == 9) || (coinIndex == 4))
426 mCountUpDelay = coinIndex == 4 ? 20 : 40;
427
428 appearCountUpReset(actor: this, forceKeeper: mExternalForceKeeper);
429 al::rotateVectorQuat(&direction, al::getQuat(actor: this));
430 al::getTransPtr(actor: this)->add(a: direction);
431 mTimeLimit = -1;
432 appearCountUpCommon(velocity: &mBlowVelocity, delay: mCountUpDelay);
433}
434
435void Coin::appearCountUpFixPos10(s32 coinIndex) {
436 al::setNerve(user: this, nerve: &NrvCoin.CountUp);
437 appearCountUpReset(actor: this, forceKeeper: mExternalForceKeeper);
438 mTimeLimit = -1;
439 mCountUpDelay = coinIndex * 8;
440 appearCountUpCommon(velocity: &mBlowVelocity, delay: mCountUpDelay);
441}
442
443void Coin::appearCoinRail() {
444 al::setNerve(user: this, nerve: &NrvCoin.WaitCoinRail);
445 appear();
446}
447
448void Coin::appearCoinChameleon(const sead::Vector3f& trans, const sead::Quatf& quat,
449 const sead::Vector3f& offset) {
450 al::setTrans(actor: this, trans);
451 al::setQuat(actor: this, quat);
452 mChameleonOffset = offset;
453 appear();
454 al::setNerve(user: this, nerve: &NrvCoin.Wait);
455}
456
457void Coin::appearLimitTime(s32 timeLimit) {
458 al::setNerve(user: this, nerve: &NrvCoin.AppearCoinLead);
459 al::invalidateClipping(actor: this);
460 appear();
461 mTimeLimit = timeLimit;
462}
463
464void Coin::appearBlow(const sead::Vector3f& velocity, s32 timeLimit) {
465 appear();
466 al::invalidateClipping(actor: this);
467 al::startHitReaction(actor: this, name: "飛出し出現");
468 al::setVelocity(actor: this, vel: velocity);
469 mTimeLimit = timeLimit;
470 al::validateHitSensors(this);
471 al::showModelIfHide(actor: this);
472
473 if (mMtxConnector != nullptr)
474 al::disconnectMtxConnector(mtxConnector: mMtxConnector);
475
476 rotate();
477 al::setNerve(user: this, nerve: &NrvCoin.PopUpNormal);
478}
479
480void Coin::rotate() {
481 if (al::isNerve(user: this, nerve: &NrvCoin.CountUp) || al::isNerve(user: this, nerve: &NrvCoin.CountUpFive)) {
482 al::rotateQuatYDirDegree(actor: this, deg: 9.5f);
483 return;
484 }
485
486 if (mMtxConnector != nullptr && al::isMtxConnectorConnecting(mtxConnector: mMtxConnector) &&
487 (al::isNerve(user: this, nerve: &NrvCoin.Appear) || al::isNerve(user: this, nerve: &NrvCoin.WaitConnectMtx))) {
488 al::connectPoseQT(actor: this, mtxConnector: mMtxConnector, quat: mPoseQuat, trans: mPoseTrans);
489 al::getTransPtr(actor: this)->add(a: mChameleonOffset);
490 } else {
491 al::setQuat(actor: this, quat: mStartingQuat);
492 }
493
494 al::rotateQuatYDirDegree(actor: this, deg: mRotateCalculator->getRotate());
495}
496
497void Coin::appearBlowUpCommon(s32 delayTime, f32 horizontalForce, f32 verticalForce, s32 coinCount,
498 s32 coinIndex) {
499 appear();
500 al::invalidateClipping(actor: this);
501 mCountUpDelay = delayTime;
502 sead::Vector3f horizontalDirection = horizontalForce * sead::Vector3f::ez;
503 al::rotateVectorDegreeY(&horizontalDirection, (360.0 / coinCount) * coinIndex);
504 mBlowVelocity = verticalForce * sead::Vector3f::ey + horizontalDirection;
505 al::setVelocityZero(this);
506 if (mCountUpDelay == 0) {
507 appearBlow(velocity: mBlowVelocity, timeLimit: 480);
508 } else {
509 al::hideModelIfShow(actor: this);
510 al::setNerve(user: this, nerve: &BlowUpDelay);
511 }
512}
513
514void Coin::appearBlowUp(s32 coinIndex, s32 coinCount) {
515 appearBlowUpCommon(delayTime: coinIndex * 5, horizontalForce: 6.0f, verticalForce: 40.0f, coinCount, coinIndex);
516}
517
518void Coin::appearBlowUpLittle(s32 coinIndex, s32 coinCount) {
519 appearBlowUpCommon(delayTime: coinIndex * 5, horizontalForce: 3.0f, verticalForce: 35.0f, coinCount, coinIndex);
520}
521
522void Coin::appearFall(const sead::Vector3f& velocity, s32 timeLimit) {
523 appear();
524 al::invalidateClipping(actor: this);
525 al::setVelocity(actor: this, vel: velocity);
526 mTimeLimit = timeLimit;
527 al::setNerve(user: this, nerve: &NrvCoin.PopUpNormal);
528}
529
530void Coin::appearPlayerDead(const sead::Vector3f& position, const sead::Vector3f& direction,
531 bool isInWater, bool isTimeBalloon) {
532 al::resetPosition(actor: this, trans: position);
533
534 if (isInWater)
535 al::setVelocity(actor: this, vel: 30.0f * sead::Vector3f::ey + direction * 15.0f);
536 else if (rs::isInLowGravityArea(actor: this))
537 al::setVelocity(actor: this, vel: 30.0f * sead::Vector3f::ey + direction * 5.0f);
538 else
539 al::setVelocity(actor: this, vel: 40.0f * sead::Vector3f::ey + direction * 5.0f);
540
541 al::onCollide(actor: this);
542 appear();
543 al::invalidateClipping(actor: this);
544 al::startHitReaction(actor: this, name: "飛出し出現[死亡]");
545 mTimeLimit = -1;
546
547 if (isTimeBalloon)
548 al::setNerve(user: this, nerve: &NrvCoin.PopUpPlayerDeadTimeBalloon);
549 else
550 al::setNerve(user: this, nerve: &NrvCoin.PopUpPlayerDead);
551}
552
553void Coin::appearPlayerDeadReplace(const sead::Vector3f& position) {
554 al::setNerve(user: this, nerve: &NrvCoin.WaitPlayerDead);
555 al::resetPosition(actor: this, trans: position);
556 al::LiveActor::appear();
557}
558
559void Coin::setShadowDropLength(f32 shadowLength) {
560 al::setShadowMaskDropLength(actor: this, dropLength: shadowLength, maskName: "本体");
561 al::expandClippingRadiusByShadowLength(actor: this, nullptr, radius: shadowLength);
562}
563
564void Coin::get() {
565 al::startHitReaction(actor: this, name: "取得");
566 al::setNerve(user: this, nerve: &NrvCoin.Got);
567}
568
569bool Coin::isGot() const {
570 return al::isNerve(user: this, nerve: &NrvCoin.Got);
571}
572
573bool Coin::isGotOrRotate() const {
574 if (al::isNerve(user: this, nerve: &NrvCoin.Got))
575 return true;
576 if (al::isNerve(user: this, nerve: &NrvCoin.AppearCoinLead))
577 return true;
578 return false;
579}
580
581bool Coin::isWait() const {
582 if (al::isNerve(user: this, nerve: &NrvCoin.Wait))
583 return true;
584 if (al::isNerve(user: this, nerve: &NrvCoin.WaitCircle))
585 return true;
586 return false;
587}
588
589bool Coin::isAnyWaitOrAppear() const {
590 if (al::isNerve(user: this, nerve: &NrvCoin.Wait))
591 return true;
592 if (al::isNerve(user: this, nerve: &NrvCoin.WaitCircle))
593 return true;
594 if (al::isNerve(user: this, nerve: &NrvCoin.WaitCoinRail))
595 return true;
596 if (al::isNerve(user: this, nerve: &NrvCoin.WaitConnectMtx))
597 return true;
598 if (al::isNerve(user: this, nerve: &NrvCoin.WaitOnDemo))
599 return true;
600 if (al::isNerve(user: this, nerve: &NrvCoin.WaitOnDemoEnd))
601 return true;
602 if (al::isNerve(user: this, nerve: &NrvCoin.WaitPlayerDead))
603 return true;
604 if (al::isNerve(user: this, nerve: &NrvCoin.PopUpNormal) && al::isGreaterEqualStep(user: this, step: 60))
605 return true;
606 if (al::isNerve(user: this, nerve: &NrvCoin.AppearCoinLead))
607 return true;
608 if (al::isNerve(user: this, nerve: &NrvCoin.Appear))
609 return true;
610 return false;
611}
612
613void Coin::exeAppear() {
614 f32 rotateOffset = al::calcNerveEaseOutValue(user: this, max: 54, start: -720.0f, end: 0.0f);
615 mRotateCalculator->setRotateOffset(rotateOffset);
616 rotate();
617
618 if (al::isNerve(user: this, nerve: &NrvCoin.Appear)) {
619 if (al::isFirstStep(user: this))
620 al::startHitReaction(actor: this, name: "通常出現");
621 al::setNerveAtGreaterEqualStep(user: this, nerve: &NrvCoin.Wait, step: 54);
622 return;
623 }
624
625 if (al::isNerve(user: this, nerve: &NrvCoin.AppearOnDemo))
626 al::setNerveAtGreaterEqualStep(user: this, nerve: &NrvCoin.WaitOnDemo, step: 54);
627}
628
629void Coin::exeAppearCoinLead() {
630 if (al::updateNerveState(user: this))
631 al::setNerve(user: this, nerve: &NrvCoin.Wait);
632}
633
634void Coin::exePopUp() {
635 if (al::isFirstStep(user: this)) {
636 al::invalidateClipping(actor: this);
637 al::onCollide(actor: this);
638 al::validateHitSensors(this);
639 }
640
641 rotate();
642
643 if (al::isInWater(this) || al::isInWaterArea(actor: this)) {
644 al::addVelocityToGravity(actor: this, force: 0.6f);
645 al::scaleVelocity(actor: this, factor: 0.95f);
646 } else {
647 if (rs::isInLowGravityArea(actor: this))
648 al::addVelocityToGravity(actor: this, force: 1.0f);
649 else
650 al::addVelocityToGravity(actor: this, force: 1.5f);
651 al::scaleVelocity(actor: this, factor: 0.99f);
652 }
653
654 if ((al::isCollidedFloorCode(this, "Poison") || al::isCollidedFloorCode(this, "DamageFire") ||
655 al::isInDeathArea(actor: this)) &&
656 al::getVelocity(actor: this).y < 0.0f) {
657 al::startHitReaction(actor: this, name: "消滅");
658 kill();
659 return;
660 }
661
662 if ((al::isNerve(user: this, nerve: &NrvCoin.PopUpPlayerDead) ||
663 al::isNerve(user: this, nerve: &NrvCoin.PopUpPlayerDeadTimeBalloon)) &&
664 al::isGreaterEqualStep(user: this, step: 5)) {
665 sead::Vector3f surfacePos = sead::Vector3f::zero;
666 sead::Vector3f surfaceNormal = sead::Vector3f::zero;
667 if (al::calcFindFireSurface(&surfacePos, &surfaceNormal, this,
668 al::getTrans(actor: this) + 50.0f * sead::Vector3f::ey,
669 sead::Vector3f::ey, 50.0f)) {
670 al::startHitReaction(actor: this, name: "消滅");
671 kill();
672 return;
673 }
674 }
675
676 if (al::isNerve(user: this, nerve: &NrvCoin.PopUpPlayerDeadTimeBalloon) &&
677 al::isGreaterEqualStep(user: this, step: 60)) {
678 al::startHitReaction(actor: this, name: "消滅");
679 kill();
680 return;
681 }
682
683 if (al::getVelocity(actor: this).y < 0.0f && al::isCollidedGround(this)) {
684 if (al::calcSpeed(actor: this) < 15.0f) {
685 if (mMtxConnector != nullptr) {
686 al::disconnectMtxConnector(mtxConnector: mMtxConnector);
687 mPoseTrans = al::getTrans(actor: this);
688 al::attachMtxConnectorToCollision(mtxConnector: mMtxConnector, actor: this, false);
689 }
690 al::offCollide(actor: this);
691 al::setVelocityZero(this);
692 al::setNerve(user: this, nerve: &NrvCoin.WaitConnectMtx);
693 return;
694 }
695 al::getVelocityPtr(actor: this)->y *= -0.75f;
696 al::startSeWithParam(this, "PgBound", al::getVelocityPtr(actor: this)->y, "");
697 }
698}
699
700void Coin::exeCountUpDelay() {
701 if (al::isGreaterEqualStep(user: this, step: mCountUpDelay)) {
702 al::setVelocity(actor: this, vel: mBlowVelocity);
703 al::setNerve(user: this, nerve: &NrvCoin.CountUp);
704 }
705}
706
707void Coin::exeCountUp() {
708 if (al::isFirstStep(user: this)) {
709 al::startAction(actor: this, actionName: "Got");
710 if (al::isNerve(user: this, nerve: &NrvCoin.CountUpFive))
711 al::startHitReaction(actor: this, name: "自動取得5枚[開始]");
712 else
713 al::startHitReaction(actor: this, name: "自動取得[開始]");
714
715 if (al::isNerve(user: this, nerve: &NrvCoin.CountUpFive))
716 for (u32 i = 0; i < 5; i++)
717 GameDataFunction::addCoin(writer: this, count: 1);
718 else
719 GameDataFunction::addCoin(writer: this, count: 1);
720 al::showModelIfHide(actor: this);
721 }
722
723 rotate();
724
725 sead::Vector3f direction = sead::Vector3f(0.0f, 0.0f, 0.0f);
726 al::calcQuatGravity(out: &direction, quat: al::getQuat(actor: this));
727 al::addVelocityToDirection(actor: this, dir: direction, force: 0.6f);
728 al::scaleVelocity(actor: this, factor: 0.99f);
729 al::getVelocity(actor: this);
730 al::getQuat(actor: this);
731
732 if (al::isGreaterEqualStep(user: this, step: 32)) {
733 al::startHitReaction(actor: this, name: "自動取得[終了]");
734 al::setNerve(user: this, nerve: &NrvCoin.GotNoCoin);
735 }
736}
737
738void Coin::exeWait() {
739 if (al::isFirstStep(user: this)) {
740 if (mTimeLimit == -1 && !al::isNerve(user: this, nerve: &NrvCoin.WaitCoinRail) &&
741 !al::isNerve(user: this, nerve: &NrvCoin.WaitCircle))
742 al::validateClipping(actor: this);
743 al::startAction(actor: this, actionName: "Wait");
744 }
745
746 if (al::isNerve(user: this, nerve: &NrvCoin.WaitOnDemo)) {
747 mRotateCalculator->increaseObjCountOffset();
748 rotate();
749 if (!rs::isActiveDemo(this))
750 al::setNerve(user: this, nerve: &NrvCoin.WaitOnDemoEnd);
751 return;
752 }
753
754 rotate();
755}
756
757void Coin::exeWaitOnDemoEnd() {
758 rotate();
759}
760
761void Coin::exeGot() {
762 if (al::isFirstStep(user: this)) {
763 al::startAction(actor: this, actionName: "Got");
764 if (!al::isNerve(user: this, nerve: &NrvCoin.GotNoCoin))
765 GameDataFunction::addCoin(writer: this, count: 1);
766
767 al::onStageSwitch(user: this, linkName: "SwitchGetOn");
768 alPadRumbleFunction::startPadRumble(actor: this, name: "コッ(最小)", near: 1000.0f, far: 5000.0f);
769 if (mWaterSurfaceShadow != nullptr)
770 mWaterSurfaceShadow->disappearShadow();
771 }
772
773 if (al::isActionEnd(actor: this)) {
774 if (mSaveObjInfo != nullptr)
775 rs::onSaveObjInfo(saveObjInfo: mSaveObjInfo);
776 kill();
777 }
778}
779
780void Coin::exeBlowUpDelay() {
781 if (al::isGreaterEqualStep(user: this, step: mCountUpDelay)) {
782 al::showModelIfHide(actor: this);
783 appearBlow(velocity: mBlowVelocity, timeLimit: 480);
784 }
785}
786
787const sead::Vector3f& CoinFunction::getAppearAboveVelocity() {
788 return sAppearAboveVelocity;
789}
790
791void CoinFunction::appearCoinBlowVeryLittle(Coin* coin, const sead::Vector3f& direction) {
792 bool isInWater = al::isInWater(coin) || al::isInWaterArea(actor: coin);
793
794 sead::Vector3f velocity = (isInWater ? 12.0f : 3.5f) * direction;
795 sead::Vector3f verticalVelocity = (isInWater ? 35.0f : 30.0f) * sead::Vector3f::ey;
796 velocity += verticalVelocity;
797
798 coin->appearBlow(velocity, timeLimit: 600);
799}
800
801void CoinFunction::appearCoinBlowLittle(Coin* coin, const sead::Vector3f& direction) {
802 bool isInWater = al::isInWater(coin) || al::isInWaterArea(actor: coin);
803
804 sead::Vector3f velocity = (isInWater ? 15.0f : 4.5f) * direction;
805 sead::Vector3f verticalVelocity = (isInWater ? 40.0f : 30.0f) * sead::Vector3f::ey;
806 velocity += verticalVelocity;
807
808 coin->appearBlow(velocity, timeLimit: 600);
809}
810
811void CoinFunction::appearCoinBlowMiddle(Coin* coin, const sead::Vector3f& direction) {
812 bool isInWater = al::isInWater(coin) || al::isInWaterArea(actor: coin);
813
814 sead::Vector3f velocity = (isInWater ? 18.0f : 6.0f) * direction;
815 sead::Vector3f verticalVelocity = (isInWater ? 45.0f : 35.0f) * sead::Vector3f::ey;
816 velocity += verticalVelocity;
817
818 coin->appearBlow(velocity, timeLimit: 600);
819}
820
821void CoinFunction::appearCoinBlowLarge(Coin* coin, const sead::Vector3f& direction) {
822 bool isInWater = al::isInWater(coin) || al::isInWaterArea(actor: coin);
823
824 sead::Vector3f velocity = (isInWater ? 23.0f : 7.5f) * direction;
825 sead::Vector3f verticalVelocity = (isInWater ? 50.0f : 40.0f) * sead::Vector3f::ey;
826 velocity += verticalVelocity;
827
828 coin->appearBlow(velocity, timeLimit: 600);
829}
830
831void CoinFunction::appearCoinBlowLargeEnemy(Coin* coin, const sead::Vector3f& direction) {
832 sead::Vector3f velocity = 3.5f * direction;
833 sead::Vector3f verticalVelocity = 30.0f * sead::Vector3f::ey;
834 velocity += verticalVelocity;
835
836 coin->appearBlow(velocity, timeLimit: 1200);
837}
838
839void CoinFunction::appearCoinBlowBoss(Coin* coin, const sead::Vector3f& direction) {
840 sead::Vector3f velocity = 8.0f * direction;
841 sead::Vector3f verticalVelocity = 40.0f * sead::Vector3f::ey;
842 velocity += verticalVelocity;
843
844 coin->appearBlow(velocity, timeLimit: 1200);
845}
846
847void CoinFunction::appearCoinBlowGk(Coin* coin, const sead::Vector3f& direction) {
848 sead::Vector3f velocity = 7.0f * direction;
849 sead::Vector3f verticalVelocity = 40.0f * sead::Vector3f::ey;
850 velocity += verticalVelocity;
851
852 coin->appearBlow(velocity, timeLimit: 1200);
853}
854