1#include "Enemy/Pecho.h"
2
3#include <math/seadVectorCalcCommon.h>
4
5#include "Library/Collision/CollisionPartsKeeperUtil.h"
6#include "Library/Item/ItemUtil.h"
7#include "Library/Joint/JointControllerKeeper.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/ActorInitFunction.h"
14#include "Library/LiveActor/ActorInitInfo.h"
15#include "Library/LiveActor/ActorInitUtil.h"
16#include "Library/LiveActor/ActorModelFunction.h"
17#include "Library/LiveActor/ActorMovementFunction.h"
18#include "Library/LiveActor/ActorPoseUtil.h"
19#include "Library/LiveActor/ActorSensorUtil.h"
20#include "Library/Math/MathUtil.h"
21#include "Library/Movement/AnimScaleController.h"
22#include "Library/Nerve/NerveSetupUtil.h"
23#include "Library/Nerve/NerveUtil.h"
24#include "Library/Player/PlayerUtil.h"
25
26#include "Util/ItemUtil.h"
27#include "Util/SensorMsgFunction.h"
28
29namespace {
30NERVE_IMPL(Pecho, Appear);
31NERVE_IMPL(Pecho, AttackSuccess);
32NERVE_IMPL(Pecho, Wait);
33NERVE_IMPL(Pecho, Find);
34NERVE_IMPL(Pecho, Move);
35NERVE_IMPL(Pecho, LiquidSign);
36NERVE_IMPL(Pecho, LiquidStart);
37NERVE_IMPL(Pecho, Liquid);
38NERVE_IMPL(Pecho, LiquidEnd);
39NERVE_IMPL(Pecho, Reset);
40
41NERVES_MAKE_NOSTRUCT(Pecho, LiquidEnd, Reset);
42NERVES_MAKE_STRUCT(Pecho, Wait, LiquidStart, Liquid, AttackSuccess, LiquidSign, Move, Appear, Find);
43} // namespace
44
45const al::AnimScaleParam gAnimScaleParam = al::AnimScaleParam();
46
47Pecho::Pecho(const char* name) : al::LiveActor(name) {}
48
49void Pecho::init(const al::ActorInitInfo& initInfo) {
50 al::initActor(actor: this, initInfo);
51 mStartingQuat = al::getQuat(actor: this);
52 mStartingTrans = al::getTrans(actor: this);
53 mBodyOrientation = al::getQuat(actor: this);
54
55 al::initJointControllerKeeper(this, 3);
56 al::initJointGlobalQuatController(this, &mBodyOrientation, "BodyRotate");
57 al::createAndSetColliderSpecialPurpose(this, "MoveLimit");
58 al::setAppearItemOffset(actor: this, offset: sead::Vector3f::ey * 150.0f);
59
60 mAnimScaleController = new al::AnimScaleController(&gAnimScaleParam);
61
62 al::invalidateCollisionParts(this);
63 al::initNerve(actor: this, nerve: &NrvPecho.Wait, maxStates: 0);
64 al::trySyncStageSwitchAppear(actor: this);
65}
66
67bool Pecho::receiveMsg(const al::SensorMsg* message, al::HitSensor* other, al::HitSensor* self) {
68 if (al::isMsgPlayerDisregard(msg: message))
69 return !isEnableCap();
70
71 if (rs::isMsgBubbleAttackToPecho(message) && isEnablePush()) {
72 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
73 startLiquidFast();
74 return true;
75 }
76
77 if (al::isMsgEnemyAttackNeedle(msg: message) && isEnablePush()) {
78 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
79 startLiquid();
80 return true;
81 }
82
83 if (rs::isMsgNpcScareByEnemy(message))
84 return true;
85
86 if (isEnablePush() && al::isSensorEnemyBody(self) &&
87 al::tryReceiveMsgPushAndAddVelocity(this, message, other, self, 3.0f)) {
88 mAnimScaleController->startAndSetScaleVelocityY(0.13f);
89 al::startHitReactionHitEffect(actor: this, name: "接触反射", other, self);
90 mVelocity.set(al::getVelocity(actor: this));
91 mVelocity.y = 0;
92 al::limitLength(out: &mVelocity, vec: mVelocity, limit: 1.0f);
93 return true;
94 }
95
96 if ((rs::isMsgCapAttack(message) || rs::isMsgBlowDown(message)) &&
97 al::isSensorEnemyBody(self) && isEnablePush()) {
98 al::startHitReaction(actor: this, name: "帽子ヒット");
99 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
100 rs::requestHitReactionToAttacker(message, self, other);
101 startLiquidCap();
102 return true;
103 }
104
105 if (rs::isMsgPlayerAndCapObjHipDropReflectAll(message) && isEnablePush()) {
106 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
107 startLiquid();
108 return true;
109 }
110
111 if (rs::isMsgPressDown(message) && al::isSensorEnemyBody(self) && isEnablePush()) {
112 al::startHitReaction(actor: this, name: "踏まれ");
113 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
114 rs::requestHitReactionToAttacker(message, self, other);
115 startLiquid();
116 return true;
117 }
118
119 if (rs::isMsgBubbleGroundTouchTrigger(message) && al::isNerve(user: this, nerve: &NrvPecho.Liquid)) {
120 mIsBubbleReaction = true;
121 al::startAction(actor: this, actionName: "LiquidBubbleReaction");
122 return true;
123 }
124
125 return false;
126}
127
128void Pecho::attackSensor(al::HitSensor* self, al::HitSensor* other) {
129 if (isEnablePush() && al::isSensorEnemyBody(self) && al::isSensorEnemyBody(other))
130 al::sendMsgPushAndKillVelocityToTarget(this, self, other);
131
132 if (isEnableAttack() && al::isSensorEnemyAttack(self)) {
133 if (isEnableSendPechoSpot()) {
134 sead::Vector3f upDir;
135 al::calcUpDir(up: &upDir, actor: this);
136 if (al::calcDistanceV(upDir, self, other) < al::getSensorRadius(other) + 20.0f)
137 al::sendMsgEnemyAttackFire(receiver: other, sender: self, "LavaRed");
138 return;
139 }
140
141 if (!al::sendMsgEnemyAttack(receiver: other, sender: self)) {
142 rs::sendMsgPushToPlayer(source: other, target: self);
143 return;
144 }
145
146 if (al::pushAndAddVelocity(this, other, self, 15.0f)) {
147 mAnimScaleController->startAndSetScaleVelocityY(0.13f);
148 al::startHitReactionHitEffect(actor: this, name: "攻撃ヒット", other, self);
149 al::setNerve(user: this, nerve: &NrvPecho.AttackSuccess);
150 return;
151 }
152 }
153}
154
155void Pecho::control() {
156 if (al::isInDeathArea(actor: this) || al::isCollidedFloorCode(this, "DamageFire") ||
157 al::isCollidedFloorCode(this, "Needle") || al::isCollidedFloorCode(this, "Poison")) {
158 al::invalidateClipping(actor: this);
159 al::setVelocityZero(this);
160 al::setNerve(user: this, nerve: &Reset);
161 return;
162 }
163
164 mAnimScaleController->update();
165 al::setScale(actor: this, scale: mAnimScaleController->getScale());
166}
167
168void Pecho::startLiquidFast() {
169 al::appearItem(actor: this);
170 al::invalidateClipping(actor: this);
171 al::setVelocityZero(this);
172 al::setNerve(user: this, nerve: &NrvPecho.LiquidSign);
173 mIsStartLiquidFast = true;
174 mLiquidStartAction = "LiquidStartFast";
175 al::setSensorRadius(this, "BodyLiquid", 290.0);
176 al::setSensorRadius(this, "AttackLiquid", 250.0);
177}
178
179void Pecho::startLiquid() {
180 al::appearItem(actor: this);
181 al::invalidateClipping(actor: this);
182 al::setVelocityZero(this);
183 al::setNerve(user: this, nerve: &NrvPecho.LiquidSign);
184 mIsStartLiquidFast = false;
185 mLiquidStartAction = "LiquidStart";
186}
187
188void Pecho::startLiquidCap() {
189 al::appearItem(actor: this);
190 al::invalidateClipping(actor: this);
191 al::setVelocityZero(this);
192 al::setNerve(user: this, nerve: &NrvPecho.LiquidSign);
193 mIsStartLiquidFast = false;
194 mLiquidStartAction = "LiquidStartCap";
195}
196
197void Pecho::updateVelocity() {
198 if (al::reboundVelocityFromCollision(actor: this, reboundStrength: 0.75f, reboundMin: 5.0f, friction: 1.0f)) {
199 f32 speedV = sead::Mathf::abs(x: al::calcSpeedV(actor: this));
200 f32 lerpValue = al::lerpValue(speedV, 5.0f, 30.0f, 0.87f, 0.75f);
201 al::startHitReaction(actor: this, name: "コリジョン反射");
202 mAnimScaleController->startAndSetScaleY(lerpValue);
203 }
204
205 if (al::isOnGroundNoVelocity(this, 0))
206 al::addVelocityToGravityFittedGround(actor: this, force: 1.0f, maxAirTime: 0);
207 else
208 al::addVelocityToGravity(actor: this, force: 1.0f);
209
210 f32 scale = al::isOnGroundNoVelocity(this, 4) ? 0.9f : 0.98f;
211 al::scaleVelocityHV(actor: this, factorH: scale, factorV: 0.98f);
212}
213
214bool Pecho::tryStartFind() {
215 if (!al::isNearPlayer(this, 800.0f))
216 return false;
217
218 al::invalidateClipping(actor: this);
219 sead::Vector3f dirH;
220 if (al::calcDirH(outVec: &dirH, vecA: al::getTrans(actor: this), vecB: al::getPlayerPos(this, 0)))
221 mNextBodyOrientation = mStartingQuat;
222 else
223 al::makeQuatUpFront(outQuat: &mNextBodyOrientation, up: sead::Vector3f::ey, front: dirH);
224
225 al::setNerve(user: this, nerve: &NrvPecho.Find);
226 return true;
227}
228
229// NON_MATCHING: regswap (https://decomp.me/scratch/D3wWz)
230void Pecho::updateVelocityEscapeWallAndFall(f32 force, f32 velocity) {
231 sead::Vector3f direction = sead::Vector3f::zero;
232 s32 hitCount = 0;
233
234 for (s32 i = 0; i < 8; i++) {
235 f32 rad = sead::Mathf::deg2rad(deg: i * 45.0f);
236 sead::Vector3f arrowDirection(sead::Mathf::sin(t: rad), 0.0f, sead::Mathf::cos(t: rad));
237
238 arrowDirection.rotate(q: al::getQuat(actor: this));
239
240 if (alCollisionUtil::checkStrikeArrow(this, al::getTrans(actor: this) + 20.0f * sead::Vector3f::ey,
241 arrowDirection * force, nullptr, nullptr) != 0) {
242 direction += arrowDirection;
243 hitCount++;
244 } else if (alCollisionUtil::checkStrikeArrow(
245 this,
246 al::getTrans(actor: this) + 20.0f * sead::Vector3f::ey + arrowDirection * force,
247 -70.0f * sead::Vector3f::ey, nullptr, nullptr) == 0) {
248 direction += arrowDirection;
249 hitCount++;
250 }
251 }
252
253 if (hitCount > 0)
254 al::addVelocity(actor: this, vel: direction * -velocity * (1.0f / hitCount));
255
256 if (al::isOnGroundNoVelocity(this, 0))
257 al::addVelocityToGravityFittedGround(actor: this, force: 1.0f, maxAirTime: 0);
258 else
259 al::addVelocityToGravity(actor: this, force: 1.0f);
260
261 al::scaleVelocity(actor: this, factor: 0.89f);
262}
263
264bool Pecho::isEnablePush() const {
265 return al::isNerve(user: this, nerve: &NrvPecho.Wait) || al::isNerve(user: this, nerve: &NrvPecho.Find) ||
266 al::isNerve(user: this, nerve: &NrvPecho.AttackSuccess) || al::isNerve(user: this, nerve: &NrvPecho.Move);
267}
268
269bool Pecho::isEnableAttack() const {
270 return al::isNerve(user: this, nerve: &NrvPecho.Wait) || al::isNerve(user: this, nerve: &NrvPecho.Find) ||
271 al::isNerve(user: this, nerve: &NrvPecho.Move) || al::isNerve(user: this, nerve: &NrvPecho.AttackSuccess) ||
272 al::isNerve(user: this, nerve: &NrvPecho.LiquidStart) || al::isNerve(user: this, nerve: &NrvPecho.Liquid);
273}
274
275bool Pecho::isEnableCap() const {
276 return al::isNerve(user: this, nerve: &NrvPecho.Appear) || al::isNerve(user: this, nerve: &NrvPecho.AttackSuccess) ||
277 al::isNerve(user: this, nerve: &NrvPecho.Wait) || al::isNerve(user: this, nerve: &NrvPecho.Find) ||
278 al::isNerve(user: this, nerve: &NrvPecho.Move);
279}
280
281bool Pecho::isEnableSendPechoSpot() const {
282 return al::isNerve(user: this, nerve: &NrvPecho.LiquidStart) || al::isNerve(user: this, nerve: &NrvPecho.Liquid);
283}
284
285void Pecho::exeAppear() {
286 if (al::isFirstStep(user: this)) {
287 al::startAction(actor: this, actionName: "Appear");
288 al::showModelIfHide(actor: this);
289 al::validateClipping(actor: this);
290 al::onCollide(actor: this);
291 }
292
293 if (al::isActionEnd(actor: this))
294 al::setNerve(user: this, nerve: &NrvPecho.Wait);
295}
296
297void Pecho::exeAttackSuccess() {
298 if (al::isFirstStep(user: this))
299 al::startAction(actor: this, actionName: "Wait");
300
301 updateVelocity();
302 al::calcMomentRollBall(&mMoment, al::getVelocity(actor: this), -al::getGravity(actor: this), 100.0f);
303 al::rotateQuatMoment(&mBodyOrientation, mBodyOrientation, mMoment);
304
305 if (al::isGreaterStep(user: this, step: 60))
306 al::setNerve(user: this, nerve: &NrvPecho.Wait);
307}
308
309void Pecho::exeWait() {
310 if (al::isFirstStep(user: this)) {
311 al::tryStartActionIfNotPlaying(actor: this, actionName: "Wait");
312 al::getRandomDirH(&mVelocity, sead::Vector3f::ey);
313 mWaitTrans.set(al::getTrans(actor: this));
314 mIsWaitTiltCounterClockwise = al::isHalfProbability();
315 }
316
317 if (al::isLessEqualStep(user: this, step: 120)) {
318 sead::Vector3f vel = sead::Vector3f::zero;
319 f32 degree =
320 al::calcNerveValue(user: this, max: 120, start: 0.0, end: mIsWaitTiltCounterClockwise ? 720.0f : -720.0f);
321 al::rotateVectorDegree(&vel, mVelocity, sead::Vector3f::ey, degree);
322 al::addVelocity(actor: this, vel: vel * 0.25f);
323 } else {
324 al::addVelocityDampToTarget(actor: this, target: mWaitTrans, force: 0.03f, damp: 0.1f);
325 }
326
327 updateVelocity();
328 al::calcMomentRollBall(&mMoment, al::getVelocity(actor: this), -al::getGravity(actor: this), 100.0f);
329 al::rotateQuatMoment(&mBodyOrientation, mBodyOrientation, mMoment);
330
331 if (al::isGreaterEqualStep(user: this, step: 80))
332 al::turnQuatYDirRadian(&mBodyOrientation, mBodyOrientation, sead::Vector3f::ey,
333 sead::Mathf::deg2rad(deg: 0.3f));
334
335 if (!tryStartFind() && al::isGreaterEqualStep(user: this, step: 140))
336 al::setNerve(user: this, nerve: &NrvPecho.Wait);
337}
338
339void Pecho::exeFind() {
340 if (al::isFirstStep(user: this))
341 al::startAction(actor: this, actionName: "Find");
342
343 al::slerpQuat(&mBodyOrientation, mBodyOrientation, mNextBodyOrientation, 0.2f);
344 updateVelocity();
345
346 if (al::isActionEnd(actor: this))
347 al::setNerve(user: this, nerve: &NrvPecho.Move);
348}
349
350void Pecho::exeMove() {
351 if (al::isFirstStep(user: this)) {
352 al::startAction(actor: this, actionName: "Move");
353 mVelocity.set(sead::Vector3f::zero);
354 }
355
356 if (al::isOnGroundNoVelocity(this, 4)) {
357 sead::Vector3f dirToActor;
358 al::calcDirToActorH(out: &dirToActor, actor: this, target: al::getPlayerActor(this, 0));
359 mVelocity += dirToActor * 0.04f;
360 al::limitLength(out: &mVelocity, vec: mVelocity, limit: 1.0f);
361 al::addVelocity(actor: this, vel: mVelocity);
362 }
363
364 updateVelocity();
365 al::calcMomentRollBall(&mMoment, al::getVelocity(actor: this), -al::getGravity(actor: this), 100.0f);
366 al::rotateQuatMoment(&mBodyOrientation, mBodyOrientation, mMoment);
367 if (!al::isNearPlayer(this, 1500.0f))
368 al::setNerve(user: this, nerve: &NrvPecho.Wait);
369}
370
371void Pecho::exeLiquidSign() {
372 if (al::isFirstStep(user: this)) {
373 mAnimScaleController->stopAndReset();
374 al::setVelocityZeroH(this);
375 }
376 al::addVelocityToGravity(actor: this, force: 1.0);
377
378 if (al::isOnGround(this, 0)) {
379 mAnimScaleController->stopAndReset();
380 sead::Quatf* quat = al::getQuatPtr(actor: this);
381 al::turnQuatYDirRate(quat, *quat, al::getOnGroundNormal(this, 0), 1.0f);
382 if (!mIsStartLiquidFast) {
383 al::setSensorRadius(this, "BodyLiquid", 0.0f);
384 al::setSensorRadius(this, "AttackLiquid", 0.0f);
385 }
386 al::setVelocityZero(this);
387 al::setNerve(user: this, nerve: &NrvPecho.LiquidStart);
388 }
389}
390
391void Pecho::exeLiquidStart() {
392 if (al::isFirstStep(user: this)) {
393 al::startAction(actor: this, actionName: mLiquidStartAction);
394 al::rotateQuatYDirRandomDegree(actor: this);
395 al::validateCollisionParts(this);
396 }
397
398 sead::Vector3f jointScale;
399 al::calcJointScale(&jointScale, actor: this, "JointRoot");
400 if (!mIsStartLiquidFast) {
401 al::setSensorRadius(this, "BodyLiquid", jointScale.x * 290.0f);
402 f32 attackLiquidRadius = mIsStartLiquidFast ? 250.0f : jointScale.x * 250.0f;
403 al::setSensorRadius(this, "AttackLiquid", attackLiquidRadius);
404 }
405
406 updateVelocityEscapeWallAndFall(force: jointScale.x * 150.0f,
407 velocity: al::calcNerveValue(user: this, min: 40, max: 80, start: 1.0f, end: 0.25f));
408
409 if (al::isActionEnd(actor: this))
410 al::setNerve(user: this, nerve: &NrvPecho.Liquid);
411}
412
413void Pecho::exeLiquid() {
414 if (al::isFirstStep(user: this)) {
415 al::startAction(actor: this, actionName: "Liquid");
416 mIsBubbleReaction = false;
417 }
418
419 if (al::isLessStep(user: this, step: 30))
420 updateVelocityEscapeWallAndFall(force: 150.0f, velocity: al::calcNerveValue(user: this, max: 30, start: 0.25f, end: 0.0f));
421 else
422 al::setVelocityZero(this);
423
424 if (mIsBubbleReaction && al::isActionEnd(actor: this)) {
425 al::startAction(actor: this, actionName: "Liquid");
426 mIsBubbleReaction = false;
427 }
428
429 if (al::isGreaterEqualStep(user: this, step: 600) && !al::isNearPlayer(this, 3000.0f))
430 al::setNerve(user: this, nerve: &LiquidEnd);
431}
432
433void Pecho::exeLiquidEnd() {
434 if (al::isFirstStep(user: this)) {
435 al::startAction(actor: this, actionName: "LiquidEnd");
436 al::invalidateCollisionParts(this);
437 }
438
439 sead::Vector3f jointScale;
440 al::calcJointScale(&jointScale, actor: this, "JointRoot");
441 al::setSensorRadius(this, "BodyLiquid", jointScale.x * 290.0f);
442 al::setSensorRadius(this, "AttackLiquid", jointScale.x * 250.0f);
443
444 if (al::isActionEnd(actor: this))
445 al::setNerve(user: this, nerve: &Reset);
446}
447
448void Pecho::exeReset() {
449 if (al::isFirstStep(user: this)) {
450 al::startHitReaction(actor: this, name: "消滅");
451 al::startAction(actor: this, actionName: "Hide");
452 al::setVelocityZero(this);
453 al::offCollide(actor: this);
454 al::hideModelIfShow(actor: this);
455 al::resetQuatPosition(actor: this, quat: mStartingQuat, trans: mStartingTrans);
456 mBodyOrientation = mStartingQuat;
457 }
458
459 if (al::isGreaterEqualStep(user: this, step: 120))
460 al::setNerve(user: this, nerve: &NrvPecho.Appear);
461}
462