#include "player.h" #include "game.h" Player createPlayer() { return (Player){ .position = Vector3Zero(), .direction = (Vector3){0.0, 0.0, 0.0}, .velocity = Vector3Zero(), .speed = 0.0, .relativeBox = (BoundingBox){ .min = (Vector3){-PLAYER_WIDTH / 2.0, -PLAYER_HEIGHT / 2.0, -PLAYER_WIDTH / 2.0}, .max = (Vector3){PLAYER_WIDTH / 2.0, PLAYER_HEIGHT / 2.0, PLAYER_WIDTH / 2.0} }, .camera = (Camera){ .position = (Vector3){0.0, 0.0, 0.0}, .target = Vector3Zero(), .up = (Vector3){0.0, 1.0, 0.0}, .fovy = 90.0, .projection = CAMERA_PERSPECTIVE }, .cameraAngle = Vector2Zero(), .selectedEntity = ENTITY_NONE, .isInteracting = false }; } // Fake physics, distance from ground. void updatePlayerHeight(Player* player, Game* game) { float height = getWorldHeightAtLocation( &game->world, player->position.x, player->position.z) + PLAYER_HEIGHT; player->position.y = height; } void updatePlayerLookingAround(Player* player, Game* game) { Settings* settings = &game->settings; Vector2* cameraAngle = &player->cameraAngle; // Get mouse speed. Vector2 mouseSpeed = Vector2Scale(GetMouseDelta(), settings->mouseSpeed); mouseSpeed = Vector2Scale(mouseSpeed, 1.0 / (PI * 2.0)); // Update camera angle. *cameraAngle = Vector2Add(*cameraAngle, mouseSpeed); cameraAngle->x = Wrap(cameraAngle->x, -PI, PI); cameraAngle->y = Clamp(cameraAngle->y, -PI / 2.5, PI / 2.5); // Set player direction. Matrix matrix = MatrixRotateXYZ( (Vector3){-cameraAngle->y, cameraAngle->x, 0.0}); player->direction = (Vector3){matrix.m2, matrix.m6, matrix.m10}; } WorldUID playerCheckCollisionWithBuildings(Player* player, Game* game) { for (int index = 0; index < WORLD_PLACE_COUNT; ++index) { WorldUID uid = game->world.places[index]; Entity* place = &game->world.entities[uid]; if (entityIsBuilding(place->id) && CheckCollisionBoxes(place->box, player->box)) { return uid; } } return ENTITY_NONE; } // Returns true if collision detection should end. bool playerHandleCollisionWithBuildingBlock(Player* player, EntityBuilding* building, BoundingBox block, Vector3 blockPosition, int x, int y, bool* isFirstCollision, Vector3* lastNormal) { int normalsCount = 4; Vector3 normals[] = { (Vector3){1.0, 0.0, 0.0}, (Vector3){-1.0, 0.0, 0.0}, (Vector3){0.0, 0.0, 1.0}, (Vector3){0.0, 0.0, -1.0} }; int closestNormalIndex = 0; float closestDistance = WORLD_SIZE.x; // Get closest normal. for (int index = 0; index < normalsCount; ++index) { float distance = Vector3Distance( player->position, Vector3Add(normals[index], blockPosition)); // Avoid issues with neighboring blocks. bool isNormalBlocked = isBuildingBlockWall(building, x + normals[index].x, y + normals[index].z); if (!isNormalBlocked && distance < closestDistance) { closestDistance = distance; closestNormalIndex = index; } } Vector3 normal = normals[closestNormalIndex]; // Is facing wall. if (Vector3DotProduct(player->velocity, normal) < 0.0) { // Handle collision. float dotProduct = Vector3DotProduct(player->direction, normal); player->velocity = Vector3Subtract(player->direction, Vector3Scale(normal, dotProduct)); player->velocity = Vector3Scale(player->velocity, player->speed); // Is at a corner. if (!*isFirstCollision && !Vector3Equals(*lastNormal, normal)) { player->velocity = (Vector3){0.0, 0.0, 0.0}; return true; } *isFirstCollision = false; *lastNormal = normal; } return false; } // TODO: Make this fucker speedy quick void playerHandleCollisionWithBuilding(Player* player, Game* game, WorldUID uid) { Entity* place = &game->world.entities[uid]; EntityBuilding* building = (EntityBuilding*)place->data; BoundingBox wallBox = (BoundingBox){ .min = (Vector3){-ENTITY_BUILDING_CUBE_SIZE.x / 2.0, -ENTITY_BUILDING_CUBE_SIZE.y / 2.0, -ENTITY_BUILDING_CUBE_SIZE.z / 2.0}, .max = (Vector3){ENTITY_BUILDING_CUBE_SIZE.x / 2.0, ENTITY_BUILDING_CUBE_SIZE.y / 2.0, ENTITY_BUILDING_CUBE_SIZE.z / 2.0} }; bool isFirstCollision = true; Vector3 lastNormal = (Vector3){0.0, 0.0, 0.0}; for (int y = 0; y < building->height; ++y) { for (int x = 0; x < building->width; ++x) { Color color = building->pixelMap[y * building->width + x]; if (color.r == 0) { continue; } Vector3 wallPosition = (Vector3){ place->position.x + x * ENTITY_BUILDING_CUBE_SIZE.x, place->position.y + ENTITY_BUILDING_CUBE_SIZE.y / 2.0, place->position.z + y * ENTITY_BUILDING_CUBE_SIZE.z }; BoundingBox box = wallBox; box.min = Vector3Add(box.min, wallPosition); box.max = Vector3Add(box.max, wallPosition); if (CheckCollisionBoxes(player->box, box)) { DrawBoundingBox(box, PURPLE); if (playerHandleCollisionWithBuildingBlock(player, building, box, wallPosition, x, y, &isFirstCollision, &lastNormal)) { return; } } } } } void updatePlayerMovement(Player* player, Game* game) { Camera* camera = &player->camera; Vector2* cameraAngle = &player->cameraAngle; Settings* settings = &game->settings; if (!game->isCursorEnabled) { updatePlayerLookingAround(player, game); } // Walking around. player->velocity = Vector3Zero(); if (IsKeyDown(settings->forwardKey)) { player->velocity.z += cosf(cameraAngle->x); player->velocity.x += -sinf(cameraAngle->x); } if (IsKeyDown(settings->leftKey)) { player->velocity.z += cosf(cameraAngle->x - (PI / 2.0)); player->velocity.x += -sinf(cameraAngle->x - (PI / 2.0)); } if (IsKeyDown(settings->backwardKey)) { player->velocity.z += -cosf(cameraAngle->x); player->velocity.x += sinf(cameraAngle->x); } if (IsKeyDown(settings->rightKey)) { player->velocity.z += cosf(cameraAngle->x + (PI / 2.0)); player->velocity.x += -sinf(cameraAngle->x + (PI / 2.0)); } player->velocity = Vector3Scale(player->velocity, PLAYER_SPEED); player->speed = Vector3Length(player->velocity); // Check collision with buildings. WorldUID buildingUID = playerCheckCollisionWithBuildings(player, game); if (buildingUID != ENTITY_NONE) { playerHandleCollisionWithBuilding(player, game, buildingUID); } // Apply velocity. player->position = Vector3Add( player->position, Vector3Scale(player->velocity, GetFrameTime())); // Update box. player->box = player->relativeBox; player->box.min = Vector3Add(player->box.min, player->position); player->box.max = Vector3Add(player->box.max, player->position); //DrawBoundingBox(player->box, YELLOW); updatePlayerHeight(player, game); // Apply camera. camera->position = player->position; camera->target = Vector3Add(player->position, player->direction); } bool playerCanEntityBeSelected(Player* player, Entity entity) { return getEntityDistance(entity, player->position) <= PLAYER_MAX_SELECT_DISTANCE; } void playerEndInteraction(Player* player, Game* game) { player->selectedEntity = ENTITY_NONE; player->isInteracting = false; hideInteractionChat(&game->interactionChat); hideInteractionMenu(&game->interactionMenu); game->interactionChat.entityId = ENTITY_NONE; game->interactionMenu.entityId = ENTITY_NONE; } void playerInteractWithEntity(Player* player, WorldUID uid, Game* game, Selection selection) { InteractionChat* chat = &game->interactionChat; InteractionMenu* menu = &game->interactionMenu; Entity* entity = &game->world.entities[uid]; // Handle selection type. switch (selection) { case SELECTION_INTERACT: clearInteractionChat(chat); resetInteractionMenu(menu); chat->entityId = entity->id; menu->entityId = entity->id; player->selectedEntity = uid; player->isInteracting = true; break; case SELECTION_LEAVE: playerEndInteraction(player, game); break; default: break; } // Interact with it. switch (interactWithEntity(entity, game, selection)) { case INTERACTION_TALK: hideInteractionMenu(menu); showInteractionChat(chat); break; case INTERACTION_SHOW_MENU: hideInteractionChat(chat); showInteractionMenu(menu); break; case INTERACTION_TALK_AND_SHOW_MENU: showInteractionChat(chat); showInteractionMenu(menu); break; case INTERACTION_END: playerEndInteraction(player, game); break; default: break; } } void playerUpdateSelectedEntity(Player* player, WorldUID uid, Game* game) { Entity* entity = &game->world.entities[uid]; InteractionChat* chat = &game->interactionChat; InteractionMenu* menu = &game->interactionMenu; // If the entity can be selected. if (!playerCanEntityBeSelected(player, *entity)) { // Leave interaction if far away. if (player->isInteracting) { playerInteractWithEntity(player, uid, game, SELECTION_LEAVE); } return; } player->selectedEntity = uid; // Draw outline. DrawBoundingBox(entity->box, player->isInteracting ? YELLOW : PINK); // Number keys for menu selection. int keyPressed = GetKeyPressed(); // Handle key presses. if (IsKeyPressed(game->settings.interactKey)) { playerInteractWithEntity(player, uid, game, SELECTION_INTERACT); } else if (IsKeyPressed(game->settings.leaveKey)) { playerInteractWithEntity(player, uid, game, SELECTION_LEAVE); } else if (IsKeyPressed(game->settings.nextMessageKey)) { playerInteractWithEntity(player, uid, game, SELECTION_NEXT_MESSAGE); } else if (keyPressed >= KEY_ONE && keyPressed <= KEY_NINE) { playerInteractWithEntity(player, uid, game, SELECTION_MENU_ITEM + (keyPressed - KEY_ONE)); } } void updatePlayer(Player* player, Game* game) { updatePlayerMovement(player, game); Ray ray = (Ray){ .position = player->position, .direction = player->direction }; if (game->isCrossHairEnabled) { DrawRay(ray, YELLOW); } if (player->isInteracting) { playerUpdateSelectedEntity(player, player->selectedEntity, game); } else { WorldUID uid = castRayAtWorld(&game->world, ray, false, NULL); if (uid == -1) { player->selectedEntity = ENTITY_NONE; } else { playerUpdateSelectedEntity(player, uid, game); } } }