diff --git a/build-windows-visual-studio/sm64ex.vcxproj b/build-windows-visual-studio/sm64ex.vcxproj
index 36b382676..e0687edc3 100644
--- a/build-windows-visual-studio/sm64ex.vcxproj
+++ b/build-windows-visual-studio/sm64ex.vcxproj
@@ -3949,6 +3949,7 @@
+
diff --git a/build-windows-visual-studio/sm64ex.vcxproj.filters b/build-windows-visual-studio/sm64ex.vcxproj.filters
index a44e70813..2dcc0a82c 100644
--- a/build-windows-visual-studio/sm64ex.vcxproj.filters
+++ b/build-windows-visual-studio/sm64ex.vcxproj.filters
@@ -14955,6 +14955,12 @@
Source Files\src\pc\network\packets
+
+ Source Files\src\pc\network\packets
+
+
+ Source Files\src\pc\network\packets
+
diff --git a/levels/wf/script.c b/levels/wf/script.c
index 6697b4973..50a0b2cbe 100644
--- a/levels/wf/script.c
+++ b/levels/wf/script.c
@@ -104,6 +104,7 @@ const LevelScript level_wf_entry[] = {
LOAD_RAW( /*seg*/ 0x0F, _common0_geoSegmentRomStart, _common0_geoSegmentRomEnd),
ALLOC_LEVEL_POOL(),
MARIO(/*model*/ MODEL_MARIO, /*behParam*/ 0x00000001, /*beh*/ bhvMario),
+ LUIGI(/*model*/ MODEL_LUIGI, /*behParam*/ 0x00000002, /*beh*/ bhvLuigi),
JUMP_LINK(script_func_global_1),
JUMP_LINK(script_func_global_2),
JUMP_LINK(script_func_global_15),
diff --git a/src/game/behaviors/bobomb.inc.c b/src/game/behaviors/bobomb.inc.c
index 2770a3363..e5b5faf46 100644
--- a/src/game/behaviors/bobomb.inc.c
+++ b/src/game/behaviors/bobomb.inc.c
@@ -17,7 +17,7 @@ void bhv_bobomb_init(void) {
o->oFriction = 0.8;
o->oBuoyancy = 1.3;
o->oInteractionSubtype = INT_SUBTYPE_KICKABLE;
- network_init_object(o, 4000);
+ network_init_object(o, 4000.0f);
}
void bobomb_spawn_coin(void) {
diff --git a/src/game/behaviors/breakable_box_small.inc.c b/src/game/behaviors/breakable_box_small.inc.c
index 639c96dab..fc4ec33b3 100644
--- a/src/game/behaviors/breakable_box_small.inc.c
+++ b/src/game/behaviors/breakable_box_small.inc.c
@@ -20,7 +20,7 @@ void bhv_breakable_box_small_init(void) {
obj_set_hitbox(o, &sBreakableBoxSmallHitbox);
o->oAnimState = 1;
o->activeFlags |= ACTIVE_FLAG_UNK9;
- network_init_object(o, 500);
+ network_init_object(o, 500.0f);
}
void small_breakable_box_spawn_dust(void) {
diff --git a/src/game/behaviors/goomba.inc.c b/src/game/behaviors/goomba.inc.c
index 5f93f277e..babe7e959 100644
--- a/src/game/behaviors/goomba.inc.c
+++ b/src/game/behaviors/goomba.inc.c
@@ -127,7 +127,7 @@ void bhv_goomba_init(void) {
o->oGravity = -8.0f / 3.0f * o->oGoombaScale;
- network_init_object(o, 4000);
+ network_init_object(o, 4000.0f);
network_init_object_field(o, &o->oGoombaTargetYaw);
network_init_object_field(o, &o->oGoombaWalkTimer);
}
diff --git a/src/game/behaviors/koopa.inc.c b/src/game/behaviors/koopa.inc.c
index 0c8302125..6eb05f774 100644
--- a/src/game/behaviors/koopa.inc.c
+++ b/src/game/behaviors/koopa.inc.c
@@ -84,6 +84,12 @@ void bhv_koopa_init(void) {
} else {
o->oKoopaAgility = 1.0f;
}
+
+ network_init_object(o, 4000.0f);
+ network_init_object_field(o, &o->oSubAction);
+ network_init_object_field(o, &o->oKoopaTargetYaw);
+ network_init_object_field(o, &o->oKoopaCountdown);
+ network_init_object_field(o, &o->oKoopaMovementType);
}
/**
@@ -105,8 +111,10 @@ static void koopa_play_footstep_sound(s8 animFrame1, s8 animFrame2) {
* running away.
*/
static s32 koopa_check_run_from_mario(void) {
- if (o->oKoopaDistanceToMario < 300.0f
- && abs_angle_diff(o->oKoopaAngleToMario, o->oMoveAngleYaw) < 0x3000) {
+ struct Object* player = nearest_player_to_object(o);
+ int distanceToPlayer = dist_between_objects(o, player);
+ int angleToPlayer = obj_angle_to_object(o, player);
+ if (distanceToPlayer < 300.0f && abs_angle_diff(angleToPlayer, o->oMoveAngleYaw) < 0x3000) {
o->oAction = KOOPA_SHELLED_ACT_RUN_FROM_MARIO;
return TRUE;
}
@@ -170,9 +178,12 @@ static void koopa_shelled_act_walk(void) {
if (o->oKoopaTurningAwayFromWall) {
o->oKoopaTurningAwayFromWall = obj_resolve_collisions_and_turn(o->oKoopaTargetYaw, 0x200);
} else {
+ struct Object* player = nearest_player_to_object(o);
+ int distanceToPlayer = dist_between_objects(o, player);
+ int angleToPlayer = obj_angle_to_object(o, player);
// If far from home, then begin turning toward home
- if (o->oDistanceToMario >= 25000.0f) {
- o->oKoopaTargetYaw = o->oAngleToMario;
+ if (distanceToPlayer >= 25000.0f) {
+ o->oKoopaTargetYaw = angleToPlayer;
}
o->oKoopaTurningAwayFromWall = obj_bounce_off_walls_edges_objects(&o->oKoopaTargetYaw);
@@ -202,18 +213,22 @@ static void koopa_shelled_act_run_from_mario(void) {
cur_obj_init_animation_with_sound(1);
koopa_play_footstep_sound(0, 11);
+ struct Object* player = nearest_player_to_object(o);
+ int distanceToPlayer = dist_between_objects(o, player);
+ int angleToPlayer = obj_angle_to_object(o, player);
+
// If far from home, run toward it
- if (o->oDistanceToMario >= 25000.0f) {
- o->oAngleToMario += 0x8000;
- o->oDistanceToMario = 0.0f;
+ if (distanceToPlayer >= 25000.0f) {
+ angleToPlayer += 0x8000;
+ distanceToPlayer = 0.0f;
}
- if (o->oTimer > 30 && o->oDistanceToMario > 800.0f) {
+ if (o->oTimer > 30 && distanceToPlayer > 800.0f) {
if (obj_forward_vel_approach(0.0f, 1.0f)) {
o->oAction = KOOPA_SHELLED_ACT_STOPPED;
}
} else {
- cur_obj_rotate_yaw_toward(o->oAngleToMario + 0x8000, 0x400);
+ cur_obj_rotate_yaw_toward(angleToPlayer + 0x8000, 0x400);
obj_forward_vel_approach(17.0f, 1.0f);
}
}
@@ -265,7 +280,9 @@ void shelled_koopa_attack_handler(s32 attackType) {
// If attacked from the side, get knocked away from mario
if (attackType != ATTACK_FROM_ABOVE && attackType != ATTACK_GROUND_POUND_OR_TWIRL) {
- o->oMoveAngleYaw = obj_angle_to_object(gMarioObject, o);
+ struct Object* player = nearest_player_to_object(o);
+ int angleToPlayer = obj_angle_to_object(o, player);
+ o->oMoveAngleYaw = angleToPlayer;
}
cur_obj_set_model(MODEL_KOOPA_WITHOUT_SHELL);
@@ -285,6 +302,10 @@ void shelled_koopa_attack_handler(s32 attackType) {
* Update function for both regular and tiny shelled koopa.
*/
static void koopa_shelled_update(void) {
+ if (!cur_obj_has_model(MODEL_KOOPA_WITH_SHELL)) {
+ cur_obj_set_model(MODEL_KOOPA_WITH_SHELL);
+ }
+
cur_obj_update_floor_and_walls();
obj_update_blinking(&o->oKoopaBlinkTimer, 20, 50, 4);
@@ -336,9 +357,13 @@ static void koopa_unshelled_act_run(void) {
if (o->oKoopaTurningAwayFromWall) {
o->oKoopaTurningAwayFromWall = obj_resolve_collisions_and_turn(o->oKoopaTargetYaw, 0x600);
} else {
+ struct Object* player = nearest_player_to_object(o);
+ int distanceToPlayer = dist_between_objects(o, player);
+ int angleToPlayer = obj_angle_to_object(o, player);
+
// If far from home, then turn toward home
- if (o->oDistanceToMario >= 25000.0f) {
- o->oKoopaTargetYaw = o->oAngleToMario;
+ if (distanceToPlayer >= 25000.0f) {
+ o->oKoopaTargetYaw = angleToPlayer;
}
// If shell exists, then turn toward shell
@@ -358,9 +383,9 @@ static void koopa_unshelled_act_run(void) {
// If mario is far away, or our running away from mario coincides with
// running toward the shell
- if (o->oDistanceToMario > 800.0f
+ if (distanceToPlayer > 800.0f
|| (shell != NULL
- && abs_angle_diff(o->oKoopaTargetYaw, o->oAngleToMario + 0x8000) < 0x2000)) {
+ && abs_angle_diff(o->oKoopaTargetYaw, angleToPlayer + 0x8000) < 0x2000)) {
// then turn toward the shell
cur_obj_rotate_yaw_toward(o->oKoopaTargetYaw, 0x600);
} else {
@@ -395,6 +420,10 @@ static void koopa_unshelled_act_dive(void) {
if (o->oTimer > 10) {
shell = cur_obj_find_nearest_object_with_behavior(bhvKoopaShell, &distToShell);
+ struct Object* player = nearest_player_to_object(o);
+ int distanceToPlayer = dist_between_objects(o, player);
+ int angleToPlayer = obj_angle_to_object(o, player);
+
// If we got the shell and mario didn't, put on the shell
//! The shell comes after koopa in processing order, and the shell is
// responsible for positioning itself under mario.
@@ -403,8 +432,7 @@ static void koopa_unshelled_act_dive(void) {
// units behind mario.
// Using this, we can get the koopa to pick up and despawn its shell
// while mario is riding it.
- if (shell != NULL && dist_between_objects(shell, gMarioObject) > 200.0f
- && distToShell < 50.0f) {
+ if (shell != NULL && distanceToPlayer && distToShell < 50.0f) {
o->oKoopaMovementType = KOOPA_BP_NORMAL;
o->oAction = KOOPA_SHELLED_ACT_LYING;
o->oForwardVel *= 0.5f;
@@ -444,6 +472,10 @@ static void koopa_unshelled_act_unused3(void) {
* Update function for koopa after losing his shell.
*/
static void koopa_unshelled_update(void) {
+ if (!cur_obj_has_model(MODEL_KOOPA_WITHOUT_SHELL)) {
+ cur_obj_set_model(MODEL_KOOPA_WITHOUT_SHELL);
+ }
+
cur_obj_update_floor_and_walls();
obj_update_blinking(&o->oKoopaBlinkTimer, 10, 15, 3);
@@ -615,7 +647,10 @@ static void koopa_the_quick_act_race(void) {
case KOOPA_THE_QUICK_SUB_ACT_RUN:
koopa_the_quick_animate_footsteps();
- if (o->parentObj->oKoopaRaceEndpointRaceStatus != 0 && o->oDistanceToMario > 1500.0f
+ struct Object* player = nearest_player_to_object(o);
+ int distanceToPlayer = dist_between_objects(o, player);
+
+ if (o->parentObj->oKoopaRaceEndpointRaceStatus != 0 && distanceToPlayer > 1500.0f
&& (o->oPathedPrevWaypointFlags & WAYPOINT_MASK_00FF) < 28) {
// Move faster if mario has already finished the race or
// cheated by shooting from cannon
@@ -796,8 +831,12 @@ void bhv_koopa_update(void) {
if (o->oKoopaMovementType >= KOOPA_BP_KOOPA_THE_QUICK_BASE) {
koopa_the_quick_update();
} else if (obj_update_standard_actions(o->oKoopaAgility * 1.5f)) {
- o->oKoopaDistanceToMario = o->oDistanceToMario;
- o->oKoopaAngleToMario = o->oAngleToMario;
+ struct Object* player = nearest_player_to_object(o);
+ int distanceToPlayer = dist_between_objects(o, player);
+ int angleToPlayer = obj_angle_to_object(o, player);
+
+ o->oKoopaDistanceToMario = distanceToPlayer;
+ o->oKoopaAngleToMario = angleToPlayer;
treat_far_home_as_mario(1000.0f);
switch (o->oKoopaMovementType) {
@@ -824,7 +863,9 @@ void bhv_koopa_update(void) {
*/
void bhv_koopa_race_endpoint_update(void) {
if (o->oKoopaRaceEndpointRaceBegun && !o->oKoopaRaceEndpointRaceEnded) {
- if (o->oKoopaRaceEndpointKoopaFinished || o->oDistanceToMario < 400.0f) {
+ struct Object* player = nearest_player_to_object(o);
+ int distanceToPlayer = dist_between_objects(o, player);
+ if (o->oKoopaRaceEndpointKoopaFinished || distanceToPlayer < 400.0f) {
o->oKoopaRaceEndpointRaceEnded = TRUE;
level_control_timer(TIMER_CONTROL_STOP);
diff --git a/src/game/behaviors/koopa_shell.inc.c b/src/game/behaviors/koopa_shell.inc.c
index c408bed76..7c4237bd7 100644
--- a/src/game/behaviors/koopa_shell.inc.c
+++ b/src/game/behaviors/koopa_shell.inc.c
@@ -16,7 +16,7 @@ void koopa_shell_spawn_water_drop(void) {
UNUSED s32 unused;
struct Object *drop;
spawn_object(o, MODEL_WAVE_TRAIL, bhvObjectWaveTrail);
- if (gMarioStates->forwardVel > 10.0f) {
+ if (gMarioStates[o->heldByPlayerIndex].forwardVel > 10.0f) {
drop = spawn_object_with_scale(o, MODEL_WHITE_PARTICLE_SMALL, bhvWaterDroplet, 1.5f);
drop->oVelY = random_float() * 30.0f;
obj_translate_xz_random(drop, 110.0f);
@@ -52,21 +52,32 @@ void koopa_shell_spawn_sparkles(f32 a) {
}
void bhv_koopa_shell_loop(void) {
+ if (o->oSyncID == 0) {
+ network_init_object(o, 500.0f);
+ network_init_object_field(o, &o->oInteractStatus);
+ network_init_object_field(o, &o->oAction);
+ }
+
struct Surface *sp34;
obj_set_hitbox(o, &sKoopaShellHitbox);
cur_obj_scale(1.0f);
+ struct Object* player = NULL;
switch (o->oAction) {
case 0:
cur_obj_update_floor_and_walls();
cur_obj_if_hit_wall_bounce_away();
- if (o->oInteractStatus & INT_STATUS_INTERACTED)
+ if (o->oInteractStatus & INT_STATUS_INTERACTED) {
o->oAction++;
+ player = nearest_player_to_object(o);
+ o->heldByPlayerIndex = (player == gMarioObject) ? 0 : 1;
+ }
o->oFaceAngleYaw += 0x1000;
cur_obj_move_standard(-20);
koopa_shell_spawn_sparkles(10.0f);
break;
case 1:
- obj_copy_pos(o, gMarioObject);
+ player = gMarioStates[o->heldByPlayerIndex].marioObj;
+ obj_copy_pos(o, player);
sp34 = cur_obj_update_floor_height_and_get_floor();
if (absf(find_water_level(o->oPosX, o->oPosZ) - o->oPosY) < 10.0f)
koopa_shell_spawn_water_drop();
@@ -77,7 +88,7 @@ void bhv_koopa_shell_loop(void) {
koopa_shell_spawn_sparkles(10.0f);
} else
koopa_shell_spawn_sparkles(10.0f);
- o->oFaceAngleYaw = gMarioObject->oMoveAngleYaw;
+ o->oFaceAngleYaw = player->oMoveAngleYaw;
if (o->oInteractStatus & INT_STATUS_STOP_RIDING) {
obj_mark_for_deletion(o);
spawn_mist_particles();
diff --git a/src/game/obj_behaviors.c b/src/game/obj_behaviors.c
index ec61c714d..2c1e2c05b 100644
--- a/src/game/obj_behaviors.c
+++ b/src/game/obj_behaviors.c
@@ -515,6 +515,27 @@ s32 is_point_within_radius_of_mario(f32 x, f32 y, f32 z, s32 dist) {
return FALSE;
}
+/**
+ * Returns either gMarioObject or gLuigiObject depending on what is closer
+ */
+struct MarioState* nearest_mario_state_to_object(struct Object *obj) {
+ f32 mx = gMarioState[0].marioObj->header.gfx.pos[0] - obj->oPosX;
+ f32 my = gMarioState[0].marioObj->header.gfx.pos[1] - obj->oPosY;
+ f32 mz = gMarioState[0].marioObj->header.gfx.pos[2] - obj->oPosZ;
+ mx *= mx;
+ my *= my;
+ mz *= mz;
+
+ f32 lx = gMarioState[1].marioObj->header.gfx.pos[0] - obj->oPosX;
+ f32 ly = gMarioState[1].marioObj->header.gfx.pos[1] - obj->oPosY;
+ f32 lz = gMarioState[1].marioObj->header.gfx.pos[2] - obj->oPosZ;
+ lx *= lx;
+ ly *= ly;
+ lz *= lz;
+
+ return (mx + my + mz <= lx + ly + lz) ? &gMarioState[0] : &gMarioState[1];
+}
+
/**
* Returns either gMarioObject or gLuigiObject depending on what is closer
*/
diff --git a/src/pc/network/network.h b/src/pc/network/network.h
index dcb43668b..22af5d3f6 100644
--- a/src/pc/network/network.h
+++ b/src/pc/network/network.h
@@ -35,6 +35,7 @@ struct SyncObject {
float maxSyncDistance;
bool owned;
unsigned int ticksSinceUpdate;
+ void* behavior;
u8 extraFieldCount;
void* extraFields[MAX_SYNC_OBJECT_FIELDS];
};
diff --git a/src/pc/network/packets/packet_object.c b/src/pc/network/packets/packet_object.c
index 20b8341f4..a627a9d6d 100644
--- a/src/pc/network/packets/packet_object.c
+++ b/src/pc/network/packets/packet_object.c
@@ -23,6 +23,7 @@ void network_init_object(struct Object *o, float maxSyncDistance) {
so->owned = false;
so->ticksSinceUpdate = -1;
so->extraFieldCount = 0;
+ so->behavior = o->behavior;
memset(so->extraFields, 0, sizeof(void*) * MAX_SYNC_OBJECT_FIELDS);
}
@@ -41,6 +42,7 @@ void network_send_object(struct Object* o) {
struct Packet p;
packet_init(&p, PACKET_OBJECT, reliable);
packet_write(&p, &o->oSyncID, 4);
+ packet_write(&p, &so->behavior, sizeof(void*));
packet_write(&p, &o->activeFlags, 2);
packet_write(&p, &o->oPosX, 28);
packet_write(&p, &o->oAction, 4);
@@ -57,6 +59,10 @@ void network_send_object(struct Object* o) {
if (o->activeFlags == ACTIVE_FLAG_DEACTIVATED) { forget_sync_object(so); }
+ if (o->behavior != so->behavior) {
+ printf("network_send_object() BEHAVIOR MISMATCH!\n");
+ }
+
network_send(&p);
}
@@ -91,6 +97,7 @@ void network_receive_object(struct Packet* p) {
}
// write object flags
+ packet_read(p, &so->behavior, sizeof(void*));
packet_read(p, &o->activeFlags, 2);
packet_read(p, &o->oPosX, 28);
packet_read(p, &o->oAction, 4);
@@ -106,11 +113,14 @@ void network_receive_object(struct Packet* p) {
packet_read(p, so->extraFields[i], 4);
}
+ if (o->behavior != so->behavior) {
+ printf("network_receive_object() BEHAVIOR MISMATCH!\n");
+ }
+
// deactivated
if (o->activeFlags == ACTIVE_FLAG_DEACTIVATED) {
forget_sync_object(so);
}
-
}
float player_distance(struct MarioState* marioState, struct Object* o) {