#include "GameState.hpp" #include #include #include #include "Event.hpp" #include "Packet.hpp" #include "Game.hpp" #include "Plugin.hpp" void GameState::Update(double deltaTime) { OPTICK_EVENT(); if (!gameStatus.isGameStarted) { if (!receivedEnoughChunks) { receivedEnoughChunks = world.GetSectionsList().size() >= 49; } if (receivedJoinGame && receivedEnoughChunks && receivedFirstPlayerPosAndLook) { gameStatus.isGameStarted = true; LOG(INFO) << "Game is started"; PUSH_EVENT("RemoveLoadingScreen", 0); auto packetPerformRespawn = std::make_shared(0); PUSH_EVENT("SendPacket", std::static_pointer_cast(packetPerformRespawn)); } } if (!gameStatus.isGameStarted) return; std::chrono::steady_clock clock; static auto timeOfPreviousSendedPacket(clock.now()); auto delta = clock.now() - timeOfPreviousSendedPacket; using namespace std::chrono_literals; if (delta >= 50ms) { auto packetToSend = std::make_shared( player->pos.x, player->pos.y, player->pos.z, player->yaw, player->pitch, player->onGround); auto packet = std::static_pointer_cast(packetToSend); PUSH_EVENT("SendPacket", packet); timeOfPreviousSendedPacket = clock.now(); } bool prevOnGround = player->onGround; world.UpdatePhysics(deltaTime); if (player->onGround != prevOnGround) { auto updatePacket = std::make_shared( player->pos.x, player->pos.y, player->pos.z, player->onGround); auto packet = std::static_pointer_cast(updatePacket); PUSH_EVENT("SendPacket", packet); } double playerYaw = Entity::DecodeYaw(player->yaw); double playerPitch = Entity::DecodePitch(player->pitch); glm::vec3 direction; direction.x = cos(glm::radians(playerYaw)) * cos(glm::radians(playerPitch)); direction.y = sin(glm::radians(playerPitch)); direction.z = sin(glm::radians(playerYaw)) * cos(glm::radians(playerPitch)); RaycastResult raycast = world.Raycast(player->pos + player->EyeOffset, direction); if (raycast.isHit != selectionStatus.isBlockSelected || ((raycast.isHit == true && selectionStatus.isBlockSelected == true) && selectionStatus.selectedBlock != raycast.hitBlock)) { PUSH_EVENT("SelectedBlockChanged", 0); } if (raycast.isHit) { selectionStatus.selectedBlock = raycast.hitBlock; selectionStatus.distanceToSelectedBlock = (player->pos - raycast.hitPos).GetLength(); } else { selectionStatus.selectedBlock = Vector(0, 0, 0); selectionStatus.distanceToSelectedBlock = 0.0f; } selectionStatus.isBlockSelected = raycast.isHit; selectionStatus.raycastHit = raycast.hitPos; if (timeStatus.doDaylightCycle) timeStatus.interpolatedTimeOfDay += 20.0 * deltaTime; } void GameState::UpdatePacket(std::shared_ptr ptr) { switch ((PacketNamePlayCB)ptr->GetPacketId()) { case SpawnObject: { auto packet = std::static_pointer_cast(ptr); Entity entity = CreateObject(static_cast(packet->Type)); entity.entityId = packet->EntityId; entity.pos = VectorF(packet->X, packet->Y, packet->Z); entity.uuid = packet->ObjectUuid; entity.vel = Entity::DecodeVelocity(packet->VelocityX, packet->VelocityY, packet->VelocityZ); entity.yaw = packet->Yaw / 256.0; entity.pitch = packet->Pitch / 256.0; entity.renderColor = glm::vec3(0, 1, 0); world.AddEntity(entity); PUSH_EVENT("EntityChanged", entity.entityId); break; } case SpawnExperienceOrb: break; case SpawnGlobalEntity: break; case SpawnMob: { auto packet = std::static_pointer_cast(ptr); Entity entity; entity.entityId = packet->EntityId; entity.pos = VectorF(packet->X, packet->Y, packet->Z); entity.uuid = packet->EntityUuid; entity.vel = Entity::DecodeVelocity(packet->VelocityX, packet->VelocityY, packet->VelocityZ); entity.yaw = packet->Yaw / 256.0; entity.pitch = packet->Pitch / 256.0; entity.renderColor = glm::vec3(0, 0, 1); world.AddEntity(entity); PUSH_EVENT("EntityChanged", entity.entityId); break; } case SpawnPainting: break; case SpawnPlayer: { auto packet = std::static_pointer_cast(ptr); Entity entity; entity.entityId = packet->EntityId; entity.pos = VectorF(packet->X, packet->Y, packet->Z); entity.uuid = packet->PlayerUuid; entity.yaw = packet->Yaw / 256.0; entity.pitch = packet->Pitch / 256.0; entity.renderColor = glm::vec3(1, 0, 0); entity.height = 1.8; entity.width = 0.6; world.AddEntity(entity); PUSH_EVENT("EntityChanged", entity.entityId); break; } case AnimationCB: break; case Statistics: break; case BlockBreakAnimation: break; case UpdateBlockEntity: break; case BlockAction: break; case BlockChange: { auto packet = std::static_pointer_cast(ptr); world.ParseChunkData(packet); break; } case BossBar: break; case ServerDifficulty: break; case TabCompleteCB: break; case ChatMessageCB: { auto packet = std::static_pointer_cast(ptr); LOG(INFO) << "Message (" << int(packet->Position) << "): " << packet->JsonData.ToPlainText(); PluginSystem::CallOnChatMessage(packet->JsonData, packet->Position); break; } case MultiBlockChange: { auto packet = std::static_pointer_cast(ptr); world.ParseChunkData(packet); break; } case ConfirmTransactionCB: { auto packet = std::static_pointer_cast(ptr); if (packet->WindowId == 0) { try { playerInventory.ConfirmTransaction(*packet); } catch (std::exception &e) { PUSH_EVENT("Disconnected", std::string("Transaction failed")); } } break; } case CloseWindowCB: break; case OpenWindow: { auto packet = std::static_pointer_cast(ptr); LOG(INFO) << "Open new window " << packet->WindowTitle << ": " << packet->WindowId; break; } case WindowItems: { auto packet = std::static_pointer_cast(ptr); if (packet->WindowId == 0) { playerInventory.WindowId = 0; playerInventory.slots = packet->SlotData; } break; } case WindowProperty: break; case SetSlot: { auto packet = std::static_pointer_cast(ptr); if (packet->WindowId == 0) { playerInventory.slots[packet->Slot] = packet->SlotData; } break; } case SetCooldown: break; case PluginMessageCB: break; case NamedSoundEffect: break; case DisconnectPlay: { auto packet = std::static_pointer_cast(ptr); LOG(INFO) << "Disconnect reason: " << packet->Reason; PUSH_EVENT("Disconnected", packet->Reason); break; } case EntityStatus: break; case Explosion: break; case UnloadChunk: { auto packet = std::static_pointer_cast(ptr); world.ParseChunkData(packet); break; } case ChangeGameState: break; case KeepAliveCB: { LOG(WARNING) << "Receive KeepAlive packet in GameState handler"; break; } case ChunkData: { auto packet = std::static_pointer_cast(ptr); world.ParseChunkData(packet); break; } case Effect: break; case Particle: break; case JoinGame: { auto packet = std::static_pointer_cast(ptr); Entity entity; entity.entityId = packet->EntityId; entity.width = 0.6; entity.height = 1.8; world = World(packet->Dimension); world.AddEntity(entity); player = world.GetEntityPtr(entity.entityId); playerStatus.eid = packet->EntityId; gameStatus.gamemode = (packet->Gamemode & 0b11111011); gameStatus.dimension = packet->Dimension; gameStatus.difficulty = packet->Difficulty; gameStatus.maxPlayers = packet->MaxPlayers; gameStatus.levelType = packet->LevelType; gameStatus.reducedDebugInfo = packet->ReducedDebugInfo; LOG(INFO) << "Gamemode is " << gameStatus.gamemode << ", Difficulty is " << (int)gameStatus.difficulty << ", Level Type is " << gameStatus.levelType; PUSH_EVENT("PlayerConnected", 0); receivedJoinGame = true; auto packetSettings = std::make_shared("en_us", 0x14, 0, true, 0x7F, 1); PUSH_EVENT("SendPacket", std::static_pointer_cast(packetSettings)); std::string brandStr("\x08""AltCraft"); std::vector brandData; std::copy(brandStr.begin(), brandStr.end(), std::back_inserter(brandData)); auto packetPluginBrand = std::make_shared("MC|Brand", brandData); PUSH_EVENT("SendPacket", std::static_pointer_cast(packetPluginBrand)); break; } case Map: break; case EntityRelativeMove: { auto packet = std::static_pointer_cast(ptr); Entity &entity = world.GetEntity(packet->EntityId); entity.pos = entity.pos + Entity::DecodeDeltaPos(packet->DeltaX, packet->DeltaY, packet->DeltaZ); break; } case EntityLookAndRelativeMove: { auto packet = std::static_pointer_cast(ptr); Entity &entity = world.GetEntity(packet->EntityId); entity.pos = entity.pos + Entity::DecodeDeltaPos(packet->DeltaX, packet->DeltaY, packet->DeltaZ); entity.pitch = packet->Pitch / 256.0; entity.yaw = packet->Yaw / 256.0; break; } case EntityLook: { auto packet = std::static_pointer_cast(ptr); Entity &entity = world.GetEntity(packet->EntityId); entity.pitch = packet->Pitch / 256.0; entity.yaw = packet->Yaw / 256.0; break; } case EntityCB: break; case VehicleMove: break; case OpenSignEditor: break; case PlayerAbilitiesCB: break; case CombatEvent: break; case PlayerListItem: break; case PlayerPositionAndLookCB: { auto packet = std::static_pointer_cast(ptr); if ((packet->Flags & 0x10) != 0) { player->pitch += packet->Pitch; } else { player->pitch = packet->Pitch; } if ((packet->Flags & 0x08) != 0) { player->yaw += packet->Yaw; } else { player->yaw = packet->Yaw; } if ((packet->Flags & 0x01) != 0) { player->pos.x += packet->X; } else { player->pos.x = packet->X; } if ((packet->Flags & 0x02) != 0) { player->pos.y += packet->Y; } else { player->pos.y = packet->Y; } if ((packet->Flags & 0x04) != 0) { player->pos.z += packet->Z; } else { player->pos.z = packet->Z; } PUSH_EVENT("PlayerPosChanged", player->pos); LOG(INFO) << "PlayerPos is " << player->pos << "\t\tAngle: " << player->yaw << "," << player->pitch;; receivedFirstPlayerPosAndLook = true; auto packetResponse = std::make_shared(packet->TeleportId); PUSH_EVENT("SendPacket", std::static_pointer_cast(packetResponse)); break; } case UseBed: break; case UnlockRecipes: break; case DestroyEntities: { auto packet = std::static_pointer_cast(ptr); for (unsigned int entityId : packet->EntityIds) { world.DeleteEntity(entityId); } break; } case RemoveEntityEffect: break; case ResourcePackSend: break; case Respawn: { auto packet = std::static_pointer_cast(ptr); Entity entity; entity.entityId = player->entityId; entity.width = 0.6; entity.height = 1.8; world = World(packet->Dimension); world.AddEntity(entity); player = world.GetEntityPtr(entity.entityId); gameStatus.gamemode = (packet->Gamemode & 0b11111011); gameStatus.dimension = packet->Dimension; gameStatus.difficulty = packet->Difficulty; gameStatus.levelType = packet->LevelType; SetState(State::Loading); gameStatus.isGameStarted = false; receivedEnoughChunks = false; break; } case EntityHeadLook: break; case SelectAdvancementTab: break; case WorldBorder: break; case Camera: break; case HeldItemChangeCB: break; case DisplayScoreboard: break; case EntityMetadata: break; case AttachEntity: break; case EntityVelocity: { auto packet = std::static_pointer_cast(ptr); Entity &entity = world.GetEntity(packet->EntityId); entity.vel = Entity::DecodeVelocity(packet->VelocityX, packet->VelocityY, packet->VelocityZ); break; } case EntityEquipment: break; case SetExperience: break; case UpdateHealth: { auto packet = std::static_pointer_cast(ptr); playerStatus.health = packet->Health; if (playerStatus.health <= 0) { LOG(INFO) << "Player is dead. Need respawn..."; SetState(State::NeedRespawn); } break; } case ScoreboardObjective: break; case SetPassengers: break; case Teams: break; case UpdateScore: break; case SpawnPosition: { auto packet = std::static_pointer_cast(ptr); gameStatus.spawnPosition = packet->Location; LOG(INFO) << "Spawn position is " << gameStatus.spawnPosition; break; } case TimeUpdate: { auto packet = std::static_pointer_cast(ptr); timeStatus.doDaylightCycle = timeStatus.timeOfDay != packet->TimeOfDay; timeStatus.worldAge = packet->WorldAge; timeStatus.timeOfDay = packet->TimeOfDay; timeStatus.interpolatedTimeOfDay = timeStatus.timeOfDay; break; } case Title: break; case SoundEffect: break; case PlayerListHeaderAndFooter: break; case CollectItem: break; case EntityTeleport: { auto packet = std::static_pointer_cast(ptr); Entity &entity = world.GetEntity(packet->EntityId); entity.pos = VectorF(packet->X, packet->Y, packet->Z); entity.pitch = packet->Pitch / 256.0; entity.yaw = packet->Yaw / 256.0; break; } case Advancements: break; case EntityProperties: break; case EntityEffect: break; default: LOG(WARNING) << "Server sent unknown packet"; break; } while (!playerInventory.pendingTransactions.empty()) { auto packet = std::make_shared(playerInventory.pendingTransactions.front()); playerInventory.pendingTransactions.pop(); PUSH_EVENT("SendPacket", std::static_pointer_cast(packet)); } } void GameState::HandleMovement(GameState::MoveType direction, float deltaTime) { if (!gameStatus.isGameStarted) return; const double playerSpeed = 43; float velocity = playerSpeed * deltaTime; double playerYaw = Entity::DecodeYaw(player->yaw); double playerPitch = Entity::DecodePitch(player->pitch); glm::vec3 front, right, worldUp, up; worldUp = glm::vec3(0.0f, 1.0f, 0.0f); front.x = cos(glm::radians(playerYaw)) * cos(glm::radians(playerPitch)); front.y = player->isFlying ? sin(glm::radians(playerPitch)) : 0; front.z = sin(glm::radians(playerYaw)) * cos(glm::radians(playerPitch)); front = glm::normalize(front); right = glm::normalize(glm::cross(front, worldUp)); up = glm::normalize(glm::cross(right, front)); glm::vec3 vel = player->vel.glm(); switch (direction) { case FORWARD: { vel += front * velocity; break; } case BACKWARD: { vel -= front * velocity; break; } case RIGHT: { vel += right * velocity; break; } case LEFT: { vel -= right * velocity; break; } case JUMP: if (player->onGround && !player->isFlying) { vel.y += 10; player->onGround = false; } else if (player->isFlying) { vel += up * velocity; } break; } player->vel = VectorF(vel.x, vel.y, vel.z); } void GameState::HandleRotation(double yaw, double pitch) { if (!gameStatus.isGameStarted) return; double playerYaw = Entity::DecodeYaw(player->yaw); double playerPitch = Entity::DecodePitch(player->pitch); playerYaw += yaw; playerPitch += pitch; if (playerPitch > 89.0) playerPitch = 89.0; if (playerPitch < -89.0) playerPitch = -89.0; player->yaw = Entity::EncodeYaw(playerYaw); player->pitch = Entity::EncodePitch(playerPitch); } glm::mat4 GameState::GetViewMatrix() { double playerYaw = Entity::DecodeYaw(player->yaw); double playerPitch = Entity::DecodePitch(player->pitch); glm::vec3 front, right, worldUp, up; worldUp = glm::vec3(0.0f, 1.0f, 0.0f); front.x = cos(glm::radians(playerYaw)) * cos(glm::radians(playerPitch)); front.y = sin(glm::radians(playerPitch)); front.z = sin(glm::radians(playerYaw)) * cos(glm::radians(playerPitch)); front = glm::normalize(front); right = glm::normalize(glm::cross(front, worldUp)); up = glm::normalize(glm::cross(right, front)); glm::vec3 eyePos = player->pos.glm(); eyePos += player->EyeOffset.glm(); return glm::lookAt(eyePos, eyePos + front, up); } // TODO: it should actually be something like this: // function start_digging(): // send_packet(packet_type=start_digging_packet) // delay(time=selected_block_dig_time, action=finish_digging) void GameState::StartDigging() { if (!selectionStatus.isBlockSelected) return; auto packetStart = std::make_shared(0, selectionStatus.selectedBlock, 1); auto packet = std::static_pointer_cast(packetStart); PUSH_EVENT("SendPacket", packet); FinishDigging(); } void GameState::FinishDigging() { auto packetFinish = std::make_shared(2, selectionStatus.selectedBlock, 1); auto packet = std::static_pointer_cast(packetFinish); PUSH_EVENT("SendPacket", packet); } // TODO: it should actually be something like this: // function cancel_digging(): // if finish_digging is in delayed_actions: // send_packet(packet_type=start_digging_packet) // remove_delayed_action(finish_digging) void GameState::CancelDigging() { auto packetCancel = std::make_shared(1, selectionStatus.selectedBlock, 1); auto packet = std::static_pointer_cast(packetCancel); PUSH_EVENT("SendPacket", packet); } BlockFacing detectHitFace(const VectorF& raycastHit, const Vector& selectedBlock) { auto vec = VectorF(selectedBlock.x + .5, selectedBlock.y + .5, selectedBlock.z + .5) - raycastHit; // TODO: move these vectors to Vector.hpp static const auto vecUp = VectorF(0, 1, 0); static const auto vecRight = VectorF(1, 0, 0); static const auto vecForward = VectorF(0, 0, -1); const double up = vec.cosBetween(vecUp); const double down = -up; const double right = vec.cosBetween(vecRight); const double left = -right; const double forward = vec.cosBetween(vecForward); const double backward = -forward; const double min_cos = _min(up, down, right, left, forward, backward); if (std::abs(min_cos - down) < DBL_EPSILON) return BlockFacing::Bottom; else if (std::abs(min_cos - up) < DBL_EPSILON) return BlockFacing::Top; else if (std::abs(min_cos - forward) < DBL_EPSILON) return BlockFacing::North; else if (std::abs(min_cos - backward) < DBL_EPSILON) return BlockFacing::South; else if (std::abs(min_cos - left) < DBL_EPSILON) return BlockFacing::West; else return BlockFacing::East; } void GameState::PlaceBlock() { if (!selectionStatus.isBlockSelected) return; BlockFacing face = detectHitFace(selectionStatus.raycastHit, selectionStatus.selectedBlock); auto packetPlace = std::make_shared( selectionStatus.selectedBlock, (unsigned char)face, 0, 0, 0, 0); auto packet = std::static_pointer_cast(packetPlace); PUSH_EVENT("SendPacket", packet); } void GameState::PerformRespawn() { auto packetPerformRespawn = std::make_shared(0); PUSH_EVENT("SendPacket", std::static_pointer_cast(packetPerformRespawn)); }