mirror of
				https://github.com/coop-deluxe/sm64coopdx.git
				synced 2025-10-30 08:01:01 +00:00 
			
		
		
		
	Synchronized coin collection
Reimplemented how randomness is synchronized
This commit is contained in:
		
							parent
							
								
									5ec9ab9ec0
								
							
						
					
					
						commit
						444c1fdd3b
					
				
					 16 changed files with 182 additions and 43 deletions
				
			
		| 
						 | 
				
			
			@ -3944,6 +3944,8 @@
 | 
			
		|||
    <ClCompile Include="..\src\pc\ini.c" />
 | 
			
		||||
    <ClCompile Include="..\src\pc\mixer.c" />
 | 
			
		||||
    <ClCompile Include="..\src\pc\network\network.c" />
 | 
			
		||||
    <ClCompile Include="..\src\pc\network\packets\packet_collect_coin.c" />
 | 
			
		||||
    <ClCompile Include="..\src\pc\network\packets\packet_collect_star.c" />
 | 
			
		||||
    <ClCompile Include="..\src\pc\network\packets\packet_inside_painting.c" />
 | 
			
		||||
    <ClCompile Include="..\src\pc\network\packets\packet_level_warp.c" />
 | 
			
		||||
    <ClCompile Include="..\src\pc\network\packets\packet_object.c" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14961,6 +14961,12 @@
 | 
			
		|||
    <ClCompile Include="..\src\pc\network\packets\packet_inside_painting.c">
 | 
			
		||||
      <Filter>Source Files\src\pc\network\packets</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="..\src\pc\network\packets\packet_collect_star.c">
 | 
			
		||||
      <Filter>Source Files\src\pc\network\packets</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="..\src\pc\network\packets\packet_collect_coin.c">
 | 
			
		||||
      <Filter>Source Files\src\pc\network\packets</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ClCompile Include="..\actors\common0.h">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -376,5 +376,15 @@ struct MarioState
 | 
			
		|||
//       HOWEVER, simply increasing this to 3 will not magically work
 | 
			
		||||
//       many things will have to be overhauled!
 | 
			
		||||
#define MAX_PLAYERS 2
 | 
			
		||||
// still deciding to increase it?
 | 
			
		||||
// networking will have to be rewritten to have more than one destination. 'reliable' messages would need to be sent per-player
 | 
			
		||||
// things that base priority on whether they are the host or not would need priority based on player index instead
 | 
			
		||||
// player 2's mario2.geo file will need a different one for player 3, 4, 5, etc... and will need values within it adjusted in a similar manner (diff them)
 | 
			
		||||
// read all of the code surrounding a search through the entire codebase of the following:
 | 
			
		||||
// gLuigiObject
 | 
			
		||||
// gMarioObject
 | 
			
		||||
// gMarioState[0]
 | 
			
		||||
// gMarioState[1]
 | 
			
		||||
// luigi
 | 
			
		||||
 | 
			
		||||
#endif // _SM64_TYPES_H_
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,7 +29,6 @@
 | 
			
		|||
#define BHV_CMD_GET_ADDR_OF_CMD(index) (uintptr_t)(&gCurBhvCommand[index])
 | 
			
		||||
 | 
			
		||||
static u16 gRandomSeed16;
 | 
			
		||||
static u16 gSyncRandom;
 | 
			
		||||
 | 
			
		||||
// Unused function that directly jumps to a behavior command and resets the object's stack index.
 | 
			
		||||
