// 命令: 去传送点 private async Task CommandGotoTeleportPoint(int source, List <object> args, string raw) { if (args.Count <= 0) { Notify.Alert($"[{ResourceDisplayName}]传送失败, 请输入传送点名称"); return; } if (args.Count >= 2) { Notify.Alert($"[{ResourceDisplayName}]传送失败, 传送点名称不包含空格"); return; } var commandName = args[0].ToString(); if (await PlayerTeleportPoints.Existed(commandName)) { await Teleport.TeleportToCoords(PlayerTeleportPoints[commandName].Position); Game.PlayerPed.Heading = PlayerTeleportPoints[commandName].Heading; TriggerServerEvent("wlPlayerTeleportPoint:RecordCommandUsage", Game.Player.ServerId, commandName); } else { Notify.Alert($"[{ResourceDisplayName}]传送失败, 是否输入了不存在的传送命令?"); } }
// 命令: 制作传送点 private async Task CommandMakeTeleportPoint(int source, List <object> args, string raw) { if (args.Count <= 0) { Notify.Alert($"[{ResourceDisplayName}]请输入传送点名称"); return; } var commandName = args[0].ToString(); /* - Failed to use regex in Fivem Mono runtime library - System.TimeSpan is not accessible * var matchPattern = @"^[a-zA-Z0-9_\p{L}]{1,16}$"; // 只能包含大小写字母, 数字, 中文以及下划线, 长度不大于16 * if (!Regex.IsMatch(commandName, matchPattern)) * { * Notify.Alert($"[{ResourceDisplayName}]名称不合法, 是否过长或者使用了特殊符号?"); * return; * } */ if (commandName.Length > 16 || commandName.Any(c => !char.IsLetterOrDigit(c))) { Notify.Alert($"[{ResourceDisplayName}]名称不合法, 是否过长或者使用了特殊符号?"); return; } if (await PlayerTeleportPoints.Existed(commandName)) { Notify.Alert($"[{ResourceDisplayName}]创建失败, 名称已经被使用, 请使用新的名称"); return; } await PlayerTeleportPoints.AddNewCommand(commandName); // 再次确认传送命令是否写入 - 考虑网络波动情况 if (await PlayerTeleportPoints.Existed(commandName)) { Notify.Success($"[{ResourceDisplayName}]创建成功, 呼唤基友一起到这里玩耍吧"); } else { Notify.Alert($"[{ResourceDisplayName}]创建失败, 与服务器的连接不稳定, 请重试"); } }
/// <summary> /// Teleport to the specified <see cref="pos"/>. /// </summary> /// <param name="pos"></param> /// <param name="safeModeDisabled"></param> /// <returns></returns> public static async Task TeleportToCoords(Vector3 pos, bool safeModeDisabled = false) { if (!safeModeDisabled) { // Is player in a vehicle and the driver? Then we'll use that to teleport. var veh = GetVehicle(); bool inVehicle() => veh != null && veh.Exists() && Game.PlayerPed == veh.Driver; bool vehicleRestoreVisibility = inVehicle() && veh.IsVisible; bool pedRestoreVisibility = Game.PlayerPed.IsVisible; // Freeze vehicle or player location and fade out the entity to the network. if (inVehicle()) { veh.IsPositionFrozen = true; if (veh.IsVisible) { NetworkFadeOutEntity(veh.Handle, true, false); } } else { ClearPedTasksImmediately(Game.PlayerPed.Handle); Game.PlayerPed.IsPositionFrozen = true; if (Game.PlayerPed.IsVisible) { NetworkFadeOutEntity(Game.PlayerPed.Handle, true, false); } } // Fade out the screen and wait for it to be faded out completely. DoScreenFadeOut(500); while (!IsScreenFadedOut()) { await BaseScript.Delay(0); } // This will be used to get the return value from the groundz native. float groundZ = 850.0f; // Bool used to determine if the groundz coord could be found. bool found = false; // Loop from 950 to 0 for the ground z coord, and take away 25 each time. for (float zz = 950.0f; zz >= 0f; zz -= 25f) { float z = zz; // The z coord is alternating between a very high number, and a very low one. // This way no matter the location, the actual ground z coord will always be found the fastest. // If going from top > bottom then it could take a long time to reach the bottom. And vice versa. // By alternating top/bottom each iteration, we minimize the time on average for ANY location on the map. if (zz % 2 != 0) { z = 950f - zz; } // Request collision at the coord. I've never actually seen this do anything useful, but everyone keeps telling me this is needed. // It doesn't matter to get the ground z coord, and neither does it actually prevent entities from falling through the map, nor does // it seem to load the world ANY faster than without, but whatever. RequestCollisionAtCoord(pos.X, pos.Y, z); // Request a new scene. This will trigger the world to be loaded around that area. NewLoadSceneStart(pos.X, pos.Y, z, pos.X, pos.Y, z, 50f, 0); // Timer to make sure things don't get out of hand (player having to wait forever to get teleported if something fails). int tempTimer = GetGameTimer(); // Wait for the new scene to be loaded. while (IsNetworkLoadingScene()) { // If this takes longer than 1 second, just abort. It's not worth waiting that long. if (GetGameTimer() - tempTimer > 1000) { Log.Debug("Waiting for the scene to load is taking too long (more than 1s). Breaking from wait loop."); break; } await BaseScript.Delay(0); } // If the player is in a vehicle, teleport the vehicle to this new position. if (inVehicle()) { SetEntityCoords(veh.Handle, pos.X, pos.Y, z, false, false, false, true); } // otherwise, teleport the player to this new position. else { SetEntityCoords(Game.PlayerPed.Handle, pos.X, pos.Y, z, false, false, false, true); } // Reset the timer. tempTimer = GetGameTimer(); // Wait for the collision to be loaded around the entity in this new location. while (!HasCollisionLoadedAroundEntity(Game.PlayerPed.Handle)) { // If this takes too long, then just abort, it's not worth waiting that long since we haven't found the real ground coord yet anyway. if (GetGameTimer() - tempTimer > 1000) { Log.Debug("Waiting for the collision is taking too long (more than 1s). Breaking from wait loop."); break; } await BaseScript.Delay(0); } // Check for a ground z coord. found = GetGroundZFor_3dCoord(pos.X, pos.Y, z, ref groundZ, false); // If we found a ground z coord, then teleport the player (or their vehicle) to that new location and break from the loop. if (found) { Log.Debug($"Ground coordinate found: {groundZ}"); if (inVehicle()) { SetEntityCoords(veh.Handle, pos.X, pos.Y, groundZ, false, false, false, true); // We need to unfreeze the vehicle because sometimes having it frozen doesn't place the vehicle on the ground properly. veh.IsPositionFrozen = false; veh.PlaceOnGround(); // Re-freeze until screen is faded in again. veh.IsPositionFrozen = true; } else { SetEntityCoords(Game.PlayerPed.Handle, pos.X, pos.Y, groundZ, false, false, false, true); } break; } // Wait 10ms before trying the next location. await BaseScript.Delay(10); } // If the loop ends but the ground z coord has not been found yet, then get the nearest vehicle node as a fail-safe coord. if (!found) { var safePos = pos; GetNthClosestVehicleNode(pos.X, pos.Y, pos.Z, 0, ref safePos, 0, 0, 0); // Notify the user that the ground z coord couldn't be found, so we will place them on a nearby road instead. Notify.Alert("Could not find a safe ground coord. Placing you on the nearest road instead."); Log.Debug("Could not find a safe ground coord. Placing you on the nearest road instead."); // Teleport vehicle, or player. if (inVehicle()) { SetEntityCoords(veh.Handle, safePos.X, safePos.Y, safePos.Z, false, false, false, true); veh.IsPositionFrozen = false; veh.PlaceOnGround(); veh.IsPositionFrozen = true; } else { SetEntityCoords(Game.PlayerPed.Handle, safePos.X, safePos.Y, safePos.Z, false, false, false, true); } } // Once the teleporting is done, unfreeze vehicle or player and fade them back in. if (inVehicle()) { if (vehicleRestoreVisibility) { NetworkFadeInEntity(veh.Handle, true); if (!pedRestoreVisibility) { Game.PlayerPed.IsVisible = false; } } veh.IsPositionFrozen = false; } else { if (pedRestoreVisibility) { NetworkFadeInEntity(Game.PlayerPed.Handle, true); } Game.PlayerPed.IsPositionFrozen = false; } // Fade screen in and reset the camera angle. DoScreenFadeIn(500); SetGameplayCamRelativePitch(0.0f, 1.0f); } // Disable safe teleporting and go straight to the specified coords. else { RequestCollisionAtCoord(pos.X, pos.Y, pos.Z); // Teleport directly to the coords without trying to get a safe z pos. if (Game.PlayerPed.IsInVehicle() && GetVehicle().Driver == Game.PlayerPed) { SetEntityCoords(GetVehicle().Handle, pos.X, pos.Y, pos.Z, false, false, false, true); } else { SetEntityCoords(Game.PlayerPed.Handle, pos.X, pos.Y, pos.Z, false, false, false, true); } } }