-- Zones script for GameGuru -- Written by Chris Stapleton local zones = {} local max = math.max local min = math.min function zone_init_name(e, name) zones[e] = {name = name, poly = nil, monitored = false} end local function wnPntInZone( P, V) -- Copyright 2000 softSurfer, 2012 Dan Sunday -- Lua conversion by Chris Stapleton, 2017 -- This code may be freely used and modified for any purpose -- providing that this copyright notice is included with it. -- SoftSurfer makes no warranty for this code, and cannot be held -- liable for any real or imagined damage resulting from its use. -- Users of this code must verify correctness for their application. local function isLeft( P0, P1, P2 ) return ( (P1.x - P0.x) * (P2.z - P0.z) - (P2.x - P0.x) * (P1.z - P0.z) ) end local wn = 0 -- the winding number counter -- loop through all edges of the polygon for i = 1,#V - 1 do if V[i].z <= P.z then if V[i+1].z > P.z then -- an upward crossing if (isLeft( V[i], V[i+1], P) > 0) then -- P left of edge wn = wn + 1 -- have a valid up intersect end end else -- start y > P.y (no test needed) if (V[i+1].z <= P.z) then -- a downward crossing if (isLeft( V[i], V[i+1], P) < 0) then -- P right of edge wn = wn - 1 -- have a valid down intersect end end end end return wn ~= 0 end local function FindZoneFromWaypoints(e) local poly = {} -- find initial waypoint path to follow local pathIndex, pointIndex = -1, -1; local pointClosest = math.huge; local DX, DZ, Dist local Ent = g_Entity[e] for path = 1, AIGetTotalPaths() do for point = 1, AIGetPathCountPoints( path ) do DX = Ent.x - AIPathGetPointX( path, point ) DZ = Ent.z - AIPathGetPointZ( path, point ) Dist = DX*DX + DZ*DZ if Dist < pointClosest then pointClosest = Dist pathIndex = path pointIndex = point end end end -- only use if waypoint within 200 units of entity if pointClosest > 200 * 200 then return 0,0,0,0,'none' end if pathIndex ~= -1 then local rl = AIGetPathCountPoints( pathIndex ) -- and is 'enclosed' zone DX = AIPathGetPointX( pathIndex, 1 ) - AIPathGetPointX( pathIndex, rl ) DZ = AIPathGetPointZ( pathIndex, 1 ) - AIPathGetPointZ( pathIndex, rl ) if DX*DX + DZ*DZ > 200 * 200 then return 0,0,0,0,'none' end -- now we have found the closest zone copy it local wp = {} local xMin, zMin = math.huge, math.huge local xMax, zMax = 0, 0 for i = 1, rl do wp[i] = { x = AIPathGetPointX( pathIndex, i ), z = AIPathGetPointZ( pathIndex, i ) }; xMin, zMin = min( xMin, wp[i].x ), min( zMin, wp[i].z ) xMax, zMax = max( xMax, wp[i].x ), max( zMax, wp[i].z ) end -- close zone wp[rl + 1] = { x = wp[1].x, z = wp[1].z } -- now check that Entity is inside zone, if not reverse -- order of waypoints and try again if wnPntInZone({x = Ent.x, z = Ent.z}, wp) then return xMin, xMax, zMin, zMax, wp else local rwp = {} for i = 0, rl do rwp[i + 1].x = wp[rl - i].x rwp[i + 1].z = wp[rl - i].z end if wnPntInZone({x = Ent.x, z = Ent.z}, rwp) then return xMin, xMax, zMin, zMax, rwp end end end return 0,0,0,0,'none' end local function PlayerInZone(z) local Px, Pz = g_PlayerPosX, g_PlayerPosZ if Px < z.xMin or Px > z.xMax or Pz < z.zMin or Pz > z.zMax then return false else return wnPntInZone({x = Px, z = Pz}, z.poly) end end local function AllPointsInZone(z1, z2) if z2.poly == nil or z2.poly == 'none' then return false end for i = 1,#z1.poly - 1 do local p = z1.poly[i] if p.x < z2.xMin or p.x > z2.xMax or p.z < z2.zMin or p.z > z2.zMax then return false end if not wnPntInZone({x = p.x, z = p.z}, z2.poly) then return false end end return true end function PointIsInZone(x, z, zone) for k, v in pairs(zones) do if v.name == zone and v.poly and v.poly ~= 'none' then if x >= v.xMin and x <= v.xMax and z >= v.zMin and z <= v.zMax then return wnPntInZone({x = x, z = z}, v.poly) end end end return false end function ZonePointIn(x, z) -- simply returns first zone found for k, v in pairs(zones) do if v.poly and v.poly ~= 'none' and wnPntInZone({x = x, z = z}, v.poly) then return v.name end end return 'None' end function PlayerIsInZone(zone) return PointIsInZone(g_PlayerPosX, g_PlayerPosZ, zone) end function ZonesPlayerIn() local zList = {} for k, v in pairs(zones) do if PlayerInZone(v) then zList[#zList + 1] = v.name end end return zList end function MonitorZoneOn(zone) for k, v in pairs(zones) do if v.name == zone and v.state == 'ready' then zones[k].monitored = true zones[k].timer = g_Time + 500 return true end end return false end function MonitorZoneOff(zone) for k, v in pairs(zones) do if v.name == zone and v.poly and v.poly ~= 'none' then zones[k].monitored = false zones[k].timer = math.huge end end end -- these functions only work if monitoring is on function PlayerEnteredZone(zone) for k, v in pairs(zones) do if v.name == zone and v.monitored then if v.pez then zones[k].pez = false return true end break end end return false end function PlayerExitedZone(zone) for k, v in pairs(zones) do if v.name == zone and v.monitored then if v.pxz then zones[k].pxz = false return true end break end end return false end function WptInZoneDefinition(x, y) -- return true if the specified point is used in any zone definition for k, v in pairs(zones) do if v.poly and v.poly ~= 'none' then if x >= v.xMin and x <= v.xMax and z >= v.zMin and z <= v.zMax then for i = 1,#v.poly - 1 do if v.poly[i].x == x and v.poly[i].z == z then return true end end end end end return false end function zone_main(e) local z = zones[e] if z == nil then return end if z.poly == nil then z.xMin, z.xMax, z.zMin, z.zMax, z.poly = FindZoneFromWaypoints(e) if z.poly ~= 'none' then z.state = 'init' else return end end if z.state == 'init' then -- we'll try for 1 second to find all zones z.timer = g_Time + 1000 z.state = 'relate' end if z.state == 'relate' then if g_Time < z.timer then -- TBD calculate if this zone is inside another, if so set z.inZone -- to zone containing it (note this can be recursive, the zone we are -- inside might itself be inside another, so we wil need to do this -- multiple times) for k, v in pairs(zones) do -- don't check ourself if k ~= e then -- for now just stop when we find ourslves inside any zone local insideZone = AllPointsInZone(z, v) if insideZone then z.inZone = k break end end end else z.state = 'ready' end return end -- PromptLocal(e, z.state .. ", " .. (z.inZone or 'outside')) if z.monitored then z.state = 'monitor' -- monitor player entering/leaving zone -- do this every half a second if g_Time > z.timer then z.timer = g_Time + 500 local playerInZone = PlayerInZone(z) if z.piz == nil then z.piz = playerInZone return end if playerInZone then if not z.piz then -- player has entered zone z.piz = true z.pez = true end else if z.piz then -- player has exited zone z.piz = false z.pxz = true end end end end end