DarkstaR
09-30-2015, 01:40 PM
A Practical Example of IPC
Since the last examples were a bit simple, I wanted to go ahead and make a script that used IPC in a cool way. Well, not to toot my own horn, but I fucking delivered; and i'm open sourcing it. This script is far from perfect, but its a good start, and I took care of all the complex math already.
The purpose of this script is to run up to 20 drones around the map that will mimic the exiva's casted by the main player, and communicate that data back to said player. All of the results will display on a HUD, showing things like "very far to the east of thais" or "far south of venore". If any of the drones encounter the target player (readas: if he's close by, next to the drone, or above/below the drone), the script will say something like this in the server log:
The target player is near "My Exiva Dude 3" in "Thais".
If the target player is far or very far from all of the drones, though, some magic takes place. The script will use some moderately advanced trigonometry to triangulate the position of the player, based on the areas from each exiva. It does this by calculating a region that the target player MUST be inside of. From there, it looks at a list of known towns and hunting spots (thanks to @Rydan (http://forums.xenobot.net/member.php?u=582), @Fatality (http://forums.xenobot.net/member.php?u=35996), and @kimse (http://forums.xenobot.net/member.php?u=6417) for helping me with these lists) in that region, and tells you the top six locations where the player might be. If there's no known locations inside of the area, it will tell you the center xy position and the outline of the polygon, so you can take a look at the data on tibia.xyz (http://tibia.xyz) or another mapping site. If all else fails, you will still have a large list of exiva results to help locate the player on your own.
Before I jump in to the actual code, I'd like to note that me and @kimse (http://forums.xenobot.net/member.php?u=6417) have determined the optimal locations for each drone without the need for PACC: Thais Bless, Folda Boat, Carlin Temple, Kaz North, Ab Boat, Demon Oak Reward, and Venore Boat. If you exiva from either Darashia Carpet or Darashia Boat on the main character, this will cover the entire map very well.
http://i.imgur.com/qWp4P5n.jpg
With further talking, here's the code.
exiva_server.lua, this goes on the main character
dofile("exiva_common.lua")
-- variables for triangulation
local triangulated = false
local lastExivaMessageTime = 0
local exivaData = {}
local latestMessages = {}
-- create title HUD
local HUDTitle = HUD.CreateTextDisplay(CONFIG.HUDLocation.x, CONFIG.HUDLocation.y, "", unpack(CONFIG.HUDColors.title))
-- create HUD lines for each exiva result
local HUDsDisplayed = 0
local HUDData = {}
for index = 1, 20 do
HUDData[index] = HUD.CreateTextDisplay(
CONFIG.HUDLocation.x + 5,
-- +12 pixels to the y axis for every item
CONFIG.HUDLocation.y + ((index - 1) * 12) + 12,
"",
-- text color
unpack(CONFIG.HUDColors.message)
)
end
-- create two server sockets to communicate with clients
local pubsock = IpcPublisherSocket.New("pub-socket", CONFIG.IPCPorts.pubsub)
local repsock = IpcResponderSocket.New("rep-socket", CONFIG.IPCPorts.repreq)
-- detect exiva casts
LocalSpeechProxy.New("speech-proxy"):OnReceive(function(proxy, type, speaker, level, message)
if (speaker == Self.Name() and string.starts(message, "exiva")) then
-- get the name of the exiva target
local targetPlayer = string.sub(message, 8)
targetPlayer = string.sub(targetPlayer, 1, string.len(targetPlayer) - 1)
-- reset all of the HUD stuff for the last exiva target
HUDTitle:SetText(CONFIG.HUDTitle .. targetPlayer)
HUDsDisplayed = 0
for _, HUDLine in pairs(HUDData) do
HUDLine:SetText("")
end
-- reset all of the triangulation stuff
triangulated = false
exivaData = {}
lastExivaMessageTime = 0
-- find the result of this exiva (comes before the speech does) and store it
for i = #latestMessages, 1, -1 do
if (string.starts(latestMessages[i], targetPlayer)) then
local locationString, townName = parseExivaMessage(latestMessages[i], targetPlayer)
local message = createIpcMessage(locationString, townName)
handleExivaResult(message)
break
end
end
latestMessages = {}
-- publish the target name to the clients
pubsock:PublishMessage("target", targetPlayer)
end
end)
-- catch all generic messages and store them to use later
GenericTextMessageProxy.New("generic-proxy"):OnReceive(function (proxy, message)
latestMessages[#latestMessages+1] = message
end)
-- Handle incoming exiva results from clients and triangulate after they're all received
Module.New('rep-listen', function()
local success, request = repsock:GetRequest()
while (success) do
-- tell the req socket we've got the message
repsock:SendResponse("ack")
-- handle it
handleExivaResult(request)
-- check for new messages
success, request = repsock:GetRequest()
end
if (not triangulated and lastExivaMessageTime ~= 0 and os.clock() - lastExivaMessageTime > 1.5) then
triangulated = true
triangulate()
end
end)
function handleExivaResult(result)
--parse the message
local parts = string.split(result, "|")
local showMessage = parts[1] .. " of " .. parts[4]
-- update the hud
HUDsDisplayed = HUDsDisplayed + 1
HUDData[HUDsDisplayed]:SetText(showMessage)
-- store this for use with triangulator
exivaData[HUDsDisplayed] = {message = parts[1], x = tonumber(parts[2]), y = tonumber(parts[3]), town = parts[4], drone = parts[5]}
lastExivaMessageTime = os.clock()
end
-- triginometry code to overlap exiva regions
ANGLES =
{
SOUTH_EAST = 7426.0,
EAST = 6977.0,
NORTH_EAST = 6528.0,
NORTH = 6079.0,
NORTH_WEST = 5627.0,
WEST = 5171.0,
SOUTH_WEST = 4722.0,
SOUTH = 4273.0,
SWEEP = 449.0
}
ANGLE_MAPPING =
{
{"south-east", ANGLES.SOUTH_EAST},
{"north-east", ANGLES.NORTH_EAST},
{"north-west", ANGLES.NORTH_WEST},
{"south-west", ANGLES.SOUTH_WEST},
{"east", ANGLES.EAST},
{"north", ANGLES.NORTH},
{"west", ANGLES.WEST},
{"south", ANGLES.SOUTH},
}
function createPoint(x, y, radius, angle)
local nx = x + math.cos(angle * 0.01745) * radius
local ny = y + math.sin(angle * 0.01745) * radius
return {x = nx, y = ny}
end
function exivaMessageToPolygon(message, x, y)
local radiusLow = 0
local radiusHigh = 0
local angleStart = 0
local angleEnd = 0
local angleMiddle = 0
if (string.find(message, "very far")) then
radiusLow = 276
radiusHigh = 4000
elseif (string.find(message, "far")) then
radiusLow = 101
radiusHigh = 275
else
return true, {}
end
for _, angle in pairs(ANGLE_MAPPING) do
if (string.find(message, angle[1], 1, true)) then
angleStart = angle[2] / 10.0
angleEnd = (angle[2] / 10.0) + (ANGLES.SWEEP / 10.0)
angleMiddle = (angle[2] / 10.0) + (ANGLES.SWEEP / 20.0)
break
else
end
end
-- polygon is a sort of pointy cone, and has 6 points (example of an exiva to the north):
--[[
_3_
2__--- ---__4
\ /
\ 6 /
\__- -__/
1 5
--]]
local poly =
{
createPoint(x, y, radiusLow, angleStart),
createPoint(x, y, radiusHigh, angleStart),
createPoint(x, y, radiusHigh, angleMiddle),
createPoint(x, y, radiusHigh, angleEnd),
createPoint(x, y, radiusLow, angleEnd),
createPoint(x, y, radiusLow, angleMiddle),
}
return false, poly
end
function triangulate()
-- generate the polygons for each exiva cast
local polygons = {}
for i, data in ipairs(exivaData) do
local found, polygon = exivaMessageToPolygon(data.message, data.x, data.y)
if (found) then
if (data.self) then
print("Target player is very close to you")
else
print("Target player is very close to '%s' in '%s'", data.drone, data.town)
end
return nil
end
polygons[i] = polygon
end
-- clip the polygons againmst eachother to come up with the overlapping area
-- and also against the Tibia map to get rid of excess areas
local clipped = clip(polygons[1], TIBIA_MAP_POLYGON)
for i = 2, #polygons do
clipped = clip(clipped, polygons[i])
end
-- find the center of the final polygon
local center = centroid(clipped)
local locations = {}
for name, pos in pairs(HUNTING_SPOTS) do
local point = {x = pos[1], y = pos[2]}
if (pointinpoly(point, clipped)) then
locations[#locations+1] = {name = name, dist = distance(point, center)}
end
end
table.sort(locations, function(a,b) return a.dist<b.dist end)
if (#locations > 6) then
for i = 6, #locations do
locations[i] = nil
end
end
--print("Center: %s", table.serialize(center))
--print("Polygon: %s", table.serialize(clipped))
--print("Locations: %s", table.serialize(locations))
for i, place in pairs(locations) do
print("Target player might be in '%s' (this location is %d SQM from the center of the estimated region)", place.name, place.dist)
end
if (#locations == 0) then
print("No location found.")
print("Center: %s", table.serialize(center))
print("Polygon: %s", table.serialize(clipped))
end
end
-- polygon specific triginometry
function distance(point1, point2)
return math.sqrt(math.pow(math.abs(point1.x - point2.x), 2) + math.pow(math.abs(point1.y - point2.y), 2))
end
function pointinpoly(point, polygon)
local hit = false
local j = #polygon
for i, vertex in ipairs(polygon) do
if ( ((polygon[i].y > point.y) ~= (polygon[j].y > point.y)) and
(point.x < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x) ) then
hit = not hit
end
j = i
end
return hit
end
function centroid(polygon)
local minx = 65535
local maxx = 0
local miny = 65535
local maxy = 0
for i, 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 {x = minx + (maxx - minx) / 2.0, y = miny + (maxy - miny) / 2.0}
end
function inside(p, cp1, cp2)
return (cp2.x - cp1.x) * (p.y-cp1.y) > (cp2.y - cp1.y) * (p.x - cp1.x)
end
function intersection(cp1, cp2, s, e)
local dcx, dcy = cp1.x - cp2.x, cp1.y - cp2.y
local dpx, dpy = s.x - e.x, s.y - e.y
local n1 = cp1.x * cp2.y - cp1.y * cp2.x
local n2 = s.x * e.y - s.y * e.x
local n3 = 1 / (dcx * dpy - dcy * dpx)
local x = (n1 * dpx - n2 * dcx) * n3
local y = (n1 * dpy - n2 * dcy) * n3
return {x = x, y = y}
end
function clip(subjectPolygon, clipPolygon)
local outputList = subjectPolygon
local cp1 = clipPolygon[#clipPolygon]
for _, cp2 in ipairs(clipPolygon) do -- WP clipEdge is cp1,cp2 here
local inputList = outputList
outputList = {}
local s = inputList[#inputList]
for _, e in ipairs(inputList) do
if inside(e, cp1, cp2) then
if not inside(s, cp1, cp2) then
outputList[#outputList+1] = intersection(cp1, cp2, s, e)
end
outputList[#outputList+1] = e
elseif inside(s, cp1, cp2) then
outputList[#outputList+1] = intersection(cp1, cp2, s, e)
end
s = e
end
cp1 = cp2
end
return outputList
end
exiva_client.lua, this gets execute by each of the drone characters
dofile("exiva_common.lua")
-- vars
local currentTargetName = "---------------------------------"
-- create two client sockets to communicate with server
local subsock = IpcSubscriberSocket.New("sub-socket", CONFIG.IPCPorts.pubsub)
local reqsock = IpcRequestorSocket.New("req-sock", CONFIG.IPCPorts.repreq)
subsock:AddTopic("target")
-- detect exiva results
GenericTextMessageProxy.New("generic-proxy"):OnReceive(function (proxy, message)
if (string.starts(message, currentTargetName)) then
-- parse the message and find the closest town
local locationString, townName = parseExivaMessage(message, currentTargetName)
-- send results to the server
local message = createIpcMessage(locationString, townName)
reqsock:SendRequest(message)
reqsock:GetResponse()
end
end)
-- Handle incoming exiva requests from server
Module.New('sub-listen', function()
local hasMessage, topic, data = subsock:Recv()
while (hasMessage) do
currentTargetName = data
Self.Say("exiva \"" .. currentTargetName)
hasMessage, topic, data = subsock:Recv()
end
end)
exiva_common.lua, don't run this on any chars, it's just a library
--[[ CONFIG START ]] --
CONFIG =
{
HUDTitle = "Exiva On ",
HUDLocation = { x = 25, y = 5 },
HUDColors =
{
message = {95, 247, 247},
title = {225, 225, 225},
},
IPCPorts =
{
pubsub = 30269,
repreq = 30268,
}
}
--[[ UTILS START ]] --
TIBIA_MAP_POLYGON =
{
{x = 31904, y = 31004},
{x = 33750, y = 31004},
{x = 33750, y = 33020},
{x = 31904, y = 33020},
}
HUNTING_SPOTS =
{
["Tarpit Tomb"] = {33232, 32704, 7},
["Peninsula Tomb"] = {33025, 32868, 7},
["Hydra Mountain (North)"] = {32979,32627,7},
["Hydra Mountain (South)"] = {32988,32683,7},
["New Ancient Scarabs"] = {33359, 32657, 12},
["GS Tomb"] = {33047, 32571, 11},
["Shadow Tomb"] = {33245, 32853, 13},
["Ancient Ruins Tomb"] = {33290, 32557 , 13},
["Lion's Rock"] = {33146, 32338, 7},
["Darashia Dragons"] = {33265, 32277, 7},
["Darashia Minotaurs"] = {33308, 32285, 7},
["Darashia North East Wasps"] = {33307, 32341, 7},
["Darashia City wasps"] = {33210, 32384, 7},
["Drefia Wyrms"] = {33089, 32394, 13},
["Drefia Grims"] = {33044, 32457, 13},
["Ape City Surface"] = {32812, 32547, 7},
["Banuta Main Floor"] = {32822, 32577, 11},
["Medusa Tower"] = {32867, 32831, 7},
["Old Water Elementals"] = {32532, 32821, 7},
["Forbidden Lands Hydras"] = {32976, 32549, 7},
["Banuta -1"] = {32785, 32583, 12},
["Banuta -2"] = {32782, 32572, 13},
["Banuta -3"] = {32771, 32609, 14},
["Banuta -4"] = {32801, 32626, 15},
["Asura Palace"] = {32947, 32681, 7},
["New Water Elementals"] = {32644, 32995, 8},
["Orc Fortress"] = {32916, 31776, 7},
["Dark Cathedral"] = {32662, 32346, 7},
["Venore Coryms"] = {33051, 32120, 11},
["Venore Dragons"] = {32807,32157,7},
["Plains of Havoc Temple"] = {32816,32272,7},
["Plains of Havoc Center"] = {32728,32276,7},
["Outlaw Camp"] = {32663,32346,7},
["Outside Orc Fortress - West"] = {32800,31790,7},
["Outside Orc Fortress - North"] = {32927,31710,7},
["PoI DLs"] = {32789, 32334, 12},
["PoI DT Seal"] = {32778, 32237, 15},
["PoI Hellhound Seal"] = {32880, 32229, 15},
["PoI Juggernaut Seal"] = {32872, 32279, 15},
["PoI Undead Dragon Seal"] = {32833, 32278, 15},
["PoI Plauge Seal"] = {32843, 32349, 15},
["Cyclops Mountain"] = {32458, 32073, 7},
["Greenshore"] = {32260, 32074, 7},
["Mintwallin"] = {32414, 32132, 15},
["Fibula"] = {32175,32409,7},
["Thais Blessing temple"] = {32345,32365,7},
["Thais South Cyclops"] = {32417,32387,7},
["Fibula"] = {32168, 32379, 9},
["Cyclopolis"] = {33242, 31681, 9},
["Hero Fortress"] = {33323, 31580, 10},
["Edron Bog Raiders"] = {33345, 31761, 13},
["Edron Trolls"] = {33118, 31778, 7},
["Edron Goblins"] = {33120, 31832, 7},
["Edron Behemoths"] = {33301, 31698, 15},
["Inq Spectres"] = {33111, 31775, 13},
["Inq HFFs"] = {33164, 31712, 13},
["Edron Demons"] = {33103, 31710, 15},
["Maze of Lost Souls"] = {32467, 31645, 11},
["Demona"] = {32479,31627,14},
["Ghost Lands"] = {32217,31819,7},
["Banshee Quest"] = {32233,31864,11},
["Femur Hills"] = {32564, 31806, 7},
["Liberty Bay Wyrms Mountain"] = {32417, 32761, 7},
["Quara Grotto"] = {32243, 32899, 8},
["Ramoa (BB Island)"] = {31932,32577,7},
["Goroma"] = {32130, 32560, 7},
["Goroma Serpent Spawns"] = {31928, 32631, 11},
["Talahu (Hydra Island)"] = {31958,32664,7},
["Malada (GS Island)"] = {32012,32718,7},
["Nargor Pirates"] = {31990, 32859, 6},
["Liberty Bay Wyrms Underground"] = {32419, 32847, 9},
["Oramond Boat"] = {33490, 31986, 7},
["Oramond Minotaurs South"] = {33597, 32028, 7},
["Oramond Minotaurs East"] = {33704, 31939, 7},
["Oramond Furies / Undead Drags"] = {33650, 31839, 7},
["Seacrest Serpents Entrance"] = {33545, 31860, 7},
["Oramond West"] = {33543, 31893, 7},
["Oramond Tower"] = {33550, 31929, 7},
["Oramond Hydras"] = {33486, 31924, 10},
["Southeast Oramond Minotaurs"] = {33628, 32053, 7},
["Oramond Catacombs"] = {33469, 31762, 8},
["Seacrest Serpents North"] = {33490, 31685, 15},
["Seacrest Serpents South"] = {33468, 31816, 15},
["Oramond Grims/Demons"] = {33469, 32056, 10},
["Roshamuul South of bridge"] = {33572, 32553, 7},
["Roshamuul Bridge"] = {33541, 32529, 6},
["Guzzlemaw Valley"] = {33626, 32483, 7},
["Roshamuul Prison"] = {33567, 32379, 10},
["Roshamuul Depot"] = {33547, 32380, 7},
["War Golems"] = {32904, 31288, 7},
["Mistrock Cyclops"] = {32591, 31437, 7},
["Yalahar Arena Quarter"] = {32676, 31230, 7},
["Yalahar Alchemist Quarter"] = {32692, 31133, 7},
["Hellspawns"] = {32859, 31046, 7},
["Yalahar Grim Reapers"] = {32774, 31060, 8},
["Vengoth"] = {32953, 31463, 7},
["Fenrock DLs"] = {32593, 31364, 15},
["Svargrond Barbarian Camp"] = {32021, 31406, 7},
["Svargrond Arena"] = {32192, 31081, 7},
["Nibelor"] = {32359, 31080, 7},
["Okolnir"] = {32233, 31417, 7},
["New Chosens"] = {33179, 31196, 7},
["Corruption Hole"] = {33298, 31145, 9},
["Lizard City"] = {33078, 31186, 7},
["Killer Caimans"] = {33101, 31447, 7},
["Souleaters"] = {33164, 31059, 7},
["Lizard Village"] = {33238, 31165, 7},
["Farmine Dragons"] = {33190, 31293, 7},
["Kazordoon Dwarf Mines"] = {32502,31919,8},
["Lower Spike"] = {32243, 32604, 8},
["Hive"] = {33532, 31252, 7},
["Warzone 1/2/3"] = {33036, 31933, 12},
["Deeplings"] = {33457, 31194, 12},
}
NPC_DATA =
{
{name = "Anderson", destinations = {["folda"] = {32046,31580,7}, ["tibia"] = {32234,31675,7}, ["vega"] = {32022,31692,7},}},
{name = "Barry", destinations = {["magician"] = {32884,31156,7}, ["sunken"] = {32884,31164,7},}},
{name = "Brodrosch", destinations = {["cormaya"] = {33309,31989,15}, ["farmine"] = {33024,31552,10},}},
{name = "Bruce", destinations = {["alchemist"] = {32737,31113,7}, ["cemetery"] = {32745,31113,7},}},
{name = "Buddel", destinations = {["barbarian camp"] = {32021,31294,7}, ["helheim"] = {32462,31174,7}, ["okolnir"] = {32224,31381,7}, ["svargrond"] = {32256,31197,7}, ["tyrsung"] = {32333,31227,7},}},
{name = "Captain Bluebear", destinations = {["ab'dendriel"] = {32733,31668,6}, ["carlin"] = {32387,31821,6}, ["edron"] = {33193,31784,3}, ["liberty bay"] = {32283,32893,6}, ["oramond"] = {33479,31985,7}, ["port hope"] = {32530,32784,6}, ["roshamuul"] = {33493, 32568, 7}, ["svargrond"] = {32341,31108,6}, ["venore"] = {32954,32023,6}, ["yalahar"] = {32816,31272,6},}},
{name = "Captain Breezelda", destinations = {["carlin"] = {32387,31821,6}, ["thais"] = {32312,32211,6}, ["venore"] = {32954,32023,6},}},
{name = "Captain Chelop", destinations = {["thais"] = {32312,32211,6},}},
{name = "Captain Cookie", destinations = {["liberty bay"] = {32298,32896,6},}},
{name = "Captain Fearless", destinations = {["ab'dendriel"] = {32733,31668,6}, ["ankrahmun"] = {33091,32883,6}, ["carlin"] = {32387,31821,6}, ["darashia"] = {33289,32480,6}, ["edron"] = {33175,31764,6}, ["gray island"] = {33190, 31984, 7}, ["liberty bay"] = {32283,32893,6}, ["port hope"] = {32530,32784,6}, ["svargrond"] = {32341,31108,6}, ["thais"] = {32312,32211,6}, ["yalahar"] = {32816,31272,6},}},
{name = "Captain Greyhound", destinations = {["ab'dendriel"] = {32733,31668,6}, ["edron"] = {33175,31764,6}, ["svargrond"] = {32341,31108,6}, ["thais"] = {32312,32211,6}, ["venore"] = {32954,32023,6}, ["yalahar"] = {32816,31272,6},}},
{name = "Captain Gulliver", destinations = {["thais"] = {32312,32211,6},}},
{name = "Captain Haba", destinations = {["hunt"] = {31942,31047,6}, ["svargrond"] = {32339,31117,7},}},
{name = "Captain Jack", destinations = {["tibia"] = {32205,31756,6},}},
{name = "Captain Max", destinations = {["calassa"] = {31920,32710,7}, ["liberty bay"] = {32298,32896,6}, ["yalahar"] = {32804,31270,6},}},
{name = "Captain Seagull", destinations = {["carlin"] = {32387,31821,6}, ["edron"] = {33175,31764,6}, ["gray island"] = {33190, 31984, 7}, ["thais"] = {32312,32211,6}, ["venore"] = {32954,32023,6}, ["yalahar"] = {32816,31272,6},}},
{name = "Captain Seahorse", destinations = {["ab'dendriel"] = {32733,31668,6}, ["ankrahmun"] = {33091,32883,6}, ["carlin"] = {32387,31821,6}, ["cormaya"] = {33288,31956,6}, ["gray island"] = {33190, 31984, 7}, ["liberty bay"] = {32283,32893,6}, ["port hope"] = {32530,32784,6}, ["thais"] = {32312,32211,6}, ["venore"] = {32954,32023,6},}},
{name = "Captain Sinbeard", destinations = {["darashia"] = {33289,32480,6}, ["edron"] = {33175,31764,6}, ["liberty bay"] = {32283,32893,6}, ["port hope"] = {32530,32784,6}, ["venore"] = {32954,32023,6}, ["yalahar"] = {32816,31272,6},}},
{name = "Captain Waverider", destinations = {["liberty bay"] = {32350,32856,7}, ["passage"] = {32132,32912,7}, ["peg leg"] = {32346,32625,7},}},
{name = "Carlson", destinations = {["folda"] = {32046,31580,7}, ["senja"] = {32126,31665,7}, ["tibia"] = {32234,31675,7},}},
{name = "Charles", destinations = {["ankrahmun"] = {33091,32883,6}, ["darashia"] = {33289,32480,6}, ["edron"] = {33175,31764,6}, ["liberty bay"] = {32283,32893,6}, ["thais"] = {32312,32211,6}, ["venore"] = {32954,32023,6}, ["yalahar"] = {32816,31272,6},}},
{name = "Chemar", destinations = {["edron"] = {33193,31784,3}, ["farmine"] = {32983,31539,1}, ["femor hills"] = {32535,31837,4}, ["kazordoon"] = {32588, 31942, 0}, ["svargrond"] = {32254,31097,4},}},
{name = "Dalbrect", destinations = {["passage"] = {32190,31957,6},}},
{name = "Eremo", destinations = {["cormaya"] = {33288,31956,6},}},
{name = "Gewen", destinations = {["darashia"] = {33269,32441,6}, ["edron"] = {33193,31784,3}, ["farmine"] = {32983,31539,1}, ["femor hills"] = {32535,31837,4}, ["svargrond"] = {32254,31097,4},}},
{name = "Gurbasch", destinations = {["farmine"] = {33024,31552,10}, ["kazordoon"] = {32658, 31957, 15},}},
{name = "Hal", destinations = {["alchemist"] = {32688,31187,7}, ["arena"] = {32688,31195,7},}},
{name = "Harlow", destinations = {["vengoth"] = {32857,31549,7}, ["yalahar"] = {32837,31364,7},}},
{name = "Imbul", destinations = {["banuta"] = {32826, 32631, 7}, ["center"] = {32628,32771,7}, ["chor"] = {32968,32799,7}, ["east"] = {32679,32777,7}, ["mountain hideout"] = {32987, 32730, 7},}},
{name = "Iyad", destinations = {["darashia"] = {33269,32441,6}, ["edron"] = {33193,31784,3}, ["farmine"] = {32983,31539,1}, ["femor hills"] = {32535,31837,4}, ["kazordoon"] = {32588, 31942, 0},}},
{name = "Jack Fate", destinations = {["ankrahmun"] = {33091,32883,6}, ["darashia"] = {33289,32480,6}, ["edron"] = {33175,31764,6}, ["goroma"] = {32161,32558,6}, ["liberty bay"] = {32283,32893,6}, ["port hope"] = {32530,32784,6}, ["thais"] = {32312,32211,6}, ["venore"] = {32954,32023,6}, ["yalahar"] = {32816,31272,6},}},
{name = "Karith", destinations = {["ab'dendriel"] = {32733,31668,6}, ["ankrahmun"] = {33091,32883,6}, ["carlin"] = {32387,31821,6}, ["darashia"] = {33289,32480,6}, ["liberty bay"] = {32283,32893,6}, ["port hope"] = {32530,32784,6}, ["thais"] = {32312,32211,6}, ["venore"] = {32954,32023,6},}},
{name = "Lorek", destinations = {["banuta"] = {32826, 32631, 7}, ["center"] = {32628,32771,7}, ["chor"] = {32968,32799,7}, ["mountain hideout"] = {32987, 32730, 7}, ["west"] = {32558,32780,7},}},
{name = "Maris", destinations = {["fenrock"] = {32564,31313,7}, ["mistrock"] = {32640,31439,7}, ["yalahar"] = {32649,31292,6},}},
{name = "Melian", destinations = {["darashia"] = {33269,32441,6}, ["edron"] = {33193,31784,3}, ["femor hills"] = {32535,31837,4}, ["kazordoon"] = {32588, 31942, 0}, ["svargrond"] = {32254,31097,4},}},
{name = "Nielson", destinations = {["folda"] = {32046,31580,7}, ["senja"] = {32126,31665,7}, ["vega"] = {32022,31692,7},}},
{name = "Old Adall", destinations = {["banuta"] = {32826, 32631, 7}, ["chor"] = {32968,32799,7}, ["east"] = {32679,32777,7}, ["mountain hideout"] = {32987, 32730, 7}, ["west"] = {32558,32780,7},}},
{name = "Oliver", destinations = {["factory"] = {32895,31233,7}, ["sunken"] = {32895,31225,7},}},
{name = "Pemaret", destinations = {["edron"] = {33175,31764,6}, ["eremo's island"] = {33315,31882,7},}},
{name = "Peter", destinations = {["factory"] = {32860,31302,7}, ["trade"] = {32853,31302,7},}},
{name = "Petros", destinations = {["ankrahmun"] = {33091,32883,6}, ["gray island"] = {33190, 31984, 7}, ["liberty bay"] = {32283,32893,6}, ["port hope"] = {32530,32784,6}, ["venore"] = {32954,32023,6}, ["yalahar"] = {32816,31272,6},}},
{name = "Pino", destinations = {["darashia"] = {33269,32441,6}, ["farmine"] = {32983,31539,1}, ["femor hills"] = {32535,31837,4}, ["kazordoon"] = {32588, 31942, 0}, ["svargrond"] = {32254,31097,4},}},
{name = "Rapanaio", destinations = {["isle of evil"] = {32667,31452,7}, ["kazordoon"] = {32700,31989,15},}},
{name = "Reed", destinations = {["cemetery"] = {32798,31103,7}, ["magician"] = {32806,31103,7},}},
{name = "Scrutinon", destinations = {["ab'dendriel"] = {32733,31668,6}, ["darashia"] = {33289,32480,6}, ["venore"] = {32954,32023,6},}},
{name = "Sebastian", destinations = {["liberty bay"] = {32316,32702,7}, ["meriana"] = {32346,32625,7}, ["nargor"] = {32025,32812,7},}},
{name = "Svenson", destinations = {["senja"] = {32126,31665,7}, ["tibia"] = {32234,31675,7}, ["vega"] = {32022,31692,7},}},
{name = "Tarak", destinations = {["monument tower"] = {32941,31182,7}, ["yalahar"] = {32916,31199,7},}},
{name = "Thorgrin", destinations = {["cormaya"] = {33309,31989,15}, ["kazordoon"] = {33309,31989,15},}},
{name = "Tony", destinations = {["arena"] = {32695,31253,7}, ["foreign"] = {32695,31260,7},}},
{name = "Uzon", destinations = {["darashia"] = {33269,32441,6}, ["edron"] = {33193,31784,3}, ["farmine"] = {32983,31539,1}, ["kazordoon"] = {32588, 31942, 0}, ["svargrond"] = {32254,31097,4},}},
}
-- make a list of town names and their locations
local TownLocations = {["Demon Oak Rewards"] = {32710,32398,7}}
for k, v in pairs(NPC_DATA) do
for name, loc in pairs (v.destinations) do
if (not string.find(name, "passage") and
not string.find(name, "east") and
not string.find(name, "tibia") and
not string.find(name, "passage") and
not string.find(name, "hunt") and
not string.find(name, "center") and
not string.find(name, "west")) then
TownLocations[name] = loc
HUNTING_SPOTS[name] = loc
end
end
end
-- internal funcs
function createIpcMessage(locationString, townName)
return locationString .. "|" .. Self.Position().x .. "|" .. Self.Position().y .. "|" .. townName .. "|" .. Self.Name()
end
function parseExivaMessage(message, currentTargetName)
local distance = 65535
local townName = ""
for name, loc in pairs(TownLocations) do
local checkDistance = Self.DistanceFromPosition(loc[1], loc[2], loc[3])
if (checkDistance < distance) then
townName = name
distance = checkDistance
end
end
-- get the location data from the message and merge it with the town
local locationString = string.sub(message, string.len(currentTargetName) + 4)
locationString = string.sub(locationString, 1, string.len(locationString) - 1)
return locationString, townName
end
-- string helpers
function string.starts(String,Start)
return string.sub(String,1,string.len(Start))==Start
end
function string:split(sep)
local sep, fields = sep or ":", {}
local pattern = string.format("([^%s]+)", sep)
self:gsub(pattern, function(c) fields[#fields+1] = c end)
return fields
end
Powered by vBulletin® Version 4.2.5 Copyright © 2025 vBulletin Solutions Inc. All rights reserved.