Prevent several possible crashes and hangs, limit more struct fields to read-only
This commit is contained in:
parent
cb499491f4
commit
b863cc80c8
|
@ -83,7 +83,8 @@ override_field_invisible = {
|
|||
}
|
||||
|
||||
override_field_immutable = {
|
||||
"MarioState": [ "playerIndex", "controller" ],
|
||||
"MarioState": [ "playerIndex", "controller", "marioObj", "marioBodyState", "statusForCamera" ],
|
||||
"ObjectNode": [ "next", "prev" ],
|
||||
"Character": [ "*" ],
|
||||
"NetworkPlayer": [ "*" ],
|
||||
"TextureInfo": [ "*" ],
|
||||
|
@ -96,7 +97,11 @@ override_field_immutable = {
|
|||
"ModFile": [ "*" ],
|
||||
"BassAudio": [ "*" ],
|
||||
"Painting": [ "id", "imageCount", "textureType", "textureWidth", "textureHeight" ],
|
||||
"SpawnInfo": [ "syncID" ]
|
||||
"SpawnInfo": [ "syncID" ],
|
||||
"CustomLevelInfo": [ "next" ],
|
||||
"GraphNode": [ "next", "prev", "parent" ],
|
||||
"ObjectWarpNode": [ "next "],
|
||||
"SpawnInfo": [ "next" ],
|
||||
}
|
||||
|
||||
override_field_version_excludes = {
|
||||
|
@ -265,7 +270,29 @@ def get_struct_field_info(struct, field):
|
|||
|
||||
return fid, ftype, fimmutable, lvt, lot
|
||||
|
||||
def output_nuke_struct(struct):
|
||||
sid = struct['identifier']
|
||||
print('function Nuke' + sid + "(struct)")
|
||||
for field in struct['fields']:
|
||||
fid, ftype, fimmutable, lvt, lot = get_struct_field_info(struct, field)
|
||||
if fimmutable == 'true':
|
||||
continue
|
||||
if sid in override_field_invisible:
|
||||
if fid in override_field_invisible[sid]:
|
||||
continue
|
||||
if lvt == 'LVT_COBJECT':
|
||||
print(' Nuke' + ftype.replace('struct ', '') + '(struct.' + fid + ')')
|
||||
elif lvt == 'LVT_COBJECT_P':
|
||||
print(' struct.' + fid + ' = nil')
|
||||
else:
|
||||
print(' struct.' + fid + ' = 0')
|
||||
print('end')
|
||||
print('')
|
||||
|
||||
def build_struct(struct):
|
||||
# debug print out lua nuke functions
|
||||
# output_nuke_struct(struct)
|
||||
|
||||
sid = struct['identifier']
|
||||
|
||||
# build up table and track column width
|
||||
|
|
|
@ -539,7 +539,7 @@
|
|||
| fullName | `string` | read-only |
|
||||
| levelNum | `integer` | |
|
||||
| modIndex | `integer` | |
|
||||
| next | [CustomLevelInfo](structs.md#CustomLevelInfo) | |
|
||||
| next | [CustomLevelInfo](structs.md#CustomLevelInfo) | read-only |
|
||||
| script | `Pointer` <`LevelScript`> | read-only |
|
||||
| scriptEntryName | `string` | read-only |
|
||||
| shortName | `string` | read-only |
|
||||
|
@ -802,9 +802,9 @@
|
|||
| children | [GraphNode](structs.md#GraphNode) | |
|
||||
| extraFlags | `integer` | |
|
||||
| flags | `integer` | |
|
||||
| next | [GraphNode](structs.md#GraphNode) | |
|
||||
| parent | [GraphNode](structs.md#GraphNode) | |
|
||||
| prev | [GraphNode](structs.md#GraphNode) | |
|
||||
| next | [GraphNode](structs.md#GraphNode) | read-only |
|
||||
| parent | [GraphNode](structs.md#GraphNode) | read-only |
|
||||
| prev | [GraphNode](structs.md#GraphNode) | read-only |
|
||||
| type | `integer` | |
|
||||
|
||||
[:arrow_up_small:](#)
|
||||
|
@ -1070,8 +1070,8 @@
|
|||
| invincTimer | `integer` | |
|
||||
| isSnoring | `integer` | |
|
||||
| knockbackTimer | `integer` | |
|
||||
| marioBodyState | [MarioBodyState](structs.md#MarioBodyState) | |
|
||||
| marioObj | [Object](structs.md#Object) | |
|
||||
| marioBodyState | [MarioBodyState](structs.md#MarioBodyState) | read-only |
|
||||
| marioObj | [Object](structs.md#Object) | read-only |
|
||||
| minimumBoneY | `number` | |
|
||||
| nonInstantWarpPos | [Vec3f](structs.md#Vec3f) | read-only |
|
||||
| numCoins | `integer` | |
|
||||
|
@ -1095,7 +1095,7 @@
|
|||
| splineKeyframeFraction | `number` | |
|
||||
| splineState | `integer` | |
|
||||
| squishTimer | `integer` | |
|
||||
| statusForCamera | [PlayerCameraState](structs.md#PlayerCameraState) | |
|
||||
| statusForCamera | [PlayerCameraState](structs.md#PlayerCameraState) | read-only |
|
||||
| terrainSoundAddend | `integer` | |
|
||||
| twirlYaw | `integer` | |
|
||||
| unkB0 | `integer` | |
|
||||
|
@ -1996,8 +1996,8 @@
|
|||
| Field | Type | Access |
|
||||
| ----- | ---- | ------ |
|
||||
| gfx | [GraphNodeObject](structs.md#GraphNodeObject) | read-only |
|
||||
| next | [ObjectNode](structs.md#ObjectNode) | |
|
||||
| prev | [ObjectNode](structs.md#ObjectNode) | |
|
||||
| next | [ObjectNode](structs.md#ObjectNode) | read-only |
|
||||
| prev | [ObjectNode](structs.md#ObjectNode) | read-only |
|
||||
|
||||
[:arrow_up_small:](#)
|
||||
|
||||
|
@ -2214,10 +2214,10 @@
|
|||
| activeAreaIndex | `integer` | |
|
||||
| areaIndex | `integer` | |
|
||||
| behaviorArg | `integer` | |
|
||||
| next | [SpawnInfo](structs.md#SpawnInfo) | |
|
||||
| next | [SpawnInfo](structs.md#SpawnInfo) | read-only |
|
||||
| startAngle | [Vec3s](structs.md#Vec3s) | read-only |
|
||||
| startPos | [Vec3s](structs.md#Vec3s) | read-only |
|
||||
| syncID | `integer` | read-only |
|
||||
| syncID | `integer` | |
|
||||
| unk18 | [GraphNode](structs.md#GraphNode) | |
|
||||
|
||||
[:arrow_up_small:](#)
|
||||
|
|
|
@ -594,6 +594,7 @@ struct GraphNode *geo_remove_child(struct GraphNode *graphNode) {
|
|||
if (graphNode == NULL) { return NULL; }
|
||||
|
||||
parent = graphNode->parent;
|
||||
if (!parent) { return NULL; }
|
||||
firstChild = &parent->children;
|
||||
|
||||
// Remove link with siblings
|
||||
|
|
|
@ -296,7 +296,10 @@ void load_mario_area(void) {
|
|||
stop_sounds_in_continuous_banks();
|
||||
load_area(gMarioSpawnInfo->areaIndex);
|
||||
|
||||
if (!gCurrentArea) { return; }
|
||||
|
||||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!gMarioStates[i].spawnInfo) { continue; }
|
||||
gMarioStates[i].spawnInfo->areaIndex = gCurrentArea->index;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ void bhv_purple_switch_loop(void) {
|
|||
u8 anyPlayerOnPlatform = FALSE;
|
||||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!is_player_active(&gMarioStates[i])) { continue; }
|
||||
if (gMarioStates[i].marioObj->platform == o) {
|
||||
if (gMarioStates[i].marioObj && gMarioStates[i].marioObj->platform == o) {
|
||||
anyPlayerOnPlatform = TRUE;
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -11579,7 +11579,7 @@ void fov_default(struct MarioState *m) {
|
|||
camera_approach_f32_symmetric_bool(&gFOVState.fov, 45.f, (45.f - gFOVState.fov) / 30.f);
|
||||
gFOVState.unusedIsSleeping = 0;
|
||||
}
|
||||
if (m->area->camera->cutscene == CUTSCENE_0F_UNUSED) {
|
||||
if (m->area && m->area->camera && m->area->camera->cutscene == CUTSCENE_0F_UNUSED) {
|
||||
gFOVState.fov = 45.f;
|
||||
}
|
||||
}
|
||||
|
@ -11600,7 +11600,7 @@ void approach_fov_60(UNUSED struct MarioState *m) {
|
|||
void approach_fov_45(struct MarioState *m) {
|
||||
f32 targetFoV = gFOVState.fov;
|
||||
|
||||
if (m->area->camera->mode == CAMERA_MODE_FIXED && m->area->camera->cutscene == 0) {
|
||||
if (m->area && m->area->camera && m->area->camera->mode == CAMERA_MODE_FIXED && m->area->camera->cutscene == 0) {
|
||||
targetFoV = 45.f;
|
||||
} else {
|
||||
targetFoV = 45.f;
|
||||
|
@ -11620,7 +11620,7 @@ void approach_fov_80(UNUSED struct MarioState *m) {
|
|||
void set_fov_bbh(struct MarioState *m) {
|
||||
f32 targetFoV = gFOVState.fov;
|
||||
|
||||
if (m->area->camera->mode == CAMERA_MODE_FIXED && m->area->camera->cutscene == 0) {
|
||||
if (m->area && m->area->camera && m->area->camera->mode == CAMERA_MODE_FIXED && m->area->camera->cutscene == 0) {
|
||||
targetFoV = 60.f;
|
||||
} else {
|
||||
targetFoV = 45.f;
|
||||
|
|
|
@ -345,9 +345,11 @@ void mario_drop_held_object(struct MarioState *m) {
|
|||
// ! When dropping an object instead of throwing it, it will be put at Mario's
|
||||
// y-positon instead of the HOLP's y-position. This fact is often exploited when
|
||||
// cloning objects.
|
||||
m->heldObj->oPosX = m->marioBodyState->heldObjLastPosition[0];
|
||||
m->heldObj->oPosY = m->pos[1];
|
||||
m->heldObj->oPosZ = m->marioBodyState->heldObjLastPosition[2];
|
||||
if (m->marioBodyState) {
|
||||
m->heldObj->oPosX = m->marioBodyState->heldObjLastPosition[0];
|
||||
m->heldObj->oPosY = m->pos[1];
|
||||
m->heldObj->oPosZ = m->marioBodyState->heldObjLastPosition[2];
|
||||
}
|
||||
|
||||
m->heldObj->oMoveAngleYaw = m->faceAngle[1];
|
||||
|
||||
|
@ -369,9 +371,11 @@ void mario_throw_held_object(struct MarioState *m) {
|
|||
|
||||
obj_set_held_state(m->heldObj, bhvCarrySomething5);
|
||||
|
||||
m->heldObj->oPosX = m->marioBodyState->heldObjLastPosition[0] + 32.0f * sins(m->faceAngle[1]);
|
||||
m->heldObj->oPosY = m->marioBodyState->heldObjLastPosition[1];
|
||||
m->heldObj->oPosZ = m->marioBodyState->heldObjLastPosition[2] + 32.0f * coss(m->faceAngle[1]);
|
||||
if (m->marioBodyState) {
|
||||
m->heldObj->oPosX = m->marioBodyState->heldObjLastPosition[0] + 32.0f * sins(m->faceAngle[1]);
|
||||
m->heldObj->oPosY = m->marioBodyState->heldObjLastPosition[1];
|
||||
m->heldObj->oPosZ = m->marioBodyState->heldObjLastPosition[2] + 32.0f * coss(m->faceAngle[1]);
|
||||
}
|
||||
|
||||
m->heldObj->oMoveAngleYaw = m->faceAngle[1];
|
||||
|
||||
|
@ -1278,6 +1282,8 @@ static u8 resolve_player_collision(struct MarioState* m, struct MarioState* m2)
|
|||
f32 extentY = m->marioObj->hitboxHeight;
|
||||
f32 radius = m->marioObj->hitboxRadius * 2.0f;
|
||||
|
||||
if (!m->marioBodyState || !m2->marioBodyState) { return FALSE; }
|
||||
|
||||
f32* localTorso = m->marioBodyState->torsoPos;
|
||||
f32* remoteTorso = m2->marioBodyState->torsoPos;
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ s32 is_anim_past_end(struct MarioState *m) {
|
|||
*/
|
||||
s16 set_mario_animation(struct MarioState *m, s32 targetAnimID) {
|
||||
struct Object *o = m->marioObj;
|
||||
if (!o || !m->animation) { return 0; }
|
||||
struct Animation *targetAnim = m->animation->targetAnim;
|
||||
|
||||
if (load_patchable_table(m->animation, targetAnimID)) {
|
||||
|
@ -118,6 +119,7 @@ s16 set_mario_animation(struct MarioState *m, s32 targetAnimID) {
|
|||
*/
|
||||
s16 set_mario_anim_with_accel(struct MarioState *m, s32 targetAnimID, s32 accel) {
|
||||
struct Object *o = m->marioObj;
|
||||
if (!o || !m->animation) { return 0; }
|
||||
struct Animation *targetAnim = m->animation->targetAnim;
|
||||
|
||||
if (load_patchable_table(m->animation, targetAnimID)) {
|
||||
|
|
|
@ -523,6 +523,7 @@ s8 is_point_within_radius_of_mario(f32 x, f32 y, f32 z, s32 dist) {
|
|||
if (!is_player_active(&gMarioStates[i])) { continue; }
|
||||
if (!gMarioStates[i].visibleToEnemies) { continue; }
|
||||
struct Object* player = gMarioStates[i].marioObj;
|
||||
if (!player) { continue; }
|
||||
f32 mGfxX = player->header.gfx.pos[0];
|
||||
f32 mGfxY = player->header.gfx.pos[1];
|
||||
f32 mGfxZ = player->header.gfx.pos[2];
|
||||
|
@ -540,6 +541,7 @@ s8 is_point_within_radius_of_any_player(f32 x, f32 y, f32 z, s32 dist) {
|
|||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!is_player_active(&gMarioStates[i])) { continue; }
|
||||
struct Object* player = gMarioStates[i].marioObj;
|
||||
if (!player) { continue; }
|
||||
f32 mGfxX = player->header.gfx.pos[0];
|
||||
f32 mGfxY = player->header.gfx.pos[1];
|
||||
f32 mGfxZ = player->header.gfx.pos[2];
|
||||
|
@ -606,6 +608,7 @@ struct MarioState* nearest_mario_state_to_object(struct Object *obj) {
|
|||
struct MarioState* nearest = NULL;
|
||||
f32 nearestDist = 0;
|
||||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!gMarioStates[i].marioObj) { continue; }
|
||||
if (gMarioStates[i].marioObj == obj) { continue; }
|
||||
if (!gMarioStates[i].visibleToEnemies) { continue; }
|
||||
if (!is_player_active(&gMarioStates[i])) { continue; }
|
||||
|
@ -624,6 +627,7 @@ struct MarioState* nearest_possible_mario_state_to_object(struct Object *obj) {
|
|||
struct MarioState* nearest = NULL;
|
||||
f32 nearestDist = 0;
|
||||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!gMarioStates[i].marioObj) { continue; }
|
||||
if (gMarioStates[i].marioObj == obj) { continue; }
|
||||
if (!is_player_active(&gMarioStates[i])) { continue; }
|
||||
float dist = dist_between_objects(obj, gMarioStates[i].marioObj);
|
||||
|
@ -655,6 +659,7 @@ struct MarioState *nearest_interacting_mario_state_to_object(struct Object *obj)
|
|||
f32 nearestDist = 0;
|
||||
|
||||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!gMarioStates[i].marioObj) { continue; }
|
||||
if (gMarioStates[i].marioObj == obj) { continue; }
|
||||
if (gMarioStates[i].interactObj != obj) { continue; }
|
||||
if (!gMarioStates[i].visibleToEnemies) { continue; }
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "pc/network/network_player.h"
|
||||
|
||||
struct Object *debug_print_obj_collision(struct Object *a) {
|
||||
if (!a) { return NULL; }
|
||||
struct Object *sp24;
|
||||
|
||||
for (s32 i = 0; i < a->numCollidedObjs; i++) {
|
||||
|
@ -22,6 +23,7 @@ struct Object *debug_print_obj_collision(struct Object *a) {
|
|||
}
|
||||
|
||||
int detect_player_hitbox_overlap(struct MarioState* local, struct MarioState* remote, f32 scale) {
|
||||
if (!local || !remote) { return FALSE; }
|
||||
if (local->marioObj == NULL || local->marioObj->oIntangibleTimer != 0) { return FALSE; }
|
||||
if (remote->marioObj == NULL || remote->marioObj->oIntangibleTimer != 0) { return FALSE; }
|
||||
|
||||
|
@ -63,6 +65,7 @@ int detect_player_hitbox_overlap(struct MarioState* local, struct MarioState* re
|
|||
}
|
||||
|
||||
s32 detect_object_hitbox_overlap(struct Object *a, struct Object *b) {
|
||||
if (!a || !b) { return 0; }
|
||||
f32 sp3C = a->oPosY - a->hitboxDownOffset;
|
||||
f32 sp38 = b->oPosY - b->hitboxDownOffset;
|
||||
f32 dx = a->oPosX - b->oPosX;
|
||||
|
@ -105,6 +108,8 @@ s32 detect_object_hitbox_overlap(struct Object *a, struct Object *b) {
|
|||
}
|
||||
|
||||
s32 detect_object_hurtbox_overlap(struct Object *a, struct Object *b) {
|
||||
if (!a || !b) { return 0; }
|
||||
|
||||
f32 sp3C = a->oPosY - a->hitboxDownOffset;
|
||||
f32 sp38 = b->oPosY - b->hitboxDownOffset;
|
||||
f32 sp34 = a->oPosX - b->oPosX;
|
||||
|
@ -137,26 +142,30 @@ s32 detect_object_hurtbox_overlap(struct Object *a, struct Object *b) {
|
|||
}
|
||||
|
||||
void clear_object_collision(struct Object *a) {
|
||||
if (!a) { return; }
|
||||
struct Object *sp4 = (struct Object *) a->header.next;
|
||||
|
||||
while (sp4 != a) {
|
||||
while (sp4 && sp4 != a) {
|
||||
sp4->numCollidedObjs = 0;
|
||||
sp4->collidedObjInteractTypes = 0;
|
||||
if (sp4->oIntangibleTimer > 0) {
|
||||
sp4->oIntangibleTimer--;
|
||||
}
|
||||
if (sp4 == (struct Object *)sp4->header.next) { break; }
|
||||
sp4 = (struct Object *) sp4->header.next;
|
||||
}
|
||||
}
|
||||
|
||||
void check_collision_in_list(struct Object *a, struct Object *b, struct Object *c) {
|
||||
if (!a) { return; }
|
||||
if (a->oIntangibleTimer == 0) {
|
||||
while (b != c) {
|
||||
while (b && b != c) {
|
||||
if (b->oIntangibleTimer == 0) {
|
||||
if (detect_object_hitbox_overlap(a, b) && b->hurtboxRadius != 0.0f) {
|
||||
detect_object_hurtbox_overlap(a, b);
|
||||
}
|
||||
}
|
||||
if (b == (struct Object *)b->header.next) { break; }
|
||||
b = (struct Object *) b->header.next;
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +175,7 @@ void check_player_object_collision(void) {
|
|||
struct Object *sp1C = (struct Object *) &gObjectLists[OBJ_LIST_PLAYER];
|
||||
struct Object *sp18 = (struct Object *) sp1C->header.next;
|
||||
|
||||
while (sp18 != sp1C) {
|
||||
while (sp18 && sp18 != sp1C) {
|
||||
check_collision_in_list(sp18, (struct Object *) sp18->header.next, sp1C);
|
||||
check_collision_in_list(sp18, (struct Object *) gObjectLists[OBJ_LIST_POLELIKE].next,
|
||||
(struct Object *) &gObjectLists[OBJ_LIST_POLELIKE]);
|
||||
|
@ -202,8 +211,9 @@ void check_pushable_object_collision(void) {
|
|||
struct Object *sp1C = (struct Object *) &gObjectLists[OBJ_LIST_PUSHABLE];
|
||||
struct Object *sp18 = (struct Object *) sp1C->header.next;
|
||||
|
||||
while (sp18 != sp1C) {
|
||||
while (sp18 && sp18 != sp1C) {
|
||||
check_collision_in_list(sp18, (struct Object *) sp18->header.next, sp1C);
|
||||
if (sp18 == (struct Object *)sp18->header.next) { break; }
|
||||
sp18 = (struct Object *) sp18->header.next;
|
||||
}
|
||||
}
|
||||
|
@ -212,7 +222,7 @@ void check_destructive_object_collision(void) {
|
|||
struct Object *sp1C = (struct Object *) &gObjectLists[OBJ_LIST_DESTRUCTIVE];
|
||||
struct Object *sp18 = (struct Object *) sp1C->header.next;
|
||||
|
||||
while (sp18 != sp1C) {
|
||||
while (sp18 && sp18 != sp1C) {
|
||||
if (sp18->oDistanceToMario < 2000.0f && !(sp18->activeFlags & ACTIVE_FLAG_UNK9)) {
|
||||
check_collision_in_list(sp18, (struct Object *) sp18->header.next, sp1C);
|
||||
check_collision_in_list(sp18, (struct Object *) gObjectLists[OBJ_LIST_GENACTOR].next,
|
||||
|
@ -222,6 +232,7 @@ void check_destructive_object_collision(void) {
|
|||
check_collision_in_list(sp18, (struct Object *) gObjectLists[OBJ_LIST_SURFACE].next,
|
||||
(struct Object *) &gObjectLists[OBJ_LIST_SURFACE]);
|
||||
}
|
||||
if (sp18 == (struct Object *)sp18->header.next) { break; }
|
||||
sp18 = (struct Object *) sp18->header.next;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2532,6 +2532,7 @@ s32 cur_obj_wait_then_blink(s32 timeUntilBlinking, s32 numBlinks) {
|
|||
s32 cur_obj_is_mario_ground_pounding_platform(void) {
|
||||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!is_player_active(&gMarioStates[i])) { continue; }
|
||||
if (!gMarioStates[i].marioObj) { continue; }
|
||||
if (gMarioStates[i].marioObj->platform == o) {
|
||||
if (gMarioStates[i].action == ACT_GROUND_POUND_LAND) {
|
||||
return TRUE;
|
||||
|
@ -2554,6 +2555,7 @@ void spawn_mist_particles_with_sound(u32 sp18) {
|
|||
void cur_obj_push_mario_away(f32 radius) {
|
||||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
struct Object* player = gMarioStates[i].marioObj;
|
||||
if (!player) { continue; }
|
||||
f32 marioRelX = player->oPosX - o->oPosX;
|
||||
f32 marioRelZ = player->oPosZ - o->oPosZ;
|
||||
f32 marioDist = sqrtf(sqr(marioRelX) + sqr(marioRelZ));
|
||||
|
@ -2570,6 +2572,7 @@ void cur_obj_push_mario_away(f32 radius) {
|
|||
void cur_obj_push_mario_away_from_cylinder(f32 radius, f32 extentY) {
|
||||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
struct Object* player = gMarioStates[i].marioObj;
|
||||
if (!player) { continue; }
|
||||
f32 marioRelY = player->oPosY - o->oPosY;
|
||||
|
||||
if (marioRelY < 0.0f) {
|
||||
|
@ -2733,6 +2736,7 @@ s32 cur_obj_mario_far_away(void) {
|
|||
for (s32 i = 0; i < MAX_PLAYERS; i++) {
|
||||
if (!is_player_active(&gMarioStates[i])) { continue; }
|
||||
struct Object* player = gMarioStates[i].marioObj;
|
||||
if (!player) { continue; }
|
||||
f32 dx = o->oHomeX - player->oPosX;
|
||||
f32 dy = o->oHomeY - player->oPosY;
|
||||
f32 dz = o->oHomeZ - player->oPosZ;
|
||||
|
@ -2880,6 +2884,7 @@ void cur_obj_if_hit_wall_bounce_away(void) {
|
|||
}
|
||||
|
||||
s32 cur_obj_hide_if_mario_far_away_y(f32 distY) {
|
||||
if (!gMarioStates[0].marioObj) { return FALSE; }
|
||||
if (absf(o->oPosY - gMarioStates[0].marioObj->oPosY) < distY * draw_distance_scalar()) {
|
||||
cur_obj_unhide();
|
||||
return FALSE;
|
||||
|
|
|
@ -347,10 +347,13 @@ void bhv_mario_update(void) {
|
|||
* including firstObj itself. Return the number of objects that were updated.
|
||||
*/
|
||||
s32 update_objects_starting_at(struct ObjectNode *objList, struct ObjectNode *firstObj) {
|
||||
if (!firstObj) { return 0; }
|
||||
|
||||
s32 count = 0;
|
||||
|
||||
while (objList != firstObj) {
|
||||
gCurrentObject = (struct Object *) firstObj;
|
||||
if (!gCurrentObject) { break; }
|
||||
|
||||
gCurrentObject->header.gfx.node.flags |= GRAPH_RENDER_HAS_ANIMATION;
|
||||
cur_obj_update();
|
||||
|
@ -372,11 +375,13 @@ s32 update_objects_starting_at(struct ObjectNode *objList, struct ObjectNode *fi
|
|||
* updated)
|
||||
*/
|
||||
s32 update_objects_during_time_stop(struct ObjectNode *objList, struct ObjectNode *firstObj) {
|
||||
if (!firstObj) { return 0; }
|
||||
s32 count = 0;
|
||||
s32 unfrozen;
|
||||
|
||||
while (objList != firstObj) {
|
||||
gCurrentObject = (struct Object *) firstObj;
|
||||
if (!gCurrentObject) { break; }
|
||||
|
||||
unfrozen = FALSE;
|
||||
|
||||
|
@ -433,10 +438,12 @@ s32 update_objects_in_list(struct ObjectNode *objList) {
|
|||
* Unload any objects in the list that have been deactivated.
|
||||
*/
|
||||
s32 unload_deactivated_objects_in_list(struct ObjectNode *objList) {
|
||||
if (!objList) { return 0; }
|
||||
struct ObjectNode *obj = objList->next;
|
||||
|
||||
while (objList != obj) {
|
||||
gCurrentObject = (struct Object *) obj;
|
||||
if (!gCurrentObject) { break; }
|
||||
|
||||
obj = obj->next;
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
#define MATRIX_STACK_SIZE 32
|
||||
#define MATRIX_STACK_SIZE 64
|
||||
|
||||
f32 gProjectionMaxNearValue = 5;
|
||||
s16 gProjectionVanillaNearValue = 100;
|
||||
|
@ -327,9 +327,18 @@ void patch_mtx_interpolated(f32 delta) {
|
|||
static u8 increment_mat_stack() {
|
||||
Mtx *mtx = alloc_display_list(sizeof(*mtx));
|
||||
Mtx *mtxPrev = alloc_display_list(sizeof(*mtxPrev));
|
||||
if (mtx == NULL || mtxPrev == NULL) { LOG_ERROR("Failed to allocate our matrices for the matrix stack."); return FALSE; }
|
||||
if (mtx == NULL || mtxPrev == NULL) {
|
||||
LOG_ERROR("Failed to allocate our matrices for the matrix stack.");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gMatStackIndex++;
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) {
|
||||
LOG_ERROR("Exceeded matrix stack size.");
|
||||
gMatStackIndex = MATRIX_STACK_SIZE - 1;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
mtxf_to_mtx(mtx, gMatStack[gMatStackIndex]);
|
||||
mtxf_to_mtx(mtxPrev, gMatStackPrev[gMatStackIndex]);
|
||||
gMatStackFixed[gMatStackIndex] = mtx;
|
||||
|
@ -529,7 +538,7 @@ static void geo_process_camera(struct GraphNodeCamera *node) {
|
|||
Mat4 cameraTransform;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB.
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
Mtx *rollMtx = alloc_display_list(sizeof(*rollMtx));
|
||||
if (rollMtx == NULL) { return; }
|
||||
|
@ -588,7 +597,7 @@ static void geo_process_translation_rotation(struct GraphNodeTranslationRotation
|
|||
Vec3f translation;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
vec3s_to_vec3f(translation, node->translation);
|
||||
mtxf_rotate_zxy_and_translate(mtxf, translation, node->rotation);
|
||||
|
@ -617,7 +626,7 @@ static void geo_process_translation(struct GraphNodeTranslation *node) {
|
|||
Vec3f translation;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
vec3s_to_vec3f(translation, node->translation);
|
||||
mtxf_rotate_zxy_and_translate(mtxf, translation, gVec3sZero);
|
||||
|
@ -645,7 +654,7 @@ static void geo_process_rotation(struct GraphNodeRotation *node) {
|
|||
Mat4 mtxf;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
mtxf_rotate_zxy_and_translate(mtxf, gVec3fZero, node->rotation);
|
||||
mtxf_mul(gMatStack[gMatStackIndex + 1], mtxf, gMatStack[gMatStackIndex]);
|
||||
|
@ -681,7 +690,7 @@ static void geo_process_scale(struct GraphNodeScale *node) {
|
|||
Vec3f prevScaleVec;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
vec3f_set(scaleVec, node->scale, node->scale, node->scale);
|
||||
mtxf_scale_vec3f(gMatStack[gMatStackIndex + 1], gMatStack[gMatStackIndex], scaleVec);
|
||||
|
@ -717,7 +726,7 @@ static void geo_process_billboard(struct GraphNodeBillboard *node) {
|
|||
Vec3f translation;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
s16 nextMatStackIndex = gMatStackIndex + 1;
|
||||
|
||||
|
@ -890,7 +899,7 @@ static void geo_process_animated_part(struct GraphNodeAnimatedPart *node) {
|
|||
Vec3f translationPrev;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
u16 *animAttribute = gCurrAnimAttribute;
|
||||
u8 animType = gCurAnimType;
|
||||
|
@ -983,7 +992,7 @@ static void geo_process_shadow(struct GraphNodeShadow *node) {
|
|||
f32 shadowScale;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
if (gCurGraphNodeCamera != NULL && gCurGraphNodeObject != NULL) {
|
||||
if (gCurGraphNodeHeldObject != NULL) {
|
||||
|
@ -1191,6 +1200,9 @@ static void geo_process_object(struct Object *node) {
|
|||
s32 hasAnimation = (node->header.gfx.node.flags & GRAPH_RENDER_HAS_ANIMATION) != 0;
|
||||
Vec3f scalePrev;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
if (node->hookRender) {
|
||||
smlua_call_event_hooks_object_param(HOOK_ON_OBJECT_RENDER, node);
|
||||
}
|
||||
|
@ -1379,7 +1391,7 @@ void geo_process_held_object(struct GraphNodeHeldObject *node) {
|
|||
Vec3f scalePrev;
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if (gMatStackIndex >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { LOG_ERROR("Preventing attempt to exceed the maximum size %i for our matrix stack with size of %i.", MATRIX_STACK_SIZE - 1, gMatStackIndex); return; }
|
||||
|
||||
#ifdef F3DEX_GBI_2
|
||||
gSPLookAt(gDisplayListHead++, &lookAt);
|
||||
|
@ -1476,6 +1488,7 @@ void geo_process_node_and_siblings(struct GraphNode *firstNode) {
|
|||
s16 iterateChildren = TRUE;
|
||||
struct GraphNode *curGraphNode = firstNode;
|
||||
if (curGraphNode == NULL) { return; }
|
||||
u32 depthSanity = 0;
|
||||
|
||||
struct GraphNode *parent = curGraphNode->parent;
|
||||
|
||||
|
@ -1489,6 +1502,13 @@ void geo_process_node_and_siblings(struct GraphNode *firstNode) {
|
|||
if (curGraphNode == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Sanity check our stack index, If we above or equal to our stack size. Return to prevent OOB\.
|
||||
if ((gMatStackIndex + 1) >= MATRIX_STACK_SIZE) { break; }
|
||||
|
||||
// Break out of endless loops
|
||||
if (++depthSanity > 5000) { break; }
|
||||
|
||||
if (curGraphNode->flags & GRAPH_RENDER_ACTIVE) {
|
||||
if (curGraphNode->flags & GRAPH_RENDER_CHILDREN_FIRST) {
|
||||
geo_try_process_children(curGraphNode);
|
||||
|
|
|
@ -583,6 +583,7 @@ s8 correct_shadow_solidity_for_animations(s32 playerIndex, u8 initialSolidity, s
|
|||
|
||||
extern struct MarioState gMarioStates[];
|
||||
player = gMarioStates[playerIndex].marioObj;
|
||||
if (!player) { return SHADOW_SOLIDITY_NO_SHADOW; }
|
||||
|
||||
animFrame = player->header.gfx.animInfo.animFrame;
|
||||
switch (player->header.gfx.animInfo.animID) {
|
||||
|
|
|
@ -141,8 +141,8 @@ void unused_deallocate(struct LinkedList *freeList, struct LinkedList *node) {
|
|||
static void deallocate_object(struct ObjectNode *freeList, struct ObjectNode *obj) {
|
||||
if (!obj || !freeList) { return; }
|
||||
// Remove from object list
|
||||
obj->next->prev = obj->prev;
|
||||
obj->prev->next = obj->next;
|
||||
if (obj->next) { obj->next->prev = obj->prev; }
|
||||
if (obj->prev) { obj->prev->next = obj->next; }
|
||||
|
||||
// Insert at beginning of free list
|
||||
obj->next = freeList->next;
|
||||
|
|
|
@ -404,7 +404,7 @@ static struct LuaObjectField sCustomLevelInfoFields[LUA_CUSTOM_LEVEL_INFO_FIELD_
|
|||
{ "fullName", LVT_STRING_P, offsetof(struct CustomLevelInfo, fullName), true, LOT_NONE },
|
||||
{ "levelNum", LVT_S16, offsetof(struct CustomLevelInfo, levelNum), false, LOT_NONE },
|
||||
{ "modIndex", LVT_S32, offsetof(struct CustomLevelInfo, modIndex), false, LOT_NONE },
|
||||
{ "next", LVT_COBJECT_P, offsetof(struct CustomLevelInfo, next), false, LOT_CUSTOMLEVELINFO },
|
||||
{ "next", LVT_COBJECT_P, offsetof(struct CustomLevelInfo, next), true, LOT_CUSTOMLEVELINFO },
|
||||
{ "script", LVT_LEVELSCRIPT_P, offsetof(struct CustomLevelInfo, script), true, LOT_POINTER },
|
||||
{ "scriptEntryName", LVT_STRING_P, offsetof(struct CustomLevelInfo, scriptEntryName), true, LOT_NONE },
|
||||
{ "shortName", LVT_STRING_P, offsetof(struct CustomLevelInfo, shortName), true, LOT_NONE },
|
||||
|
@ -630,9 +630,9 @@ static struct LuaObjectField sGraphNodeFields[LUA_GRAPH_NODE_FIELD_COUNT] = {
|
|||
{ "extraFlags", LVT_U8, offsetof(struct GraphNode, extraFlags), false, LOT_NONE },
|
||||
{ "flags", LVT_S16, offsetof(struct GraphNode, flags), false, LOT_NONE },
|
||||
// { "georef", LVT_???, offsetof(struct GraphNode, georef), true, LOT_??? }, <--- UNIMPLEMENTED
|
||||
{ "next", LVT_COBJECT_P, offsetof(struct GraphNode, next), false, LOT_GRAPHNODE },
|
||||
{ "parent", LVT_COBJECT_P, offsetof(struct GraphNode, parent), false, LOT_GRAPHNODE },
|
||||
{ "prev", LVT_COBJECT_P, offsetof(struct GraphNode, prev), false, LOT_GRAPHNODE },
|
||||
{ "next", LVT_COBJECT_P, offsetof(struct GraphNode, next), true, LOT_GRAPHNODE },
|
||||
{ "parent", LVT_COBJECT_P, offsetof(struct GraphNode, parent), true, LOT_GRAPHNODE },
|
||||
{ "prev", LVT_COBJECT_P, offsetof(struct GraphNode, prev), true, LOT_GRAPHNODE },
|
||||
{ "type", LVT_S16, offsetof(struct GraphNode, type), false, LOT_NONE },
|
||||
};
|
||||
|
||||
|
@ -855,8 +855,8 @@ static struct LuaObjectField sMarioStateFields[LUA_MARIO_STATE_FIELD_COUNT] = {
|
|||
{ "invincTimer", LVT_S16, offsetof(struct MarioState, invincTimer), false, LOT_NONE },
|
||||
{ "isSnoring", LVT_U8, offsetof(struct MarioState, isSnoring), false, LOT_NONE },
|
||||
{ "knockbackTimer", LVT_U8, offsetof(struct MarioState, knockbackTimer), false, LOT_NONE },
|
||||
{ "marioBodyState", LVT_COBJECT_P, offsetof(struct MarioState, marioBodyState), false, LOT_MARIOBODYSTATE },
|
||||
{ "marioObj", LVT_COBJECT_P, offsetof(struct MarioState, marioObj), false, LOT_OBJECT },
|
||||
{ "marioBodyState", LVT_COBJECT_P, offsetof(struct MarioState, marioBodyState), true, LOT_MARIOBODYSTATE },
|
||||
{ "marioObj", LVT_COBJECT_P, offsetof(struct MarioState, marioObj), true, LOT_OBJECT },
|
||||
{ "minimumBoneY", LVT_F32, offsetof(struct MarioState, minimumBoneY), false, LOT_NONE },
|
||||
{ "nonInstantWarpPos", LVT_COBJECT, offsetof(struct MarioState, nonInstantWarpPos), true, LOT_VEC3F },
|
||||
{ "numCoins", LVT_S16, offsetof(struct MarioState, numCoins), false, LOT_NONE },
|
||||
|
@ -880,7 +880,7 @@ static struct LuaObjectField sMarioStateFields[LUA_MARIO_STATE_FIELD_COUNT] = {
|
|||
{ "splineKeyframeFraction", LVT_F32, offsetof(struct MarioState, splineKeyframeFraction), false, LOT_NONE },
|
||||
{ "splineState", LVT_S32, offsetof(struct MarioState, splineState), false, LOT_NONE },
|
||||
{ "squishTimer", LVT_U8, offsetof(struct MarioState, squishTimer), false, LOT_NONE },
|
||||
{ "statusForCamera", LVT_COBJECT_P, offsetof(struct MarioState, statusForCamera), false, LOT_PLAYERCAMERASTATE },
|
||||
{ "statusForCamera", LVT_COBJECT_P, offsetof(struct MarioState, statusForCamera), true, LOT_PLAYERCAMERASTATE },
|
||||
{ "terrainSoundAddend", LVT_U32, offsetof(struct MarioState, terrainSoundAddend), false, LOT_NONE },
|
||||
{ "twirlYaw", LVT_S16, offsetof(struct MarioState, twirlYaw), false, LOT_NONE },
|
||||
{ "unkB0", LVT_S16, offsetof(struct MarioState, unkB0), false, LOT_NONE },
|
||||
|
@ -1759,9 +1759,9 @@ static struct LuaObjectField sObjectHitboxFields[LUA_OBJECT_HITBOX_FIELD_COUNT]
|
|||
|
||||
#define LUA_OBJECT_NODE_FIELD_COUNT 3
|
||||
static struct LuaObjectField sObjectNodeFields[LUA_OBJECT_NODE_FIELD_COUNT] = {
|
||||
{ "gfx", LVT_COBJECT, offsetof(struct ObjectNode, gfx), true, LOT_GRAPHNODEOBJECT },
|
||||
{ "next", LVT_COBJECT_P, offsetof(struct ObjectNode, next), false, LOT_OBJECTNODE },
|
||||
{ "prev", LVT_COBJECT_P, offsetof(struct ObjectNode, prev), false, LOT_OBJECTNODE },
|
||||
{ "gfx", LVT_COBJECT, offsetof(struct ObjectNode, gfx), true, LOT_GRAPHNODEOBJECT },
|
||||
{ "next", LVT_COBJECT_P, offsetof(struct ObjectNode, next), true, LOT_OBJECTNODE },
|
||||
{ "prev", LVT_COBJECT_P, offsetof(struct ObjectNode, prev), true, LOT_OBJECTNODE },
|
||||
};
|
||||
|
||||
#define LUA_OBJECT_WARP_NODE_FIELD_COUNT 3
|
||||
|
@ -1922,10 +1922,10 @@ static struct LuaObjectField sSpawnInfoFields[LUA_SPAWN_INFO_FIELD_COUNT] = {
|
|||
{ "areaIndex", LVT_S8, offsetof(struct SpawnInfo, areaIndex), false, LOT_NONE },
|
||||
{ "behaviorArg", LVT_U32, offsetof(struct SpawnInfo, behaviorArg), false, LOT_NONE },
|
||||
// { "behaviorScript", LVT_???, offsetof(struct SpawnInfo, behaviorScript), false, LOT_??? }, <--- UNIMPLEMENTED
|
||||
{ "next", LVT_COBJECT_P, offsetof(struct SpawnInfo, next), false, LOT_SPAWNINFO },
|
||||
{ "next", LVT_COBJECT_P, offsetof(struct SpawnInfo, next), true, LOT_SPAWNINFO },
|
||||
{ "startAngle", LVT_COBJECT, offsetof(struct SpawnInfo, startAngle), true, LOT_VEC3S },
|
||||
{ "startPos", LVT_COBJECT, offsetof(struct SpawnInfo, startPos), true, LOT_VEC3S },
|
||||
{ "syncID", LVT_U32, offsetof(struct SpawnInfo, syncID), true, LOT_NONE },
|
||||
{ "syncID", LVT_U32, offsetof(struct SpawnInfo, syncID), false, LOT_NONE },
|
||||
{ "unk18", LVT_COBJECT_P, offsetof(struct SpawnInfo, unk18), false, LOT_GRAPHNODE },
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue