From 20103bd2874ffd4ae294a618cf66fe715e5976e2 Mon Sep 17 00:00:00 2001 From: MysterD Date: Sun, 2 Apr 2023 21:18:17 -0700 Subject: [PATCH] Fix player interactions Separate normal player collisions from pvp Normal collisions use local state PVP collisions use rollback state Make squish when bouncing completely local Increase rollback buffer --- include/types.h | 1 + src/game/interaction.c | 196 ++++++++++++++++------------- src/game/mario.c | 41 +++--- src/game/mario_actions_automatic.c | 1 + src/game/object_collision.c | 30 ++--- src/game/object_collision.h | 1 + src/pc/network/network_player.c | 3 +- 7 files changed, 148 insertions(+), 125 deletions(-) diff --git a/include/types.h b/include/types.h index 1ce0b95e..f2aa8066 100644 --- a/include/types.h +++ b/include/types.h @@ -398,6 +398,7 @@ struct MarioState /*????*/ Vec3f wallNormal; /*????*/ u8 visibleToEnemies; /*????*/ u32 cap; + /*????*/ u8 bounceSquishTimer; }; struct TextureInfo diff --git a/src/game/interaction.c b/src/game/interaction.c index dd5c2f96..03d0950e 100644 --- a/src/game/interaction.c +++ b/src/game/interaction.c @@ -24,6 +24,7 @@ #include "sm64.h" #include "sound_init.h" #include "rumble_init.h" +#include "object_collision.h" #include "hardcoded.h" #include "pc/configfile.h" @@ -210,11 +211,9 @@ u32 determine_interaction(struct MarioState *m, struct Object *o) { s16 dYawToObject = mario_obj_angle_to_object(m, o) - m->faceAngle[1]; if (m->flags & MARIO_PUNCHING) { - // 120 degrees total, or 60 each way interaction = INT_PUNCH; } if (m->flags & MARIO_KICKING) { - // 120 degrees total, or 60 each way interaction = INT_KICK; } if (m->flags & MARIO_TRIPPING) { @@ -1281,10 +1280,8 @@ static u8 resolve_player_collision(struct MarioState* m, struct MarioState* m2) u32 interaction = determine_interaction(m, m2->marioObj); f32 aboveFloor = m2->pos[1] - m2->floorHeight; if ((interaction & INT_HIT_FROM_ABOVE) && (aboveFloor < 1)) { - if (m2->playerIndex == 0) { - network_player_local_restore_lag_state(); - m2->squishTimer = max(m2->squishTimer, 4); - } + m2->bounceSquishTimer = max(m2->bounceSquishTimer, 4); + f32 velY; if (m2->action == ACT_CROUCHING) { mario_stop_riding_and_holding(m); @@ -1374,110 +1371,127 @@ u32 interact_player(struct MarioState* m, UNUSED u32 interactType, struct Object if (m2 == NULL) { return FALSE; } if (m2->action == ACT_JUMBO_STAR_CUTSCENE) { return FALSE; } - // set my local player to the state I was in when they attacked - if (m2->playerIndex == 0) { - network_player_local_set_lag_state(&gNetworkPlayers[m->playerIndex]); - } - // vanish cap players can't interact u32 vanishFlags = (MARIO_VANISH_CAP | MARIO_CAP_ON_HEAD); if ((m->flags & vanishFlags) == vanishFlags) { - network_player_local_restore_lag_state(); return FALSE; } if ((m2->flags & vanishFlags) == vanishFlags) { - network_player_local_restore_lag_state(); return FALSE; } // don't do further interactions if we've hopped on top if (resolve_player_collision(m, m2)) { - network_player_local_restore_lag_state(); return FALSE; } + // don't touch each other on level load + if (gCurrentArea == NULL || gCurrentArea->localAreaTimer < 60) { + return FALSE; + } + + return FALSE; +} + +u32 interact_player_pvp(struct MarioState* attacker, struct MarioState* victim) { + if (!is_player_active(attacker)) { return FALSE; } + if (!is_player_active(victim)) { return FALSE; } + if (gServerSettings.playerInteractions == PLAYER_INTERACTIONS_NONE) { return FALSE; } + if (attacker->action == ACT_JUMBO_STAR_CUTSCENE) { return FALSE; } + if (attacker->action == ACT_JUMBO_STAR_CUTSCENE) { return FALSE; } + + // vanish cap players can't interact + u32 vanishFlags = (MARIO_VANISH_CAP | MARIO_CAP_ON_HEAD); + if ((attacker->flags & vanishFlags) == vanishFlags) { return FALSE; } + if ((victim->flags & vanishFlags) == vanishFlags) { return FALSE; } + // don't attack each other on level load - if (gCurrentArea == NULL || gCurrentArea->localAreaTimer < 60) { + if (gCurrentArea == NULL || gCurrentArea->localAreaTimer < 60) { return FALSE; } + + // make sure it passes pvp checks before rollback + if (!passes_pvp_interaction_checks(attacker, victim)) { return FALSE; } + + // set my local player to the state I was in when they attacked + if (victim->playerIndex == 0) { + network_player_local_set_lag_state(&gNetworkPlayers[victim->playerIndex]); + } + + // make sure we overlap + f32 overlapScale = (attacker->playerIndex == 0) ? 0.6f : 1.0f; + if (!detect_player_hitbox_overlap(attacker, victim, overlapScale)) { network_player_local_restore_lag_state(); return FALSE; } - u32 interaction = determine_interaction(m, o); - - if ((interaction & INT_ANY_ATTACK) && !(interaction & INT_HIT_FROM_ABOVE) && passes_pvp_interaction_checks(m, m2)) { - bool allow = true; - smlua_call_event_hooks_mario_params_ret_bool(HOOK_ALLOW_PVP_ATTACK, m, m2, &allow); - - if (!allow) { - // Lua blocked the interaction - network_player_local_restore_lag_state(); - return false; - } - - // determine if slide attack should be ignored - if ((interaction & INT_ATTACK_SLIDE) || player_is_sliding(m2)) { - // determine the difference in velocities - Vec3f velDiff; - vec3f_dif(velDiff, m->vel, m2->vel); - - if (m->action == ACT_SLIDE_KICK_SLIDE || m->action == ACT_SLIDE_KICK) { - if (vec3f_length(m->vel) < 15) { - return FALSE; - } - } else { - if (vec3f_length(m->vel) < 40) { - // the difference vectors are not different enough, do not attack - return FALSE; - } - } - if (vec3f_length(m2->vel) > vec3f_length(m->vel)) { - // the one being attacked is going faster, do not attack - return FALSE; - } - } - - // restore to current state - u32 m2action = m2->action; - u32 m2flags = m2->flags; + // see if it was an attack + u32 interaction = determine_interaction(attacker, victim->marioObj); + if (!(interaction & INT_ANY_ATTACK) || (interaction & INT_HIT_FROM_ABOVE) || !passes_pvp_interaction_checks(attacker, victim)) { network_player_local_restore_lag_state(); - - // determine if ground pound should be ignored - if (m->action == ACT_GROUND_POUND) { - // not moving down yet? - if (m->actionState == 0) { - return FALSE; - } - m2->squishTimer = max(m2->squishTimer, 20); - } - - if (m2->playerIndex == 0) { - m2->interactObj = m->marioObj; - if (interaction & INT_KICK) { - if (m2action == ACT_FIRST_PERSON) { - // without this branch, the player will be stuck in first person - raise_background_noise(2); - set_camera_mode(m2->area->camera, -1, 1); - m2->input &= ~INPUT_FIRST_PERSON; - } - set_mario_action(m2, ACT_FREEFALL, 0); - } - if (!(m2flags & MARIO_METAL_CAP)) { - m->marioObj->oDamageOrCoinValue = determine_player_damage_value(interaction); - if (m->flags & MARIO_METAL_CAP) { - m->marioObj->oDamageOrCoinValue *= 2; - } - } - } - m2->invincTimer = max(m2->invincTimer, 3); - take_damage_and_knock_back(m2, m->marioObj); - bounce_back_from_attack(m, interaction); - m2->interactObj = NULL; - - smlua_call_event_hooks_mario_params(HOOK_ON_PVP_ATTACK, m, m2); return FALSE; } + // call the lua hook + bool allow = true; + smlua_call_event_hooks_mario_params_ret_bool(HOOK_ALLOW_PVP_ATTACK, attacker, victim, &allow); + if (!allow) { + // Lua blocked the interaction + network_player_local_restore_lag_state(); + return FALSE; + } + + // determine if slide attack should be ignored + if ((interaction & INT_ATTACK_SLIDE) || player_is_sliding(victim)) { + // determine the difference in velocities + Vec3f velDiff; + vec3f_dif(velDiff, attacker->vel, victim->vel); + + if (attacker->action == ACT_SLIDE_KICK_SLIDE || attacker->action == ACT_SLIDE_KICK) { + // if the difference vectors are not different enough, do not attack + if (vec3f_length(attacker->vel) < 15) { return FALSE; } + } else { + // if the difference vectors are not different enough, do not attack + if (vec3f_length(attacker->vel) < 40) { return FALSE; } + } + + // if the victim is going faster, do not attack + if (vec3f_length(victim->vel) > vec3f_length(attacker->vel)) { return FALSE; } + } + + // restore to current state + u32 victimAction = victim->action; + u32 victimFlags = victim->flags; network_player_local_restore_lag_state(); + + // determine if ground pound should be ignored + if (attacker->action == ACT_GROUND_POUND) { + // not moving down yet? + if (attacker->actionState == 0) { return FALSE; } + victim->bounceSquishTimer = max(victim->bounceSquishTimer, 20); + } + + if (victim->playerIndex == 0) { + victim->interactObj = attacker->marioObj; + if (interaction & INT_KICK) { + if (victimAction == ACT_FIRST_PERSON) { + // without this branch, the player will be stuck in first person + raise_background_noise(2); + set_camera_mode(victim->area->camera, -1, 1); + victim->input &= ~INPUT_FIRST_PERSON; + } + set_mario_action(victim, ACT_FREEFALL, 0); + } + if (!(victimFlags & MARIO_METAL_CAP)) { + attacker->marioObj->oDamageOrCoinValue = determine_player_damage_value(interaction); + if (attacker->flags & MARIO_METAL_CAP) { attacker->marioObj->oDamageOrCoinValue *= 2; } + } + } + + victim->invincTimer = max(victim->invincTimer, 3); + take_damage_and_knock_back(victim, attacker->marioObj); + bounce_back_from_attack(attacker, interaction); + victim->interactObj = NULL; + + smlua_call_event_hooks_mario_params(HOOK_ON_PVP_ATTACK, attacker, victim); return FALSE; } @@ -2228,6 +2242,16 @@ void mario_process_interactions(struct MarioState *m) { } } + if (!(m->action & ACT_FLAG_INTANGIBLE) && is_player_active(m)) { + for (s32 i = 0; i < MAX_PLAYERS; i++) { + struct NetworkPlayer* np2 = &gNetworkPlayers[i]; + if (!np2->connected) { continue; } + if (&gMarioStates[i] == m) { continue; } + interact_player_pvp(m, &gMarioStates[i]); + } + network_player_local_restore_lag_state(); + } + if (m->invincTimer > 0 && !sDelayInvincTimer) { m->invincTimer -= 1; } diff --git a/src/game/mario.c b/src/game/mario.c index cf1d1b81..1d5ec3c8 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -1330,28 +1330,29 @@ u8 sSquishScaleOverTime[16] = { 0x46, 0x32, 0x32, 0x3C, 0x46, 0x50, 0x50, 0x3C, * Applies the squish to Mario's model via scaling. */ void squish_mario_model(struct MarioState *m) { - if (m->squishTimer != 0xFF) { - // If no longer squished, scale back to default. - // Also handles the Tiny Mario and Huge Mario cheats. - if (m->squishTimer == 0) { - vec3f_set(m->marioObj->header.gfx.scale, 1.0f, 1.0f, 1.0f); - } - // If timer is less than 16, rubber-band Mario's size scale up and down. - else if (m->squishTimer <= 16) { - m->squishTimer -= 1; + if (m->squishTimer == 0xFF && m->bounceSquishTimer == 0) { return; } - m->marioObj->header.gfx.scale[1] = - 1.0f - ((sSquishScaleOverTime[15 - m->squishTimer] * 0.6f) / 100.0f); - m->marioObj->header.gfx.scale[0] = - ((sSquishScaleOverTime[15 - m->squishTimer] * 0.4f) / 100.0f) + 1.0f; - - m->marioObj->header.gfx.scale[2] = m->marioObj->header.gfx.scale[0]; - } else { - m->squishTimer -= 1; - - vec3f_set(m->marioObj->header.gfx.scale, 1.4f, 0.4f, 1.4f); - } + // If no longer squished, scale back to default. + // Also handles the Tiny Mario and Huge Mario cheats. + u8 squishTimer = (m->squishTimer > m->bounceSquishTimer) ? m->squishTimer : m->bounceSquishTimer; + if (squishTimer == 0) { + vec3f_set(m->marioObj->header.gfx.scale, 1.0f, 1.0f, 1.0f); + return; } + + // If timer is less than 16, rubber-band Mario's size scale up and down. + if (squishTimer <= 16) { + squishTimer--; + + m->marioObj->header.gfx.scale[1] = 1.0f - ((sSquishScaleOverTime[15 - squishTimer] * 0.6f) / 100.0f); + m->marioObj->header.gfx.scale[0] = ((sSquishScaleOverTime[15 - squishTimer] * 0.4f) / 100.0f) + 1.0f; + m->marioObj->header.gfx.scale[2] = m->marioObj->header.gfx.scale[0]; + } else { + vec3f_set(m->marioObj->header.gfx.scale, 1.4f, 0.4f, 1.4f); + } + + if (m->squishTimer > 0) { m->squishTimer--; } + if (m->bounceSquishTimer > 0) { m->bounceSquishTimer--; } } /** diff --git a/src/game/mario_actions_automatic.c b/src/game/mario_actions_automatic.c index 5751cb3b..84732b5f 100644 --- a/src/game/mario_actions_automatic.c +++ b/src/game/mario_actions_automatic.c @@ -983,6 +983,7 @@ s32 act_bubbled(struct MarioState* m) { m->heldByObj = NULL; m->marioObj->oIntangibleTimer = -1; m->squishTimer = 0; + m->bounceSquishTimer = 0; set_mario_animation(m, MARIO_ANIM_SLEEP_IDLE); // force inputs diff --git a/src/game/object_collision.c b/src/game/object_collision.c index 4a578762..6634139e 100644 --- a/src/game/object_collision.c +++ b/src/game/object_collision.c @@ -21,12 +21,10 @@ struct Object *debug_print_obj_collision(struct Object *a) { return NULL; } -int detect_player_hitbox_overlap(struct MarioState* local, struct MarioState* remote) { +int detect_player_hitbox_overlap(struct MarioState* local, struct MarioState* remote, f32 scale) { if (local->marioObj == NULL || local->marioObj->oIntangibleTimer != 0) { return FALSE; } if (remote->marioObj == NULL || remote->marioObj->oIntangibleTimer != 0) { return FALSE; } - network_player_local_set_lag_state(&gNetworkPlayers[remote->playerIndex]); - struct Object* a = local->marioObj; f32* aTorso = local->marioBodyState->torsoPos; @@ -41,39 +39,26 @@ int detect_player_hitbox_overlap(struct MarioState* local, struct MarioState* re f32 collisionRadius = (a->hitboxRadius + b->hitboxRadius) * 2.25f; f32 distance = sqrtf(dx * dx + dz * dz); - if (collisionRadius > distance) { + if (collisionRadius * scale > distance) { f32 sp20 = a->hitboxHeight + sp3C; f32 sp1C = b->hitboxHeight + sp38; if (sp3C > sp1C) { - network_player_local_restore_lag_state(); return FALSE; } if (sp20 < sp38) { - network_player_local_restore_lag_state(); return FALSE; } if (a->numCollidedObjs >= 4) { - network_player_local_restore_lag_state(); return FALSE; } if (b->numCollidedObjs >= 4) { - network_player_local_restore_lag_state(); return FALSE; } - a->collidedObjs[a->numCollidedObjs] = b; - b->collidedObjs[b->numCollidedObjs] = a; - a->collidedObjInteractTypes |= b->oInteractType; - b->collidedObjInteractTypes |= a->oInteractType; - a->numCollidedObjs++; - b->numCollidedObjs++; - network_player_local_restore_lag_state(); return TRUE; } - //! no return value - network_player_local_restore_lag_state(); return FALSE; } @@ -200,7 +185,16 @@ void check_player_object_collision(void) { extern struct MarioState gMarioStates[]; for (s32 i = 1; i < MAX_PLAYERS; i++) { - detect_player_hitbox_overlap(&gMarioStates[0], &gMarioStates[i]); + if (detect_player_hitbox_overlap(&gMarioStates[0], &gMarioStates[i], 1.0f)) { + struct Object* a = gMarioStates[0].marioObj; + struct Object* b = gMarioStates[i].marioObj; + a->collidedObjs[a->numCollidedObjs] = b; + b->collidedObjs[b->numCollidedObjs] = a; + a->collidedObjInteractTypes |= b->oInteractType; + b->collidedObjInteractTypes |= a->oInteractType; + a->numCollidedObjs++; + b->numCollidedObjs++; + } } } diff --git a/src/game/object_collision.h b/src/game/object_collision.h index 3357d197..d1609789 100644 --- a/src/game/object_collision.h +++ b/src/game/object_collision.h @@ -1,6 +1,7 @@ #ifndef OBJECT_COLLISION_H #define OBJECT_COLLISION_H +int detect_player_hitbox_overlap(struct MarioState* local, struct MarioState* remote, f32 scale); void detect_object_collisions(void); #endif // OBJECT_COLLISION_H diff --git a/src/pc/network/network_player.c b/src/pc/network/network_player.c index 4203016d..2b190baf 100644 --- a/src/pc/network/network_player.c +++ b/src/pc/network/network_player.c @@ -16,7 +16,7 @@ struct NetworkPlayer *gNetworkPlayerLocal = NULL; struct NetworkPlayer *gNetworkPlayerServer = NULL; static char sDefaultPlayerName[] = "Player"; -#define MAX_LOCAL_PLAYER_STATES 15 +#define MAX_LOCAL_PLAYER_STATES 20 struct LagState { struct MarioState m; struct MarioBodyState bodyState; @@ -178,6 +178,7 @@ void network_player_local_set_lag_state(struct NetworkPlayer* otherNp) { if (!sLocalPlayerStatesReady) { return; } s32 pingToTicks = (otherNp->ping / 1000.0f) * 30; + pingToTicks += 2; if (pingToTicks > (MAX_LOCAL_PLAYER_STATES-1)) { pingToTicks = (MAX_LOCAL_PLAYER_STATES-1); }