Path: blob/master/SonicMania/Objects/Puyo/PuyoBean.c
338 views
// ---------------------------------------------------------------------1// RSDK Project: Sonic Mania2// Object Description: PuyoBean Object3// Object Author: Christian Whitehead/Simon Thomley/Hunter Bridges4// Decompiled by: Rubberduckycooly & RMGRich5// ---------------------------------------------------------------------67#include "Game.h"89ObjectPuyoBean *PuyoBean;1011void PuyoBean_Update(void)12{13RSDK_THIS(PuyoBean);1415StateMachine_Run(self->state);16}1718void PuyoBean_LateUpdate(void) {}1920void PuyoBean_StaticUpdate(void)21{22RSDK.ProcessAnimation(&PuyoBean->junkBeanAnimator);2324if (--PuyoBean->shinkDelay <= 0) {25PuyoBean->shinkDelay = 15 * RSDK.Rand(1, 24);2627RSDK.SetSpriteAnimation(PuyoBean->aniFrames, PUYOBEAN_JUNK + PUYOBEAN_ANI_BOUNCE, &PuyoBean->junkBeanAnimator, true, 0);28}29}3031void PuyoBean_Draw(void)32{33RSDK_THIS(PuyoBean);3435if (SceneInfo->state != ENGINESTATE_FROZEN) {36if (self->isJunk)37RSDK.DrawSprite(&PuyoBean->junkBeanAnimator, NULL, false);38else39RSDK.DrawSprite(&self->beanAnimator, NULL, false);40}41}4243void PuyoBean_Create(void *data)44{45RSDK_THIS(PuyoBean);46if (!SceneInfo->inEditor) {47self->visible = true;48self->drawGroup = PuyoGame ? Zone->objectDrawGroup[0] : Zone->objectDrawGroup[1];49self->active = ACTIVE_NORMAL;50self->updateRange.x = 0x800000;51self->updateRange.y = 0x800000;5253self->angle = 0xC0;54self->position.x = (self->position.x & 0xFFF00000) + 0x80000;55self->position.y = (self->position.y & 0xFFF00000) + 0x80000;56self->origin.x = self->position.x - 0x280000;57self->origin.y = self->position.y - 0x80000;58self->controllerID = CONT_P1;5960self->type = VOID_TO_INT(data);61if (self->type == PUYOBEAN_JUNK)62self->isJunk = true;6364RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type, &self->beanAnimator, true, 0);65}66}6768void PuyoBean_StageLoad(void)69{70PuyoBean->aniFrames = RSDK.LoadSpriteAnimation("Puyo/PuyoBeans.bin", SCOPE_STAGE);71RSDK.SetSpriteAnimation(PuyoBean->aniFrames, PUYOBEAN_JUNK + PUYOBEAN_ANI_BOUNCE, &PuyoBean->junkBeanAnimator, true, 10);7273PuyoBean->hitboxBean.left = -8;74PuyoBean->hitboxBean.top = -8;75PuyoBean->hitboxBean.right = 8;76PuyoBean->hitboxBean.bottom = 8;7778PuyoBean->sfxLand = RSDK.GetSfx("Puyo/Land.wav");79PuyoBean->sfxRotate = RSDK.GetSfx("Puyo/Rotate.wav");80PuyoBean->sfxJunk = RSDK.GetSfx("Puyo/Junk.wav");81PuyoBean->sfxFall = RSDK.GetSfx("Puyo/Fall.wav");82PuyoBean->chainFrames[0] = RSDK.GetSfx("Puyo/Chain0.wav");83PuyoBean->chainFrames[1] = RSDK.GetSfx("Puyo/Chain1.wav");84PuyoBean->chainFrames[2] = RSDK.GetSfx("Puyo/Chain2.wav");85PuyoBean->chainFrames[3] = RSDK.GetSfx("Puyo/Chain3.wav");86PuyoBean->chainFrames[4] = RSDK.GetSfx("Puyo/Chain4.wav");87PuyoBean->chainFrames[5] = RSDK.GetSfx("Puyo/Chain5.wav");8889for (int32 i = 0; i < 0x100; ++i) PuyoBean->playfield[i] = NULL;90}9192EntityPuyoBean *PuyoBean_GetPuyoBean(int32 playerID, int32 x, int32 y)93{94if (x >= 0 && y >= 0 && x < PUYO_PLAYFIELD_W && y < PUYO_PLAYFIELD_H)95return PuyoBean->playfield[128 * playerID + 8 * y + x];9697return NULL;98}99100void PuyoBean_Input_Player(void)101{102RSDK_THIS(PuyoBean);103104if (self->controllerID < PLAYER_COUNT) {105RSDKControllerState *controller = &ControllerInfo[self->controllerID];106RSDKAnalogState *stick = &AnalogStickInfoL[self->controllerID];107108self->down = controller->keyDown.down;109self->left = controller->keyLeft.down;110self->right = controller->keyRight.down;111112#if MANIA_USE_PLUS113self->down |= stick->vDelta < -0.3;114self->left |= stick->hDelta < -0.3;115self->right |= stick->hDelta > 0.3;116#else117self->down |= stick->vDeltaL < -0.3;118self->left |= stick->hDeltaL < -0.3;119self->right |= stick->hDeltaL > 0.3;120#endif121122if (self->left && self->right) {123self->left = false;124self->right = false;125}126self->rotateLeft = controller->keyB.press || controller->keyC.press || controller->keyY.press;127self->rotateRight = controller->keyA.press || controller->keyX.press;128self->forceRotateLeft = false;129self->forceRotateRight = false;130}131}132133void PuyoBean_DestroyPuyoBeans(void)134{135foreach_all(PuyoBean, bean)136{137if (bean->stateInput && bean->state == PuyoBean_State_Controlled) {138if (bean->partner)139destroyEntity(bean->partner);140141destroyEntity(bean);142}143}144}145146void PuyoBean_HandleBeanLinks(void)147{148RSDK_THIS(PuyoBean);149150if (self->stillPos.x >= 0 && self->stillPos.y < PUYO_PLAYFIELD_H && self->stillPos.x < PUYO_PLAYFIELD_W) {151self->linkSides = 0;152self->linkCount = 0;153154int32 ny = self->stillPos.y - 1;155self->linkBeans[0] = NULL;156if (ny > -1) {157EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, self->stillPos.x, ny);158if (bean && bean->type == self->type) {159self->linkSides |= 1;160self->linkBeans[0] = bean;161}162}163164ny = self->stillPos.y + 1;165self->linkBeans[1] = NULL;166if (ny < PUYO_PLAYFIELD_H) {167EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, self->stillPos.x, ny);168if (bean && bean->type == self->type) {169self->linkSides |= 2;170self->linkBeans[1] = bean;171}172}173174int32 nx = self->stillPos.x - 1;175self->linkBeans[2] = NULL;176if (nx > -1) {177EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, nx, self->stillPos.y);178if (bean && bean->type == self->type) {179self->linkSides |= 4;180self->linkBeans[2] = bean;181}182}183184nx = self->stillPos.x + 1;185self->linkBeans[3] = NULL;186if (nx < PUYO_PLAYFIELD_W) {187EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, nx, self->stillPos.y);188if (bean && bean->type == self->type) {189self->linkSides |= 8;190self->linkBeans[3] = bean;191}192}193}194}195196void PuyoBean_CheckBeanLinks(EntityPuyoBean *bean, EntityPuyoBean *curLink)197{198RSDK_THIS(PuyoBean);199200EntityPuyoBean **linkBeans = bean->linkBeans;201for (int32 i = 0; i < 4; ++i) {202EntityPuyoBean *link = bean->linkBeans[i];203204if (link && link != curLink) {205if (++self->linkCount > 2) // Beans can only be connected on 2 axis at a time206return;207208PuyoBean_CheckBeanLinks(link, bean);209}210211++linkBeans;212}213}214215void PuyoBean_HandleMoveBounds(void)216{217RSDK_THIS(PuyoBean);218219EntityPuyoBean *partner = self->partner;220int32 entityX = self->stillPos.x;221int32 entityY = self->stillPos.y;222int32 partnerX = partner->stillPos.x;223int32 partnerY = partner->stillPos.y;224225EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, entityX, entityY);226EntityPuyoBean *bean2 = PuyoBean_GetPuyoBean(self->playerID, partnerX, partnerY);227228self->position.x -= entityX << 20;229self->position.y -= entityY << 20;230231bool32 canMoveRight = self->velocity.x < 0;232if (self->velocity.x >= 0) {233canMoveRight =234((self->angle > 0x40 && self->rotateSpeed > 0) || (self->angle < 0xC0 && self->rotateSpeed < 0)) && (self->targetAngle == 0x80);235}236237if (entityX < 0 || partnerX < 0 || (canMoveRight && (bean || bean2))) {238++entityX;239++partnerX;240}241242bool32 canMoveLeft = self->velocity.x > 0;243if (self->velocity.x <= 0) {244canMoveLeft = ((self->angle < 0x40 && self->rotateSpeed < 0) || (self->angle > 0xC0 && self->rotateSpeed > 0)) && (self->targetAngle == 0x00);245}246247if (entityX >= PUYO_PLAYFIELD_W || partnerX >= PUYO_PLAYFIELD_W || (canMoveLeft && (bean || bean2))) {248--entityX;249--partnerX;250}251252bool32 canMoveUp =253((self->angle > 0x00 && self->rotateSpeed > 0) || (self->angle < 0x80 && self->rotateSpeed < 0)) && (self->targetAngle == 0x40);254if (entityY >= PUYO_PLAYFIELD_H || partnerY >= PUYO_PLAYFIELD_H || (canMoveUp && (bean || bean2))) {255--entityY;256--partnerY;257}258259self->stillPos.x = entityX;260self->stillPos.y = entityY;261partner->stillPos.x = partnerX;262partner->stillPos.y = partnerY;263264self->position.x += self->stillPos.x << 20;265self->position.y += self->stillPos.y << 20;266}267268bool32 PuyoBean_CheckAIRotationDisabled(EntityPuyoBean *bean)269{270if (bean->targetAngle != 0x40 && bean->targetAngle != 0xC0)271return 0;272273uint8 disabledAxis = 0;274int32 y = bean->stillPos.y;275276if (bean->stillPos.x > 0) {277int32 nx = bean->stillPos.x - 1;278if (PuyoBean_GetPuyoBean(bean->playerID, nx, y))279disabledAxis = 1;280}281else {282disabledAxis = 1;283}284285if (bean->stillPos.x < (PUYO_PLAYFIELD_W - 1)) {286int32 nx = bean->stillPos.x + 1;287if (PuyoBean_GetPuyoBean(bean->playerID, nx, y))288disabledAxis |= 2;289}290else {291disabledAxis |= 2;292}293294return disabledAxis == 3;295}296297void PuyoBean_CheckCollisions(void)298{299RSDK_THIS(PuyoBean);300301EntityPuyoBean *partner = self->partner;302uint8 wallCollisions = 0;303304if (self->position.x - self->origin.x != self->stillPos.x << 20) {305int32 nx = self->stillPos.x - 1;306307if (self->stillPos.x <= 0 || PuyoBean_GetPuyoBean(self->playerID, nx, self->stillPos.y)) {308self->left = false;309wallCollisions = 1;310}311312nx = self->stillPos.x + 1;313if (self->stillPos.x >= (PUYO_PLAYFIELD_W - 1) || PuyoBean_GetPuyoBean(self->playerID, nx, self->stillPos.y)) {314self->right = false;315wallCollisions |= 2;316}317318if (wallCollisions == 3) {319if (self->angle == 0x40 || self->angle == 0xC0) {320if (self->rotateRight)321self->forceRotateLeft = true;322else if (self->rotateLeft)323self->forceRotateRight = true;324}325326self->rotateRight = false;327self->rotateLeft = false;328}329}330331if (partner->position.x - partner->origin.x != partner->stillPos.x << 20) {332if (partner->stillPos.x >= 1) {333int32 nx = partner->stillPos.x - 1;334335if (PuyoBean_GetPuyoBean(self->playerID, nx, partner->stillPos.y)) {336self->left = false;337}338}339else {340self->left = false;341}342343int32 nx = partner->stillPos.x + 1;344if (partner->stillPos.x > 4 || PuyoBean_GetPuyoBean(self->playerID, nx, partner->stillPos.y)) {345self->right = false;346}347}348349self->onGround = false;350partner->onGround = false;351352if (self->position.y & 0xF0000) {353int32 ny = self->stillPos.y + 1;354355if (self->stillPos.y == 13 || PuyoBean_GetPuyoBean(self->playerID, self->stillPos.x, ny)) {356self->onGround = true;357}358}359360if (partner->position.y & 0xF0000) {361int32 ny = partner->stillPos.y + 1;362363if (partner->stillPos.y == 13 || PuyoBean_GetPuyoBean(self->playerID, partner->stillPos.x, ny)) {364partner->onGround = true;365}366}367368self->onGround |= partner->onGround;369}370371int32 PuyoBean_GetBeanChainRemovalCount(int32 playerID, EntityPuyoBean *bean, int32 x, int32 y)372{373PuyoBean->beanLinkTable[PUYO_PLAYFIELD_W * y + x] = true;374375int32 beanLinkCount = 0;376int32 junkBeanCount = 0;377378for (int32 i = 0; i < 4; ++i) {379int32 nx = x;380int32 ny = y;381switch (i) {382case 0: nx = x - 1; break;383case 1: nx = x + 1; break;384case 2: ny = y - 1; break;385case 3: ny = y + 1; break;386default: break;387}388389EntityPuyoBean *beanState = PuyoBean_GetPuyoBean(playerID, nx, ny);390if (beanState) {391if (bean->type == beanState->type) {392PuyoBean_SetupBeanLinkTable(playerID, nx, ny, false);393beanLinkCount += PuyoBean->beanLinkCount;394}395}396397for (int32 b = 0; b < PuyoBean->beanLinkCount; ++b) {398Vector2 positions[4];399400positions[0].x = PuyoBean->beanLinkPositions[b].x - 1;401positions[0].y = PuyoBean->beanLinkPositions[b].y;402403positions[1].x = PuyoBean->beanLinkPositions[b].x + 1;404positions[1].y = PuyoBean->beanLinkPositions[b].y;405406positions[2].x = PuyoBean->beanLinkPositions[b].x;407positions[2].y = PuyoBean->beanLinkPositions[b].y - 1;408409positions[3].x = PuyoBean->beanLinkPositions[b].x;410positions[3].y = PuyoBean->beanLinkPositions[b].y + 1;411412for (int32 p = 0; p < 4; ++p) {413int32 bx = positions[p].x;414int32 by = positions[p].y;415416beanState = PuyoBean_GetPuyoBean(playerID, bx, by);417if (beanState && !PuyoBean->beanLinkTable[bx + PUYO_PLAYFIELD_W * by]) {418if (beanState->isJunk) {419PuyoBean->beanLinkTable[bx + PUYO_PLAYFIELD_W * by] = true;420++junkBeanCount;421}422}423}424}425}426427return junkBeanCount + beanLinkCount + 1;428}429430int32 PuyoBean_GetAvailableLinks(int32 playerID, EntityPuyoBean *bean, int32 x, int32 y)431{432int32 availableLinks = 0;433for (int32 i = 0; i < 4; ++i) {434int32 bx = x;435int32 by = y;436switch (i) {437default: break;438case 0: bx = x - 1; break;439case 1: bx = x + 1; break;440case 2: by = y - 1; break;441case 3: by = y + 1; break;442}443444EntityPuyoBean *beanState = PuyoBean_GetPuyoBean(playerID, bx, by);445if (beanState && beanState->type != bean->type) {446PuyoBean_SetupBeanLinkTable(playerID, bx, by, true);447448if (PuyoBean->beanLinkCount >= 3 && !PuyoBean_CheckLinkPosAvailable(playerID, x, y)) {449availableLinks++;450}451}452}453454return availableLinks;455}456457bool32 PuyoBean_CheckLinkPosAvailable(int32 playerID, int32 x, int32 y)458{459bool32 beanLinkTable[0x101];460memset(beanLinkTable, 0, sizeof(beanLinkTable));461462for (int32 b = 0; b < PuyoBean->beanLinkCount; ++b) {463Vector2 possibleLinks[4];464465possibleLinks[0].x = PuyoBean->beanLinkPositions[b].x - 1;466possibleLinks[0].y = PuyoBean->beanLinkPositions[b].y;467468possibleLinks[1].x = PuyoBean->beanLinkPositions[b].x + 1;469possibleLinks[1].y = PuyoBean->beanLinkPositions[b].y;470471possibleLinks[2].x = PuyoBean->beanLinkPositions[b].x;472possibleLinks[2].y = PuyoBean->beanLinkPositions[b].y - 1;473474possibleLinks[3].x = PuyoBean->beanLinkPositions[b].x;475possibleLinks[3].y = PuyoBean->beanLinkPositions[b].y + 1;476477for (int32 i = 0; i < 4; ++i) {478int32 bx = possibleLinks[i].x;479int32 by = possibleLinks[i].y;480481if (!beanLinkTable[bx + PUYO_PLAYFIELD_W * by]) {482beanLinkTable[bx + PUYO_PLAYFIELD_W * by] = true;483484if (bx != x || by != y) {485EntityPuyoBean *beanState = PuyoBean_GetPuyoBean(playerID, bx, by);486if (bx >= 0 && by >= 0 && bx <= (PUYO_PLAYFIELD_W - 1) && by <= (PUYO_PLAYFIELD_H - 1) && !beanState)487return true;488}489}490}491}492493return false;494}495496void PuyoBean_SetupBeanLinkTable(int32 playerID, int32 x, int32 y, bool32 useTempTable)497{498bool32 tempBeanLinkTable[0x101];499memset(tempBeanLinkTable, 0, sizeof(tempBeanLinkTable));500501bool32 *beanLinkTable = useTempTable ? tempBeanLinkTable : PuyoBean->beanLinkTable;502503if (!beanLinkTable[x + PUYO_PLAYFIELD_W * y]) {504for (int32 i = 0; i < (PUYO_PLAYFIELD_W * PUYO_PLAYFIELD_H); ++i) {505PuyoBean->beanLinkPositions[i].x = 0;506PuyoBean->beanLinkPositions[i].y = 0;507}508509PuyoBean->beanLinkCount = 0;510if (x >= 0 && y >= 0 && x <= (PUYO_PLAYFIELD_W - 1) && y <= (PUYO_PLAYFIELD_H - 1)) {511EntityPuyoBean *beanState = PuyoBean_GetPuyoBean(playerID, x, y);512if (beanState) {513PuyoBean->beanLinkPositions[0].x = x;514PuyoBean->beanLinkPositions[0].y = y;515int32 linkCount = 1;516517for (int32 p = 0; p < linkCount; ++p) {518if (linkCount >= 0xFF)519break;520521int32 bx = PuyoBean->beanLinkPositions[linkCount].x;522int32 by = PuyoBean->beanLinkPositions[linkCount].y;523524EntityPuyoBean *startBean = NULL, *curBean = NULL;525startBean = PuyoBean_GetPuyoBean(playerID, bx, by);526527beanLinkTable[bx + PUYO_PLAYFIELD_W * by] = true;528529if (startBean) {530int32 nx = bx - 1;531if (nx >= 0) {532if (!beanLinkTable[nx + PUYO_PLAYFIELD_W * by] && by >= 0 && nx <= (PUYO_PLAYFIELD_W - 1)533&& by <= (PUYO_PLAYFIELD_H - 1)) {534curBean = PuyoBean_GetPuyoBean(playerID, nx, by);535536if (curBean && beanState->type == curBean->type) {537PuyoBean->beanLinkPositions[linkCount].x = nx;538PuyoBean->beanLinkPositions[linkCount++].y = by;539}540}541}542543nx = bx + 1;544if (nx <= (PUYO_PLAYFIELD_W - 1)) {545if (!beanLinkTable[nx + PUYO_PLAYFIELD_W * by] && nx >= 0 && by >= 0 && by <= (PUYO_PLAYFIELD_H - 1)) {546curBean = PuyoBean_GetPuyoBean(playerID, nx, by);547548if (curBean && beanState->type == curBean->type) {549PuyoBean->beanLinkPositions[linkCount].x = nx;550PuyoBean->beanLinkPositions[linkCount++].y = by;551}552}553}554555int32 ny = by - 1;556if (ny >= 0) {557if (!beanLinkTable[bx + PUYO_PLAYFIELD_W * ny] && bx >= 0 && bx <= (PUYO_PLAYFIELD_W - 1)558&& ny <= (PUYO_PLAYFIELD_H - 1)) {559curBean = PuyoBean_GetPuyoBean(playerID, bx, ny);560561if (curBean && beanState->type == curBean->type) {562PuyoBean->beanLinkPositions[linkCount].x = bx;563PuyoBean->beanLinkPositions[linkCount++].y = ny;564}565}566}567568ny = by + 1;569if (ny <= (PUYO_PLAYFIELD_H - 1)) {570if (!beanLinkTable[bx + PUYO_PLAYFIELD_W * ny] && bx >= 0 && ny >= 0 && bx <= 5) {571curBean = PuyoBean_GetPuyoBean(playerID, bx, ny);572573if (curBean && beanState->type == curBean->type) {574PuyoBean->beanLinkPositions[linkCount].x = bx;575PuyoBean->beanLinkPositions[linkCount++].y = ny;576}577}578}579}580}581582PuyoBean->beanLinkCount = linkCount;583}584}585}586}587588uint8 PuyoBean_GetColumnHeight(int32 playerID, int32 column, EntityPuyoBean *bean, EntityPuyoBean *partner)589{590if (column < 0 || column >= PUYO_PLAYFIELD_W)591return PUYO_PLAYFIELD_H - 1;592593int32 height = 0;594for (int32 y = (PUYO_PLAYFIELD_H - 1); y >= 0; --y) {595if (bean && column == bean->stillPos.x && y == bean->stillPos.y)596break;597598if (partner && column == partner->stillPos.x && y == partner->stillPos.y)599break;600601if (!PuyoBean_GetPuyoBean(playerID, column, y))602break;603604++height;605}606607return height;608}609610void PuyoBean_CalculateStillPos(EntityPuyoBean *bean)611{612if (bean->state == PuyoBean_State_PartnerControlled) {613EntityPuyoBean *partner = bean->partner;614615switch (partner->targetAngle >> 6) {616case 0:617bean->stillPos.y = partner->stillPos.y;618bean->stillPos.x = partner->stillPos.x + 1;619break;620621case 1:622bean->stillPos.x = partner->stillPos.x;623bean->stillPos.y = partner->stillPos.y + 1;624break;625626case 2:627bean->stillPos.y = partner->stillPos.y;628bean->stillPos.x = partner->stillPos.x - 1;629break;630631case 3:632bean->stillPos.x = partner->stillPos.x;633bean->stillPos.y = partner->stillPos.y - 1;634break;635636default:637bean->stillPos.x = partner->stillPos.x;638bean->stillPos.y = partner->stillPos.y;639break;640}641}642else {643bean->stillPos.x = (bean->position.x - bean->origin.x) >> 20;644bean->stillPos.y = (bean->position.y - bean->origin.y) >> 20;645}646}647648void PuyoBean_State_PartnerControlled(void)649{650RSDK_THIS(PuyoBean);651652RSDK.ProcessAnimation(&self->beanAnimator);653}654655void PuyoBean_State_Controlled(void)656{657RSDK_THIS(PuyoBean);658659EntityPuyoBean *partner = self->partner;660661RSDK.ProcessAnimation(&self->beanAnimator);662663if (self->beanAnimator.animationID == self->type + PUYOBEAN_ANI_BOUNCE && self->beanAnimator.frameID == self->beanAnimator.frameCount - 1)664RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type, &self->beanAnimator, true, 0);665666StateMachine_Run(self->stateInput);667668bool32 prevOnGround = self->onGround;669PuyoBean_CheckCollisions();670self->fallDelay = self->down ? 1 : PuyoBean->fallDelays[self->selectedLevel];671672if (!self->rotateSpeed) {673self->targetAngle = self->angle;674self->forceRotationActive = false;675676if (self->rotateRight) {677self->rotateSpeed = -8;678679self->targetAngle = self->angle - 0x40;680if (self->targetAngle < 0)681self->targetAngle += 0x100;682683RSDK.PlaySfx(PuyoBean->sfxRotate, false, 255);684}685else if (self->rotateLeft) {686self->targetAngle = (self->angle + 0x40) & 0xFF;687self->rotateSpeed = 8;688689RSDK.PlaySfx(PuyoBean->sfxRotate, false, 255);690}691else {692if (self->forceRotateLeft) {693self->forceRotationActive = true;694695self->rotateSpeed = -8;696self->targetAngle = (self->angle + 0x80) & 0xFF;697698RSDK.PlaySfx(PuyoBean->sfxRotate, false, 255);699}700else if (self->forceRotateRight) {701self->forceRotationActive = true;702703self->rotateSpeed = 8;704self->targetAngle = (self->angle + 0x80) & 0xFF;705706RSDK.PlaySfx(PuyoBean->sfxRotate, false, 255);707}708}709710if (self->forceRotateLeft || self->forceRotateRight) {711if (self->angle == 0xC0)712self->rotationDir = 1;713else if (self->angle == 0x40)714self->rotationDir = -1;715}716}717718if (self->rotateSpeed < 0) {719self->rotateSpeed++;720self->angle -= 8 * (self->forceRotationActive != false) + 8;721self->angle &= 0xFF;722}723else if (self->rotateSpeed > 0) {724self->rotateSpeed--;725self->angle += 8 * (self->forceRotationActive != false) + 8;726self->angle &= 0xFF;727}728729if (!self->onGround && ++self->fallTimer >= self->fallDelay) {730self->position.y += 0x80000;731self->fallTimer = 0;732}733734if (!(self->moveTimer & 3)) {735if (self->left) {736if (self->velocity.x > 0)737self->moveTimer = 0;738739self->velocity.x = -0x80000;740}741else if (self->right) {742if (self->velocity.x < 0)743self->moveTimer = 0;744745self->velocity.x = 0x80000;746}747}748749if (!self->left && !self->right && !(self->moveTimer & 3)) {750self->moveTimer = 0;751self->velocity.x = 0;752}753else {754if (++self->moveTimer >= 16) {755if ((self->moveTimer & 3) == 1 || (self->moveTimer & 3) == 2)756self->position.x += self->velocity.x;757}758else {759if (self->moveTimer < 3)760self->position.x += self->velocity.x;761}762}763764PuyoBean_CalculateStillPos(self);765PuyoBean_CalculateStillPos(partner);766PuyoBean_HandleMoveBounds();767768partner->position.x = self->position.x + (RSDK.Cos256(self->angle) << 12);769partner->position.y = self->position.y + (RSDK.Sin256(self->angle) << 12);770771if (self->onGround) {772if (!prevOnGround) {773RSDK.SetSpriteAnimation(PuyoBean->aniFrames, partner->type + PUYOBEAN_ANI_BOUNCE, &partner->beanAnimator, false, 0);774RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type + PUYOBEAN_ANI_BOUNCE, &self->beanAnimator, false, 0);775776RSDK.PlaySfx(PuyoBean->sfxLand, false, 255);777}778if (++self->idleTimer > 60 || self->down) {779self->position.x = ((self->origin.x + (self->stillPos.x << 20)) & 0xFFF00000) + 0x80000;780self->position.y = ((self->origin.y + (self->stillPos.y << 20)) & 0xFFF00000) + 0x80000;781partner->position.x = ((partner->origin.x + (partner->stillPos.x << 20)) & 0xFFF00000) + 0x80000;782partner->position.y = ((partner->origin.y + (partner->stillPos.y << 20)) & 0xFFF00000) + 0x80000;783784RSDK.SetSpriteAnimation(PuyoBean->aniFrames, partner->type + PUYOBEAN_ANI_BOUNCE, &partner->beanAnimator, false, 0);785RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type + PUYOBEAN_ANI_BOUNCE, &self->beanAnimator, false, 0);786787self->left = false;788self->right = false;789self->down = false;790self->rotateRight = false;791self->rotateLeft = false;792self->popTimer = 0;793partner->popTimer = 0;794795partner->state = PuyoBean_State_Falling;796self->state = PuyoBean_State_Falling;797}798}799else {800if (self->idleTimer > 0)801self->idleTimer++;802}803}804805void PuyoBean_State_BeanIdle(void)806{807RSDK_THIS(PuyoBean);808809// Check if bean should be falling810if (self->stillPos.y < (PUYO_PLAYFIELD_H - 1)) {811int32 ny = self->stillPos.y + 1;812813if (self->stillPos.x < 0 || ny < 0 || self->stillPos.x > (PUYO_PLAYFIELD_W - 1) || ny > (PUYO_PLAYFIELD_H - 1)814|| !PuyoBean_GetPuyoBean(self->playerID, self->stillPos.x, ny)) {815PuyoBean->playfield[128 * self->playerID + 8 * self->stillPos.y + self->stillPos.x] = NULL;816817RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type + PUYOBEAN_ANI_BOUNCE, &self->beanAnimator, true, 0);818819self->timer = 0;820self->velocity.y = 0;821self->linkBeans[0] = NULL;822self->linkBeans[1] = NULL;823self->linkBeans[2] = NULL;824self->linkBeans[3] = NULL;825self->state = PuyoBean_State_Falling;826self->popTimer = 0;827}828}829830if (self->state == PuyoBean_State_BeanIdle && !PuyoBean->disableBeanLink[self->playerID]) {831// Handle Bean links & Combos832PuyoBean_HandleBeanLinks();833if (self->linkSides > 0)834PuyoBean_CheckBeanLinks(self, NULL);835836if (self->linkCount <= 2) {837self->connectTimer = 0;838}839else if (++self->connectTimer > 2) {840RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type + PUYOBEAN_ANI_CONNECT, &self->beanAnimator, true, self->linkSides);841842self->timer = 0;843self->connectTimer = 0;844self->state = PuyoBean_State_BeginBeanPop;845}846}847848if (self->state == PuyoBean_State_BeanIdle) {849// Handle Bean animations850if (self->linkSides) {851RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type + PUYOBEAN_ANI_CONNECT, &self->beanAnimator, true, self->linkSides);852}853else {854if (self->timer <= 0) {855if (self->beanAnimator.animationID - self->type == PUYOBEAN_ANI_CONNECT) {856RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type + PUYOBEAN_ANI_IDLE, &self->beanAnimator, true, 0);857}858else {859RSDK.ProcessAnimation(&self->beanAnimator);860861if (self->beanAnimator.frameID == self->beanAnimator.frameCount - 1)862self->timer = RSDK.Rand(120, 240);863}864}865else {866if (!--self->timer)867RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type + PUYOBEAN_ANI_IDLE, &self->beanAnimator, true, 0);868}869}870}871}872873void PuyoBean_State_Falling(void)874{875RSDK_THIS(PuyoBean);876877if (++self->timer > 8) {878self->velocity.y += 0x3800;879self->position.y += self->velocity.y;880881foreach_active(PuyoBean, bean)882{883if (bean != self && bean->state == PuyoBean_State_Falling && self->position.x == bean->position.x) {884if (self->position.y + 0x100000 > bean->position.y && self->position.y < bean->position.y) {885self->position.y = bean->position.y - 0x100000;886self->velocity.y = bean->velocity.y;887foreach_break;888}889}890}891892PuyoBean_CalculateStillPos(self);893894if (self->stillPos.y >= 0) {895if (self->stillPos.y > (PUYO_PLAYFIELD_H - 1))896self->stillPos.y = (PUYO_PLAYFIELD_H - 1);897898int32 y = self->stillPos.y;899int32 ny = y + 1;900901if (y >= (PUYO_PLAYFIELD_H - 1) || PuyoBean_GetPuyoBean(self->playerID, self->stillPos.x, ny)) {902if ((self->position.y & 0xF0000) >= 0x80000) {903self->position.y = (y << 20) + self->origin.y + 0x80000;904self->timer = 0;905int32 playfieldSlot = 128 * self->playerID + 8 * self->stillPos.y + self->stillPos.x;906int32 entitySlot = 0x600 + playfieldSlot;907908if (self->isJunk) {909PuyoBean->playfield[playfieldSlot] = RSDK_GET_ENTITY(entitySlot, PuyoBean);910self->state = PuyoBean_State_JunkLand;911if (self->velocity.y > 0x8000)912RSDK.PlaySfx(PuyoBean->sfxJunk, false, 255);913}914else {915PuyoBean->playfield[playfieldSlot] = RSDK_GET_ENTITY(entitySlot, PuyoBean);916self->state = PuyoBean_State_BeanLand;917if (self->velocity.y > 0x8000)918RSDK.PlaySfx(PuyoBean->sfxLand, false, 255);919}920921self->velocity.y = 0;922RSDK.AddDrawListRef(self->drawGroup, entitySlot);923RSDK.CopyEntity(RSDK_GET_ENTITY(entitySlot, PuyoBean), self, true);924}925}926}927}928}929930void PuyoBean_State_BeanLand(void)931{932RSDK_THIS(PuyoBean);933934RSDK.ProcessAnimation(&self->beanAnimator);935936if (self->beanAnimator.frameID == self->beanAnimator.frameCount - 1) {937EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, self->stillPos.x, self->stillPos.y);938if (bean && bean != self)939destroyEntity(bean);940941self->timer = RSDK.Rand(120, 240);942self->state = PuyoBean_State_BeanIdle;943}944}945946void PuyoBean_State_JunkLand(void)947{948RSDK_THIS(PuyoBean);949950if (++self->timer == 2) {951self->timer = 0;952self->state = PuyoBean_State_JunkIdle;953954PuyoBean_CalculateStillPos(self);955}956}957958void PuyoBean_State_JunkIdle(void)959{960RSDK_THIS(PuyoBean);961962bool32 popJunk = false;963964self->linkBeans[0] = NULL;965if (self->stillPos.x > -1) {966int32 y = self->stillPos.y - 1;967if (self->stillPos.x >= 0 && y >= 0 && self->stillPos.x <= (PUYO_PLAYFIELD_W - 1) && y <= (PUYO_PLAYFIELD_H - 1)) {968EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, self->stillPos.x, y);969970if (bean && bean->state == PuyoBean_State_BeanPop && bean->popTimer < 2)971popJunk = true;972}973}974975self->linkBeans[1] = NULL;976if (!popJunk && self->stillPos.y < PUYO_PLAYFIELD_H) {977int32 y = self->stillPos.y + 1;978if (self->stillPos.x >= 0 && y >= 0 && self->stillPos.x <= (PUYO_PLAYFIELD_W - 1) && y <= (PUYO_PLAYFIELD_H - 1)) {979EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, self->stillPos.x, y);980if (bean && bean->state == PuyoBean_State_BeanPop && bean->popTimer < 2)981popJunk = true;982}983}984985self->linkBeans[2] = NULL;986if (!popJunk && self->stillPos.x > -1) {987int32 x = self->stillPos.x - 1;988if (x >= 0 && self->stillPos.y >= 0 && x <= (PUYO_PLAYFIELD_W - 1) && self->stillPos.y <= (PUYO_PLAYFIELD_H - 1)) {989EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, x, self->stillPos.y);990if (bean && bean->state == PuyoBean_State_BeanPop && bean->popTimer < 2)991popJunk = true;992}993}994995self->linkBeans[3] = NULL;996if (!popJunk && self->stillPos.x < PUYO_PLAYFIELD_W) {997int32 x = self->stillPos.x + 1;998if (x >= 0 && self->stillPos.y >= 0 && x <= (PUYO_PLAYFIELD_W - 1) && self->stillPos.y <= (PUYO_PLAYFIELD_H - 1)) {999EntityPuyoBean *bean = PuyoBean_GetPuyoBean(self->playerID, x, self->stillPos.y);1000if (bean && bean->state == PuyoBean_State_BeanPop && bean->popTimer < 2)1001popJunk = true;1002}1003}10041005if (popJunk) {1006RSDK.SetSpriteAnimation(PuyoBean->aniFrames, PUYOBEAN_JUNK + PUYOBEAN_ANI_IDLE, &self->beanAnimator, true, 0);10071008self->state = PuyoBean_State_JunkPopped;1009self->timer = 0;1010}1011else {1012if (self->stillPos.y < (PUYO_PLAYFIELD_H - 1)) {1013int32 y = self->stillPos.y + 1;1014if (self->stillPos.x < 0 || y < 0 || self->stillPos.x > (PUYO_PLAYFIELD_W - 1) || y > (PUYO_PLAYFIELD_H - 1)1015|| !PuyoBean_GetPuyoBean(self->playerID, self->stillPos.x, y)) {1016PuyoBean->playfield[128 * self->playerID + 8 * self->stillPos.y + self->stillPos.x] = NULL;10171018self->velocity.y = 0;1019self->popTimer = 0;1020self->state = PuyoBean_State_Falling;1021self->timer = 0;1022}1023}1024}1025}10261027void PuyoBean_State_JunkPopped(void)1028{1029RSDK_THIS(PuyoBean);10301031bool32 animationFinished = false;1032if (self->beanAnimator.frameID == self->beanAnimator.frameCount - 1) {1033animationFinished = true;1034self->visible = false;1035}1036else {1037RSDK.ProcessAnimation(&self->beanAnimator);1038}10391040if (self->timer < 26)1041self->timer++;10421043if (self->timer == 26)1044PuyoBean->playfield[128 * self->playerID + 8 * self->stillPos.y + self->stillPos.x] = NULL;10451046if (self->timer >= 26 && animationFinished)1047destroyEntity(self);1048}10491050void PuyoBean_State_BeginBeanPop(void)1051{1052RSDK_THIS(PuyoBean);10531054PuyoBean_HandleBeanLinks();10551056++self->timer;1057self->visible = !(self->timer & 1);10581059if (self->timer == 24) {1060self->timer = RSDK.Rand(8, 16);1061self->popTimer = 0;1062RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type + PUYOBEAN_ANI_POP, &self->beanAnimator, true, 0);10631064self->state = PuyoBean_State_BeanPop;1065RSDK.PlaySfx(PuyoBean->chainFrames[MIN(PuyoBean->comboChainCount[self->playerID], 5)], false, 255);1066}1067}10681069void PuyoBean_State_BeanPop(void)1070{1071RSDK_THIS(PuyoBean);10721073if (self->popTimer >= 2)1074PuyoBean->playfield[128 * self->playerID + 8 * self->stillPos.y + self->stillPos.x] = NULL;1075else1076self->popTimer++;10771078if (self->timer <= 0) {1079for (int32 angle = 0; angle < 0x100; angle += 0x20) {1080int32 x = RSDK.Cos256(angle) << 10;1081int32 y = RSDK.Sin256(angle) << 10;1082EntityDebris *debris = CREATE_ENTITY(Debris, NULL, x + self->position.x, y + self->position.y);10831084debris->state = Debris_State_Fall;1085debris->gravityStrength = 0x4000;1086debris->timer = 14;1087debris->velocity.x = RSDK.Cos256(angle) << 9;1088debris->velocity.y = RSDK.Sin256(angle) << 9;1089debris->drawGroup = Zone->objectDrawGroup[1] + 1;1090RSDK.SetSpriteAnimation(PuyoBean->aniFrames, self->type + PUYOBEAN_ANI_DEBRIS, &debris->animator, true, 0);1091}10921093destroyEntity(self);1094}1095else {1096self->timer--;1097}1098}10991100void PuyoBean_State_MatchLoseFall(void)1101{1102RSDK_THIS(PuyoBean);11031104if (self->timer <= 0) {1105self->velocity.y += 0x3800;1106self->position.y += self->velocity.y;11071108Vector2 range = { 0x800000, 0x800000 };1109if (!RSDK.CheckOnScreen(self, &range))1110destroyEntity(self);1111}1112else {1113self->velocity.y = 0;1114self->timer--;1115}1116}11171118#if GAME_INCLUDE_EDITOR1119void PuyoBean_EditorDraw(void) { RSDK.DrawSprite(&PuyoBean->junkBeanAnimator, NULL, false); }11201121void PuyoBean_EditorLoad(void)1122{1123PuyoBean->aniFrames = RSDK.LoadSpriteAnimation("Puyo/PuyoBeans.bin", SCOPE_STAGE);11241125RSDK.SetSpriteAnimation(PuyoBean->aniFrames, PUYOBEAN_BLUE + PUYOBEAN_ANI_FLASH, &PuyoBean->junkBeanAnimator, true, 0);1126}1127#endif11281129void PuyoBean_Serialize(void) {}113011311132