Something went wrong. Try again.
Base for making Paper Mario (N64) mods, built on the decompilation docs.starhaven.dev/papermario-dx
paper-mario n64
Something went wrong. Try again.
86 kB · 2629 lines
C
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630#include "common.h"#include "bound_script.h"#include "npc.h"#include "entity.h"#include "effects.h"#include "sprite.h"#include "world/partners.h"#include "sprite/npc/WorldWatt.h"
s16 gNpcCount;static NpcList gWorldNpcList;static NpcList gBattleNpcList;static NpcList* gCurrentNpcListPtr;static s8 gNpcPlayerCollisionsEnabled;
void destroy_pending_enemies(void);
#define PAL_ANIM_END 0xFF
enum PalSwapState { PAL_SWAP_HOLD_A = 0, PAL_SWAP_A_TO_B = 1, PAL_SWAP_HOLD_B = 2, PAL_SWAP_B_TO_A = 3,};
// palette types for watt palette animationsenum { WATT_DEFAULT = 0, WATT_BRIGHTEST = 1, WATT_BRIGHTER = 2,};
u8 wWattIdlePalettesAnim[] = { WATT_BRIGHTEST, 2, WATT_DEFAULT, 52, WATT_BRIGHTEST, 4, WATT_DEFAULT, 54, WATT_DEFAULT, 54, WATT_BRIGHTEST, 2, WATT_DEFAULT, 28, WATT_BRIGHTEST, 2, WATT_DEFAULT, 6, WATT_BRIGHTEST, 2, WATT_DEFAULT, 44, WATT_BRIGHTEST, 2, WATT_DEFAULT, 44, PAL_ANIM_END};
void STUB_npc_callback(Npc* npc) {}
void mtx_ident_mirror_y(Matrix4f mtx) { guMtxIdentF(mtx); mtx[0][0] = 1.0f; mtx[1][1] = -1.0f; mtx[2][2] = 1.0f; mtx[3][3] = 1.0f;}
void clear_npcs(void) { s32 i;
if (gGameStatusPtr->context == CONTEXT_WORLD) { gCurrentNpcListPtr = &gWorldNpcList; } else { gCurrentNpcListPtr = &gBattleNpcList; }
for (i = 0; i < MAX_NPCS; i++) { (*gCurrentNpcListPtr)[i] = nullptr; }
gNpcCount = 0; gNpcPlayerCollisionsEnabled = true;}
void init_npc_list(void) { if (gGameStatusPtr->context == CONTEXT_WORLD) { gCurrentNpcListPtr = &gWorldNpcList; } else { gCurrentNpcListPtr = &gBattleNpcList; }
gNpcCount = 0; gNpcPlayerCollisionsEnabled = true;}
s32 create_npc_impl(NpcBlueprint* blueprint, AnimID* limitAnimList, s32 isPeachNpc) { Npc* npc; s32 i; s32 j;
for (i = 0; i < MAX_NPCS; i++) { if ((*gCurrentNpcListPtr)[i] == nullptr) { break; } } ASSERT(i < MAX_NPCS);
(*gCurrentNpcListPtr)[i] = npc = heap_malloc(sizeof(*npc)); gNpcCount++; ASSERT(npc != nullptr);
npc->flags = blueprint->flags | (NPC_FLAG_TOUCHES_GROUND | NPC_FLAG_DIRTY_SHADOW | NPC_FLAG_HAS_SHADOW | NPC_FLAG_ENABLED); if (isPeachNpc) { npc->flags |= NPC_FLAG_USES_PLAYER_AUX_SPRITE; }
npc->collisionDiameter = 32; npc->collisionHeight = 64; npc->renderMode = 13; npc->userData.any = nullptr; npc->yaw = 0.0f; npc->jumpVel = 0.0f; npc->pos.x = 0.0f; npc->pos.y = 0.0f; npc->pos.z = 0.0f; npc->colliderPos.x = 0.0f; npc->colliderPos.y = 0.0f; npc->colliderPos.z = 0.0f; npc->rotPivotOffsetY = 0.0f; npc->rot.x = 0.0f; npc->rot.y = 0.0f; npc->rot.z = 0.0f; npc->homePos.x = 0.0f; npc->homePos.y = 0.0f; npc->homePos.z = 0.0f; npc->verticalRenderOffset = 0; npc->alpha = 255; npc->hideAlpha = 255; npc->jumpScale = 1.0f; npc->moveSpeed = 4.0f; npc->scale.x = 1.0f; npc->scale.y = 1.0f; npc->scale.z = 1.0f; npc->curAnim = blueprint->initialAnim; npc->animationSpeed = 1.0f; npc->renderYaw = 0.0f; npc->imgfxType = IMGFX_CLEAR; npc->imgfxFlags = 0; npc->collisionChannel = COLLIDER_FLAG_IGNORE_NPC; npc->isFacingAway = false; npc->yawCamOffset = 0; npc->turnAroundYawAdjustment = 0; npc->curFloor = NO_COLLIDER; npc->curWall = NO_COLLIDER; npc->palSwapType = NPC_PAL_ADJUST_NONE; npc->palSwapPrevType = NPC_PAL_ADJUST_NONE; npc->screenSpaceOffset2D[0] = 0.0f; npc->screenSpaceOffset2D[1] = 0.0f; npc->verticalStretch = 1.0f;
for (j = 0; j < ARRAY_COUNT(npc->decorations); j++) { npc->decorations[j] = 0; npc->decorationType[j] = 0; }
npc->onUpdate = blueprint->onUpdate; if (npc->onUpdate == nullptr) { npc->onUpdate = &STUB_npc_callback; }
npc->onRender = blueprint->onRender; if (npc->onRender == nullptr) { npc->onRender = &STUB_npc_callback; } if (!isPeachNpc) { npc->limitAnimList = limitAnimList; if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE)) { if (!(npc->flags & NPC_FLAG_PARTNER)) { npc->spriteInstanceID = spr_load_npc_sprite(npc->curAnim, limitAnimList); } else { npc->spriteInstanceID = spr_load_npc_sprite(npc->curAnim | SPRITE_ID_TAIL_ALLOCATE, limitAnimList); } } else { npc->flags |= NPC_FLAG_INVISIBLE; } }
npc->shadowIndex = create_shadow_type(SHADOW_VARYING_CIRCLE, npc->pos.x, npc->pos.y, npc->pos.z); npc->shadowScale = 1.0f;
if (gGameStatusPtr->context != CONTEXT_WORLD) { i |= BATTLE_NPC_ID_BIT; } return i;}
s32 create_basic_npc(NpcBlueprint* blueprint) { return create_npc_impl(blueprint, nullptr, false);}
s32 create_standard_npc(NpcBlueprint* blueprint, AnimID* limitAnimList) { return create_npc_impl(blueprint, limitAnimList, false);}
s32 create_peach_npc(NpcBlueprint* blueprint) { return create_npc_impl(blueprint, nullptr, true);}
void free_npc_by_index(s32 listIndex) { Npc* npc; s32 i;
listIndex &= ~BATTLE_NPC_ID_BIT;
npc = (*gCurrentNpcListPtr)[listIndex]; if (npc != nullptr) { if (npc->flags) { if (npc->userData.any != nullptr) { heap_free(npc->userData.any); npc->userData.any = nullptr; }
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { ASSERT((npc->flags & NPC_FLAG_HAS_NO_SPRITE) || !spr_free_sprite(npc->spriteInstanceID)); }
delete_shadow(npc->shadowIndex);
for (i = 0; i < MAX_NPC_DECORATIONS; i++) { npc_remove_decoration(npc, i); }
if (npc->flags & NPC_FLAG_MOTION_BLUR) { disable_npc_blur(npc); }
heap_free((*gCurrentNpcListPtr)[listIndex]); (*gCurrentNpcListPtr)[listIndex] = nullptr; gNpcCount--; } }}
void free_npc(Npc* npc) { s32 i;
if (npc->userData.any != nullptr) { heap_free(npc->userData.any); npc->userData.any = nullptr; }
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { ASSERT((npc->flags & NPC_FLAG_HAS_NO_SPRITE) || !spr_free_sprite(npc->spriteInstanceID)); }
delete_shadow(npc->shadowIndex);
for (i = 0; i < MAX_NPC_DECORATIONS; i++) { npc_remove_decoration(npc, i); }
if (npc->flags & NPC_FLAG_MOTION_BLUR) { disable_npc_blur(npc); }
heap_free(npc);
for (i = 0; i < MAX_NPCS; i++) { if ((*gCurrentNpcListPtr)[i] == npc) { break; } }
(*gCurrentNpcListPtr)[i] = nullptr; gNpcCount--;}
Npc* get_npc_by_index(s32 listIndex) { return (*gCurrentNpcListPtr)[listIndex & ~BATTLE_NPC_ID_BIT];}
// checks for lateral collision with the world directly in front of the NPC and in an X pattern offset 45 degrees from// the forward direction. when a collision is detected, the NPC position is adjusted an appropriate NPC flags are set// to signal the collision has occurred.void npc_do_world_collision(Npc* npc) { f32 testAngle; s32 hit; f32 testX; f32 testY; f32 testZ;
if (npc->flags & NPC_FLAG_IGNORE_WORLD_COLLISION) { npc->flags |= NPC_FLAG_WORLD_COLLISION_DIRTY; } else if ((npc->pos.x != npc->colliderPos.x) || (npc->pos.y != npc->colliderPos.y) || (npc->pos.z != npc->colliderPos.z) || npc->flags & NPC_FLAG_WORLD_COLLISION_DIRTY) { npc->flags &= ~NPC_FLAG_WORLD_COLLISION_DIRTY; testAngle = clamp_angle(npc->yaw); testX = npc->pos.x; testY = npc->pos.y; testZ = npc->pos.z;
if (!(npc->flags & NPC_FLAG_PARTNER)) { hit = npc_test_move_simple_with_slipping( npc->collisionChannel, &testX, &testY, &testZ, 0, testAngle, npc->collisionHeight, npc->collisionDiameter ); } else { hit = npc_test_move_complex_with_slipping( npc->collisionChannel, &testX, &testY, &testZ, 0, testAngle, npc->collisionHeight, npc->collisionDiameter ); }
if (hit) { npc->flags |= (NPC_FLAG_COLLIDING_WITH_WORLD | NPC_FLAG_COLLIDING_FORWARD_WITH_WORLD); npc->curWall = NpcHitQueryColliderID; npc->pos.x = testX; npc->pos.z = testZ; } else { npc->flags &= ~(NPC_FLAG_COLLIDING_WITH_WORLD | NPC_FLAG_COLLIDING_FORWARD_WITH_WORLD); }
testAngle = clamp_angle(npc->yaw + 45.0f); testX = npc->pos.x; testY = npc->pos.y; testZ = npc->pos.z;
if (!(npc->flags & NPC_FLAG_PARTNER)) { hit = npc_test_move_simple_with_slipping( npc->collisionChannel, &testX, &testY, &testZ, 0, testAngle, npc->collisionHeight, npc->collisionDiameter ); } else { hit = npc_test_move_taller_with_slipping( npc->collisionChannel, &testX, &testY, &testZ, 0, testAngle, npc->collisionHeight, npc->collisionDiameter ); }
if (hit) { npc->flags |= NPC_FLAG_COLLIDING_WITH_WORLD; npc->pos.x = testX; npc->pos.z = testZ; } else { npc->flags &= ~NPC_FLAG_COLLIDING_WITH_WORLD; }
testAngle = clamp_angle(npc->yaw - 45.0f); testX = npc->pos.x; testY = npc->pos.y; testZ = npc->pos.z; if (!(npc->flags & NPC_FLAG_PARTNER)) { hit = npc_test_move_simple_with_slipping( npc->collisionChannel, &testX, &testY, &testZ, 0, testAngle, npc->collisionHeight, npc->collisionDiameter ); } else { hit = npc_test_move_taller_with_slipping( npc->collisionChannel, &testX, &testY, &testZ, 0, testAngle, npc->collisionHeight, npc->collisionDiameter ); }
if (hit != 0) { npc->flags |= NPC_FLAG_COLLIDING_WITH_WORLD; npc->pos.x = testX; npc->pos.z = testZ; } else { npc->flags &= ~NPC_FLAG_COLLIDING_WITH_WORLD; }
if (npc->flags & NPC_FLAG_PARTNER) { testAngle = clamp_angle(npc->yaw + 45.0f + 180.0f); testX = npc->pos.x; testY = npc->pos.y; testZ = npc->pos.z; if (npc_test_move_simple_with_slipping( npc->collisionChannel, &testX, &testY, &testZ, 0, testAngle, npc->collisionHeight, npc->collisionDiameter ) != 0) { npc->flags |= NPC_FLAG_COLLIDING_WITH_WORLD; npc->pos.x = testX; npc->pos.z = testZ; } else { npc->flags &= ~NPC_FLAG_COLLIDING_WITH_WORLD; }
testAngle = clamp_angle((npc->yaw - 45.0f) + 180.0f); testX = npc->pos.x; testY = npc->pos.y; testZ = npc->pos.z; if (npc_test_move_simple_with_slipping( npc->collisionChannel, &testX, &testY, &testZ, 0, testAngle, npc->collisionHeight, npc->collisionDiameter ) != 0) { npc->flags |= NPC_FLAG_COLLIDING_WITH_WORLD; npc->pos.x = testX; npc->pos.z = testZ; return; } npc->flags &= ~NPC_FLAG_COLLIDING_WITH_WORLD; } }}
void npc_do_other_npc_collision(Npc* npc) { Npc* otherNpc; f32 angle; f32 thisX, thisY, thisZ; f32 thisBuf; f32 otherX, otherZ; f32 otherBuf; f32 xDiff, zDiff; f32 dist; s32 collision; s32 i;
if (npc->flags & NPC_FLAG_IGNORE_CHAR_COLLISION) { return; }
npc->flags &= ~NPC_FLAG_COLLIDING_WITH_NPC; thisBuf = npc->collisionDiameter * 0.5f; thisX = npc->pos.x; thisY = npc->pos.y; thisZ = npc->pos.z;
for (i = 0; i < MAX_NPCS; i++) { otherNpc = get_npc_by_index(i); if (otherNpc != nullptr && npc != otherNpc) { if (otherNpc->flags != 0 && !(otherNpc->flags & (NPC_FLAG_SUSPENDED | NPC_FLAG_IGNORE_CHAR_COLLISION))) { if (!(otherNpc->pos.y + otherNpc->collisionHeight < thisY) && !(thisY + npc->collisionHeight < otherNpc->pos.y)) { otherX = otherNpc->pos.x; xDiff = otherX - thisX; otherZ = otherNpc->pos.z; zDiff = otherZ - thisZ; otherBuf = otherNpc->collisionDiameter * 0.5f; dist = sqrtf(SQ(xDiff) + SQ(zDiff));
if (!(thisBuf + otherBuf <= dist)) { collision = false; if (npc->flags & NPC_FLAG_PARTNER) { collision = gPartnerStatus.partnerActionState == PARTNER_ACTION_NONE; } else if (!(otherNpc->flags & NPC_FLAG_PARTNER) || gPartnerStatus.partnerActionState == PARTNER_ACTION_NONE) { collision = true; }
if (collision) { angle = DEG_TO_RAD(atan2(otherX, otherZ, thisX, thisZ)); dist = (thisBuf + otherBuf) - dist; xDiff = dist * sin_rad(angle); zDiff = -dist * cos_rad(angle); thisX += xDiff * 0.1f; thisZ += zDiff * 0.1f; } npc->flags |= NPC_FLAG_COLLIDING_WITH_NPC; } } } } } npc->pos.x = thisX; npc->pos.z = thisZ;}
s32 npc_do_player_collision(Npc* npc) { PlayerStatus* playerStatus = &gPlayerStatus; f32 playerX, playerZ; f32 playerYaw, yaw; f32 npcX, npcZ; f32 dist, colDist, distToNpc; f32 npcColRadius, playerColRadius; f32 deltaX, deltaZ;
if (npc->flags & NPC_FLAG_IGNORE_CHAR_COLLISION) { return false; }
if (npc->flags & NPC_FLAG_PARTNER) { return false; }
if (!gNpcPlayerCollisionsEnabled) { return false; }
if (playerStatus->pos.y + playerStatus->colliderHeight < npc->pos.y) { return false; }
if (npc->pos.y + npc->collisionHeight < playerStatus->pos.y) { return false; }
playerX = playerStatus->pos.x; playerZ = playerStatus->pos.z;
npcColRadius = npc->collisionDiameter / 2; playerColRadius = playerStatus->colliderDiameter / 2;
npcX = npc->pos.x; npcZ = npc->pos.z;
deltaX = playerX - npcX; deltaZ = playerZ - npcZ;
distToNpc = sqrtf(SQ(deltaX) + SQ(deltaZ)); colDist = npcColRadius + playerColRadius; if (colDist < distToNpc) { return false; }
playerStatus->animFlags |= PA_FLAG_NPC_COLLIDED;
npcX = npc->colliderPos.x; npcZ = npc->colliderPos.z;
deltaX = playerX - npcX; deltaZ = playerZ - npcZ;
dist = sqrtf(SQ(deltaX) + SQ(deltaZ)); yaw = atan2(playerX, playerZ, npcX, npcZ);
playerYaw = playerStatus->targetYaw; dist = colDist - dist; deltaX = dist * sin_rad(DEG_TO_RAD(yaw)); deltaZ = -dist * cos_rad(DEG_TO_RAD(yaw));
if (playerStatus->animFlags & PA_FLAG_RIDING_PARTNER) { if (fabsf(get_clamped_angle_diff(yaw, playerYaw)) < 45.0f) { playerStatus->pos.x -= deltaX; playerStatus->pos.z -= deltaZ; gPartnerNpc->pos.x -= deltaX; gPartnerNpc->pos.z -= deltaZ; } else { playerStatus->pos.x -= deltaX * 0.5f; playerStatus->pos.z -= deltaZ * 0.5f; gPartnerNpc->pos.x -= deltaX * 0.5f; gPartnerNpc->pos.z -= deltaZ * 0.5f; } } else { if (playerStatus->flags & (PS_FLAG_JUMPING | PS_FLAG_FALLING)) { playerStatus->pos.x -= deltaX * 0.4f; playerStatus->pos.z -= deltaZ * 0.4f; } else { dist = get_clamped_angle_diff(yaw, playerYaw); // required to match if (fabsf(dist) < 45.0f) { playerStatus->pos.x -= deltaX; playerStatus->pos.z -= deltaZ; } else { playerStatus->pos.x -= deltaX * 0.5f; playerStatus->pos.z -= deltaZ * 0.5f; } } } npc->pos.x = npc->colliderPos.x; npc->pos.y = npc->colliderPos.y; npc->pos.z = npc->colliderPos.z; return true;}
// update NPC position using gravitational acceleration = 1.0// if the NPC is within 16 units of the floor, they are snapped to itvoid npc_try_apply_gravity(Npc* npc) { f32 x, y, z, testLength; f32 length; s32 hitID;
if (!(npc->flags & NPC_FLAG_GRAVITY)) { return; }
if (npc->flags & NPC_FLAG_JUMPING) { npc->flags &= ~NPC_FLAG_GROUNDED; return; }
npc->jumpScale = 1.0f; npc->jumpVel -= npc->jumpScale; npc->pos.y += npc->jumpVel;
x = npc->pos.x; y = npc->pos.y + 13; z = npc->pos.z; testLength = length = fabsf(npc->jumpVel) + 16;
if (!(npc->flags & NPC_FLAG_PARTNER)) { hitID = npc_raycast_down_sides(npc->collisionChannel, &x, &y, &z, &length); } else { hitID = npc_raycast_down_around(npc->collisionChannel, &x, &y, &z, &length, npc->yaw, npc->collisionDiameter); }
if (hitID && length <= testLength) { npc->jumpVel = 0.0f; npc->flags |= NPC_FLAG_GROUNDED; npc->pos.y = y; npc->curFloor = NpcHitQueryColliderID; } else { npc->flags &= ~NPC_FLAG_GROUNDED; }}
// perform only collision traces and snapping to ground from gravity codes32 npc_try_snap_to_ground(Npc* npc, f32 velocity) { f32 x, y, z, testLength; f32 length; b32 hitGround;
if (npc->flags & (NPC_FLAG_GRAVITY | NPC_FLAG_FLYING)) { return false; }
if (npc->flags & NPC_FLAG_JUMPING) { npc->flags &= ~NPC_FLAG_GROUNDED; return false; }
length = testLength = fabsf(velocity) + 16; x = npc->pos.x; y = npc->pos.y + 13; z = npc->pos.z;
if (npc->flags & NPC_FLAG_PARTNER) { hitGround = npc_raycast_down_around(npc->collisionChannel, &x, &y, &z, &length, npc->yaw, npc->collisionDiameter); } else { hitGround = npc_raycast_down_sides(npc->collisionChannel, &x, &y, &z, &length); }
if (hitGround && length <= testLength) { npc->pos.y = y; npc->curFloor = NpcHitQueryColliderID; npc->flags |= NPC_FLAG_GROUNDED; return true; }
npc->flags &= ~NPC_FLAG_GROUNDED; return false;}
void update_npcs(void) { PlayerStatus* playerStatus = &gPlayerStatus; f32 x, y, z; f32 hitYaw, hitPitch, hitLength;
playerStatus->animFlags &= ~PA_FLAG_NPC_COLLIDED; if (!(gOverrideFlags & (GLOBAL_OVERRIDES_800 | GLOBAL_OVERRIDES_400))) { s32 i;
for (i = 0; i < MAX_NPCS; i++) { Npc* npc = (*gCurrentNpcListPtr)[i];
if (npc == nullptr) { continue; }
if (npc->flags == 0) { continue; }
if (npc->flags & (NPC_FLAG_SUSPENDED | NPC_FLAG_INACTIVE)) { npc_do_world_collision(npc); continue; }
// run update callback npc->onUpdate(npc);
if (npc->flags & NPC_FLAG_IGNORE_ENTITY_COLLISION) { npc->collisionChannel |= COLLISION_IGNORE_ENTITIES; } else { npc->collisionChannel &= ~COLLISION_IGNORE_ENTITIES; }
npc->curFloor = NO_COLLIDER; npc->curWall = NO_COLLIDER; npc->flags &= ~(NPC_FLAG_COLLIDING_FORWARD_WITH_WORLD | NPC_FLAG_COLLIDING_WITH_WORLD);
npc_do_world_collision(npc); npc_try_apply_gravity(npc); npc_try_snap_to_ground(npc, 0.0f); npc_do_player_collision(npc); npc_do_other_npc_collision(npc);
if (npc->flags & NPC_FLAG_MOTION_BLUR) { update_npc_blur(npc); }
if ((npc->pos.y < -2000.0f) && !(npc->flags & NPC_FLAG_PARTNER)) { npc->pos.y = playerStatus->pos.y; npc->jumpVel = 0.0f; npc->moveSpeed = 0.0f; npc->jumpScale = 0.0f; npc->flags &= ~NPC_FLAG_JUMPING; }
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE)) { if (npc->curAnim != 0) { if (npc->spriteInstanceID >= 0) { spr_update_sprite(npc->spriteInstanceID, npc->curAnim, npc->animationSpeed); } } } } else { spr_update_player_sprite(PLAYER_SPRITE_AUX1, npc->curAnim, npc->animationSpeed); }
if (npc->flags & NPC_FLAG_HAS_SHADOW) { Shadow* shadow = get_shadow_by_index(npc->shadowIndex); EntityModel* entityModel = get_entity_model(shadow->entityModelID);
entityModel->flags &= ~ENTITY_MODEL_FLAG_REFLECT; if (npc->flags & NPC_FLAG_REFLECT_WALL) { entityModel->flags |= ENTITY_MODEL_FLAG_REFLECT; }
x = npc->pos.x; y = npc->pos.y; z = npc->pos.z; if (npc->flags & NPC_FLAG_NO_SHADOW_RAYCAST) { if (npc->flags & NPC_FLAG_DONT_UPDATE_SHADOW_Y) { shadow->pos.x = npc->pos.x; shadow->pos.z = npc->pos.z; } else { shadow->pos.x = npc->pos.x; shadow->pos.y = npc->pos.y; shadow->pos.z = npc->pos.z; } } else { if (x != npc->colliderPos.x || y != npc->colliderPos.y || z != npc->colliderPos.z || (npc->flags & NPC_FLAG_DIRTY_SHADOW)) { x = npc->pos.x; y = npc->pos.y + (npc->collisionHeight / 2); z = npc->pos.z; hitLength = 1000.0f; entity_raycast_down(&x, &y, &z, &hitYaw, &hitPitch, &hitLength); set_npc_shadow_scale(shadow, hitLength, npc->collisionDiameter); shadow->pos.x = x; shadow->pos.y = y; shadow->pos.z = z; shadow->rot.x = hitYaw; shadow->rot.y = npc->renderYaw; shadow->rot.z = hitPitch; shadow->scale.x *= npc->shadowScale; npc->flags &= ~NPC_FLAG_DIRTY_SHADOW; } } }
npc->colliderPos.x = npc->pos.x; npc->colliderPos.y = npc->pos.y; npc->colliderPos.z = npc->pos.z; npc_update_decorations(npc);
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE)) { if (npc->spriteInstanceID < 0) { npc->spriteInstanceID++; if (npc->spriteInstanceID == -1) { npc->spriteInstanceID = spr_load_npc_sprite(npc->curAnim, npc->limitAnimList); ASSERT(npc->spriteInstanceID >= 0); spr_update_sprite(npc->spriteInstanceID, npc->curAnim, npc->animationSpeed); } } } } } }}
f32 npc_get_render_yaw(Npc* npc) { Camera* camera = &gCameras[gCurrentCamID]; f32 cameraYaw; f32 camRelativeYaw; f32 yaw; s32 direction;
if (!(gOverrideFlags & (GLOBAL_OVERRIDES_PREV_800 | GLOBAL_OVERRIDES_PREV_400))) { cameraYaw = camera->curYaw; camRelativeYaw = get_clamped_angle_diff(cameraYaw, npc->yaw);
if (camRelativeYaw < -5.0f && camRelativeYaw > -175.0f) { direction = 0; camRelativeYaw = 0.0f; } else if (camRelativeYaw > 5.0f && camRelativeYaw < 175.0f) { direction = 1; camRelativeYaw = 180.0f; } else { // direction is close to flipping, use saved value direction = 2; camRelativeYaw = npc->yawCamOffset; }
npc->yawCamOffset = yaw = clamp_angle(camRelativeYaw);
if (!(npc->flags & NPC_FLAG_IGNORE_CAMERA_FOR_YAW)) { if (npc->isFacingAway != direction && direction != 2) { npc->isFacingAway = direction;
if (npc->isFacingAway) { npc->turnAroundYawAdjustment = 180; } else { npc->turnAroundYawAdjustment = -180; }
if (fabsf(get_clamped_angle_diff(cameraYaw, npc->yaw)) >= 90.0f) { npc->turnAroundYawAdjustment = -npc->turnAroundYawAdjustment; } }
if (npc->turnAroundYawAdjustment != 0) { if (npc->turnAroundYawAdjustment < 0) { npc->turnAroundYawAdjustment += 20; } if (npc->turnAroundYawAdjustment > 0) { npc->turnAroundYawAdjustment -= 20; } }
if (npc->flags & NPC_FLAG_FLIP_INSTANTLY) { npc->turnAroundYawAdjustment = 0; }
npc->renderYaw = clamp_angle(clamp_angle(npc->turnAroundYawAdjustment + yaw) - cameraYaw); yaw = npc->renderYaw; } else { yaw = npc->renderYaw; } } else { yaw = npc->renderYaw; } return yaw;}
void appendGfx_npc(void* data) { Npc* npc = data; Matrix4f mtx1, mtx2; f32 renderYaw = npc_get_render_yaw(npc);
guTranslateF(mtx1, npc->pos.x, npc->pos.y + npc->verticalRenderOffset, npc->pos.z); if (npc->flags & NPC_FLAG_UPSIDE_DOWN) { mtx_ident_mirror_y(mtx2); guMtxCatF(mtx2, mtx1, mtx1); }
if (npc->rotPivotOffsetY != 0.0f) { guTranslateF(mtx2, 0.0f, npc->rotPivotOffsetY, 0.0f); guMtxCatF(mtx2, mtx1, mtx1); }
if (npc->rot.y != 0.0f) { guRotateF(mtx2, npc->rot.y, 0.0f, 1.0f, 0.0f); guMtxCatF(mtx2, mtx1, mtx1); }
if (npc->rot.x != 0.0f) { guRotateF(mtx2, npc->rot.x, 1.0f, 0.0f, 0.0f); guMtxCatF(mtx2, mtx1, mtx1); }
if (npc->rot.z != 0.0f) { guRotateF(mtx2, npc->rot.z, 0.0f, 0.0f, 1.0f); guMtxCatF(mtx2, mtx1, mtx1); }
if (npc->rotPivotOffsetY != 0.0f) { guTranslateF(mtx2, 0.0f, -npc->rotPivotOffsetY, 0.0f); guMtxCatF(mtx2, mtx1, mtx1); }
if ((npc->screenSpaceOffset2D[0] != 0.0f) || (npc->screenSpaceOffset2D[1] != 0.0f)) { guTranslateF(mtx1, npc->screenSpaceOffset2D[0], npc->screenSpaceOffset2D[1], 0.0f); guMtxCatF(mtx2, mtx1, mtx1); }
if (npc->scale.x * SPRITE_WORLD_SCALE_D != 1.0f || (npc->scale.y * npc->verticalStretch) * SPRITE_WORLD_SCALE_D != 1.0f || npc->scale.z * SPRITE_WORLD_SCALE_D != 1.0f) { guScaleF( mtx2, npc->scale.x * SPRITE_WORLD_SCALE_D, (npc->scale.y * npc->verticalStretch) * SPRITE_WORLD_SCALE_D, npc->scale.z * SPRITE_WORLD_SCALE_D ); guMtxCatF(mtx2, mtx1, mtx1); }
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE) && (npc->curAnim != 0) && (npc->spriteInstanceID >= 0)) { npc_draw_with_palswap(npc, renderYaw, mtx1); npc->animNotifyValue = spr_get_notify_value(npc->spriteInstanceID); } } else { npc_draw_with_palswap(npc, renderYaw, mtx1); npc->animNotifyValue = func_802DDEC4(PLAYER_SPRITE_AUX1); }
if (npc->flags & NPC_FLAG_REFLECT_WALL) { guTranslateF(mtx1, npc->pos.x, npc->pos.y + npc->verticalRenderOffset, -npc->pos.z); if (npc->flags & NPC_FLAG_UPSIDE_DOWN) { mtx_ident_mirror_y(mtx2); guMtxCatF(mtx2, mtx1, mtx1); } if ((npc->rot.y != 0.0f) || (npc->rot.x != 0.0f) || (npc->rot.z != 0.0f)) { guRotateRPYF(mtx2, npc->rot.x, npc->rot.y, npc->rot.z); guMtxCatF(mtx2, mtx1, mtx1); }
if (npc->scale.x * SPRITE_WORLD_SCALE_D != 1.0f || (npc->scale.y * npc->verticalStretch) * SPRITE_WORLD_SCALE_D != 1.0f || npc->scale.z * SPRITE_WORLD_SCALE_D != 1.0f) { guScaleF( mtx2, SPRITE_WORLD_SCALE_D * npc->scale.x, SPRITE_WORLD_SCALE_D * npc->scale.y * npc->verticalStretch, SPRITE_WORLD_SCALE_D * npc->scale.z ); guMtxCatF(mtx2, mtx1, mtx1); }
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE) && (npc->curAnim != 0)) { spr_draw_npc_sprite(npc->spriteInstanceID, renderYaw, 0, 0, mtx1); } } else { spr_draw_player_sprite(PLAYER_SPRITE_AUX1 | DRAW_SPRITE_OVERRIDE_YAW, renderYaw, 0, 0, mtx1); } }
if (npc->flags & NPC_FLAG_REFLECT_FLOOR) { guTranslateF(mtx1, npc->pos.x, -(npc->pos.y + npc->verticalRenderOffset), npc->pos.z); mtx_ident_mirror_y(mtx2); guMtxCatF(mtx2, mtx1, mtx1);
if (npc->rot.y != 0.0f || npc->rot.x != 0.0f || npc->rot.z != 0.0f) { guRotateRPYF(mtx2, npc->rot.x, npc->rot.y, npc->rot.z); guMtxCatF(mtx2, mtx1, mtx1); }
if (npc->scale.x * SPRITE_WORLD_SCALE_D != 1.0f || (npc->scale.y * npc->verticalStretch) * SPRITE_WORLD_SCALE_D != 1.0f || npc->scale.z * SPRITE_WORLD_SCALE_D != 1.0f) { guScaleF( mtx2, SPRITE_WORLD_SCALE_D * npc->scale.x, SPRITE_WORLD_SCALE_D * npc->scale.y * npc->verticalStretch, SPRITE_WORLD_SCALE_D * npc->scale.z ); guMtxCatF(mtx2, mtx1, mtx1); } if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE) && (npc->curAnim != 0)) { spr_draw_npc_sprite(npc->spriteInstanceID, renderYaw, 0, 0, mtx1); } } else { spr_draw_player_sprite(PLAYER_SPRITE_AUX1 | DRAW_SPRITE_OVERRIDE_YAW, renderYaw, 0, 0, mtx1); } } npc->onRender(npc);}
void render_npcs(void) { RenderTask renderTask; RenderTask* renderTaskPtr = &renderTask; Camera* cam = &gCameras[gCurrentCamID]; f32 x, y, z, s; f32 renderDist; s32 i;
for (i = 0; i < MAX_NPCS; i++) { Npc* npc = (*gCurrentNpcListPtr)[i]; if ((npc != nullptr) && (npc->flags != 0) && !(npc->flags & (NPC_FLAG_SUSPENDED | NPC_FLAG_HAS_NO_SPRITE | NPC_FLAG_INACTIVE | NPC_FLAG_INVISIBLE))) { transform_point(cam->mtxPerspective, npc->pos.x, npc->pos.y, npc->pos.z, 1.0f, &x, &y, &z, &s); if (!(s < 0.01) || !(s > -0.01)) { renderDist = ((z * 5000.0f) / s) + 5000.0f; if (renderDist < 0.0f) { renderDist = 0.0f; } else if (renderDist > 10000.0f) { renderDist = 10000.0f; }
renderTaskPtr->dist = -renderDist; renderTaskPtr->appendGfxArg = npc; renderTaskPtr->appendGfx = appendGfx_npc; renderTaskPtr->renderMode = npc->renderMode;
if (npc->flags & NPC_FLAG_HIDING) { u8 r, g, b, a; mdl_get_shroud_tint_params(&r, &g, &b, &a); npc->hideAlpha = 255 - a; } else { npc->hideAlpha = 255; }
if (npc->hideAlpha != 0) { queue_render_task(renderTaskPtr); }
if (npc->flags & NPC_FLAG_MOTION_BLUR) { renderTaskPtr->dist = -renderDist; renderTaskPtr->appendGfx = appendGfx_npc_blur; renderTaskPtr->appendGfxArg = npc; renderTaskPtr->renderMode = RENDER_MODE_SURFACE_XLU_LAYER1; queue_render_task(renderTaskPtr); } } } }}
void npc_move_heading(Npc* npc, f32 speed, f32 yaw) { f32 angle = DEG_TO_RAD(yaw); f32 sin = sin_rad(angle); f32 cos = cos_rad(angle);
npc->pos.x += speed * sin; npc->pos.z -= speed * cos;}
Npc* get_npc_unsafe(s32 npcID) { s32 i; Npc* npc;
for (i = 0; i < MAX_NPCS; i++) { npc = (*gCurrentNpcListPtr)[i]; if (npc != nullptr && npc->flags != 0 && npc->npcID == npcID) { break; } } ASSERT(i < MAX_NPCS);
return npc;}
Npc* get_npc_safe(s32 npcID) { s32 i; Npc* npc;
for (i = 0; i < MAX_NPCS; i++) { npc = (*gCurrentNpcListPtr)[i]; if (npc != nullptr && npc->flags != 0 && npc->npcID == npcID) { break; } } if (i >= MAX_NPCS) { return nullptr; }
return npc;}
void enable_npc_shadow(Npc* npc) { Shadow* shadow;
if (!(npc->flags & NPC_FLAG_HAS_SHADOW)) { shadow = get_shadow_by_index(npc->shadowIndex); shadow->flags &= ~ENTITY_FLAG_HIDDEN; npc->flags |= (NPC_FLAG_DIRTY_SHADOW | NPC_FLAG_HAS_SHADOW); }}
void disable_npc_shadow(Npc* npc) { Shadow* shadow;
if (npc->flags & NPC_FLAG_HAS_SHADOW) { shadow = get_shadow_by_index(npc->shadowIndex); shadow->flags |= ENTITY_FLAG_HIDDEN; npc->flags &= ~NPC_FLAG_HAS_SHADOW; npc->flags &= ~NPC_FLAG_DIRTY_SHADOW; }}
void set_npc_sprite(Npc* npc, s32 anim, AnimID* limitAnimList) { ASSERT((npc->flags & NPC_FLAG_HAS_NO_SPRITE) || spr_free_sprite(npc->spriteInstanceID) == 0);
npc->limitAnimList = limitAnimList;
if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE)) { npc->spriteInstanceID = spr_load_npc_sprite(anim, limitAnimList); ASSERT(npc->spriteInstanceID >= 0); }
npc->curAnim = anim;
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE)) { spr_update_sprite(npc->spriteInstanceID, anim, npc->animationSpeed); } }}
void enable_npc_blur(Npc* npc) { if (!(npc->flags & NPC_FLAG_MOTION_BLUR)) { NpcMotionBlur* motionBlur; s32 i;
npc->flags |= NPC_FLAG_MOTION_BLUR;
motionBlur = heap_malloc(sizeof(*motionBlur)); npc->userData.motion = motionBlur; ASSERT(motionBlur != nullptr); motionBlur->unused = 0; motionBlur->index = 0;
for (i = 0; i < ARRAY_COUNT(motionBlur->posX); i++) { motionBlur->posX[i] = npc->pos.x; motionBlur->posY[i] = npc->pos.y; motionBlur->posZ[i] = npc->pos.z; } }}
void disable_npc_blur(Npc* npc) { if (npc->flags & NPC_FLAG_MOTION_BLUR) { npc->flags &= ~NPC_FLAG_MOTION_BLUR;
heap_free(npc->userData.motion); npc->userData.motion = nullptr; }}
void update_npc_blur(Npc* npc) { NpcMotionBlur* motionBlur = npc->userData.motion; s32 index = motionBlur->index;
motionBlur->posX[index] = npc->pos.x; motionBlur->posY[index] = npc->pos.y; motionBlur->posZ[index] = npc->pos.z;
index++; if (index >= 20) { index = 0; }
motionBlur->index = index;}
void appendGfx_npc_blur(void* data) { Npc* npc = (Npc*) data; Matrix4f mtx, tempMtx; f32 x, y, z; f32 yaw; s32 strideIdx; s32 drawIdx; s32 bufPos; NpcMotionBlur* blur;
strideIdx = 0; drawIdx = 0; blur = npc->userData.motion; bufPos = blur->index;
while (true) { bufPos--; strideIdx++;
if (bufPos < 0) { bufPos = NPC_BLUR_FRAMES - 1; }
if (bufPos == blur->index) { break; }
// only draw every third blur frame if (strideIdx < 3) { continue; }
strideIdx = 0; drawIdx++;
// draw three blur samples if (drawIdx > 3) { break; }
x = blur->posX[bufPos]; y = blur->posY[bufPos]; z = blur->posZ[bufPos]; set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_SET_ALPHA, 255, 255, 255, 120 - (drawIdx * 20), 0); yaw = npc->renderYaw; guTranslateF(mtx, x, y, z);
if (npc->rot.y != 0.0f) { guRotateF(tempMtx, npc->rot.y, 0.0f, 1.0f, 0.0f); guMtxCatF(tempMtx, mtx, mtx); } if (npc->rot.x != 0.0f) { guRotateF(tempMtx, npc->rot.y, 0.0f, 1.0f, 0.0f); guMtxCatF(tempMtx, mtx, mtx); } if (npc->rot.z != 0.0f) { guRotateF(tempMtx, npc->rot.y, 0.0f, 1.0f, 0.0f); guMtxCatF(tempMtx, mtx, mtx); }
if ((npc->scale.x * SPRITE_WORLD_SCALE_D) != 1.0 || ((npc->scale.y * npc->verticalStretch) * SPRITE_WORLD_SCALE_D) != 1.0 || (npc->scale.z * SPRITE_WORLD_SCALE_D) != 1.0) { guScaleF( tempMtx, npc->scale.x * SPRITE_WORLD_SCALE_D, (npc->scale.y * npc->verticalStretch) * SPRITE_WORLD_SCALE_D, npc->scale.z * SPRITE_WORLD_SCALE_D ); guMtxCatF(tempMtx, mtx, mtx); }
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE)) { spr_draw_npc_sprite(npc->spriteInstanceID, yaw, 0, 0, mtx); } } else { spr_draw_player_sprite(PLAYER_SPRITE_AUX1 | DRAW_SPRITE_OVERRIDE_YAW, yaw, 0, 0, mtx); } } npc_imgfx_update(npc);}
void npc_enable_collisions(void) { gNpcPlayerCollisionsEnabled = true;}
void npc_disable_collisions(void) { gNpcPlayerCollisionsEnabled = false;}
void func_8003B1A8(void) {}
void npc_reload_all(void) { s32 i; s32 j;
for (i = 0; i < MAX_NPCS; i++) { Npc* npc = (*gCurrentNpcListPtr)[i]; if (npc != nullptr) { if (npc->flags && !(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE)) { if (!(npc->flags & NPC_FLAG_PARTNER)) { npc->spriteInstanceID = spr_load_npc_sprite(npc->curAnim, npc->limitAnimList); } else { npc->spriteInstanceID = spr_load_npc_sprite(npc->curAnim | SPRITE_ID_TAIL_ALLOCATE, npc->limitAnimList); } } if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE) && (npc->palSwapType != NPC_PAL_ADJUST_NONE)) { npc->originalPalettesList = spr_get_npc_palettes(SPR_UNPACK_SPR(npc->curAnim)); npc->originalPalettesCount = 0; while (npc->originalPalettesList[npc->originalPalettesCount] != (PAL_PTR) -1) { npc->originalPalettesCount++; } npc->spriteColorVariations = spr_get_npc_color_variations(SPR_UNPACK_SPR(npc->curAnim)); } if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { if (!(npc->flags & NPC_FLAG_HAS_NO_SPRITE)) { for (j = 0; j < MAX_NPC_DECORATIONS; j++) { npc_reset_current_decoration(npc, j); } npc_imgfx_update(npc); } } } } } }}
void set_npc_yaw(Npc* npc, f32 yaw) { npc->yaw = yaw;
if (get_clamped_angle_diff(gCameras[gCurrentCameraID].curYaw, yaw) >= 0.0f) { npc->yawCamOffset = 180; npc->isFacingAway = true; } else { npc->yawCamOffset = 0; npc->isFacingAway = false; }}
void npc_set_palswap_mode_A(Npc* npc, s32 mode) { if (npc->palSwapType != mode) { npc->palSwapPrevType = npc->palSwapType; npc->palSwapType = mode; npc->palAnimState = PAL_SWAP_HOLD_A; npc->resetPalAdjust = 1; }}
void npc_set_palswap_mode_B(Npc* npc, s32 mode) { if (npc->palSwapType != mode) { npc->palSwapPrevType = npc->palSwapType; npc->palSwapType = mode; npc->palAnimState = PAL_SWAP_HOLD_A; npc->resetPalAdjust = -1; }}
void npc_revert_palswap_mode(Npc* npc) { if (npc->palSwapPrevType == NPC_PAL_ADJUST_NONE) { npc->palSwapType = NPC_PAL_ADJUST_NONE; return; } npc->palSwapType = npc->palSwapPrevType; npc->palSwapPrevType = NPC_PAL_ADJUST_NONE; npc->palAnimState = PAL_SWAP_HOLD_A; npc->resetPalAdjust = 1;}
void npc_set_palswap_1(Npc* npc, s32 palIndexA, s32 palIndexB, s32 timeHoldA, s32 timeAB) { npc->blendPalA = palIndexA; npc->blendPalB = palIndexB; npc->palswapTimeHoldA = timeHoldA; npc->palswapTimeAtoB = timeAB;}
void npc_set_palswap_2(Npc* npc, s32 timeHoldB, s32 timeBA, s32 palIndexC, s32 palIndexD) { npc->palswapTimeHoldB = timeHoldB; npc->palswapTimeBtoA = timeBA; npc->blendPalC = palIndexC; npc->blendPalD = palIndexD;}
void npc_draw_with_palswap(Npc* npc, s32 yaw, Matrix4f mtx) { switch (npc->palSwapType) { case NPC_PAL_ADJUST_NONE: npc_render_without_adjusted_palettes(npc, yaw, mtx); break; case NPC_PAL_ADJUST_WATT_IDLE: npc_render_with_watt_idle_palettes(npc, yaw, mtx); break; case NPC_PAL_ADJUST_BLEND_PALETTES_UNIFORM_INTERVALS: npc_render_with_single_pal_blending(npc, yaw, false, mtx); break; case NPC_PAL_ADJUST_BLEND_PALETTES_VARYING_INTERVALS: npc_render_with_single_pal_blending(npc, yaw, true, mtx); break; case NPC_PAL_ADJUST_BLEND_DOUBLE_PALETTES: npc_render_with_double_pal_blending(npc, yaw, mtx); break; }}
void npc_render_without_adjusted_palettes(Npc* npc, s32 arg1, Matrix4f mtx) { if (npc->resetPalAdjust != 0) { npc->verticalStretch = 1.0f; npc->screenSpaceOffset2D[0] = 0.0f; npc->screenSpaceOffset2D[1] = 0.0f; npc->resetPalAdjust = 0; }
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { s32 alpha = (npc->alpha * npc->hideAlpha / 255); u32 mask; if (alpha < 255) { mask = DRAW_SPRITE_OVERRIDE_ALPHA; } else { mask = 0; } spr_draw_npc_sprite(npc->spriteInstanceID | mask, arg1, alpha, nullptr, mtx); } else { spr_draw_player_sprite(PLAYER_SPRITE_AUX1 | DRAW_SPRITE_OVERRIDE_YAW, arg1, 0, 0, mtx); }}
void npc_render_with_watt_idle_palettes(Npc* npc, s32 arg1, Matrix4f mtx) { s32 i, j; s32 brightness; PAL_PTR src; PAL_PTR dst;
if (npc->resetPalAdjust != 0) { npc->originalPalettesList = spr_get_npc_palettes(SPR_UNPACK_SPR(npc->curAnim)); npc->originalPalettesCount = 0; while ((s32) npc->originalPalettesList[npc->originalPalettesCount] != -1) { npc->originalPalettesCount++; }
npc->spriteColorVariations = spr_get_npc_color_variations(SPR_UNPACK_SPR(npc->curAnim)); for (i = 0; i < npc->originalPalettesCount; i++) { dst = npc->copiedPalettes[i]; src = npc->originalPalettesList[i]; if (src != nullptr) { for (j = 0; j < SPR_PAL_SIZE; j++) { *dst++ = *src++; } } }
npc->palAnimState = -2; npc->palBlendAlpha = 0; npc->resetPalAdjust = 0; npc->nextPalTime = 0; }
if (npc->nextPalTime == 0) { npc->palAnimState += 2; brightness = wWattIdlePalettesAnim[npc->palAnimState]; if (brightness == 255) { npc->palAnimState = 0; } npc->nextPalTime = wWattIdlePalettesAnim[npc->palAnimState + 1] / 2; }
brightness = wWattIdlePalettesAnim[npc->palAnimState]; npc->nextPalTime--;
switch (brightness) { case WATT_DEFAULT: for (i = 0; i < npc->spriteColorVariations; i++) { dst = npc->copiedPalettes[i]; src = npc->originalPalettesList[i]; if (src != nullptr) { for (j = 0; j < SPR_PAL_SIZE; j++) { *dst++ = *src++; } } } break; case WATT_BRIGHTEST: for (i = 0; i < npc->spriteColorVariations; i++) { // use watt's Brightest palettes dst = npc->copiedPalettes[i]; src = npc->originalPalettesList[npc->spriteColorVariations * SPR_PAL_WorldWatt_Brightest + i]; if (src != nullptr) { for (j = 0; j < SPR_PAL_SIZE; j++) { *dst++ = *src++; } } } break; case WATT_BRIGHTER: for (i = 0; i < npc->spriteColorVariations; i++) { // use watt's Brighter palettes dst = npc->copiedPalettes[i]; src = npc->originalPalettesList[npc->spriteColorVariations * SPR_PAL_WorldWatt_Brighter + i]; if (src != nullptr) { for (j = 0; j < SPR_PAL_SIZE; j++) { *dst++ = *src++; } } } break; }
for (i = 0; i < npc->originalPalettesCount; i++) { npc->adjustedPalettes[i] = npc->copiedPalettes[i]; }
if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { s32 alpha = npc->alpha * npc->hideAlpha / 255; u32 mask = 0; if (alpha < 255) { mask |= DRAW_SPRITE_OVERRIDE_ALPHA; } mask |= DRAW_SPRITE_OVERRIDE_PALETTES; spr_draw_npc_sprite(npc->spriteInstanceID | mask, arg1, alpha, npc->adjustedPalettes, mtx); } npc->palBlendAlpha--;}
u16 npc_blend_palette_colors(u16 colorA, u16 colorB, s32 lerpAlpha) { u8 r = (UNPACK_PAL_R(colorA) * (255 - lerpAlpha) + UNPACK_PAL_R(colorB) * lerpAlpha) / 255; u8 g = (UNPACK_PAL_G(colorA) * (255 - lerpAlpha) + UNPACK_PAL_G(colorB) * lerpAlpha) / 255; u8 b = (UNPACK_PAL_B(colorA) * (255 - lerpAlpha) + UNPACK_PAL_B(colorB) * lerpAlpha) / 255; u8 a = UNPACK_PAL_A(colorB);
return PACK_PAL_RGBA(r, g, b, a);}
void npc_render_with_single_pal_blending(Npc* npc, s32 yaw, b32 hasDifferentIntervals, Matrix4f mtx) { PAL_PTR color1; PAL_PTR color2; PAL_PTR outColor; s32 i, j; s32 blendAlpha;
// copy palettes from sprite data if (npc->resetPalAdjust != 0) { if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { npc->originalPalettesList = spr_get_npc_palettes(SPR_UNPACK_SPR(npc->curAnim)); }
npc->originalPalettesCount = 0; while ((s32) npc->originalPalettesList[npc->originalPalettesCount] != -1) { npc->originalPalettesCount++; }
if (npc->resetPalAdjust == 1) { npc->palAnimState = PAL_SWAP_HOLD_A; npc->palBlendAlpha = 0; } else { npc->palAnimState = PAL_SWAP_HOLD_A; npc->palBlendAlpha = 255; }
for (i = 0; i < npc->originalPalettesCount; i++) { color1 = npc->copiedPalettes[i]; color2 = npc->originalPalettesList[i]; npc->adjustedPalettes[i] = color1; if (color2 != nullptr) { for (j = 0; j < SPR_PAL_SIZE; j++) { *color1++ = *color2++; } } }
if (!hasDifferentIntervals) { npc->palswapTimeAtoB = npc->palswapTimeHoldA; npc->palswapTimeHoldB = npc->palswapTimeHoldA; npc->palswapTimeBtoA = npc->palswapTimeAtoB; npc->palswapTimeHoldA = 0; }
npc->nextPalTime = npc->palswapTimeHoldA; npc->palBlendAlpha = 0; npc->palAnimState = PAL_SWAP_HOLD_A; npc->resetPalAdjust = 0; }
// blending from A -> B switch (npc->palAnimState) { case PAL_SWAP_HOLD_A: if (npc->nextPalTime != 0) { npc->nextPalTime--; break; } else { npc->palBlendAlpha = 0; npc->palAnimState = PAL_SWAP_A_TO_B; } // fallthrough case PAL_SWAP_A_TO_B: npc->palBlendAlpha += 25600 / npc->palswapTimeAtoB; if (npc->palBlendAlpha > 25500) { npc->palBlendAlpha = 25500; } blendAlpha = npc->palBlendAlpha / 100; // blend two palettes outColor = npc->copiedPalettes[0]; color2 = npc->originalPalettesList[npc->blendPalA]; color1 = npc->originalPalettesList[npc->blendPalB]; npc->adjustedPalettes[0] = outColor;
for (j = 0; j < SPR_PAL_SIZE; j++) { *outColor++ = npc_blend_palette_colors(*color2++, *color1++, blendAlpha); }
if (blendAlpha == 255) { npc->palAnimState = PAL_SWAP_HOLD_B; npc->nextPalTime = npc->palswapTimeHoldB; } break; }
// blending from B -> A switch (npc->palAnimState) { case PAL_SWAP_HOLD_B: if (npc->nextPalTime != 0) { npc->nextPalTime--; break; } else { npc->palBlendAlpha = 0; npc->palAnimState = PAL_SWAP_B_TO_A; } // fallthrough case PAL_SWAP_B_TO_A: npc->palBlendAlpha += 25600 / npc->palswapTimeBtoA; if (npc->palBlendAlpha > 25500) { npc->palBlendAlpha = 25500; } blendAlpha = npc->palBlendAlpha / 100; // blend two palettes outColor = npc->copiedPalettes[0]; color2 = npc->originalPalettesList[npc->blendPalB]; color1 = npc->originalPalettesList[npc->blendPalA]; npc->adjustedPalettes[0] = outColor;
for (j = 0; j < SPR_PAL_SIZE; j++) { *outColor++ = npc_blend_palette_colors(*color2++, *color1++, blendAlpha); }
if (blendAlpha == 255) { npc->palAnimState = PAL_SWAP_HOLD_A; npc->nextPalTime = npc->palswapTimeHoldA; } break; }
switch (npc->palAnimState) { case PAL_SWAP_HOLD_A: case PAL_SWAP_A_TO_B: case PAL_SWAP_HOLD_B: case PAL_SWAP_B_TO_A: if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { u32 mask = 0; blendAlpha = npc->alpha * npc->hideAlpha / 255; if (blendAlpha < 255) { mask |= DRAW_SPRITE_OVERRIDE_ALPHA; } mask |= DRAW_SPRITE_OVERRIDE_PALETTES; spr_draw_npc_sprite(npc->spriteInstanceID | mask, yaw, blendAlpha, npc->adjustedPalettes, mtx); } break; }}
void npc_render_with_double_pal_blending(Npc* npc, s32 yaw, Matrix4f mtx) { PAL_PTR color1; PAL_PTR color2; PAL_PTR outColor; s32 i, j; u8 blendAlpha;
// copy palettes from sprite data if (npc->resetPalAdjust != 0) { if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { npc->originalPalettesList = spr_get_npc_palettes(SPR_UNPACK_SPR(npc->curAnim)); }
npc->originalPalettesCount = 0; while ((s32) npc->originalPalettesList[npc->originalPalettesCount] != -1) { npc->originalPalettesCount++; }
if (npc->resetPalAdjust == 1) { npc->palAnimState = PAL_SWAP_HOLD_A; npc->palBlendAlpha = 0; } else { npc->palAnimState = PAL_SWAP_HOLD_A; npc->palBlendAlpha = 255; }
for (i = 0; i < npc->originalPalettesCount; i++) { color1 = npc->copiedPalettes[i]; color2 = npc->originalPalettesList[i]; npc->adjustedPalettes[i] = color1; if (color2 != nullptr) { for (j = 0; j < SPR_PAL_SIZE; j++) { *color1++ = *color2++; } } }
npc->nextPalTime = npc->palswapTimeHoldA; npc->palBlendAlpha = 0; npc->palAnimState = PAL_SWAP_HOLD_A; npc->resetPalAdjust = 0; }
// blending from A -> B switch (npc->palAnimState) { case PAL_SWAP_HOLD_A: if (npc->nextPalTime != 0) { npc->nextPalTime--; break; } npc->palBlendAlpha = 0; npc->palAnimState = PAL_SWAP_A_TO_B; // fallthrough case PAL_SWAP_A_TO_B: npc->palBlendAlpha += 25600 / npc->palswapTimeAtoB; if (npc->palBlendAlpha > 25500) { npc->palBlendAlpha = 25500; } blendAlpha = npc->palBlendAlpha / 100;
// blend first two palettes outColor = npc->copiedPalettes[0]; color2 = npc->originalPalettesList[npc->blendPalA]; color1 = npc->originalPalettesList[npc->blendPalB]; npc->adjustedPalettes[0] = outColor;
for (j = 0; j < SPR_PAL_SIZE; j++) { *outColor++ = npc_blend_palette_colors(*color2++, *color1++, blendAlpha); }
// blend next palettes outColor = npc->copiedPalettes[3]; color2 = npc->originalPalettesList[npc->blendPalC]; color1 = npc->originalPalettesList[npc->blendPalD]; npc->adjustedPalettes[3] = outColor;
for (j = 0; j < SPR_PAL_SIZE; j++) { *outColor++ = npc_blend_palette_colors(*color2++, *color1++, blendAlpha); }
if (blendAlpha == 255) { npc->palAnimState = PAL_SWAP_HOLD_B; npc->nextPalTime = npc->palswapTimeHoldB; } break; }
// blending from B -> A switch (npc->palAnimState) { case PAL_SWAP_HOLD_B: if (npc->nextPalTime != 0) { npc->nextPalTime--; break; } else { npc->palBlendAlpha = 0; npc->palAnimState = PAL_SWAP_B_TO_A; } // fallthrough case PAL_SWAP_B_TO_A: npc->palBlendAlpha += 25600 / npc->palswapTimeBtoA; if (npc->palBlendAlpha > 25500) { npc->palBlendAlpha = 25500; } blendAlpha = npc->palBlendAlpha / 100;
// blend first two palettes outColor = npc->copiedPalettes[0]; color2 = npc->originalPalettesList[npc->blendPalB]; color1 = npc->originalPalettesList[npc->blendPalA]; npc->adjustedPalettes[0] = outColor;
for (j = 0; j < SPR_PAL_SIZE; j++) { *outColor++ = npc_blend_palette_colors(*color2++, *color1++, blendAlpha); }
// blend next palettes outColor = npc->copiedPalettes[1]; /// @bug? should this be index 3? color2 = npc->originalPalettesList[npc->blendPalD]; color1 = npc->originalPalettesList[npc->blendPalC]; npc->adjustedPalettes[3] = npc->copiedPalettes[3];
for (j = 0; j < SPR_PAL_SIZE; j++) { *outColor++ = npc_blend_palette_colors(*color2++, *color1++, blendAlpha); }
if (blendAlpha == 255) { npc->palAnimState = PAL_SWAP_HOLD_A; npc->nextPalTime = npc->palswapTimeHoldA; } break; }
switch (npc->palAnimState) { case PAL_SWAP_HOLD_A: case PAL_SWAP_A_TO_B: case PAL_SWAP_HOLD_B: case PAL_SWAP_B_TO_A: if (!(npc->flags & NPC_FLAG_USES_PLAYER_AUX_SPRITE)) { u32 mask = 0; blendAlpha = npc->alpha * npc->hideAlpha / 255; if (blendAlpha < 255) { mask |= DRAW_SPRITE_OVERRIDE_ALPHA; } mask |= DRAW_SPRITE_OVERRIDE_PALETTES; spr_draw_npc_sprite(npc->spriteInstanceID | mask, yaw, blendAlpha, npc->adjustedPalettes, mtx); } break; }}
void npc_set_decoration(Npc* npc, s32 idx, s32 decorationType) { npc_remove_decoration(npc, idx); npc->decorationType[idx] = decorationType; npc->changedDecoration[idx] = 1; npc->decorationInitialized[idx] = 0;}
void npc_remove_decoration(Npc* npc, s32 idx) { switch (npc->decorationType[idx]) { case NPC_DECORATION_NONE: npc_remove_decoration_none(npc, idx); break; case NPC_DECORATION_BOWSER_AURA: npc_remove_decoration_bowser_aura(npc, idx); break; case NPC_DECORATION_SWEAT: npc_remove_decoration_sweat(npc, idx); break; case NPC_DECORATION_SEEING_STARS: npc_remove_decoration_seeing_stars(npc, idx); break; case NPC_DECORATION_WHITE_GLOW_FRONT: npc_remove_decoration_glow_in_front(npc, idx); break; case NPC_DECORATION_WHITE_GLOW_BEHIND: npc_remove_decoration_glow_behind(npc, idx); break; case NPC_DECORATION_CHARGED: npc_remove_decoration_charged(npc, idx); break; } npc->decorationType[idx] = NPC_DECORATION_NONE;}
void npc_update_decorations(Npc* npc) { s32 i;
for (i = 0; i < MAX_NPC_DECORATIONS; i++) { switch (npc->decorationType[i]) { case NPC_DECORATION_NONE: npc_update_decoration_none(npc, i); break; case NPC_DECORATION_BOWSER_AURA: npc_update_decoration_bowser_aura(npc, i); break; case NPC_DECORATION_SWEAT: npc_update_decoration_sweat(npc, i); break; case NPC_DECORATION_SEEING_STARS: npc_update_decoration_seeing_stars(npc, i); break; case NPC_DECORATION_WHITE_GLOW_FRONT: npc_update_decoration_glow_in_front(npc, i); break; case NPC_DECORATION_WHITE_GLOW_BEHIND: npc_update_decoration_glow_behind(npc, i); break; case NPC_DECORATION_CHARGED: npc_update_decoration_charged(npc, i); break; } }}
void npc_reset_current_decoration(Npc* npc, s32 idx) { switch (npc->decorationType[idx]) { case NPC_DECORATION_NONE: case NPC_DECORATION_BOWSER_AURA: case NPC_DECORATION_SWEAT: case NPC_DECORATION_SEEING_STARS: case NPC_DECORATION_WHITE_GLOW_FRONT: case NPC_DECORATION_WHITE_GLOW_BEHIND: break; case NPC_DECORATION_CHARGED: npc->decorationInitialized[idx] = 0; break; }}
void npc_update_decoration_none(Npc* npc, s32 idx) {}
void npc_remove_decoration_none(Npc* npc, s32 idx) {}
void npc_update_decoration_bowser_aura(Npc* npc, s32 idx) { AuraFXData* data;
switch (npc->decorationInitialized[idx]) { case 0: fx_aura(FX_AURA_BLUE, npc->pos.x, npc->pos.y, npc->pos.z, 1.0f, &npc->decorations[idx]); npc->decorationInitialized[idx] = 1; // fallthrough case 1: data = npc->decorations[idx]->data.aura; data->posA.x = npc->pos.x; data->posA.y = npc->pos.y; data->posA.z = npc->pos.z; data->scale.x = (npc->scale.x * npc->collisionDiameter) * 0.01; data->scale.y = (npc->scale.y * npc->collisionHeight) * 0.01; data->renderYaw = npc->renderYaw; break; }}
void npc_remove_decoration_bowser_aura(Npc* npc, s32 idx) { npc->decorations[idx]->data.aura->fadeTime = 5;}
void npc_update_decoration_sweat(Npc* npc, s32 idx) { switch (npc->decorationInitialized[idx]) { case 0: if (npc->yawCamOffset > 90) { fx_sweat(0, npc->pos.x, npc->pos.y + npc->collisionHeight, npc->pos.z, 5.0f, 45.0f, 20); } else { fx_sweat(0, npc->pos.x, npc->pos.y + npc->collisionHeight, npc->pos.z, 5.0f, -45.0f, 20); } npc->decorationGlowPhase[idx] = 10; npc->decorationInitialized[idx] = 1; break; case 1: if (npc->decorationGlowPhase[idx] != 0) { npc->decorationGlowPhase[idx]--; } else { npc->decorationInitialized[idx] = 0; } break; }}
void npc_remove_decoration_sweat(Npc* npc, s32 idx) {}
void npc_update_decoration_seeing_stars(Npc* npc, s32 idx) { StarsOrbitingFXData* data;
switch (npc->decorationInitialized[idx]) { case 0: fx_stars_orbiting( 0, npc->pos.x, npc->pos.y + npc->collisionHeight, npc->pos.z, 20.0f, 3, &npc->decorations[idx] ); npc->decorationInitialized[idx] = 1; break; case 1: data = npc->decorations[idx]->data.starsOrbiting; data->pos.x = npc->pos.x; data->pos.y = npc->pos.y + npc->collisionHeight; data->pos.z = npc->pos.z; break; }}
void npc_remove_decoration_seeing_stars(Npc* npc, s32 idx) { remove_effect(npc->decorations[idx]);}
void npc_update_decoration_glow_in_front(Npc* npc, s32 idx) { EnergyOrbWaveFXData* data;
switch (npc->decorationInitialized[idx]) { case 0: npc->decorations[idx] = fx_energy_orb_wave( FX_ENERGY_ORB_WAVE_PALE_ORB, npc->pos.x, npc->pos.y + npc->collisionHeight * 0.5, npc->pos.z, npc->scale.x * 0.8 + 0.2f, -1 ); npc->decorationInitialized[idx] = 1; break; case 1: data = npc->decorations[idx]->data.energyOrbWave; data->pos.x = npc->pos.x; data->pos.y = npc->pos.y + npc->collisionHeight * 0.5 * npc->scale.x; data->pos.z = npc->pos.z; data->scale = npc->scale.x * 0.8 + 0.2f; break; }}
void npc_remove_decoration_glow_in_front(Npc* npc, s32 idx) { remove_effect(npc->decorations[idx]);}
void npc_update_decoration_glow_behind(Npc* npc, s32 idx) { EnergyOrbWaveFXData* data;
switch (npc->decorationInitialized[idx]) { case 0: npc->decorations[idx] = fx_energy_orb_wave( FX_ENERGY_ORB_WAVE_PALE_ORB, npc->pos.x, npc->pos.y + npc->collisionHeight * 0.5, npc->pos.z - 5.0f, 1.0f, 0 ); npc->decorationInitialized[idx] = 1; break; case 1: data = npc->decorations[idx]->data.energyOrbWave; data->pos.x = npc->pos.x; data->pos.y = npc->pos.y + npc->collisionHeight * 0.5; data->pos.z = npc->pos.z - 5.0f; data->scale = 1.0f; break; }}
void npc_remove_decoration_glow_behind(Npc* npc, s32 idx) { remove_effect(npc->decorations[idx]);}
void npc_update_decoration_charged(Npc* npc, s32 idx) {#define RGBA_BUF_SIZE 20 u8 rbuf[RGBA_BUF_SIZE]; u8 gbuf[RGBA_BUF_SIZE]; u8 bbuf[RGBA_BUF_SIZE]; s32 color; s32 alpha; s32 i;
if (npc->decorationInitialized[idx] == 0) { set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_ALLOC_COLOR_BUF, RGBA_BUF_SIZE, 0, 0, 255, 0); npc->decorationInitialized[idx] = 1; } if (npc->decorationInitialized[idx] == 1) { npc->decorationGlowPhase[idx] += 7; if (npc->decorationGlowPhase[idx] >= 360) { npc->decorationGlowPhase[idx] %= 360; }
for (i = 0; i < RGBA_BUF_SIZE; i++) { rbuf[i] = (cosine(npc->decorationGlowPhase[idx] + (25 * i)) + 1.0) * 80.0f; gbuf[i] = (cosine(npc->decorationGlowPhase[idx] + (25 * i) + 45) + 1.0) * 80.0f; bbuf[i] = (cosine(npc->decorationGlowPhase[idx] + (25 * i) + 90) + 1.0) * 80.0f; }
alpha = 255; for (i = 0; i < RGBA_BUF_SIZE; i++) { color = (rbuf[i] << 24) | (gbuf[i] << 16) | (bbuf[i] << 8) | alpha; set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_COLOR_BUF_SET_MODULATE, i, color, 0, 255, 0); } }#undef RGBA_BUF_SIZE}
void npc_remove_decoration_charged(Npc* npc, s32 idx) {}
Npc* npc_find_closest(f32 x, f32 y, f32 z, f32 radius) { Npc* closestNpc = nullptr; f32 closestDist = radius; f32 maxDist = radius; s32 i;
for (i = 0; i < ARRAY_COUNT(*gCurrentNpcListPtr); i++) { Npc* npc = (*gCurrentNpcListPtr)[i];
if (npc != nullptr && npc->flags != 0 && !(npc->flags & NPC_FLAG_PARTNER)) { if (!(npc->flags & (NPC_FLAG_SUSPENDED | NPC_FLAG_INACTIVE))) { f32 distance = fabsf(dist2D(npc->pos.x, npc->pos.z, x, z));
if (distance <= maxDist) { if (distance < closestDist) { closestDist = distance; closestNpc = npc; } } } } }
return closestNpc;}
Npc* npc_find_closest_simple(f32 x, f32 y, f32 z, f32 radius) { Npc* closestNpc = nullptr; f32 closestDist = radius; f32 maxDist = radius; s32 i;
for (i = 0; i < ARRAY_COUNT(*gCurrentNpcListPtr); i++) { Npc* npc = (*gCurrentNpcListPtr)[i];
if (npc != nullptr && npc->flags != 0 && (npc->flags & NPC_FLAG_PARTNER)) { if (!(npc->flags & (NPC_FLAG_SUSPENDED | NPC_FLAG_INACTIVE))) { f32 distance = fabsf(dist2D(npc->pos.x, npc->pos.z, x, z));
if (distance <= maxDist) { if (distance < closestDist) { closestDist = distance; closestNpc = npc; } } } } }
return closestNpc;}
s32 npc_find_standing_on_entity(s32 entityIndex) { s32 idx = entityIndex | COLLISION_WITH_ENTITY_BIT; s32 y = get_entity_by_index(idx)->pos.y - 10.0f; s32 hit; s32 i;
for (i = 0; i < ARRAY_COUNT(*gCurrentNpcListPtr); i++) { Npc* npc = (*gCurrentNpcListPtr)[i];
if (npc == nullptr) { continue; } if (npc->flags == 0) { continue; } if (npc->flags & (NPC_FLAG_SUSPENDED | NPC_FLAG_INACTIVE)) { continue; } if (npc->pos.y < y) { continue; } if (npc->flags & (NPC_FLAG_IGNORE_ENTITY_COLLISION | NPC_FLAG_FLYING)) { hit = npc_get_collider_below(npc); if (hit != 0) { if (idx == hit) { return i; } } } else { hit = npc->curFloor; if (npc->curFloor & COLLISION_WITH_ENTITY_BIT) { if (idx == hit) { return i; } } } }
return -1;}
s32 npc_get_collider_below(Npc* npc) { f32 x; f32 y; f32 z; f32 yaw;
if (npc->flags & NPC_FLAG_PARTNER) { y = get_shadow_by_index(npc->shadowIndex)->pos.y + 13.0f; } else { y = npc->pos.y + 13.0f; }
yaw = 16.0f; x = npc->pos.x; z = npc->pos.z;
if (npc_raycast_down_sides( COLLIDER_FLAG_IGNORE_SHELL | COLLIDER_FLAG_IGNORE_PLAYER | COLLIDER_FLAG_IGNORE_NPC, &x, &y, &z, &yaw )) { if (yaw <= 16.0f) { return NpcHitQueryColliderID; } } return 0;}
void npc_imgfx_update(Npc* npc) { s32 imgfxType = npc->imgfxType; s32 imgfxArg1 = npc->imgfxArg1; s32 imgfxArg2 = npc->imgfxArg2; s32 imgfxArg3 = npc->imgfxArg3; s32 imgfxArg4 = npc->imgfxArg4; s32 imgfxFlags = npc->imgfxFlags;
set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_CLEAR, 0, 0, 0, 0, 0);
switch (imgfxType) { case IMGFX_CLEAR: npc->renderMode = RENDER_MODE_ALPHATEST; set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_CLEAR, 0, 0, 0, 0, imgfxFlags); break; case IMGFX_UNK_2: case IMGFX_RESET: npc->renderMode = RENDER_MODE_ALPHATEST; // fallthrough case IMGFX_UNK_1: set_npc_imgfx_all(npc->spriteInstanceID, imgfxType, 0, 0, 0, 0, imgfxFlags); break; case IMGFX_SET_WAVY: npc->renderMode = RENDER_MODE_ALPHATEST; set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_SET_WAVY, imgfxArg1, imgfxArg2, imgfxArg3, 0, imgfxFlags); break; case IMGFX_SET_COLOR: npc->renderMode = RENDER_MODE_ALPHATEST; set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_SET_COLOR, imgfxArg1, imgfxArg2, imgfxArg3, 255, imgfxFlags); break; case IMGFX_SET_ALPHA: npc->renderMode = RENDER_MODE_SURFACE_XLU_LAYER2; set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_SET_ALPHA, 255, 255, 255, imgfxArg1, imgfxFlags); break; case IMGFX_SET_TINT: npc->renderMode = RENDER_MODE_SURFACE_XLU_LAYER2; set_npc_imgfx_all( npc->spriteInstanceID, IMGFX_SET_TINT, imgfxArg1, imgfxArg2, imgfxArg3, imgfxArg4, imgfxFlags ); break; case IMGFX_SET_WHITE_FADE: npc->renderMode = RENDER_MODE_ALPHATEST; set_npc_imgfx_all( npc->spriteInstanceID, IMGFX_SET_WHITE_FADE, imgfxArg1, imgfxArg2, imgfxArg3, 255, imgfxFlags ); break; case IMGFX_SET_CREDITS_FADE: npc->renderMode = RENDER_MODE_SURFACE_XLU_LAYER2; set_npc_imgfx_all( npc->spriteInstanceID, IMGFX_SET_CREDITS_FADE, imgfxArg1, imgfxArg2, imgfxArg3, imgfxArg4, imgfxFlags ); break; case IMGFX_SET_ANIM: npc->renderMode = RENDER_MODE_ALPHATEST; set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_SET_ANIM, imgfxArg1, imgfxArg2, imgfxArg3, 0, imgfxFlags); break; case IMGFX_HOLOGRAM: npc->renderMode = RENDER_MODE_SURFACE_XLU_LAYER2; set_npc_imgfx_all( npc->spriteInstanceID, IMGFX_HOLOGRAM, imgfxArg1, imgfxArg2, imgfxArg3, imgfxArg4, imgfxFlags ); break; case IMGFX_FILL_COLOR: npc->renderMode = RENDER_MODE_ALPHATEST; set_npc_imgfx_all( npc->spriteInstanceID, IMGFX_FILL_COLOR, imgfxArg1, imgfxArg2, imgfxArg3, 255, imgfxFlags ); break; case IMGFX_OVERLAY: npc->renderMode = RENDER_MODE_ALPHATEST; set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_OVERLAY, imgfxArg1, 255, 0, 255, imgfxFlags); break; case IMGFX_OVERLAY_XLU: npc->renderMode = RENDER_MODE_SURFACE_XLU_LAYER2; set_npc_imgfx_all(npc->spriteInstanceID, IMGFX_OVERLAY, imgfxArg1, imgfxArg2, 0, imgfxArg2, imgfxFlags); break; }}
void npc_set_imgfx_params(Npc* npc, s32 imgfxType, s32 arg2, s32 arg3, s32 arg4, s32 arg5, s32 arg6) { npc->imgfxType = imgfxType; npc->imgfxArg1 = arg2; npc->imgfxArg2 = arg3; npc->imgfxArg3 = arg4; npc->imgfxArg4 = arg5; npc->imgfxFlags = arg6; npc_imgfx_update(npc);}
void init_encounter_status(void) { EncounterStatus* currentEncounter = &gCurrentEncounter; s32 i, j;
for (i = 0; i < ARRAY_COUNT(currentEncounter->encounterList); i++) { currentEncounter->encounterList[i] = 0; }
currentEncounter->flags = ENCOUNTER_FLAG_NONE; currentEncounter->numEncounters = 0; currentEncounter->firstStrikeType = FIRST_STRIKE_NONE; currentEncounter->hitType = 0; currentEncounter->battleTriggerCooldown = 0; currentEncounter->npcGroupList = 0; currentEncounter->battleTransitionState = BATTLE_TRANSITION_STATE_STARTED; currentEncounter->dropWhackaBump = false;
for (i = 0; i < ARRAY_COUNT(currentEncounter->defeatFlags); i++) { for (j = 0; j < ARRAY_COUNT(currentEncounter->defeatFlags[i]); j++) { currentEncounter->defeatFlags[i][j] = 0; } }
for (i = 0; i < ARRAY_COUNT(currentEncounter->recentMaps); i++) { currentEncounter->recentMaps[i] = -1; }
func_80045AC0(); gEncounterState = ENCOUNTER_STATE_NONE; create_worker_scene(nullptr, worker_draw_npc_do_nothing);}
void clear_encounter_status(void) { EncounterStatus* currentEncounter = &gCurrentEncounter; s32 i, j;
for (i = 0; i < ARRAY_COUNT(currentEncounter->encounterList); i++) { currentEncounter->encounterList[i] = 0; }
if (gGameStatusPtr->didAreaChange) { for (i = 0; i < ARRAY_COUNT(currentEncounter->defeatFlags); i++) { for (j = 0; j < ARRAY_COUNT(currentEncounter->defeatFlags[i]); j++) { currentEncounter->defeatFlags[i][j] = 0; } }
if (gGameStatusPtr->didAreaChange) { for (i = 0; i < ARRAY_COUNT(currentEncounter->recentMaps); i++) { currentEncounter->recentMaps[i] = -1; } } }
currentEncounter->numEncounters = 0; currentEncounter->firstStrikeType = FIRST_STRIKE_NONE; currentEncounter->hitType = 0; currentEncounter->battleTriggerCooldown = 0; currentEncounter->curAreaIndex = gGameStatusPtr->areaID; currentEncounter->curMapIndex = gGameStatusPtr->mapID; currentEncounter->curEntryIndex = gGameStatusPtr->entryID; currentEncounter->npcGroupList = 0; currentEncounter->battleTransitionState = BATTLE_TRANSITION_STATE_STARTED; currentEncounter->scriptedBattle = false;
func_80045AC0(); gEncounterState = ENCOUNTER_STATE_NONE; create_worker_scene(nullptr, worker_draw_npc_do_nothing);}
void set_battle_transition_state(s8 state) { gCurrentEncounter.battleTransitionState = state;}
void update_encounters(void) { // scripts have already updated, so pending enemies may now be destroyed destroy_pending_enemies();
switch (gEncounterState) { case ENCOUNTER_STATE_NONE: break; case ENCOUNTER_STATE_CREATE: create_encounters(); break; case ENCOUNTER_STATE_NEUTRAL: update_encounters_neutral(); break; case ENCOUNTER_STATE_PRE_BATTLE: update_encounters_pre_battle(); break; case ENCOUNTER_STATE_CONVERSATION: update_encounters_conversation(); break; case ENCOUNTER_STATE_POST_BATTLE: update_encounters_post_battle(); break; }
update_merlee_messages();
// handle additional destruction requested during the encounter update destroy_pending_enemies();}
void draw_encounter_ui(void) { switch (gEncounterState) { case ENCOUNTER_STATE_NONE: break; case ENCOUNTER_STATE_CREATE: init_encounters_ui(); break; case ENCOUNTER_STATE_NEUTRAL: draw_encounters_neutral(); break; case ENCOUNTER_STATE_PRE_BATTLE: draw_encounters_pre_battle(); break; case ENCOUNTER_STATE_CONVERSATION: draw_encounters_conversation(); break; case ENCOUNTER_STATE_POST_BATTLE: draw_encounters_post_battle(); break; }
draw_merlee_messages();}
void draw_first_strike_ui(void) { switch (gEncounterState) { case ENCOUNTER_STATE_NONE: break; case ENCOUNTER_STATE_PRE_BATTLE: show_first_strike_message(); break; }}
void worker_draw_npc_do_nothing(void) {}
void make_npcs(s32 flags, s32 mapID, s32* npcGroupList) { EncounterStatus* currentEncounter = &gCurrentEncounter; s32 i; s32 j;
currentEncounter->resetMapEncounterFlags = flags; currentEncounter->mapID = mapID; currentEncounter->npcGroupList = npcGroupList;
if (gGameStatusPtr->didAreaChange) { for (i = 0; i < ARRAY_COUNT(currentEncounter->defeatFlags); i++) { for (j = 0; j < ARRAY_COUNT(currentEncounter->defeatFlags[i]); j++) { currentEncounter->defeatFlags[i][j] = 0; } }
if (gGameStatusPtr->didAreaChange) { for (i = 0; i < ARRAY_COUNT(currentEncounter->recentMaps); i++) { currentEncounter->recentMaps[i] = -1; } } }
if (npcGroupList != nullptr) { gEncounterState = ENCOUNTER_STATE_CREATE; EncounterStateChanged = true; gEncounterSubState = ENCOUNTER_SUBSTATE_CREATE_INIT; }}
void kill_encounter(Enemy* enemy) { Encounter* encounter = gCurrentEncounter.encounterList[enemy->encounterIndex]; s32 i;
for (i = 0; i < encounter->count; i++) { Enemy* currentEnemy = encounter->enemy[i]; if (currentEnemy != nullptr) { kill_enemy(currentEnemy); } }}
// checks whether any script registered to this enemy is still runningb32 enemy_has_live_scripts(Enemy* enemy) { s32 i;
for (i = 0; i < ARRAY_COUNT(enemy->scripts.all); i++) { if (is_bound_script_running(&enemy->scripts.all[i])) { return true; } } return false;}
// terminates every script registered to this enemyvoid kill_enemy_scripts(Enemy* enemy) { b32 killedScript; s32 i;
// Finally may register another script, so keep checking until none remain do { killedScript = false; for (i = 0; i < ARRAY_COUNT(enemy->scripts.all); i++) { Evt* script = get_bound_script(&enemy->scripts.all[i]);
if (script != nullptr && script->terminationState == EVT_TERMINATION_NONE) { kill_script(script); killedScript = true; } } } while (killedScript);}
// destroys an enemy after its registered scripts and their children have terminatedvoid destroy_enemy(Enemy* enemy) { EncounterStatus* encounterStatus = &gCurrentEncounter; Encounter* encounter = encounterStatus->encounterList[enemy->encounterIndex]; s32 i; s32 j;
for (i = 0; i < encounter->count; i++) { Enemy* currentEnemy = encounter->enemy[i]; if (currentEnemy == enemy) { break; } }
enemy->scripts.interact.source = nullptr; enemy->scripts.ai.source = nullptr; enemy->scripts.hit.source = nullptr; enemy->scripts.aux.source = nullptr; enemy->scripts.defeat.source = nullptr;
if (enemy->npcID != (s16) DX_DEBUG_DUMMY_ID) { free_npc(get_npc_unsafe(enemy->npcID)); }
for (j = 0; j < ARRAY_COUNT(encounter->enemy); j++) { if (encounter->enemy[j] == enemy) { encounter->enemy[j] = nullptr; } }
if (!(enemy->flags & ENEMY_FLAG_DO_NOT_KILL) && (!(enemy->flags & ENEMY_FLAG_ENABLE_HIT_SCRIPT) || (enemy == encounterStatus->curEnemy)) && !(enemy->flags & ENEMY_FLAG_PASSIVE) && !(enemy->flags & ENEMY_FLAG_FLED)) { set_defeated(encounterStatus->mapID, encounter->encounterID + i); }
if (encounterStatus->curEnemy == enemy) { encounterStatus->curEnemy = nullptr; }
heap_free(enemy);}
// terminates an enemy's scripts and marks it for destructionvoid kill_enemy(Enemy* enemy) { if (enemy == nullptr || enemy->deletePending) { return; }
enemy->deletePending = true; enemy->flags |= ENEMY_FLAG_DISABLE_AI; kill_enemy_scripts(enemy);}
// destroys enemies once their registered scripts have finished Finallyvoid destroy_pending_enemies(void) { EncounterStatus* encounterStatus = &gCurrentEncounter; s32 i, j;
for (i = 0; i < encounterStatus->numEncounters; i++) { Encounter* encounter = encounterStatus->encounterList[i];
if (encounter == nullptr) { continue; }
for (j = 0; j < encounter->count; j++) { Enemy* enemy = encounter->enemy[j];
if (enemy != nullptr && enemy->deletePending) { kill_enemy_scripts(enemy); if (!enemy_has_live_scripts(enemy)) { destroy_enemy(enemy); } } } }}
s32 bind_enemy_ai(Enemy* enemy, EvtScript* aiScriptBytecode) { Evt* aiScript;
kill_bound_script(&enemy->scripts.ai); enemy->scripts.ai.source = aiScriptBytecode; aiScript = start_script(aiScriptBytecode, EVT_PRIORITY_A, 0); assign_bound_script(&enemy->scripts.ai, aiScript); aiScript->owner1.enemy = enemy; aiScript->owner2.npcID = enemy->npcID; return aiScript->id;}
s32 bind_enemy_aux(Enemy* enemy, EvtScript* auxScriptBytecode) { Evt* auxScript;
kill_bound_script(&enemy->scripts.aux); enemy->scripts.aux.source = auxScriptBytecode; auxScript = start_script(auxScriptBytecode, EVT_PRIORITY_A, 0); assign_bound_script(&enemy->scripts.aux, auxScript); auxScript->owner1.enemy = enemy; auxScript->owner2.npcID = enemy->npcID; return auxScript->id;}
s32 bind_enemy_interact(Enemy* enemy, EvtScript* interactScriptBytecode) { Evt* interactScript;
kill_bound_script(&enemy->scripts.interact); enemy->scripts.interact.source = interactScriptBytecode; interactScript = start_script(interactScriptBytecode, EVT_PRIORITY_A, 0); assign_bound_script(&enemy->scripts.interact, interactScript); interactScript->owner1.enemy = enemy; interactScript->owner2.npcID = enemy->npcID; return interactScript->id;}
void bind_npc_ai(s32 npcID, EvtScript* npcAiBytecode) { s32 i, j;
for (i = 0; i < gCurrentEncounter.numEncounters; i++) { Encounter* currentEncounter = gCurrentEncounter.encounterList[i]; if (currentEncounter != nullptr) { for (j = 0; j < currentEncounter->count; j++) { Enemy* currentEnemy = currentEncounter->enemy[j]; if ((currentEnemy != nullptr) && (currentEnemy->npcID == npcID)) { bind_enemy_ai(currentEnemy, npcAiBytecode); break; } } } }}
void bind_npc_aux(s32 npcID, EvtScript* npcAuxBytecode) { s32 i, j;
for (i = 0; i < gCurrentEncounter.numEncounters; i++) { Encounter* currentEncounter = gCurrentEncounter.encounterList[i]; if (currentEncounter != nullptr) { for (j = 0; j < currentEncounter->count; j++) { Enemy* currentEnemy = currentEncounter->enemy[j]; if ((currentEnemy != nullptr) && (currentEnemy->npcID == npcID)) { bind_enemy_aux(currentEnemy, npcAuxBytecode); break; } } } }}
void bind_npc_interact(s32 npcID, EvtScript* npcInteractBytecode) { s32 i, j;
for (i = 0; i < gCurrentEncounter.numEncounters; i++) { Encounter* currentEncounter = gCurrentEncounter.encounterList[i]; if (currentEncounter != nullptr) { for (j = 0; j < currentEncounter->count; j++) { Enemy* currentEnemy = currentEncounter->enemy[j]; if ((currentEnemy != nullptr) && (currentEnemy->npcID == npcID)) { bind_enemy_interact(currentEnemy, npcInteractBytecode); break; } } } }}
Enemy* get_enemy(s32 npcID) { s32 i, j;
for (i = 0; i < gCurrentEncounter.numEncounters; i++) { Encounter* currentEncounter = gCurrentEncounter.encounterList[i]; if (currentEncounter != nullptr) { for (j = 0; j < currentEncounter->count; j++) { Enemy* currentEnemy = currentEncounter->enemy[j]; if ((currentEnemy != nullptr) && (currentEnemy->npcID == npcID)) { return currentEnemy; } } } } PANIC();}
Enemy* get_enemy_safe(s32 npcID) { s32 i, j;
for (i = 0; i < gCurrentEncounter.numEncounters; i++) { Encounter* currentEncounter = gCurrentEncounter.encounterList[i];
if (currentEncounter != nullptr) { for (j = 0; j < currentEncounter->count; j++) { Enemy* currentEnemy = currentEncounter->enemy[j]; if ((currentEnemy != nullptr) && (currentEnemy->npcID == npcID)) { return currentEnemy; } } } } return nullptr;}