static void goto_behavior_unused(const BehaviorScript *bhvAddr) {
 | 
			
		||||
| 
						 | 
				
			
			@ -37,52 +36,34 @@ static void goto_behavior_unused(const BehaviorScript *bhvAddr) {
 | 
			
		|||
    gCurrentObject->bhvStackIndex = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void random_sync_reset(void) {
 | 
			
		||||
    // seed the sync'd random seed with enough synchronzied information to be "unique enough"
 | 
			
		||||
    gSyncRandom = (u16)gCurrentObject->oPosX
 | 
			
		||||
                ^ (u16)gCurrentObject->oPosY
 | 
			
		||||
                ^ (u16)gCurrentObject->oPosZ
 | 
			
		||||
                ^ (u16)gCurrentObject->oVelX
 | 
			
		||||
                ^ (u16)gCurrentObject->oVelY
 | 
			
		||||
                ^ (u16)gCurrentObject->oVelZ
 | 
			
		||||
                ^ (u16)gCurrentObject->oAction;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generate a pseudorandom integer from 0 to 65535 from the synchronized seed, and update the seed.
 | 
			
		||||
u16 random_sync_u16(void) {
 | 
			
		||||
    u16 temp1, temp2;
 | 
			
		||||
 | 
			
		||||
    if (gSyncRandom == 22026) {
 | 
			
		||||
        gSyncRandom = 0;
 | 
			
		||||
void force_replicable_seed(u8 always) {
 | 
			
		||||
    // force the seed to consistent values
 | 
			
		||||
    extern u16 gRandomSeed16;
 | 
			
		||||
    extern u32 gGlobalTimer;
 | 
			
		||||
    static u32 lastTimer = 0;
 | 
			
		||||
    static f32 lastPos[3] = { 0 };
 | 
			
		||||
    if (gGlobalTimer == lastTimer
 | 
			
		||||
        && lastPos[0] == gCurrentObject->oPosX / 10
 | 
			
		||||
        && lastPos[1] == gCurrentObject->oPosY / 10
 | 
			
		||||
        && lastPos[2] == gCurrentObject->oPosZ / 10
 | 
			
		||||
        && !always) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    temp1 = (gSyncRandom & 0x00FF) << 8;
 | 
			
		||||
    temp1 = temp1 ^ gSyncRandom;
 | 
			
		||||
 | 
			
		||||
    gSyncRandom = ((temp1 & 0x00FF) << 8) + ((temp1 & 0xFF00) >> 8);
 | 
			
		||||
 | 
			
		||||
    temp1 = ((temp1 & 0x00FF) << 1) ^ gSyncRandom;
 | 
			
		||||
    temp2 = (temp1 >> 1) ^ 0xFF80;
 | 
			
		||||
 | 
			
		||||
    if ((temp1 & 1) == 0) {
 | 
			
		||||
        if (temp2 == 43605) {
 | 
			
		||||
            gSyncRandom = 0;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            gSyncRandom = temp2 ^ 0x1FF4;
 | 
			
		||||
        }
 | 
			
		||||
    gRandomSeed16 = (u16)(gCurrentObject->oPosX / 1000.0f)
 | 
			
		||||
                  ^ (u16)(gCurrentObject->oPosY / 1000.0f)
 | 
			
		||||
                  ^ (u16)(gCurrentObject->oPosZ / 1000.0f);
 | 
			
		||||
    if (!always) {
 | 
			
		||||
        lastPos[0] = gCurrentObject->oPosX / 10;
 | 
			
		||||
        lastPos[1] = gCurrentObject->oPosY / 10;
 | 
			
		||||
        lastPos[2] = gCurrentObject->oPosZ / 10;
 | 
			
		||||
        lastTimer = gGlobalTimer;
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
        gSyncRandom = temp2 ^ 0x8180;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return gSyncRandom;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generate a pseudorandom integer from 0 to 65535 from the random seed, and update the seed.
 | 
			
		||||
u16 random_u16(void) {
 | 
			
		||||
    // override this function for synchronized entities
 | 
			
		||||
    if (gCurrentObject->oSyncID != 0) { return random_sync_u16(); }
 | 
			
		||||
    if (gCurrentObject->oSyncID != 0) { force_replicable_seed(FALSE); }
 | 
			
		||||
 | 
			
		||||
    u16 temp1, temp2;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,6 +19,7 @@
 | 
			
		|||
 | 
			
		||||
#define obj_and_int(object, offset, value) object->OBJECT_FIELD_S32(offset) &= (s32)(value)
 | 
			
		||||
 | 
			
		||||
void force_replicable_seed(u8 always);
 | 
			
		||||
u16 random_u16(void);
 | 
			
		||||
float random_float(void);
 | 
			
		||||
s32 random_sign(void);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -176,6 +176,7 @@ void bully_step(void) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void bully_spawn_coin(void) {
 | 
			
		||||
    force_replicable_seed(TRUE);
 | 
			
		||||
    struct Object *coin = spawn_object(o, MODEL_YELLOW_COIN, bhvMovingYellowCoin);
 | 
			
		||||
#ifdef VERSION_JP //TODO: maybe move this ifdef logic to the header?
 | 
			
		||||
    cur_obj_play_sound_2(SOUND_GENERAL_COIN_SPURT);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -49,6 +49,7 @@ void bhv_temp_coin_loop(void) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void bhv_coin_init(void) {
 | 
			
		||||
    force_replicable_seed(FALSE);
 | 
			
		||||
    o->oVelY = random_float() * 10.0f + 30 + o->oCoinUnk110;
 | 
			
		||||
    o->oForwardVel = random_float() * 10.0f;
 | 
			
		||||
    o->oMoveAngleYaw = random_u16();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -285,7 +285,6 @@ void huge_goomba_weakly_attacked(void) {
 | 
			
		|||
 */
 | 
			
		||||
void bhv_goomba_update(void) {
 | 
			
		||||
    // PARTIAL_UPDATE
 | 
			
		||||
    random_sync_reset();
 | 
			
		||||
 | 
			
		||||
    f32 animSpeed;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ extern OSMesg D_80339CD4;
 | 
			
		|||
extern struct VblankHandler gGameVblankHandler;
 | 
			
		||||
extern uintptr_t gPhysicalFrameBuffers[3];
 | 
			
		||||
extern uintptr_t gPhysicalZBuffer;
 | 
			
		||||
extern void *D_80339CF0[2];
 | 
			
		||||
extern void *D_80339CF0[MAX_PLAYERS];
 | 
			
		||||
extern void *D_80339CF4;
 | 
			
		||||
extern struct SPTask *gGfxSPTask;
 | 
			
		||||
extern Gfx *gDisplayListHead;
 | 
			
		||||
| 
						 | 
				
			
			@ -51,7 +51,7 @@ extern struct DemoInput gRecordedDemoInput;
 | 
			
		|||
 | 
			
		||||
// this area is the demo input + the header. when the demo is loaded in, there is a header the size
 | 
			
		||||
// of a single word next to the input list. this word is the current ID count.
 | 
			
		||||
extern struct MarioAnimation D_80339D10[2];
 | 
			
		||||
extern struct MarioAnimation D_80339D10[MAX_PLAYERS];
 | 
			
		||||
extern struct MarioAnimation gDemo;
 | 
			
		||||
 | 
			
		||||
extern u8 gMarioAnims[];
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -748,6 +748,11 @@ void reset_mario_pitch(struct MarioState *m) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
u32 interact_coin(struct MarioState *m, UNUSED u32 interactType, struct Object *o) {
 | 
			
		||||
    if (m != &gMarioStates[0]) {
 | 
			
		||||
        // only collect locally
 | 
			
		||||
        return FALSE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m->numCoins += o->oDamageOrCoinValue;
 | 
			
		||||
    m->healCounter += 4 * o->oDamageOrCoinValue;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -762,6 +767,8 @@ u32 interact_coin(struct MarioState *m, UNUSED u32 interactType, struct Object *
 | 
			
		|||
        queue_rumble_data(5, 80);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    network_send_collect_coin(o);
 | 
			
		||||
 | 
			
		||||
    return FALSE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -672,6 +672,7 @@ s32 obj_find_wall_displacement(Vec3f dist, f32 x, f32 y, f32 z, f32 radius) {
 | 
			
		|||
void obj_spawn_yellow_coins(struct Object *obj, s8 nCoins) {
 | 
			
		||||
    struct Object *coin;
 | 
			
		||||
    s8 count;
 | 
			
		||||
    force_replicable_seed(TRUE);
 | 
			
		||||
 | 
			
		||||
    for (count = 0; count < nCoins; count++) {
 | 
			
		||||
        coin = spawn_object(obj, MODEL_YELLOW_COIN, bhvMovingYellowCoin);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -743,6 +743,7 @@ static s32 obj_handle_attacks(struct ObjectHitbox *hitbox, s32 attackedMarioActi
 | 
			
		|||
                    break;
 | 
			
		||||
 | 
			
		||||
                case ATTACK_HANDLER_SQUISHED_WITH_BLUE_COIN:
 | 
			
		||||
                    force_replicable_seed(TRUE);
 | 
			
		||||
                    o->oNumLootCoins = -1;
 | 
			
		||||
                    obj_set_squished_action();
 | 
			
		||||
                    break;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1634,6 +1634,7 @@ static void obj_spawn_loot_coins(struct Object *obj, s32 numCoins, f32 sp30,
 | 
			
		|||
        spawnHeight = obj->oPosY;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    force_replicable_seed(TRUE);
 | 
			
		||||
    for (i = 0; i < numCoins; i++) {
 | 
			
		||||
        if (obj->oNumLootCoins <= 0) {
 | 
			
		||||
            break;
 | 
			
		||||
| 
						 | 
				
			
			@ -1657,6 +1658,7 @@ void obj_spawn_loot_yellow_coins(struct Object *obj, s32 numCoins, f32 sp28) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void cur_obj_spawn_loot_coin_at_mario_pos(void) {
 | 
			
		||||
    force_replicable_seed(TRUE);
 | 
			
		||||
    struct Object *coin;
 | 
			
		||||
    if (o->oNumLootCoins <= 0) {
 | 
			
		||||
        return;
 | 
			
		||||
| 
						 | 
				
			
			@ -2940,6 +2942,7 @@ s32 cur_obj_check_interacted(void) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void cur_obj_spawn_loot_blue_coin(void) {
 | 
			
		||||
    force_replicable_seed(TRUE);
 | 
			
		||||
    if (o->oNumLootCoins >= 5) {
 | 
			
		||||
        spawn_object(o, MODEL_BLUE_COIN, bhvMrIBlueCoin);
 | 
			
		||||
        o->oNumLootCoins -= 5;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -111,6 +111,7 @@ void network_update(void) {
 | 
			
		|||
            case PACKET_LEVEL_WARP: network_receive_level_warp(&p); break;
 | 
			
		||||
            case PACKET_INSIDE_PAINTING: network_receive_inside_painting(&p); break;
 | 
			
		||||
            case PACKET_COLLECT_STAR: network_receive_collect_star(&p); break;
 | 
			
		||||
            case PACKET_COLLECT_COIN: network_receive_collect_coin(&p); break;
 | 
			
		||||
            default: printf("%s received unknown packet: %d\n", NETWORKTYPESTR, p.buffer[0]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ enum PacketType {
 | 
			
		|||
    PACKET_LEVEL_WARP,
 | 
			
		||||
    PACKET_INSIDE_PAINTING,
 | 
			
		||||
    PACKET_COLLECT_STAR,
 | 
			
		||||
    PACKET_COLLECT_COIN,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct Packet {
 | 
			
		||||
| 
						 | 
				
			
			@ -84,4 +85,7 @@ void network_receive_inside_painting(struct Packet* p);
 | 
			
		|||
 | 
			
		||||
void network_send_collect_star(s16 coinScore, s16 starIndex);
 | 
			
		||||
void network_receive_collect_star(struct Packet* p);
 | 
			
		||||
 | 
			
		||||
void network_send_collect_coin(struct Object* o);
 | 
			
		||||
void network_receive_collect_coin(struct Packet* p);
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										121
									
								
								src/pc/network/packets/packet_collect_coin.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/pc/network/packets/packet_collect_coin.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,121 @@
 | 
			
		|||
#include <stdio.h>
 | 
			
		||||
#include "../network.h"
 | 
			
		||||
#include "object_fields.h"
 | 
			
		||||
#include "object_constants.h"
 | 
			
		||||
#include "course_table.h"
 | 
			
		||||
#include "src/game/interaction.h"
 | 
			
		||||
#include "src/engine/math_util.h"
 | 
			
		||||
 | 
			
		||||
static u8 localCoinId = 1;
 | 
			
		||||
 | 
			
		||||
// the remoteCoinId stuff is only valid for 'luigi' aka the one remote player
 | 
			
		||||
// will need to be extended if MAX_PLAYERS is ever increased
 | 
			
		||||
#define MAX_REMOTE_COIN_IDS 16
 | 
			
		||||
static u8 remoteCoinIds[MAX_REMOTE_COIN_IDS] = { 0 };
 | 
			
		||||
static u8 onRemoteCoinId = 0;
 | 
			
		||||
 | 
			
		||||
static f32 dist_to_pos(struct Object* o, f32* pos) {
 | 
			
		||||
    f32 x = (f32)o->oPosX - pos[0]; x *= x;
 | 
			
		||||
    f32 y = (f32)o->oPosY - pos[1]; y *= y;
 | 
			
		||||
    f32 z = (f32)o->oPosZ - pos[2]; z *= z;
 | 
			
		||||
    return (f32)sqrt(x + y + z);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct Object* find_nearest_coin(const BehaviorScript *behavior, f32* pos, s32 coinValue, float minDist) {
 | 
			
		||||
    uintptr_t *behaviorAddr = segmented_to_virtual(behavior);
 | 
			
		||||
    struct Object *closestObj = NULL;
 | 
			
		||||
    struct Object *obj;
 | 
			
		||||
    struct ObjectNode *listHead;
 | 
			
		||||
 | 
			
		||||
    extern struct ObjectNode *gObjectLists;
 | 
			
		||||
    listHead = &gObjectLists[get_object_list_from_behavior(behaviorAddr)];
 | 
			
		||||
    obj = (struct Object *) listHead->next;
 | 
			
		||||
 | 
			
		||||
    while (obj != (struct Object *) listHead) {
 | 
			
		||||
        if (obj->behavior == behaviorAddr && obj->activeFlags != ACTIVE_FLAG_DEACTIVATED && obj->oDamageOrCoinValue == coinValue) {
 | 
			
		||||
            f32 objDist = dist_to_pos(obj, pos);
 | 
			
		||||
            if (objDist < minDist) {
 | 
			
		||||
                closestObj = obj;
 | 
			
		||||
                minDist = objDist;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        obj = (struct Object *) obj->header.next;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return closestObj;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void network_send_collect_coin(struct Object* o) {
 | 
			
		||||
    struct Packet p;
 | 
			
		||||
    packet_init(&p, PACKET_COLLECT_COIN, true);
 | 
			
		||||
 | 
			
		||||
    packet_write(&p, &localCoinId, sizeof(u8));
 | 
			
		||||
    packet_write(&p, &o->behavior, sizeof(void*));
 | 
			
		||||
    packet_write(&p, &o->oPosX, sizeof(f32) * 3);
 | 
			
		||||
    packet_write(&p, &gMarioStates[0].numCoins, sizeof(s16));
 | 
			
		||||
    packet_write(&p, &o->oDamageOrCoinValue, sizeof(s32));
 | 
			
		||||
 | 
			
		||||
    network_send(&p);
 | 
			
		||||
    localCoinId++;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void network_receive_collect_coin(struct Packet* p) {
 | 
			
		||||
    u8 remoteCoinId = 0;
 | 
			
		||||
    void* behavior = NULL;
 | 
			
		||||
    f32 pos[3] = { 0 };
 | 
			
		||||
    s16 numCoins = 0;
 | 
			
		||||
    s32 coinValue = 0;
 | 
			
		||||
 | 
			
		||||
    packet_read(p, &remoteCoinId, sizeof(u8));
 | 
			
		||||
    packet_read(p, &behavior, sizeof(void*));
 | 
			
		||||
    packet_read(p, &pos, sizeof(f32) * 3);
 | 
			
		||||
    packet_read(p, &numCoins, sizeof(s16));
 | 
			
		||||
    packet_read(p, &coinValue, sizeof(s32));
 | 
			
		||||
 | 
			
		||||
    // check if remote coin id has already been seen
 | 
			
		||||
    for (int i = 0; i < MAX_REMOTE_COIN_IDS; i++) {
 | 
			
		||||
        if (remoteCoinIds[i] == remoteCoinId) {
 | 
			
		||||
            // we already saw this coin!
 | 
			
		||||
            goto SANITY_CHECK_COINS;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // cache the seen id
 | 
			
		||||
    remoteCoinIds[onRemoteCoinId] = remoteCoinId;
 | 
			
		||||
    onRemoteCoinId = (onRemoteCoinId + 1) % MAX_REMOTE_COIN_IDS;
 | 
			
		||||
 | 
			
		||||
    // make sure it's valid
 | 
			
		||||
    if (behavior == NULL) { goto SANITY_CHECK_COINS; }
 | 
			
		||||
 | 
			
		||||
    // find the coin
 | 
			
		||||
    struct Object* coin = find_nearest_coin(behavior, pos, coinValue, 1000);
 | 
			
		||||
    if (coin == NULL) { goto SANITY_CHECK_COINS; }
 | 
			
		||||
 | 
			
		||||
    // destroy coin
 | 
			
		||||
    coin->oInteractStatus = INT_STATUS_INTERACTED;
 | 
			
		||||
 | 
			
		||||
    // add to local mario's coin count
 | 
			
		||||
    gMarioStates[0].numCoins += coinValue;
 | 
			
		||||
 | 
			
		||||
    // check for 100-coin star
 | 
			
		||||
    extern s16 gCurrCourseNum;
 | 
			
		||||
    if (COURSE_IS_MAIN_COURSE(gCurrCourseNum)
 | 
			
		||||
        && gMarioStates[0].numCoins - coin->oDamageOrCoinValue < 100
 | 
			
		||||
        && gMarioStates[0].numCoins >= 100) {
 | 
			
		||||
        bhv_spawn_star_no_level_exit(6);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
SANITY_CHECK_COINS:;
 | 
			
		||||
    // make sure we're at least at the same coin count
 | 
			
		||||
    s16 oldCoinCount = gMarioStates[0].numCoins;
 | 
			
		||||
    gMarioStates[0].numCoins = max(numCoins, gMarioStates[0].numCoins);
 | 
			
		||||
 | 
			
		||||
    // check for 100-coin star
 | 
			
		||||
    if (COURSE_IS_MAIN_COURSE(gCurrCourseNum)
 | 
			
		||||
        && oldCoinCount < 100
 | 
			
		||||
        && gMarioStates[0].numCoins >= 100) {
 | 
			
		||||
        bhv_spawn_star_no_level_exit(6);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue