-- Zones script for GameGuru -- Written by Chris Stapleton local zones = {} local max = math.max local min = math.min local abs = math.abs local rnd = math.random 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 100 units of entity if pointClosest > 100 * 100 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 > 100 * 100 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 + 1) - i].x rwp[i + 1].z = wp[(rl + 1) - 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 EntityIsInZone(e, zone) local Ent = g_Entity[e] return PointIsInZone(Ent.x, Ent.y, 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 ZonesEntityIn(e) local Ent = g_Entity[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 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 function 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 = g_Time + 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 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 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 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 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 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 local function MonitorEnt(l, z) local Ent = g_Entity[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 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 = 10 -- 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 = rnd() * xRange if rnd(1, 2) == 2 then xOff = -xOff end zOff = rnd() * zRange if rnd(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, GetTerrainHeight( p.x, p.z ), 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 = rnd( 1, #v.poly - 1 ) return v.poly[vert].x, GetTerrainHeight( v.poly[vert].x, v.poly[vert].z ), v.poly[vert].z end end -- couldn't find zone so just return 0,0,0 and leave it up to the caller to -- deal with return 0, 0, 0 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 if z.monitored then z.state = 'monitor' -- monitor player/entity entering/leaving zone -- do this every tenth of a second if g_Time > z.timer then z.timer = g_Time + 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