iThorgrim
Member
LEVEL 3
90 XP
If you want, you can also use a Lua module I've created that lets you create polygons (without using radius) that weave a line between each coordinate, so that if your area isn't round but looks like a polygon you can disallow mounts.
I personally use it for housing, but you can vary the uses. I think you'll have to optimize it in the future, but you can use it easily
Name this file "RayTracing2D.lua", the comment are in french (sorry)
I personally use it for housing, but you can vary the uses. I think you'll have to optimize it in the future, but you can use it easily
Name this file "RayTracing2D.lua", the comment are in french (sorry)
Code:
--[[
Compléxité algorithmique :
reverseVertexOrder : O(n) car la fonction parcourt tous les éléments d'un tableau une seule fois.
doLineSegmentsIntersect : O(1) car la fonction ne parcourt pas de tableaux et ne contient pas de boucles.
createBoundingBox : O(n) car la fonction parcourt tous les éléments d'un tableau une seule fois.
isInsideBoundingBox : O(1) car la fonction ne parcourt pas de tableaux et ne contient pas de boucles.
RayTracing2D : O(n) car la fonction parcourt tous les éléments d'un tableau une seule fois.
]]--
local RayTrace = {}
--[[
La fonction reverseVertexOrder prend en paramètre une table d'objets contenant des verticles (points) nommée "verticles".
Elle crée une nouvelle table vide nommée "reversedVerticles".
En utilisant une boucle for qui parcourt les éléments de la table "verticles" en commençant par le dernier élément et en finissant par le premier,
elle insère chaque élément dans la table "reversedVerticles" en utilisant la fonction table.insert.
La fonction retourne finalement la table "reversedVerticles" qui contient les éléments de la table "verticles" dans l'ordre inverse.
]]--
local function reverseVertexOrder(verticles)
local reversedverticles = {}
for i = #verticles, 1, -1 do
table.insert(reversedverticles, verticles[i])
end
return reversedverticles
end
--[[
La fonction "doLineSegmentsIntersect" vérifie si un segment de ligne donné et un rayon donné se croisent.
Le point, le rayon, le vertex, le nextVertex et la position du joueur sont passés en tant que paramètres.
Les deux premières lignes de la fonction définissent les vecteurs de bord et de rayon en utilisant les positions des points de vertex,
de nextVertex par rapport à la position du joueur.
La première partie du if-statement vérifie si le produit vectoriel entre le vecteur de bord et le vecteur de rayon est négatif ou positif.
Si c'est le cas, cela signifie que le rayon et le segment de ligne ne se croisent pas et la fonction renvoie false.
La deuxième partie du if-statement vérifie si le produit vectoriel entre le vecteur de bord suivant et le vecteur de rayon est négatif ou positif.
Si c'est le cas, cela signifie que le rayon et le segment de ligne suivant ne se croisent pas et la fonction renvoie false.
Ensuite, la fonction calcule l'angle entre les vecteurs de bord et de nextEdge en utilisant la fonction math.acos et vérifie si ces angles sont inférieurs à pi/2.
Si c'est le cas, cela signifie que le rayon et les segments de ligne se croisent, et la fonction renvoie true.
Sinon, elle renvoie false.
]]--
local function doLineSegmentsIntersect(point, ray, vertex, nextVertex, playerPosition)
local edgeVector = {x = vertex.x - playerPosition.x, y = vertex.y - playerPosition.y}
local rayVector = {x = ray.x - playerPosition.x, y = ray.y - playerPosition.y}
local nextEdgeVector = {x = nextVertex.x - playerPosition.x, y = nextVertex.y - playerPosition.y}
if (edgeVector.y > 0 and rayVector.y < 0) or (edgeVector.y < 0 and rayVector.y > 0) then
return false
end
if (nextEdgeVector.y > 0 and rayVector.y < 0) or (nextEdgeVector.y < 0 and rayVector.y > 0) then
return false
end
local cross1 = edgeVector.x * rayVector.y - edgeVector.y * rayVector.x
local cross2 = nextEdgeVector.x * rayVector.y - nextEdgeVector.y * rayVector.x
if cross1 * cross2 > 0 then
return false
end
local angle1 = math.acos(edgeVector.x / math.sqrt(edgeVector.x * edgeVector.x + edgeVector.y * edgeVector.y))
local angle2 = math.acos(nextEdgeVector.x / math.sqrt(nextEdgeVector.x * nextEdgeVector.x + nextEdgeVector.y * nextEdgeVector.y))
if angle1 < math.pi / 2 and angle2 < math.pi / 2 then
return true
end
return false
end
--[[
La fonction createBoundingBox prend en entrée un polygone sous forme de tableau de sommets.
Elle initialise les variables minX, maxX, minY et maxY avec des valeurs extrêmes (respectivement math.huge et -math.huge),
qui seront utilisées pour définir les limites de la boîte englobante.
Ensuite, elle parcourt tous les sommets du polygone en utilisant une boucle for et ipairs, et met à jour les variables minX, maxX, minY et maxY,
en utilisant les fonctions math.min et math.max pour définir les valeurs minimales et maximales pour chacune des coordonnées x et y.
Enfin, la fonction affiche les valeurs minimales et maximales obtenues pour les coordonnées x et y,
puis retourne un tableau contenant ces valeurs sous forme de clés minX, maxX, minY et maxY.
]]--
function RayTrace.createBoundingBox(polygon)
local minX, maxX, minY, maxY = math.huge, -math.huge, math.huge, -math.huge
for _, vertex in ipairs(polygon) do
minX = math.min(minX, vertex.x)
maxX = math.max(maxX, vertex.x)
minY = math.min(minY, vertex.y)
maxY = math.max(maxY, vertex.y)
end
return {minX = minX, maxX = maxX, minY = minY, maxY = maxY}
end
--[[
La fonction "isInsideBoundingBox" prend en entrée un "boundingBox" et une "position".
Elle vérifie si la "position" se trouve à l'intérieur de la "boundingBox" en comparant les coordonnées x et y de "position" avec les limites de la "boundingBox"
(minX, maxX, minY, maxY).
Si les coordonnées x et y de "position" se situent entre les limites de la "boundingBox",
la fonction renvoie "true" sinon elle renvoie "false".
]]--
local function isInsideBoundingBox(boundingBox, position)
return position.x >= boundingBox.minX and position.x <= boundingBox.maxX and
position.y >= boundingBox.minY and position.y <= boundingBox.maxY
end
--[[
La fonction Player:RayTracing2D prend en entrée un polygone et une boîte englobante.
Elle vérifie si le joueur est à l'intérieur du polygone en utilisant une méthode de rayonnement 2D. Elle utilise les fonctions reverseVertexOrder,
isInsideBoundingBox et doLineSegmentsIntersect pour effectuer cette vérification.
Elle utilise la variable "position" pour stocker la position actuelle du joueur.
La variable "pData" est utilisée pour stocker si le joueur était déjà à l'intérieur du polygone lors de la dernière vérification.
Si le joueur est à l'intérieur de la boîte englobante, la fonction inverse l'ordre des sommets du polygone,
puis calcule un rayon qui part du point le plus à droite du polygone et passe par la position actuelle du joueur.
Elle compte ensuite le nombre d'intersections entre ce rayon et les segments formés par les sommets consécutifs du polygone.
Si ce nombre est impair, cela signifie que le joueur est à l'intérieur du polygone. Sinon, il est à l'extérieur.
La fonction utilise également la variable "pData" pour vérifier si le joueur était déjà à l'intérieur du polygone lors de la dernière vérification.
Si oui, la fonction retourne "true" pour indiquer que le joueur est toujours à l'intérieur du polygone.
Sinon, elle retourne "false" pour indiquer qu'il est à l'extérieur.
Enfin, elle met à jour la variable "pData" en fonction de la résultat de la vérification.
]]--
function Player:RayTracing2D(polygon, boundingBox)
local position = self:GetPosition()
local pData = self:GetData("inside_polygon") or false
if isInsideBoundingBox(boundingBox, position) then
polygon = reverseVertexOrder(polygon)
local max = -1
for i, vertex in ipairs(polygon) do
if vertex.x > max then
max = vertex.x
end
end
local ray = {x = position.x + max + 1, y = position.y}
local intersections = 0
for i = 1, #polygon do
if doLineSegmentsIntersect(position, ray, polygon[i], polygon[i % #polygon + 1], position) then
intersections = intersections + 1
end
end
if intersections % 2 == 1 and pData == false then
self:SetData("inside_polygon", true)
return true
else
if (intersections % 2 ~= 1) then
if (pData == true) then
self:SetData("inside_polygon", false)
end
return false
end
end
else
if (pData == true) then
self:SetData("inside_polygon", false)
end
return false
end
end
return RayTrace
This si your "main" script for locked mount in Polygon
Code:
local RayTrace = require("RayTracing2D")
local Points = {
[0] = { -- This is your MapID
[1519] = { -- This is your AreaID
{x = -8808.886719, y = 629.936768, z = 94.228920}, -- This is a vertices of your polygon
{x = -8794.510742, y = 637.831726, z = 94.232796},-- This is a vertices of your polygon
{x = -8800.683594, y = 650.272400, z = 94.518021},-- This is a vertices of your polygon
{x = -8815.123047, y = 643.107849, z = 94.229118},-- This is a vertices of your polygon
}
}
}
local BoundingBox = {}
function Player:GetPosition()
return {
x = self:GetX(),
y = self:GetY(),
z = self:GetZ(),
o = self:GetO(),
a = self:GetAreaId(),
m = self:GetMapId()
}
end
local function Timed(eventid, delay, repeats, player)
local position = player:GetPosition()
player:RayTracing2D(Points[position.m][position.a], BoundingBox[position.m][position.a])
local pInside = player:GetData("inside_polygon")
local pNotification = player:GetData("last_notification")
if (pInside and pNotification ~= 2) then
if player:IsMounted() then
player:Dismount()
player:SendAreaTriggerMessage("Mounts are not allowed in this zone.")
player:SetData("last_notification", 2)
end
end
end
local function onPlayerUpdateZone(event, player, newZone, newArea)
local position = player:GetPosition()
local playerEvent = player:GetData("subzone_event_id")
if (playerEvent) then
player:RemoveEventById(playerEvent)
end
if(Points[position.m] ~= nil and Points[position.m][position.a] ~= nil) then
if (BoundingBox[position.m] == nil) then
BoundingBox[position.m] = { }
end
if (BoundingBox[position.m][position.a] == nil) then
BoundingBox[position.m][position.a] = RayTrace.createBoundingBox(Points[position.m][position.a])
end
playerEvent = player:RegisterEvent(Timed, 1000, 0)
player:SetData("subzone_event_id", playerEvent)
end
end
RegisterPlayerEvent(27, onPlayerUpdateZone)
local function onPlayerCastSpell(event, player, spell)
local pInside = player:GetData("inside_polygon")
if pInside then
player:StopSpellCast()
player:SendAreaTriggerMessage("Spells or Mounts are not allowed in this area.")
end
end
RegisterPlayerEvent(5, onPlayerCastSpell)
Edit this, [0] is your MapId, [1519] is your AreaID and all subarray are all of your points (the vertices of your polygon)
Code:
local Points = {
[0] = { -- This is your MapID
[1519] = { -- This is your AreaID
{x = -8808.886719, y = 629.936768, z = 94.228920}, -- This is a vertices of your polygon
{x = -8794.510742, y = 637.831726, z = 94.232796},-- This is a vertices of your polygon
{x = -8800.683594, y = 650.272400, z = 94.518021},-- This is a vertices of your polygon
{x = -8815.123047, y = 643.107849, z = 94.229118},-- This is a vertices of your polygon
}
}
}
Last edited: