1#include "Enemy/Popn.h"
2
3#include <math/seadVector.h>
4
5#include "Library/Collision/KCollisionServer.h"
6#include "Library/Effect/EffectSystemInfo.h"
7#include "Library/Item/ItemUtil.h"
8#include "Library/Joint/JointControllerKeeper.h"
9#include "Library/LiveActor/ActorActionFunction.h"
10#include "Library/LiveActor/ActorAnimFunction.h"
11#include "Library/LiveActor/ActorAreaFunction.h"
12#include "Library/LiveActor/ActorClippingFunction.h"
13#include "Library/LiveActor/ActorCollisionFunction.h"
14#include "Library/LiveActor/ActorFlagFunction.h"
15#include "Library/LiveActor/ActorInitUtil.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/Movement/EnemyStateBlowDown.h"
21#include "Library/Nature/NatureUtil.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/PlayerUtil.h"
28#include "Util/SensorMsgFunction.h"
29
30namespace {
31NERVE_IMPL(Popn, Appear);
32NERVE_IMPL(Popn, BlowDown);
33NERVE_IMPL(Popn, Move);
34NERVE_IMPL(Popn, Wait);
35NERVE_IMPL(Popn, WaitRove);
36NERVE_IMPL(Popn, MoveStart);
37NERVE_IMPL(Popn, MoveEnd);
38NERVE_IMPL(Popn, Turn);
39
40NERVES_MAKE_NOSTRUCT(Popn, MoveEnd);
41NERVES_MAKE_STRUCT(Popn, Appear, BlowDown, Move, Wait, Turn, WaitRove, MoveStart);
42} // namespace
43
44const al::EnemyStateBlowDownParam sBlowDownParam("BlowDown", 15.0f, 30.0f, 1.0f, 0.997f, 20, 1);
45
46Popn::Popn(const char* actorName) : al::LiveActor(actorName) {}
47
48void Popn::init(const al::ActorInitInfo& info) {
49 al::initActorWithArchiveName(actor: this, initInfo: info, archiveName: mArchiveName, suffix: nullptr);
50 al::initNerve(actor: this, nerve: &NrvPopn.Appear, maxStates: 1);
51 mStateBlowDown = new al::EnemyStateBlowDown(this, &sBlowDownParam, "吹き飛び状態");
52 al::initNerveState(user: this, state: mStateBlowDown, nerve: &NrvPopn.BlowDown, hostName: "吹っ飛びやられ");
53
54 al::setColliderFilterCollisionParts(this,
55 new al::CollisionPartsFilterSpecialPurpose("MoveLimit"));
56 al::setColliderReactMovePower(this, 1);
57
58 al::initJointControllerKeeper(this, 1);
59 al::initJointGlobalQuatController(this, &mQuat, "AllRoot");
60
61 makeActorAlive();
62}
63
64void Popn::attackSensor(al::HitSensor* self, al::HitSensor* other) {
65 if (al::isNerve(user: this, nerve: &NrvPopn.BlowDown))
66 return;
67
68 if (al::isNerve(user: this, nerve: &NrvPopn.Appear) && al::isLessStep(user: this, step: 80))
69 return;
70
71 if (al::isSensorEnemyBody(self) && al::isSensorEnemyBody(other) &&
72 !al::isNerve(user: this, nerve: &NrvPopn.Move))
73 al::sendMsgPushAndKillVelocityToTarget(this, self, other);
74
75 if (al::isSensorEnemyAttack(self))
76 rs::sendMsgPushToPlayer(source: other, target: self) || al::sendMsgEnemyAttack(receiver: other, sender: self);
77}
78
79bool Popn::receiveMsg(const al::SensorMsg* message, al::HitSensor* other, al::HitSensor* self) {
80 if (rs::isMsgNpcScareByEnemy(message))
81 return true;
82 if (rs::isMsgKillByShineGet(message) || rs::isMsgKillByHomeDemo(message)) {
83 al::tryKillEmitterAndParticleAll(this);
84 kill();
85 return true;
86 }
87
88 // TODO: Fix the hacky match here
89 bool isDisregard = al::isMsgPlayerDisregard(msg: message);
90 auto* nrv = &NrvPopn.BlowDown;
91 __asm__("" ::[nrv] "r"(nrv));
92 if (isDisregard)
93 return al::isNerve(user: this, nerve: nrv);
94
95 __asm__("");
96 if (al::isNerve(user: this, nerve: nrv))
97 return false;
98
99 if (al::isSensorEnemyAttack(self))
100 return false;
101 if (al::isNerve(user: this, nerve: &NrvPopn.Appear) && al::isLessStep(user: this, step: 15))
102 return false;
103 if (al::tryReceiveMsgPushAndAddVelocityH(this, message, other, self, 2.0f))
104 return true;
105
106 if (rs::isMsgCapAttack(message) || rs::isMsgBlowDown(message) ||
107 rs::isMsgSeedAttackHold(message) || rs::isMsgWanwanEnemyAttack(message) ||
108 rs::isMsgTankExplosion(message) || rs::isMsgTankBullet(message)) {
109 al::tryDeleteEffect(this, "PopnAppear");
110 rs::requestHitReactionToAttacker(message, self, other);
111
112 if (mIsGenerateItem)
113 rs::setAppearItemFactorAndOffsetByMsg(actor: this, msg: message, sensor: other);
114
115 mStateBlowDown->start(other, self);
116 al::invalidateClipping(actor: this);
117 al::setNerve(user: this, nerve: &NrvPopn.BlowDown);
118
119 if (rs::isMsgTankBullet(message) || rs::isMsgSeedAttackHold(message) ||
120 rs::isMsgTankExplosion(message))
121 return true;
122
123 return al::isMsgKickStoneAttackHold(msg: message);
124 }
125
126 return false;
127}
128
129void Popn::control() {
130 sead::Vector3f frontDir;
131 al::calcFrontDir(front: &frontDir, actor: this);
132
133 sead::Vector3f groundNormal = sead::Vector3f::ey;
134 if (al::isOnGround(this, 0) && mIsFollowGroundNormal)
135 groundNormal.set(al::getCollidedGroundNormal(this));
136
137 sead::Vector3f up;
138 al::calcQuatUp(out: &up, quat: mQuat);
139 al::lerpVec(&up, up, groundNormal, 0.18f);
140
141 sead::Quatf upFront;
142 al::makeQuatUpFront(outQuat: &upFront, up, front: frontDir);
143 mQuat.set(upFront);
144
145 if (al::isInDeathArea(actor: this) || al::isCollidedFloorCode(this, "DamageFire") ||
146 al::isCollidedFloorCode(this, "Needle") || al::isCollidedFloorCode(this, "Poison") ||
147 al::isInWater(this)) {
148 al::startHitReaction(actor: this, name: "死亡");
149 kill();
150 }
151}
152
153void Popn::appearByGenerater(const sead::Vector3f& pos, s32 color, bool isAppear,
154 bool isGenerateItem) {
155 mIsGenerateItem = isGenerateItem;
156
157 al::resetPosition(actor: this, trans: pos);
158 al::setScale(actor: this, scale: sead::Vector3f::ones);
159
160 al::LiveActor* nearestPlayerActor = al::tryFindNearestPlayerActor(this);
161 if (nearestPlayerActor)
162 al::faceToTarget(actor: this, target: nearestPlayerActor);
163
164 if (isAppear) {
165 al::startAction(actor: this, actionName: "Appear");
166 al::setNerve(user: this, nerve: &NrvPopn.Appear);
167 } else {
168 al::startAction(actor: this, actionName: "Wait");
169 al::setNerve(user: this, nerve: &NrvPopn.Wait);
170 }
171
172 if (!mIsAngry) {
173 al::startMtpAnimAndSetFrameAndStop(this, "Color", color);
174 al::startMclAnimAndSetFrameAndStop(this, "Color", color);
175 }
176
177 appear();
178
179 if (mIsAngry) {
180 al::emitEffect(this, "RedEyes", nullptr);
181 mAwakeDistance = 4500.0f;
182 }
183}
184
185inline bool shouldAwake(f32 awakeDistance, al::LiveActor* popn) {
186 al::LiveActor* nearestPlayerActor = al::tryFindNearestPlayerActor(popn);
187 if (!nearestPlayerActor)
188 return false;
189 return al::calcDistance(actor: nearestPlayerActor, trans: al::getTrans(actor: popn)) < awakeDistance;
190}
191
192inline void applyInertia(al::LiveActor* popn) {
193 if (al::isOnGround(popn, 0)) {
194 al::scaleVelocity(actor: popn, factor: 0.8f);
195 al::addVelocityToGravityFittedGround(actor: popn, force: 0.55f, maxAirTime: 0);
196 } else {
197 al::scaleVelocityHV(actor: popn, factorH: 0.997f, factorV: 0.99f);
198 al::addVelocityToGravity(actor: popn, force: 0.55f);
199 }
200}
201
202void Popn::exeAppear() {
203 if (al::isFirstStep(user: this))
204 al::tryStartActionIfNotPlaying(actor: this, actionName: "Appear");
205
206 applyInertia(popn: this);
207
208 if (al::isActionEnd(actor: this)) {
209 if (shouldAwake(awakeDistance: mAwakeDistance, popn: this))
210 al::setNerve(user: this, nerve: &NrvPopn.Turn);
211 else
212 al::setNerve(user: this, nerve: &NrvPopn.Wait);
213 }
214}
215
216void Popn::exeWait() {
217 if (al::isFirstStep(user: this))
218 al::startAction(actor: this, actionName: "Wait");
219
220 applyInertia(popn: this);
221
222 if (shouldAwake(awakeDistance: mAwakeDistance, popn: this) && al::isOnGround(this, 0)) {
223 al::setNerve(user: this, nerve: &NrvPopn.Turn);
224 return;
225 }
226
227 s32 nerveStep = al::getNerveStep(user: this);
228 s32 actionFrameMax = al::getActionFrameMax(actor: this, actionName: "Wait");
229 if (nerveStep % actionFrameMax == 0) {
230 if (al::getRandom(max: 1.0f) < 0.3f) {
231 al::setNerve(user: this, nerve: &NrvPopn.WaitRove);
232 return;
233 }
234 }
235}
236
237void Popn::exeWaitRove() {
238 if (al::isFirstStep(user: this))
239 al::startAction(actor: this, actionName: "WaitRove");
240
241 applyInertia(popn: this);
242
243 if (shouldAwake(awakeDistance: mAwakeDistance, popn: this)) {
244 al::setNerve(user: this, nerve: &NrvPopn.Turn);
245 return;
246 }
247
248 if (al::isActionEnd(actor: this))
249 al::setNerve(user: this, nerve: &NrvPopn.Wait);
250}
251
252void Popn::exeTurn() {
253 if (al::isFirstStep(user: this))
254 al::startAction(actor: this, actionName: "Turn");
255
256 applyInertia(popn: this);
257
258 if (!shouldAwake(awakeDistance: mAwakeDistance + 500.0f, popn: this)) {
259 al::setNerve(user: this, nerve: &NrvPopn.Wait);
260 return;
261 }
262
263 f32 turnSpeed = mIsAngry ? 7.0f : 6.0f;
264 s32 nerveStep = al::getNerveStep(user: this);
265 s32 maxNerveStep = mIsAngry ? 21 : 35;
266 if (nerveStep >= 20)
267 turnSpeed *= (f32)(maxNerveStep - nerveStep) / (f32)(maxNerveStep - 20);
268
269 const sead::Vector3f& nearestPlayerPos = al::findNearestPlayerPos(this);
270 const sead::Vector3f& trans = al::getTrans(actor: this);
271 sead::Vector3f diff;
272
273 diff.x = nearestPlayerPos.x - trans.x;
274 diff.y = nearestPlayerPos.y - trans.y;
275 diff.z = nearestPlayerPos.z - trans.z;
276
277 if (!al::tryNormalizeOrZero(out: &diff))
278 al::calcFrontDir(front: &diff, actor: this);
279
280 if (rs::isPlayerHackTRex(this))
281 diff.negate();
282
283 al::turnToDirection(actor: this, dir: diff, deg: turnSpeed);
284
285 if (nerveStep >= maxNerveStep)
286 return al::setNerve(user: this, nerve: &NrvPopn.MoveStart);
287}
288
289void Popn::exeMoveStart() {
290 if (al::isFirstStep(user: this))
291 al::startAction(actor: this, actionName: "MoveStart");
292
293 applyInertia(popn: this);
294
295 if (al::isActionEnd(actor: this))
296 al::setNerve(user: this, nerve: &NrvPopn.Move);
297}
298
299void Popn::exeMove() {
300 if (al::isFirstStep(user: this)) {
301 al::startAction(actor: this, actionName: "Move");
302 al::setVelocityZero(this);
303
304 al::addVelocityToFront(actor: this, force: mIsAngry ? 17.0f : 8.1f);
305 al::addVelocityY(actor: this, y: mIsAngry ? 15.0f : 12.2f);
306
307 sead::Vector3f inertia;
308 sead::Vector3f velocity = al::getVelocity(actor: this);
309
310 al::normalize(vec: &velocity);
311 al::calcJumpInertia(&inertia, this, velocity, 0.7f);
312 al::addVelocity(actor: this, vel: inertia);
313 }
314
315 applyInertia(popn: this);
316
317 if (al::isOnGround(this, 0)) {
318 al::setVelocityZero(this);
319 al::startAction(actor: this, actionName: "MoveEnd");
320 al::setNerve(user: this, nerve: &MoveEnd);
321 }
322}
323
324void Popn::exeMoveEnd() {
325 applyInertia(popn: this);
326
327 if (al::isActionEnd(actor: this))
328 al::setNerve(user: this, nerve: &NrvPopn.Turn);
329}
330
331void Popn::exeBlowDown() {
332 if (al::isFirstStep(user: this))
333 al::onCollide(actor: this);
334
335 if (al::updateNerveState(user: this)) {
336 al::startHitReaction(actor: this, name: "死亡");
337 if (mIsGenerateItem)
338 al::appearItem(actor: this);
339 al::validateClipping(actor: this);
340 kill();
341 }
342}
343