Path: blob/master/SonicMania/Objects/ERZ/KleptoMobile.c
338 views
// ---------------------------------------------------------------------1// RSDK Project: Sonic Mania2// Object Description: KleptoMobile Object3// Object Author: Christian Whitehead/Simon Thomley/Hunter Bridges4// Decompiled by: Rubberduckycooly & RMGRich5// ---------------------------------------------------------------------67#include "Game.h"89ObjectKleptoMobile *KleptoMobile;1011void KleptoMobile_Update(void)12{13RSDK_THIS(KleptoMobile);1415KleptoMobile_HandleAnimations();1617StateMachine_Run(self->state);18}1920void KleptoMobile_LateUpdate(void) {}2122void KleptoMobile_StaticUpdate(void) {}2324void KleptoMobile_Draw(void)25{26RSDK_THIS(KleptoMobile);2728if (self->stateDraw) {29StateMachine_Run(self->stateDraw);30}31else {32RSDK.DrawSprite(&self->basicAnimator, NULL, false);33}34}3536void KleptoMobile_Create(void *data)37{38RSDK_THIS(KleptoMobile);3940if (!SceneInfo->inEditor) {41if (globals->gameMode < MODE_TIMEATTACK) {42self->updateRange.x = 0x800000;43self->updateRange.y = 0x800000;44self->visible = true;45self->drawGroup = Zone->objectDrawGroup[0];46self->drawFX = FX_FLIP;47self->explosionVolume = 0x200;4849self->type = VOID_TO_INT(data);50switch (self->type) {51case KLEPTOMOBILE_EGGMAN:52self->hitbox.left = -22;53self->hitbox.top = -22;54self->hitbox.right = 22;55self->hitbox.bottom = 22;5657self->active = ACTIVE_NORMAL;58self->health = 8;5960RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 0, &self->mobileTopAnimator, true, 1);61RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 13, &self->eggmanAnimator, true, 0);62RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 0, &self->mobileAnimator, true, 0);63RSDK.SetSpriteAnimation(PhantomKing->aniFrames, 8, &self->rubyAnimator, true, 0);6465KleptoMobile->defeated = false;6667self->stateDraw = KleptoMobile_Draw_KleptoMobile;68self->state = KleptoMobile_State_SetupArena;69break;7071case KLEPTOMOBILE_ARM_L:72case KLEPTOMOBILE_ARM_R:73self->hitbox.left = -32;74self->hitbox.top = -8;75self->hitbox.right = 32;76self->hitbox.bottom = 8;7778self->active = ACTIVE_NORMAL;79self->visible = true;8081if (self->type == KLEPTOMOBILE_ARM_R) {82RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 7, &self->orbAnimator, true, 0);83RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 8, &self->handAnimator, true, 0);84RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 9, &self->finger1Animator, true, 0);85RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 10, &self->finger2Animator, true, 0);86}87else {88self->drawGroup = Zone->playerDrawGroup[0];89RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 3, &self->orbAnimator, true, 0);90RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 4, &self->handAnimator, true, 0);91RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 5, &self->finger1Animator, true, 0);92RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 6, &self->finger2Animator, true, 0);93}9495self->stateDraw = KleptoMobile_Draw_Arm;96self->state = KleptoMobile_StateArm_Cutscene;97break;9899case KLEPTOMOBILE_HAND:100self->active = ACTIVE_NORMAL;101self->visible = true;102103RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 12, &self->handAnimator, true, 5);104RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 17, &self->orbAnimator, true, 0);105106self->stateDraw = KleptoMobile_Draw_Hand;107self->state = KleptoMobile_StateHand_Cutscene;108break;109110default: break;111}112}113else {114destroyEntity(self);115}116}117}118119void KleptoMobile_StageLoad(void)120{121KleptoMobile->aniFrames = RSDK.LoadSpriteAnimation("Phantom/KleptoMobile.bin", SCOPE_STAGE);122123KleptoMobile->sfxHit = RSDK.GetSfx("Stage/BossHit.wav");124KleptoMobile->sfxExplosion = RSDK.GetSfx("Stage/Explosion2.wav");125KleptoMobile->sfxFlail = RSDK.GetSfx("SSZ1/Flail.wav");126KleptoMobile->sfxWhack = RSDK.GetSfx("Stage/Whack.wav");127KleptoMobile->sfxPowerUp = RSDK.GetSfx("Stage/PowerUp.wav");128KleptoMobile->sfxRocketJet = RSDK.GetSfx("Stage/RocketJet.wav");129}130131void KleptoMobile_HandleAnimations(void)132{133RSDK_THIS(KleptoMobile);134135bool32 laughing = false;136RSDK.ProcessAnimation(&self->eggmanAnimator);137138if (self->invincibilityTimer > 0)139self->invincibilityTimer--;140141if (self->eggmanAnimator.animationID == 13) {142foreach_active(Player, player)143{144if (player->state == Player_State_Hurt || player->state == Player_State_Death || player->state == ERZStart_State_PlayerRebound)145laughing = true;146}147148if (laughing)149RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 14, &self->eggmanAnimator, true, 0);150}151else if (self->eggmanAnimator.animationID == 14) {152foreach_active(Player, player)153{154if (player->state == Player_State_Hurt || player->state == Player_State_Death || player->state == ERZStart_State_PlayerRebound)155laughing = true;156}157158if (self->eggmanAnimator.frameID >= 14) {159if (laughing)160RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 14, &self->eggmanAnimator, true, 7);161else162RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 13, &self->eggmanAnimator, true, 0);163}164}165else {166if (self->eggmanAnimator.animationID == 15 && !self->invincibilityTimer)167RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 13, &self->eggmanAnimator, true, 0);168}169}170171void KleptoMobile_CheckPlayerCollisions(void)172{173RSDK_THIS(KleptoMobile);174175foreach_active(Player, player)176{177if (!self->invincibilityTimer && Player_CheckBadnikTouch(player, self, &self->hitbox) && Player_CheckBossHit(player, self)) {178KleptoMobile_Hit();179}180}181}182183void KleptoMobile_Hit(void)184{185RSDK_THIS(KleptoMobile);186187if (--self->health <= 0) {188RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 16, &self->eggmanAnimator, true, 0);189190self->state = KleptoMobile_State_Destroyed;191self->timer = 0;192193if (PhantomKing->defeated)194SceneInfo->timeEnabled = false;195196Player_GiveScore(RSDK_GET_ENTITY(SLOT_PLAYER1, Player), 1000);197}198else {199self->invincibilityTimer = 48;200RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 15, &self->eggmanAnimator, true, 0);201RSDK.PlaySfx(KleptoMobile->sfxHit, false, 255);202203foreach_all(PhantomRuby, ruby)204{205ruby->position.x = self->rubyPos.x;206ruby->position.y = self->rubyPos.y;207ruby->velocity.x = -0x20000;208ruby->velocity.y = -0x40000;209ruby->state = PhantomRuby_State_MoveGravity;210211EntityKleptoMobile *arm1 = RSDK_GET_ENTITY(SceneInfo->entitySlot - 1, KleptoMobile);212arm1->timer = 0;213arm1->state = KleptoMobile_StateArm_Cutscene;214215EntityKleptoMobile *arm2 = RSDK_GET_ENTITY(SceneInfo->entitySlot + 1, KleptoMobile);216arm2->timer = 0;217arm2->state = KleptoMobile_StateArm_Cutscene;218}219220self->holdingRuby = false;221self->state = KleptoMobile_State_HitFall;222}223}224225void KleptoMobile_Explode(void)226{227RSDK_THIS(KleptoMobile);228229if (!(Zone->timer % 3)) {230int32 channel = RSDK.PlaySfx(KleptoMobile->sfxExplosion, false, 255);231232if (self->state == KleptoMobile_State_CutsceneExplode)233RSDK.SetChannelAttributes(channel, self->explosionVolume * (1 / 512.0f), 0.0, 1.0);234235if (Zone->timer & 4) {236int32 x = self->position.x + (RSDK.Rand(self->hitbox.left, self->hitbox.right) << 16);237int32 y = self->position.y + (RSDK.Rand(self->hitbox.top, self->hitbox.bottom) << 16);238CREATE_ENTITY(Explosion, INT_TO_VOID((RSDK.Rand(0, 256) > 192) + EXPLOSION_BOSS), x, y)->drawGroup = Zone->objectDrawGroup[1];239}240}241}242243void KleptoMobile_HandleFrames(void)244{245RSDK_THIS(KleptoMobile);246247self->rotation = RSDK.Sin512(2 * Zone->timer) >> 6;248self->armAngle = (self->armAngle + 12) & 0x3FF;249250int32 moveX = 0x1C00 * RSDK.Sin512(-self->rotation) + self->position.x;251int32 moveY = 0x1C00 * RSDK.Cos512(-self->rotation) + self->position.y;252253int32 angle = self->armAngle;254255for (int32 i = 0; i < 10; i += 2) {256self->armPositions[i].x = moveX + 2 * RSDK.Cos1024(angle) * RSDK.Cos512(self->rotation);257self->armPositions[i].y = moveY + 2 * RSDK.Cos1024(angle) * RSDK.Sin512(self->rotation);258self->armAngles[i] = angle & 0x3FF;259angle += 0x200;260261self->armPositions[i + 1].x = moveX + 2 * RSDK.Cos1024(angle) * RSDK.Cos512(self->rotation);262self->armPositions[i + 1].y = moveY + 2 * RSDK.Cos1024(angle) * RSDK.Sin512(self->rotation);263self->armAngles[i + 1] = angle & 0x3FF;264265moveX += RSDK.Sin512(-self->rotation) << 10;266moveY += RSDK.Cos512(-self->rotation) << 10;267angle += 0x240;268}269}270271void KleptoMobile_SwitchToKing(void)272{273EntityPhantomKing *kingPtr = NULL;274275foreach_active(PhantomKing, king)276{277if (king->type == PHANTOMKING_KING) {278EntityPlayer *player1 = RSDK_GET_ENTITY(SLOT_PLAYER1, Player);279280king->direction = FLIP_X;281if (RSDK.Rand(0, 2))282king->position.x = player1->position.x + 0x1000000;283else284king->position.x = player1->position.x - 0x1000000;285286king->position.y = player1->position.y + 0x800000;287king->originPos.y = player1->position.y + 0x800000;288king->rubyPos = king->position;289king->rubyPos.x -= 0x1400 * RSDK.Sin512(-king->rotation);290king->rubyPos.y -= 0x1400 * RSDK.Cos512(-king->rotation);291king->velocity.x = 0;292king->velocity.y = 0;293king->drawRuby = true;294295for (int32 i = 0; i < 0x3FC; i += 0xAA) {296EntityPKingAttack *attack = CREATE_ENTITY(PKingAttack, INT_TO_VOID(1), king->rubyPos.x, king->rubyPos.y);297attack->angle = i;298attack->target = (Entity *)king;299}300301king->state = PhantomKing_State_FlyAround;302kingPtr = king;303304foreach_break;305}306}307308// NOTE: this will crash if kingPtr is NULL, make sure there's a phantom king in the scene!!!309foreach_active(PhantomKing, kingArm)310{311if (kingArm->type != PHANTOMKING_KING) {312kingArm->position.x = kingPtr->position.x;313kingArm->position.y = kingPtr->position.y;314315for (int32 i = 0; i < 10; ++i) kingArm->armPositions[i] = kingPtr->position;316317kingArm->velocity.x = 0;318kingArm->velocity.y = 0;319kingArm->state = PhantomKing_StateArm_Idle;320}321}322}323324void KleptoMobile_Draw_KleptoMobile(void)325{326RSDK_THIS(KleptoMobile);327328if (self->invincibilityTimer & 1) {329RSDK.CopyPalette(2, 112, 0, 136, 8);330RSDK.CopyPalette(2, 120, 0, 11, 1);331RSDK.CopyPalette(2, 121, 0, 25, 1);332RSDK.CopyPalette(2, 122, 0, 197, 6);333334RSDK.DrawSprite(&self->mobileTopAnimator, NULL, false);335RSDK.DrawSprite(&self->eggmanAnimator, NULL, false);336337self->mobileAnimator.frameID = 2;338RSDK.DrawSprite(&self->mobileAnimator, NULL, false);339340self->mobileAnimator.frameID = 0;341RSDK.DrawSprite(&self->mobileAnimator, NULL, false);342343RSDK.CopyPalette(1, 136, 0, 136, 8);344RSDK.CopyPalette(1, 11, 0, 11, 1);345RSDK.CopyPalette(1, 25, 0, 25, 1);346RSDK.CopyPalette(1, 197, 0, 197, 6);347}348else {349self->basicAnimator.frameID = 1; // this sets basicAnimator to anim 0, frame 1... and then draws it using mobileTopAnimator instead350RSDK.DrawSprite(&self->mobileTopAnimator, NULL, false);351RSDK.DrawSprite(&self->eggmanAnimator, NULL, false);352353self->mobileAnimator.frameID = 2;354RSDK.DrawSprite(&self->mobileAnimator, NULL, false);355356self->mobileAnimator.frameID = 0;357RSDK.DrawSprite(&self->mobileAnimator, NULL, false);358}359360RSDK.DrawCircle(self->rubyPos.x, self->rubyPos.y, self->circleRadius, 0x000000, 0xFF, INK_TINT, false);361}362363void KleptoMobile_State_SetupArena(void)364{365RSDK_THIS(KleptoMobile);366367self->active = ACTIVE_NORMAL;368369EntityKleptoMobile *hand = RSDK_GET_ENTITY(SceneInfo->entitySlot - 2, KleptoMobile);370RSDK.ResetEntity(hand, KleptoMobile->classID, INT_TO_VOID(KLEPTOMOBILE_HAND));371hand->position.x = self->position.x;372hand->position.y = self->position.y;373hand->parent = self;374375EntityKleptoMobile *arm2 = RSDK_GET_ENTITY(SceneInfo->entitySlot - 1, KleptoMobile);376RSDK.ResetEntity(arm2, KleptoMobile->classID, INT_TO_VOID(KLEPTOMOBILE_ARM_R));377arm2->position.x = self->position.x;378arm2->position.y = self->position.y;379arm2->parent = self;380381EntityKleptoMobile *arm1 = RSDK_GET_ENTITY(SceneInfo->entitySlot + 1, KleptoMobile);382RSDK.ResetEntity(arm1, KleptoMobile->classID, INT_TO_VOID(KLEPTOMOBILE_ARM_L));383arm1->position.x = self->position.x;384arm1->position.y = self->position.y;385arm1->parent = self;386387self->originPos.x = self->position.x;388self->originPos.y = self->position.y;389KleptoMobile->boundsM = self->position.x;390KleptoMobile->boundsL = KleptoMobile->boundsM - 0x800000;391KleptoMobile->boundsR = KleptoMobile->boundsM + 0x800000;392KleptoMobile->boundsT = (Zone->cameraBoundsT[0] + 48) << 16;393KleptoMobile->boundsB = (Zone->cameraBoundsB[0] - 96) << 16;394self->state = StateMachine_None;395}396397void KleptoMobile_State_CutsceneControlled(void)398{399RSDK_THIS(KleptoMobile);400401self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);402403self->position.x += self->velocity.x;404self->position.y += self->velocity.y;405self->originPos.x += self->velocity.x;406self->originPos.y += self->velocity.y;407408KleptoMobile_HandleFrames();409}410411void KleptoMobile_State_MoveAround(void)412{413RSDK_THIS(KleptoMobile);414415EntityPlayer *player1 = RSDK_GET_ENTITY(SLOT_PLAYER1, Player);416417self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);418KleptoMobile_CheckPlayerCollisions();419420int32 angle = RSDK.ATan2(self->position.x - player1->position.x, self->originPos.y - player1->position.y);421int32 x = (RSDK.Cos256(angle) << 15) + player1->position.x;422int32 y = (RSDK.Sin256(angle) << 15) + player1->position.y;423424if (self->circleRadius > 0)425self->circleRadius -= 8;426427if (x <= self->position.x) {428if (self->velocity.x > -0x20000)429self->velocity.x -= 0x800;430}431else {432if (self->velocity.x < 0x20000)433self->velocity.x += 0x800;434}435436int32 bottom = Zone->cameraBoundsB[0] << 16;437int32 boundary = bottom - 0x800000;438439if (y <= bottom - 0x800000) {440boundary = bottom + 0x800000;441if (y >= (Zone->cameraBoundsT[0] + 128) << 16)442boundary = y;443}444445if (boundary <= self->originPos.y) {446if (self->velocity.y > -0x20000)447self->velocity.y -= 0x800;448}449else {450if (self->velocity.y < 0x20000)451self->velocity.y += 0x800;452}453454self->position.x += self->velocity.x;455self->originPos.y += self->velocity.y;456self->direction = player1->position.x > self->position.x;457458if (--self->bashArmDelay == 1) {459self->bashArmDelay = 0;460self->timer = 0;461RSDK.PlaySfx(KleptoMobile->sfxPowerUp, false, 255);462463EntityKleptoMobile *arm2 = RSDK_GET_ENTITY(SceneInfo->entitySlot - 1, KleptoMobile);464arm2->timer = 0;465arm2->state = KleptoMobile_StateArm_ChargeAttack;466467EntityKleptoMobile *arm1 = RSDK_GET_ENTITY(SceneInfo->entitySlot + 1, KleptoMobile);468arm1->timer = 0;469arm1->state = KleptoMobile_StateArm_ChargeAttack;470self->state = KleptoMobile_State_Hover;471}472else {473if (self->canBashAttack) {474if (self->bashArmDelay <= 0 && ++self->timer == 15) {475self->timer = 0;476RSDK.PlaySfx(KleptoMobile->sfxFlail, false, 0xFF);477int32 armSlot = self->bashArmID ? SceneInfo->entitySlot + 1 : SceneInfo->entitySlot - 1;478479EntityKleptoMobile *arm = RSDK_GET_ENTITY(armSlot, KleptoMobile);480481x = self->position.x + (self->direction == FLIP_X ? -0x30000 : 0x30000);482if (arm->type == KLEPTOMOBILE_ARM_R)483x += 0x180000;484y = self->position.y + 0xD0000;485486angle = RSDK.ATan2(player1->position.x - x, player1->position.y - y);487arm->bashArmTargetPos.x = x + (RSDK.Cos256(angle) << 15);488arm->bashArmTargetPos.y = y + (RSDK.Sin256(angle) << 15);489arm->timer = 0;490arm->state = KleptoMobile_StateArm_BashAttack;491self->bashArmID ^= 1;492493if (++self->attackCount >= 5) {494self->attackCount = 0;495self->bashArmDelay = 90;496self->canBashAttack = false;497}498}499}500else {501if (abs(player1->position.x - self->position.x) < 0x1800000 && abs(player1->position.y - self->position.y) < 0x1800000)502self->canBashAttack = true;503}504505KleptoMobile_HandleFrames();506}507}508509void KleptoMobile_State_Hover(void)510{511RSDK_THIS(KleptoMobile);512513self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);514515KleptoMobile_CheckPlayerCollisions();516517if (++self->timer < 180) {518EntityPlayer *player1 = RSDK_GET_ENTITY(SLOT_PLAYER1, Player);519if (abs(player1->position.x - self->position.x) < 0xC00000 && abs(player1->position.y - self->position.y) < 0xC00000) {520RSDK.StopSfx(KleptoMobile->sfxPowerUp);521522self->timer = 14;523self->bashArmDelay = 0;524self->canBashAttack = true;525self->state = KleptoMobile_State_MoveAround;526}527}528else {529self->timer = 0;530self->velocity.x = self->direction == FLIP_NONE ? -0xA0000 : 0xA0000;531532PhantomRuby_PlaySfx(RUBYSFX_REDCUBE);533RSDK.PlaySfx(KleptoMobile->sfxRocketJet, false, 255);534self->state = KleptoMobile_State_FirstChargeAttack;535}536537KleptoMobile_HandleFrames();538}539540void KleptoMobile_HandleArmPositions(void)541{542foreach_active(KleptoMobile, eggman)543{544if (eggman->type != KLEPTOMOBILE_EGGMAN) {545EntityKleptoMobile *parent = eggman->parent;546eggman->position.x = parent->position.x;547eggman->position.y = parent->position.y;548549for (int32 i = 0; i < 10; ++i) eggman->armPositions[i] = parent->position;550551eggman->velocity.x = 0;552eggman->velocity.y = 0;553}554}555}556557void KleptoMobile_HandleChargeFinish(void)558{559RSDK_THIS(KleptoMobile);560561EntityPlayer *player1 = RSDK_GET_ENTITY(SLOT_PLAYER1, Player);562563self->timer = 0;564565PhantomRuby_PlaySfx(RSDK.Rand(RUBYSFX_ATTACK1, RUBYSFX_REDCUBE));566if (!RSDK.Rand(0, 2))567self->position.x = player1->position.x - 0x1800000;568else569self->position.x = player1->position.x + 0x1800000;570571self->originPos.y = (RSDK.Rand(-2, 3) << 21) + player1->position.y;572while (self->originPos.y > (Zone->cameraBoundsB[0] - 64) << 16 && self->originPos.y < (Zone->cameraBoundsT[0] + 64) << 16) {573self->originPos.y = (RSDK.Rand(-2, 3) << 21) + player1->position.y;574}575576self->circleRadius = 128;577578if (++self->attackCount == 4) {579self->attackCount = 0;580self->velocity.x = 0;581self->velocity.y = 0;582self->state = KleptoMobile_State_MoveAround;583}584else if (player1->position.x >= self->position.x) {585self->velocity.x = 0xA0000;586self->direction = FLIP_X;587}588else {589self->velocity.x = -0xA0000;590self->direction = FLIP_NONE;591}592593KleptoMobile_HandleArmPositions();594}595596void KleptoMobile_State_FirstChargeAttack(void)597{598RSDK_THIS(KleptoMobile);599600self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);601602if (self->circleRadius < 128)603self->circleRadius += 8;604605if (++self->timer >= 180) {606KleptoMobile_HandleChargeFinish();607self->state = KleptoMobile_State_NextChargeAttacks;608}609610self->position.x += self->velocity.x;611self->originPos.y += self->velocity.y;612613KleptoMobile_HandleFrames();614}615616void KleptoMobile_State_NextChargeAttacks(void)617{618RSDK_THIS(KleptoMobile);619620self->position.y = BadnikHelpers_Oscillate(self->originPos.y, 2, 9);621622KleptoMobile_CheckPlayerCollisions();623624if (self->onScreen) {625if (self->circleRadius > 0)626self->circleRadius -= 8;627}628629if (++self->timer >= 180)630KleptoMobile_HandleChargeFinish();631632self->position.x += self->velocity.x;633self->originPos.y += self->velocity.y;634635KleptoMobile_HandleFrames();636}637638void KleptoMobile_State_Switch(void)639{640RSDK_THIS(KleptoMobile);641642if (++self->timer >= 120) {643self->timer = 0;644KleptoMobile_SwitchToKing();645self->state = StateMachine_None;646}647}648649void KleptoMobile_State_HitFall(void)650{651RSDK_THIS(KleptoMobile);652653self->position.y += self->velocity.y;654self->velocity.y -= 0x3800;655656if (self->position.y < -0x1000000) {657self->canBashAttack = false;658self->bashArmDelay = 0;659self->velocity.y = 0;660self->timer = 0;661self->state = KleptoMobile_State_Switch;662}663664KleptoMobile_HandleFrames();665}666667void KleptoMobile_StateHand_Cutscene(void)668{669RSDK_THIS(KleptoMobile);670EntityKleptoMobile *parent = self->parent;671672int32 parentX = parent->position.x;673int32 parentY = parent->position.y;674675int32 moveX = 0;676int32 moveY = -0x200000 - (RSDK.Sin256((((self->type << 7) - 0x80) ^ 0x80) + 2 * Zone->timer) << 12);677678self->direction = parent->direction;679int32 x = parent->position.x;680int32 y = parent->position.y - 0x180000;681682int32 x2 = 0;683int32 y2 = 0;684if (self->direction == FLIP_X) {685moveX = parentX + 0x200000;686x2 = ((self->position.x + parent->position.x) >> 1) + 0x80000;687}688else {689moveX = parentX - 0x200000;690x2 = ((self->position.x + parent->position.x) >> 1) - 0x80000;691}692693y2 = ((parent->position.y - 0x180000 + self->position.y) >> 1) - 0x300000;694695self->velocity.x += ((moveX - self->position.x) >> 5) - (self->velocity.x >> 3);696self->velocity.y += (((moveY + parentY) - self->position.y) >> 5) - (self->velocity.y >> 3);697self->position.y += self->velocity.y;698self->position.x += self->velocity.x;699700int32 percent = 0x1800;701for (int32 i = 0; i < 7; ++i) {702self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, x2, y2, x2, y2, self->position.x, self->position.y);703percent += 0x2000;704}705706parent->rubyPos.x = self->armPositions[6].x;707parent->rubyPos.y = self->armPositions[6].y;708709if (self->direction)710parent->rubyPos.x += 0x80000;711else712parent->rubyPos.x -= 0x80000;713714parent->rubyPos.y += 0x80000;715}716717void KleptoMobile_StateHand_Boss(void)718{719RSDK_THIS(KleptoMobile);720721EntityKleptoMobile *parent = self->parent;722723self->direction = parent->direction;724int32 parentX = parent->position.x;725int32 parentY = parent->position.y - 0x180000;726727int32 x = 0;728if (self->direction == FLIP_X)729x = ((parent->position.x + self->position.x) >> 1) + 0x80000;730else731x = ((parent->position.x + self->position.x) >> 1) - 0x80000;732int32 y = ((parentY + self->position.y) >> 1) - 0x300000;733734int32 percent = 0x1800;735for (int32 i = 0; i < 7; ++i) {736self->armPositions[i] = MathHelpers_GetBezierPoint(percent, parentX, parentY, x, y, x, y, self->position.x, self->position.y);737percent += 0x2000;738}739740parent->rubyPos.x = self->armPositions[6].x;741parent->rubyPos.y = self->armPositions[6].y;742743parent->rubyPos.x += self->direction ? 0x80000 : -0x80000;744parent->rubyPos.y += 0x80000;745}746747void KleptoMobile_Draw_Hand(void)748{749RSDK_THIS(KleptoMobile);750751EntityKleptoMobile *parent = self->parent;752753if (parent->holdingRuby)754RSDK.DrawSprite(&parent->rubyAnimator, &parent->rubyPos, false);755756for (int32 i = 0; i < 6; ++i) RSDK.DrawSprite(&self->orbAnimator, &self->armPositions[i], false);757758RSDK.DrawSprite(&self->handAnimator, &self->armPositions[6], false);759}760761void KleptoMobile_CheckPlayerCollisions_Arm(void)762{763RSDK_THIS(KleptoMobile);764765foreach_active(Player, player)766{767if (Player_CheckCollisionTouch(player, self, &self->hitbox)) {768if (player->superState == SUPERSTATE_SUPER) {769if (!player->blinkTimer) {770RSDK.PlaySfx(KleptoMobile->sfxWhack, false, 255);771player->blinkTimer = 120;772773if (self->state == KleptoMobile_StateArm_ChargeAttack) {774player->velocity.x = self->parent->velocity.x >> 2;775player->groundVel = player->velocity.x;776777if (player->position.y <= self->position.y || player->onGround) {778player->onGround = false;779player->velocity.y = -0xA0000;780}781else {782player->velocity.y = 0xA0000;783}784}785else {786player->velocity.x = self->velocity.x >> 2;787player->velocity.y = self->velocity.y >> 2;788player->groundVel = player->velocity.x;789}790791player->rotation = 0;792RSDK.SetSpriteAnimation(player->aniFrames, ANI_RUN, &player->animator, false, 0);793player->state = ERZStart_State_PlayerRebound;794}795}796else {797Player_Hurt(player, self);798}799}800}801}802803void KleptoMobile_StateArm_Cutscene(void)804{805RSDK_THIS(KleptoMobile);806EntityKleptoMobile *parent = self->parent;807808int32 parentX = parent->position.x;809int32 parentY = parent->position.y;810811int32 moveX = 0;812int32 moveY = ((RSDK.Sin256((((self->type << 7) - 128) ^ 0x80) + 2 * Zone->timer) + 512) << 12) + parentY;813self->direction = parent->direction;814815int32 x = 0, y = 0;816int32 x2 = 0, y2 = 0;817818if (self->direction == FLIP_X) {819moveX = parentX + 0x300000;820x = parentX - 0x30000;821y = parentY + 0xD0000;822823if (self->type == KLEPTOMOBILE_ARM_R) {824x += 0x180000;825moveX = parentX + 0x600000;826}827828x2 = ((self->position.x + x) >> 1) - 0x100000;829y2 = ((self->position.y + y) >> 1) + 0x100000;830}831else {832moveX = parentX - 0x300000;833x = parentX + 0x30000;834y = parentY + 0xD0000;835836if (self->type == KLEPTOMOBILE_ARM_R) {837x -= 0x180000;838moveX = parentX - 0x600000;839}840841x2 = ((self->position.x + x) >> 1) + 0x200000;842y2 = ((self->position.y + y) >> 1) + 0x200000;843}844845self->velocity.x += ((moveX - self->position.x) >> 5) - (self->velocity.x >> 3);846self->velocity.y += ((moveY - self->position.y) >> 5) - (self->velocity.y >> 3);847self->position.x += self->velocity.x;848self->position.y += self->velocity.y;849850int32 percent = 0x1800;851for (int32 i = 0; i < 7; ++i) {852self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, x2, y2, x2, y2, self->position.x, self->position.y);853percent += 0x2000;854}855856RSDK.ProcessAnimation(&self->finger1Animator);857RSDK.ProcessAnimation(&self->finger2Animator);858}859860void KleptoMobile_StateArm_Idle(void)861{862RSDK_THIS(KleptoMobile);863864EntityKleptoMobile *parent = self->parent;865866self->direction = parent->direction;867868int32 x = 0;869int32 y = parent->position.y + 0x60000;870if (self->direction == FLIP_X) {871x = parent->position.x - 0x90000;872if (self->type == KLEPTOMOBILE_ARM_R)873x += 0x180000;874}875else {876x = parent->position.x + 0x90000;877if (self->type == KLEPTOMOBILE_ARM_R)878x -= 0x180000;879}880881int32 percent = 0x1800;882for (int32 i = 0; i < 7; ++i) {883self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, self->armBezierPos.x, self->armBezierPos.y, self->armBezierPos.x,884self->armBezierPos.y, self->position.x, self->position.y);885percent += 0x2000;886}887888RSDK.ProcessAnimation(&self->finger1Animator);889RSDK.ProcessAnimation(&self->finger2Animator);890}891892void KleptoMobile_StateArm_BashAttack(void)893{894RSDK_THIS(KleptoMobile);895896EntityKleptoMobile *parent = self->parent;897898if (!self->timer) {899self->bashArmStartPos.x = self->position.x;900self->bashArmStartPos.y = self->position.y;901}902903self->direction = parent->direction;904905int32 x = 0;906int32 y = parent->position.y + 0xD0000;907908int32 x2 = 0, y2 = 0;909int32 x3 = 0, y3 = 0;910911if (self->direction == FLIP_X) {912x = parent->position.x - 0x30000;913if (self->type == KLEPTOMOBILE_ARM_R)914x += 0x180000;915}916else {917x = parent->position.x + 0x30000;918if (self->type == KLEPTOMOBILE_ARM_R)919x -= 0x180000;920}921922if (self->timer >= 4) {923x2 = x;924y2 = y;925x3 = self->position.x;926y3 = self->position.y;927self->position.x = self->bashArmTargetPos.x;928self->position.y = self->bashArmTargetPos.y;929}930else {931self->velocity.x = (self->bashArmTargetPos.x - self->bashArmStartPos.x) / 4;932self->velocity.y = (self->bashArmTargetPos.y - self->bashArmStartPos.y) / 4;933934self->position.x += self->velocity.x;935self->position.y += self->velocity.y;936937int32 distX = self->position.x - x;938int32 distY = self->position.y - y;939940int32 distValX = -distX;941int32 distValY = distY;942if (distX > 0) {943distValX = distX;944distValY = -distY;945}946947int32 timerVal = 4 - self->timer;948x2 = x + ((x + (distX >> 1) + distValY) - x) * timerVal / 4;949y2 = y + (((distY >> 1) + distValX) * timerVal) / 4;950x3 = self->position.x + ((x + (distX >> 1) + distValY) - self->position.x) * timerVal / 4;951y3 = self->position.y + (((distY >> 1) + distValX - self->position.y) * timerVal / 4);952}953954int32 percent = 0x1800;955for (int32 i = 0; i < 7; ++i) {956self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, x2, y2, x3, y3, self->position.x, self->position.y);957percent += 0x2000;958}959960if (++self->timer > 15) {961self->timer = 0;962self->state = KleptoMobile_StateArm_Cutscene;963}964965RSDK.ProcessAnimation(&self->finger1Animator);966RSDK.ProcessAnimation(&self->finger2Animator);967968if (parent->health > 0)969KleptoMobile_CheckPlayerCollisions_Arm();970}971972void KleptoMobile_StateArm_ChargeAttack(void)973{974RSDK_THIS(KleptoMobile);975976EntityKleptoMobile *parent = self->parent;977978int32 parentX = parent->position.x;979int32 parentY = parent->position.y;980981int32 moveX = 0;982int32 moveY = parentY + 0x180000;983if (self->type == KLEPTOMOBILE_ARM_R)984moveY = parentY - 0x180000;985986moveX = parentX - 0x400000;987if (self->direction == FLIP_X)988moveX = parentX + 0x400000;989990self->direction = parent->direction;991992int32 x = 0;993int32 y = parent->position.y + 0xD0000;994if (self->direction == FLIP_X) {995moveX += 0x300000;996x = parent->position.x - 0x30000;997998if (self->type == KLEPTOMOBILE_ARM_R) {999x += 0x180000;1000moveX += 0x300000;1001}1002}1003else {1004moveX -= 0x300000;1005x = parent->position.x + 0x30000;10061007if (self->type == KLEPTOMOBILE_ARM_R) {1008x -= 0x180000;1009moveX -= 0x300000;1010}1011}10121013int32 x2 = (x + self->position.x) >> 1;1014int32 y2 = (y + self->position.y) >> 1;10151016self->velocity.x += ((moveX - self->position.x) >> 5) - (self->velocity.x >> 3);1017self->velocity.y += ((moveY - self->position.y) >> 5) - (self->velocity.y >> 3);1018self->position.x += self->velocity.x;1019self->position.y += self->velocity.y;10201021int32 percent = 0x1800;1022for (int32 i = 0; i < 7; ++i) {1023self->armPositions[i] = MathHelpers_GetBezierPoint(percent, x, y, x2, y2, x2, y2, self->position.x, self->position.y);1024percent += 0x2000;1025}10261027RSDK.ProcessAnimation(&self->finger1Animator);1028RSDK.ProcessAnimation(&self->finger2Animator);10291030if (parent->health > 0)1031KleptoMobile_CheckPlayerCollisions_Arm();1032}10331034void KleptoMobile_Draw_Arm(void)1035{1036RSDK_THIS(KleptoMobile);10371038for (int32 i = 0; i < 6; ++i) RSDK.DrawSprite(&self->orbAnimator, &self->armPositions[i], false);10391040RSDK.DrawSprite(&self->handAnimator, &self->armPositions[6], false);10411042if (self->direction) {1043if (self->type == KLEPTOMOBILE_ARM_R) {1044self->armPositions[6].x += 0x280000;1045self->armPositions[6].y -= 0x80000;1046RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);10471048self->armPositions[6].y += 0x100000;1049RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);10501051self->armPositions[6].x -= 0x40000;1052self->armPositions[6].y -= 0x80000;1053RSDK.DrawSprite(&self->finger2Animator, &self->armPositions[6], false);1054}1055else {1056self->armPositions[6].x += 0x280000;1057RSDK.DrawSprite(&self->finger2Animator, &self->armPositions[6], false);10581059self->armPositions[6].x -= 0x40000;1060self->armPositions[6].y -= 0x80000;1061RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);10621063self->armPositions[6].y += 0x100000;1064RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);10651066self->armPositions[6].y -= 0x80000;1067}1068}1069else {1070if (self->type == KLEPTOMOBILE_ARM_R) {1071self->armPositions[6].x -= 0x280000;1072self->armPositions[6].y -= 0x80000;1073RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);10741075self->armPositions[6].y += 0x100000;1076RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);10771078self->armPositions[6].x += 0x40000;1079self->armPositions[6].y -= 0x80000;1080RSDK.DrawSprite(&self->finger2Animator, &self->armPositions[6], false);1081}1082else {1083self->armPositions[6].x -= 0x280000;1084RSDK.DrawSprite(&self->finger2Animator, &self->armPositions[6], false);10851086self->armPositions[6].x += 0x40000;1087self->armPositions[6].y -= 0x80000;1088RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);10891090self->armPositions[6].y += 0x100000;1091RSDK.DrawSprite(&self->finger1Animator, &self->armPositions[6], false);10921093self->armPositions[6].y -= 0x80000;1094}1095}1096}10971098void KleptoMobile_State_Destroyed(void)1099{1100RSDK_THIS(KleptoMobile);11011102RSDK.ProcessAnimation(&self->eggmanAnimator);11031104KleptoMobile_Explode();11051106if (++self->timer == 96) {1107Debris_CreateFromEntries(KleptoMobile->aniFrames, KleptoMobile->debrisInfo, 18);1108}1109else if (self->timer == 144) {1110KleptoMobile->defeated = true;1111if (PhantomKing->defeated) {1112self->position.y += 0x1000000;1113foreach_all(ERZOutro, outro)1114{1115outro->active = ACTIVE_NORMAL;1116foreach_break;1117}1118}1119self->state = KleptoMobile_State_Explode;1120}1121}11221123void KleptoMobile_State_Explode(void)1124{1125RSDK_THIS(KleptoMobile);11261127self->velocity.y += 0x2800;1128self->position.y += self->velocity.y;11291130KleptoMobile_Explode();11311132if (self->position.y >= 0x2800000) {1133if (!PhantomKing->defeated) {1134KleptoMobile_SwitchToKing();1135self->state = StateMachine_None;1136}1137}1138}11391140void KleptoMobile_State_CutsceneExplode(void)1141{1142RSDK_THIS(KleptoMobile);11431144RSDK.ProcessAnimation(&self->eggmanAnimator);11451146self->originPos.x += self->velocity.x;1147self->originPos.y += self->velocity.y;1148self->position.x = self->originPos.x;1149self->position.y = self->originPos.y;11501151if (self->explosionVolume > 0) {1152KleptoMobile_Explode();11531154if (self->timer >= 60)1155self->explosionVolume -= 2;1156self->timer++;1157}1158}11591160#if GAME_INCLUDE_EDITOR1161void KleptoMobile_EditorDraw(void)1162{1163RSDK_THIS(KleptoMobile);1164self->updateRange.x = 0x800000;1165self->updateRange.y = 0x800000;1166self->visible = true;1167self->drawFX = FX_FLIP;1168RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 0, &self->mobileTopAnimator, false, 1);1169RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 13, &self->eggmanAnimator, false, 0);1170RSDK.SetSpriteAnimation(KleptoMobile->aniFrames, 0, &self->mobileAnimator, false, 0);11711172KleptoMobile_Draw_KleptoMobile();1173}11741175void KleptoMobile_EditorLoad(void)1176{1177KleptoMobile->aniFrames = RSDK.LoadSpriteAnimation("Phantom/KleptoMobile.bin", SCOPE_STAGE);11781179RSDK_ACTIVE_VAR(KleptoMobile, type);1180RSDK_ENUM_VAR("Eggman", KLEPTOMOBILE_EGGMAN);1181}1182#endif11831184void KleptoMobile_Serialize(void) { RSDK_EDITABLE_VAR(KleptoMobile, VAR_ENUM, type); }118511861187