-- Zones library for GameGuru -- Written by Chris Stapleton local Z = {_G = _G} -- import section: modules local rad = math.rad local deg = math.deg local cos = math.cos local sin = math.sin local asin = math.asin local acos = math.acos local abs = math.abs local atan = math.atan2 local sqrt = math.sqrt local max = math.max local min = math.min local pi = math.pi local rand = math.random local sort = table.sort local pairs = pairs -- GG functions/Tables local gEnt = g_Entity local totPaths = AIGetTotalPaths local pathPoints = AIGetPathCountPoints local getPointX = AIPathGetPointX local getPointZ = AIPathGetPointZ -- temp local getTerrain = GetTerrainHeight local resPos = ResetPosition local colOff = CollisionOff local colOn = CollisionOn -- include these for debugging local Prompt = Prompt local PromptE = PromptLocal -- If using any of the functions generating random positions -- make sure random number generator is seeded by putting the -- following lines in your main script initialisation: math.randomseed(os.time()) math.random(); math.random(); math.random() _ENV = Z local function getPosX() return _G.g_PlayerPosX end local function getPosZ() return _G.g_PlayerPosZ end local function getPos2() return _G.g_PlayerPosX, _G.g_PlayerPosZ end local function getPos3() return _G.g_PlayerPosX, _G.g_PlayerPosY, _G.g_PlayerPosZ end local function getAngX() return _G.g_PlayerAngX end local function getAngY() return _G.g_PlayerAngY end local function getAng3() return _G.g_PlayerAngX, _G.g_PlayerAngY, _G.g_PlayerAngZ end local function timeNow() return _G.g_Time end local function huge() return _G.math.huge end local zones = {} 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( x, z ) local poly = {} local pathIndex, pointIndex = -1, -1; local pointClosest = huge(); local DX, DZ, Dist for path = 1, totPaths() do for point = 1, pathPoints( path ) do DX = x - getPointX( path, point ) DZ = z - getPointZ( 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 100 units of entity if pointClosest > 100 * 100 then return 0, 0, 0, 0, 'none' end if pathIndex ~= -1 then local rl = pathPoints( pathIndex ) -- and is 'enclosed' zone DX = getPointX( pathIndex, 1 ) - getPointX( pathIndex, rl ) DZ = getPointZ( pathIndex, 1 ) - getPointZ( pathIndex, rl ) if DX*DX + DZ*DZ > 100 * 100 then return 0,0,0,0,'none' end -- now we have found the closest zone copy it local wp = {} local xMin, zMin = huge(), huge() local xMax, zMax = 0, 0 for i = 1, rl do wp[i] = { x = getPointX( pathIndex, i ), z = getPointZ( 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 = x, z = z}, wp ) then return xMin, xMax, zMin, zMax, wp else local rwp = {} for i = 0, rl do rwp[ i + 1 ] = { x = wp[ (rl + 1) - i ].x, z = wp[ (rl + 1) - i ].z } end if wnPntInZone({x = x, z = z}, rwp) then return xMin, xMax, zMin, zMax, rwp end end end return 0,0,0,0,'none' end function Z.AddZone( e, name ) local z = zones[ e ] -- if there is already a zone for this entity simply return if z ~= nil then return end local Ent = gEnt[ e ] zones[ e ] = {} z = zones[ e ] z.xMin, z.xMax, z.zMin, z.zMax, z.poly = FindZoneFromWaypoints( Ent.x, Ent.z ) if z.poly ~= 'none' then z.state = 'init' z.name = name else return end end local function PlayerInZone( z ) local Px, Pz = getPos2() 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 Z.DistFromZone( x, z, zone ) local dist = huge() local poly = nil for k, v in pairs( zones ) do if v.name == zone and v.poly and v.poly ~= 'none' then if not wnPntInZone( { x = x, z = z }, v.poly ) then poly = v.poly break else dist = 0 break end end end local p1 = { d = huge() } local p2 = { d = huge() } if poly ~= nil then -- get two closest points to player local ld, dX, dZ -- first find closest waypoint for i = 1, #poly - 1 do dX, dZ = poly[ i ].x - x, poly[ i ].z - z ld = dX*dX + dZ*dZ if ld < p1.d then p1 = { d = ld, pt = i, x = poly[ i ].x, z = poly[ i ].z } end end -- if too far away bail out early if p1.d > 2000*2000 then return dist end -- next find closest adjoining waypoint local tp1, tp2 if p1.pt == #poly - 1 then tp1, tp2 = 1, p1.pt -1 elseif p1.pt == 1 then tp1, tp2 = #poly - 1, 2 else tp1, tp2 = p1.pt -1, p1.pt + 1 end dX, dZ = poly[ tp1 ].x - x, poly[ tp1 ].z - z p2.d = dX*dX + dZ*dZ dX, dZ = poly[ tp2 ].x - x, poly[ tp2 ].z - z ld = dX*dX + dZ*dZ if ld < p2.d then p2 = { d = ld, x = poly[ tp2 ].x, z = poly[ tp2 ].z } else p2.x = poly[ tp1 ].x p2.z = poly[ tp1 ].z end local a2, b2 = p1.d, p2.d -- calculate distance between points dX, dZ = p1.x - p2.x, p1.z - p2.z local c2 = dX*dX + dZ*dZ local a, b, c = sqrt( a2 ), sqrt( b2 ), sqrt( c2 ) -- now for some funky math local A = acos( ( b2 + c2 - a2 ) / ( 2 * b * c ) ) local B = acos( ( a2 + c2 - b2 ) / ( 2 * a * c ) ) if A > B then dist = sin( B ) * a else dist = sin( A ) * b end end return dist end function Z.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 Z.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 Z.PlayerIsInZone( zone ) return PointIsInZone( getPosX(), getPosZ(), zone ) end function Z.EntityIsInZone( e, zone ) local Ent = gEnt[ e ] return PointIsInZone( Ent.x, Ent.y, zone ) end function Z.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 Z.ZonesEntityIn( e ) local Ent = gEnt[ e ] local zList = {} for k, v in pairs( zones ) do if wnPntInZone( { x = Ent.x, z = Ent.z }, v.poly ) then zList[ #zList + 1 ] = v.name end end return zList end function Z.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 = timeNow() + 500 return true end end return false end function Z.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 = huge() end end end function Z.ZoneMonitorEntityOn( e, zone ) for k, v in pairs( zones ) do if v.name == zone and v.state == 'ready' then if v.eList == nil then zones[ k ].eList = {} end zones[ k ].eList[ #zones[ k ].eList + 1 ] = { Ent = e } if not v.monitored then zones[ k ].monitored = true zones[ k ].timer = timeNow() + 100 end return true end end return false end local function RemoveEntity( e, l ) for k, v in pairs( l ) do if v == e then l[ k ] = nil return l end end end function Z.ZoneMonitorEntityOff( e, zone ) for k, v in pairs( zones ) do if v.name == zone and v.poly and v.poly ~= 'none' then zones[ k ].eList = RemoveEntity( e, zones[ k ].eList ) end end end -- these functions only work if monitoring is on function Z.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 Z.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 Z.EntityEnteredZone( e, zone ) for k, v in pairs( zones ) do if v.name == zone and v.monitored then for j, w in pairs( v.eList ) do if w.Ent == e then if w.eez then zones[ k ].eList[ j ].eez = false return true end break end end break end end return false end function Z.EntityExitedZone( e, zone ) for k, v in pairs( zones ) do if v.name == zone and v.monitored then for j, w in pairs( v.eList ) do if w.Ent == e then if w.exz then zones[ k ].eList[ j ].exz = false return true end break end end break end end return false end -- special function for advanced use function Z.WptInZoneDefinition( x, z ) -- 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 local function MonitorEnt( l, z ) local Ent = gEnt[ l.Ent ] local entityInZone = wnPntInZone( { x = Ent.x, z = Ent.z }, z.poly ) if l.eiz == nil then l.eiz = entityInZone return end if entityInZone then if not l.eiz then -- entity has entered zone l.eiz = true l.eez = true end else if l.eiz then -- entity has exited zone l.eiz = false l.exz = true end end end function Z.RandomPointInZone( zone ) for k, v in pairs( zones ) do if v.name == zone and v.poly and v.poly ~= 'none' then local xRange = abs( v.xMax - v.xMin ) / 2 local zRange = abs( v.zMax - v.zMin ) / 2 local pointFound = false local triesLeft = 20 -- start in the 'centre' local centx, centz = v.xMin + xRange, v.zMin + zRange local xOff, zOff = 0, 0 while triesLeft > 0 do -- pick a random offset from the centre xOff = rand() * xRange if rand( 1, 2 ) == 2 then xOff = -xOff end zOff = rand() * zRange if rand( 1, 2 ) == 2 then zOff = -zOff end local p = { x = centx + xOff, z = centz + zOff } -- check if this point is in the zone if wnPntInZone( p, v.poly ) then -- return the point return p.x, p.z else triesLeft = triesLeft - 1 end end -- couldn't find a random point in zone within reasonable -- time so just return one of the verts local vert = rand( 1, #v.poly - 1 ) return v.poly[ vert ].x, v.poly[ vert ].z end end -- couldn't find zone so just return 0,0 and leave it up to the caller to -- deal with return 0, 0 end -- call this function periodically from the zone entity function Z.ZoneProcessor( e ) local z = zones[ e ] if z == nil then return end if z.state == 'init' then -- we'll try for 1 second to find all zones z.timer = timeNow() + 1000 z.state = 'relate' end if z.state == 'relate' then if timeNow() < 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 elseif z.monitored then z.state = 'monitor' -- monitor player/entity entering/leaving zone -- do this every tenth of a second if timeNow() > z.timer then z.timer = timeNow() + 100 if z.eList ~= nil then -- we have entities to monitor for k, v in pairs( z.eList ) do MonitorEnt( z.eList[ k ], z ) end end -- now do player 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 return Z