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
This commit is contained in:
MysterD 2023-04-02 21:18:17 -07:00
parent cdd077c9f8
commit 92a8cc8675
7 changed files with 148 additions and 125 deletions

View File

@ -398,6 +398,7 @@ struct MarioState
/*????*/ Vec3f wallNormal;
/*????*/ u8 visibleToEnemies;
/*????*/ u32 cap;
/*????*/ u8 bounceSquishTimer;
};
struct TextureInfo

View File

@ -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;
}

View File

@ -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--; }
}
/**

View File

@ -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

View File

@ -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++;
}
}
}

View File

@ -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

View File

@ -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);
}