| 1 | #include "Enemy/KaronWing.h" |
| 2 | |
| 3 | #include "Library/LiveActor/ActorActionFunction.h" |
| 4 | #include "Library/LiveActor/ActorAnimFunction.h" |
| 5 | #include "Library/LiveActor/ActorAreaFunction.h" |
| 6 | #include "Library/LiveActor/ActorClippingFunction.h" |
| 7 | #include "Library/LiveActor/ActorCollisionFunction.h" |
| 8 | #include "Library/LiveActor/ActorInitInfo.h" |
| 9 | #include "Library/LiveActor/ActorInitUtil.h" |
| 10 | #include "Library/LiveActor/ActorMovementFunction.h" |
| 11 | #include "Library/LiveActor/ActorParamMove.h" |
| 12 | #include "Library/LiveActor/ActorPoseKeeper.h" |
| 13 | #include "Library/LiveActor/ActorPoseUtil.h" |
| 14 | #include "Library/LiveActor/ActorSensorUtil.h" |
| 15 | #include "Library/Nerve/NerveSetupUtil.h" |
| 16 | #include "Library/Nerve/NerveUtil.h" |
| 17 | #include "Library/Placement/PlacementFunction.h" |
| 18 | #include "Library/Player/PlayerUtil.h" |
| 19 | |
| 20 | #include "Enemy/EnemyCap.h" |
| 21 | #include "Enemy/EnemyStateHackStart.h" |
| 22 | #include "Enemy/EnemyStateReviveInsideScreen.h" |
| 23 | #include "Enemy/EnemyStateSwoon.h" |
| 24 | #include "Enemy/FlyerStateWander.h" |
| 25 | #include "Enemy/KaronWingStateHack.h" |
| 26 | #include "Player/IUsePlayerCollision.h" |
| 27 | #include "Util/Hack.h" |
| 28 | #include "Util/PlayerUtil.h" |
| 29 | #include "Util/SensorMsgFunction.h" |
| 30 | |
| 31 | namespace { |
| 32 | NERVE_IMPL(KaronWing, Attack); |
| 33 | NERVE_IMPL(KaronWing, Break); |
| 34 | NERVE_IMPL(KaronWing, Chase); |
| 35 | NERVE_IMPL(KaronWing, DamageCap); |
| 36 | NERVE_IMPL(KaronWing, Find); |
| 37 | NERVE_IMPL(KaronWing, Hack); |
| 38 | NERVE_IMPL(KaronWing, HackStart); |
| 39 | NERVE_IMPL(KaronWing, Revive); |
| 40 | NERVE_IMPL(KaronWing, ReviveAppear); |
| 41 | NERVE_IMPL(KaronWing, Swoon); |
| 42 | NERVE_IMPL(KaronWing, Turn); |
| 43 | NERVE_IMPL(KaronWing, Wait); |
| 44 | NERVE_IMPL(KaronWing, Wander); |
| 45 | |
| 46 | NERVES_MAKE_STRUCT(KaronWing, Wait, Hack, Swoon, Break, Revive, HackStart, Wander, Attack, |
| 47 | DamageCap, Turn, Find, Chase, ReviveAppear); |
| 48 | } // namespace |
| 49 | |
| 50 | const al::ActorParamMove cMoveParam{.forceFront: 0.1f, .forceGravity: 0.0f, .decay: 0.95f, .turnDegrees: 0.7f}; |
| 51 | static FlyerStateWanderParam cWanderParam{30, 540, 180, "EnemyFly" , &cMoveParam}; |
| 52 | |
| 53 | // NON_MATCHING: creating the `EnemyStateSwoonInitParam` (https://decomp.me/scratch/CDB2W) |
| 54 | void KaronWing::init(const al::ActorInitInfo& info) { |
| 55 | al::initActor(actor: this, initInfo: info); |
| 56 | bool wearingCap = true; |
| 57 | al::tryGetArg(arg: &wearingCap, initInfo: info, key: "IsWearingCap" ); |
| 58 | if (wearingCap) |
| 59 | mEnemyCap = rs::tryCreateEnemyCap(this, info, "EnemyCapKoopa" ); |
| 60 | |
| 61 | al::initNerve(actor: this, nerve: &NrvKaronWing.Wait, maxStates: 8); |
| 62 | |
| 63 | mStateHack = new KaronWingStateHack(this, info, &mPlayerHack); |
| 64 | al::initNerveState(user: this, state: mStateHack, nerve: &NrvKaronWing.Hack, hostName: "キャプチャー" ); |
| 65 | mStateSwoon = new EnemyStateSwoon(this, "SwoonStart" , "Swoon" , "SwoonEnd" , false, true); |
| 66 | |
| 67 | EnemyStateSwoonInitParam swoonParam{"Trampled" , "BreakWait" , "Recover" , |
| 68 | "BreakReaction" , "Break" , "BreakGroundHit" }; |
| 69 | swoonParam.hasStartLandAnimation = true; |
| 70 | swoonParam.hasLockOnDelay = true; |
| 71 | swoonParam.isCancelLoopOnProhibitedArea = false; |
| 72 | swoonParam.swoonDuration = 180; |
| 73 | swoonParam.endSignDelay = 60; |
| 74 | swoonParam.endSignAnimName = "RecoverSign" ; |
| 75 | |
| 76 | mStateSwoon->initParams(initParam: swoonParam); |
| 77 | al::initNerveState(user: this, state: mStateSwoon, nerve: &NrvKaronWing.Swoon, hostName: "気絶" ); |
| 78 | |
| 79 | mStateBreak = new EnemyStateSwoon(this, "SwoonStart" , "Swoon" , "SwoonEnd" , false, true); |
| 80 | mStateBreak->initParams(initParam: swoonParam); |
| 81 | al::initNerveState(user: this, state: mStateBreak, nerve: &NrvKaronWing.Break, hostName: "壊れ" ); |
| 82 | |
| 83 | EnemyStateReviveInsideScreen* stateRevive = new EnemyStateReviveInsideScreen(this); |
| 84 | al::initNerveState(user: this, state: stateRevive, nerve: &NrvKaronWing.Revive, hostName: "画面内復活" ); |
| 85 | mSpawnTrans.set(al::getTrans(actor: this)); |
| 86 | |
| 87 | mStateHackStart = new EnemyStateHackStart(this, nullptr, nullptr); |
| 88 | al::initNerveState(user: this, state: mStateHackStart, nerve: &NrvKaronWing.HackStart, hostName: "キャプチャー開始" ); |
| 89 | |
| 90 | mStateWander = new FlyerStateWander(this, &cWanderParam); |
| 91 | al::initNerveState(user: this, state: mStateWander, nerve: &NrvKaronWing.Wander, hostName: "うろつき" ); |
| 92 | |
| 93 | mCapTargetInfo = rs::createCapTargetInfoWithPlayerCollider(this, mStateHack, nullptr); |
| 94 | if (!al::trySyncStageSwitchAppear(actor: this)) |
| 95 | makeActorAlive(); |
| 96 | } |
| 97 | |
| 98 | void KaronWing::control() { |
| 99 | if (al::isNerve(user: this, nerve: &NrvKaronWing.HackStart)) |
| 100 | return; |
| 101 | |
| 102 | if (!al::isNerve(user: this, nerve: &NrvKaronWing.Revive) && |
| 103 | (al::isCollidedFloorCode(this, "DamageFire" ) || al::isCollidedFloorCode(this, "Needle" ) || |
| 104 | al::isCollidedFloorCode(this, "Poison" ))) { |
| 105 | al::startHitReaction(actor: this, name: "死亡" ); |
| 106 | al::setNerve(user: this, nerve: &NrvKaronWing.Revive); |
| 107 | } else if (al::isInDeathArea(actor: this) && !al::isNerve(user: this, nerve: &NrvKaronWing.Revive)) { |
| 108 | al::startHitReaction(actor: this, name: "死亡" ); |
| 109 | al::setNerve(user: this, nerve: &NrvKaronWing.Revive); |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | void KaronWing::updateCollider() { |
| 114 | if (al::isNerve(user: this, nerve: &NrvKaronWing.Hack)) { |
| 115 | mStateHack->updateCollider(); |
| 116 | return; |
| 117 | } |
| 118 | al::LiveActor::updateCollider(); |
| 119 | } |
| 120 | |
| 121 | void KaronWing::attackSensor(al::HitSensor* self, al::HitSensor* other) { |
| 122 | if (al::isNerve(user: this, nerve: &NrvKaronWing.Revive)) |
| 123 | return; |
| 124 | |
| 125 | if (mPlayerHack) { |
| 126 | rs::sendMsgHackerNoReaction(mPlayerHack, other, self); |
| 127 | mStateHack->attackSensor(self, other); |
| 128 | return; |
| 129 | } |
| 130 | |
| 131 | if (al::isSensorEnemyAttack(self) && !al::isNerve(user: this, nerve: &NrvKaronWing.HackStart) && |
| 132 | !al::isNerve(user: this, nerve: &NrvKaronWing.Hack) && !al::isNerve(user: this, nerve: &NrvKaronWing.Swoon) && |
| 133 | !al::isNerve(user: this, nerve: &NrvKaronWing.Break)) { |
| 134 | if (al::sendMsgEnemyAttack(receiver: other, sender: self)) { |
| 135 | al::faceToTarget(actor: this, target: al::getSensorPos(other)); |
| 136 | setNerve(user: this, nerve: &NrvKaronWing.Attack); |
| 137 | return; |
| 138 | } |
| 139 | |
| 140 | al::sendMsgPush(receiver: other, sender: self) || rs::sendMsgPushToPlayer(source: other, target: self); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | bool KaronWing::receiveMsg(const al::SensorMsg* message, al::HitSensor* other, |
| 145 | al::HitSensor* self) { |
| 146 | if (al::isNerve(user: this, nerve: &NrvKaronWing.Revive)) { |
| 147 | return rs::isMsgPlayerDisregardHomingAttack(message) || |
| 148 | rs::isMsgPlayerDisregardTargetMarker(message) || al::isMsgPlayerDisregard(msg: message); |
| 149 | } |
| 150 | |
| 151 | if (rs::tryReceiveMsgInitCapTargetAndSetCapTargetInfo(message, mCapTargetInfo)) |
| 152 | return true; |
| 153 | |
| 154 | if (al::isNerve(user: this, nerve: &NrvKaronWing.Hack) && mStateHack->receiveMsg(message, other, self)) { |
| 155 | if (mStateHack->isEndCancel()) |
| 156 | al::setNerve(user: this, nerve: &NrvKaronWing.Swoon); |
| 157 | else if (mStateHack->isEndReset()) { |
| 158 | al::startHitReaction(actor: this, name: "消滅" ); |
| 159 | al::setNerve(user: this, nerve: &NrvKaronWing.Revive); |
| 160 | } else if (mStateHack->isEndDamage()) |
| 161 | kill(); |
| 162 | return true; |
| 163 | } |
| 164 | |
| 165 | if (rs::tryReceiveMsgNpcScareByEnemyIgnoreTargetHack(message, mCapTargetInfo)) |
| 166 | return true; |
| 167 | |
| 168 | if (!al::isNerve(user: this, nerve: &NrvKaronWing.Revive) && !al::isNerve(user: this, nerve: &NrvKaronWing.HackStart) && |
| 169 | !al::isNerve(user: this, nerve: &NrvKaronWing.Hack)) { |
| 170 | if (rs::isMsgCapEnableLockOn(message)) |
| 171 | return !rs::isOnEnemyCap(mEnemyCap); |
| 172 | if (rs::isMsgCapCancelLockOn(message)) |
| 173 | return true; |
| 174 | if (mStateSwoon->tryReceiveMsgStartLockOn(message)) |
| 175 | return !rs::isOnEnemyCap(mEnemyCap); |
| 176 | if (mStateSwoon->tryReceiveMsgStartHack(message)) { |
| 177 | al::invalidateClipping(actor: this); |
| 178 | al::setNerve(user: this, nerve: &NrvKaronWing.HackStart); |
| 179 | mPlayerHack = mStateHackStart->tryStart(message, other, self); |
| 180 | return true; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | if (al::isNerve(user: this, nerve: &NrvKaronWing.Swoon) || al::isNerve(user: this, nerve: &NrvKaronWing.Break)) { |
| 185 | if (mStateSwoon->tryReceiveMsgEnableLockOn(message)) |
| 186 | return true; |
| 187 | if (mStateSwoon->tryReceiveMsgStartHack(message)) { |
| 188 | al::setNerve(user: this, nerve: &NrvKaronWing.HackStart); |
| 189 | mPlayerHack = mStateHackStart->tryStart(message, other, self); |
| 190 | return true; |
| 191 | } |
| 192 | if (mStateSwoon->tryReceiveMsgEndSwoon(message)) |
| 193 | return true; |
| 194 | if (rs::isMsgTankExplosion(message)) |
| 195 | (al::isNerve(user: this, nerve: &NrvKaronWing.Swoon) ? mStateSwoon : mStateBreak)->requestTrampled(); |
| 196 | } |
| 197 | |
| 198 | if (al::isNerve(user: this, nerve: &NrvKaronWing.ReviveAppear) || al::isNerve(user: this, nerve: &NrvKaronWing.Wait) || |
| 199 | al::isNerve(user: this, nerve: &NrvKaronWing.Turn) || al::isNerve(user: this, nerve: &NrvKaronWing.Wander) || |
| 200 | al::isNerve(user: this, nerve: &NrvKaronWing.Find) || al::isNerve(user: this, nerve: &NrvKaronWing.Chase)) { |
| 201 | if (mStateSwoon->tryReceiveMsgAttack(message)) { |
| 202 | if (rs::tryStartEnemyCapBlowDown(mEnemyCap, other)) { |
| 203 | mDamageStartY = al::getTrans(actor: this).y; |
| 204 | al::setVelocityBlowAttackAndTurnToTarget(actor: this, target: rs::getPlayerBodyPos(this), speedH: 7.0f, |
| 205 | speedV: 25.0f); |
| 206 | al::setNerve(user: this, nerve: &NrvKaronWing.DamageCap); |
| 207 | } |
| 208 | return true; |
| 209 | } |
| 210 | |
| 211 | if (rs::isMsgPressDown(message) || rs::isMsgBlowDown(message)) { |
| 212 | rs::tryStartEnemyCapBlowDown(mEnemyCap, other); |
| 213 | rs::requestHitReactionToAttacker(message, self, other); |
| 214 | al::setNerve(user: this, nerve: &NrvKaronWing.Break); |
| 215 | return true; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | if (!mPlayerHack && !al::isNerve(user: this, nerve: &NrvKaronWing.Swoon) && |
| 220 | !al::isNerve(user: this, nerve: &NrvKaronWing.Break) && |
| 221 | al::tryReceiveMsgPushAndAddVelocity(this, message, other, self, 3.0f)) |
| 222 | return true; |
| 223 | |
| 224 | return false; |
| 225 | } |
| 226 | |
| 227 | void KaronWing::exeWait() { |
| 228 | if (al::isFirstStep(user: this)) { |
| 229 | al::validateClipping(actor: this); |
| 230 | al::startAction(actor: this, actionName: "FlyWait" ); |
| 231 | } |
| 232 | |
| 233 | al::scaleVelocity(actor: this, factor: 0.7f); |
| 234 | if (al::isGreaterEqualStep(user: this, step: 60)) |
| 235 | al::setNerve(user: this, nerve: &NrvKaronWing.Wander); |
| 236 | } |
| 237 | |
| 238 | void KaronWing::exeWander() { |
| 239 | al::updateNerveState(user: this); |
| 240 | if (al::isNearPlayer(this, 1500.0f)) |
| 241 | al::setNerve(user: this, nerve: &NrvKaronWing.Turn); |
| 242 | } |
| 243 | |
| 244 | void KaronWing::exeTurn() { |
| 245 | if (al::isFirstStep(user: this)) |
| 246 | al::startAction(actor: this, actionName: "EnemyFly" ); |
| 247 | |
| 248 | sead::Vector3f playerPos = al::getPlayerPos(this, 0); |
| 249 | al::turnToTarget(actor: this, target: playerPos, deg: 2.3f); |
| 250 | if (al::isFaceToTargetDegreeH(actor: this, target: playerPos, face: al::getFront(actor: this), degH: 2.3f)) { |
| 251 | al::setNerve(user: this, nerve: &NrvKaronWing.Find); |
| 252 | return; |
| 253 | } |
| 254 | |
| 255 | al::scaleVelocity(actor: this, factor: 0.7f); |
| 256 | } |
| 257 | |
| 258 | void KaronWing::exeFind() { |
| 259 | if (al::isFirstStep(user: this)) |
| 260 | al::startAction(actor: this, actionName: "Find" ); |
| 261 | |
| 262 | if (al::isActionEnd(actor: this)) { |
| 263 | al::setNerve(user: this, nerve: &NrvKaronWing.Chase); |
| 264 | return; |
| 265 | } |
| 266 | |
| 267 | al::scaleVelocity(actor: this, factor: 0.7f); |
| 268 | } |
| 269 | |
| 270 | void KaronWing::exeChase() { |
| 271 | if (al::isFirstStep(user: this)) |
| 272 | al::startAction(actor: this, actionName: "FlyChase" ); |
| 273 | |
| 274 | if (!al::isNearPlayer(this, 2000.0f)) { |
| 275 | al::setNerve(user: this, nerve: &NrvKaronWing.Wait); |
| 276 | return; |
| 277 | } |
| 278 | |
| 279 | al::flyAndTurnToTarget(actor: this, target: al::getPlayerPos(this, 0), forceFront: 4.0f, forceGravity: 0.0f, decay: 0.7f, deg: 2.3f); |
| 280 | } |
| 281 | |
| 282 | void KaronWing::exeRevive() { |
| 283 | if (al::updateNerveStateAndNextNerve(user: this, nerve: &NrvKaronWing.ReviveAppear)) { |
| 284 | mStateHack->resetFlyLimit(mSpawnTrans); |
| 285 | rs::tryAppearEnemyCap(mEnemyCap); |
| 286 | al::startVisAnim(this, "CapOn" ); |
| 287 | } |
| 288 | } |
| 289 | |
| 290 | void KaronWing::exeReviveAppear() { |
| 291 | if (al::isFirstStep(user: this)) |
| 292 | al::startAction(actor: this, actionName: "AppearStart" ); |
| 293 | |
| 294 | if (al::isActionEnd(actor: this)) |
| 295 | al::setNerve(user: this, nerve: &NrvKaronWing.Wait); |
| 296 | } |
| 297 | |
| 298 | void KaronWing::exeAttack() { |
| 299 | if (al::isFirstStep(user: this)) { |
| 300 | al::startAction(actor: this, actionName: "AttackHit" ); |
| 301 | al::setVelocityZero(this); |
| 302 | } |
| 303 | |
| 304 | if (al::isActionEnd(actor: this)) { |
| 305 | al::setNerve(user: this, nerve: &NrvKaronWing.Wait); |
| 306 | return; |
| 307 | } |
| 308 | |
| 309 | if (!al::isOnGround(this, 0)) |
| 310 | al::addVelocityToGravity(actor: this, force: 1.0f); |
| 311 | al::scaleVelocity(actor: this, factor: al::isOnGround(this, 0) ? 0.7f : 0.97f); |
| 312 | } |
| 313 | |
| 314 | void KaronWing::exeSwoon() { |
| 315 | if (al::isFirstStep(user: this)) |
| 316 | al::invalidateClipping(actor: this); |
| 317 | |
| 318 | if (al::updateNerveState(user: this)) { |
| 319 | al::startHitReaction(actor: this, name: "消滅" ); |
| 320 | al::setNerve(user: this, nerve: &NrvKaronWing.Revive); |
| 321 | return; |
| 322 | } |
| 323 | |
| 324 | if (!al::isOnGround(this, 0)) |
| 325 | al::addVelocityToGravity(actor: this, force: 1.0f); |
| 326 | al::scaleVelocity(actor: this, factor: al::isOnGround(this, 0) ? 0.7f : 0.97f); |
| 327 | } |
| 328 | |
| 329 | void KaronWing::exeBreak() { |
| 330 | if (al::isFirstStep(user: this)) |
| 331 | al::invalidateClipping(actor: this); |
| 332 | |
| 333 | if (al::updateNerveState(user: this)) { |
| 334 | al::setNerve(user: this, nerve: &NrvKaronWing.Wait); |
| 335 | return; |
| 336 | } |
| 337 | |
| 338 | if (!al::isOnGround(this, 0)) |
| 339 | al::addVelocityToGravity(actor: this, force: 1.0f); |
| 340 | al::scaleVelocity(actor: this, factor: al::isOnGround(this, 0) ? 0.7f : 0.97f); |
| 341 | } |
| 342 | |
| 343 | void KaronWing::exeHackStart() { |
| 344 | al::updateNerveStateAndNextNerve(user: this, nerve: &NrvKaronWing.Hack); |
| 345 | } |
| 346 | |
| 347 | void KaronWing::exeHack() { |
| 348 | al::updateNerveStateAndNextNerve(user: this, nerve: &NrvKaronWing.Swoon); |
| 349 | } |
| 350 | |
| 351 | void KaronWing::exeDamageCap() { |
| 352 | if (al::isFirstStep(user: this)) { |
| 353 | al::invalidateClipping(actor: this); |
| 354 | al::startAction(actor: this, actionName: "DamageCap" ); |
| 355 | } |
| 356 | |
| 357 | if (!al::isOnGround(this, 0)) |
| 358 | al::addVelocityToGravity(actor: this, force: 1.0f); |
| 359 | al::scaleVelocity(actor: this, factor: al::isOnGround(this, 0) ? 0.7f : 0.97f); |
| 360 | |
| 361 | if (al::isActionEnd(actor: this)) { |
| 362 | const sead::Vector3f& trans = al::getTrans(actor: this); |
| 363 | const sead::Vector3f& velocity = al::getVelocity(actor: this); |
| 364 | if (trans.y + velocity.y <= mDamageStartY || al::isOnGround(this, 0)) |
| 365 | al::setNerve(user: this, nerve: &NrvKaronWing.Wait); |
| 366 | } |
| 367 | } |
| 368 | |