Skip to content

Instantly share code, notes, and snippets.

@zeeZ
Created June 22, 2013 18:06
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save zeeZ/1fbad280c600d56f41be to your computer and use it in GitHub Desktop.
Save zeeZ/1fbad280c600d56f41be to your computer and use it in GitHub Desktop.
import threading
import time
import mmap
import urllib2
import math
import ctypes
try: import simplejson as json
except ImportError: import json
# http://www.tornadoweb.org/en/stable/
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
_MULTIPLIER = 39.3701
_MAP_INFO_URL = "https://api.guildwars2.com/v1/maps.json?map_id=%d"
_NOTIFIER = None
class Link(ctypes.Structure):
_fields_ = [
("uiVersion", ctypes.c_uint32),
("uiTick", ctypes.c_ulong),
("fAvatarPosition", ctypes.c_float * 3),
("fAvatarFront", ctypes.c_float * 3),
("fAvatarTop", ctypes.c_float * 3),
("name", ctypes.c_wchar * 256),
("fCameraPosition", ctypes.c_float * 3),
("fCameraFront", ctypes.c_float * 3),
("fCameraTop", ctypes.c_float * 3),
("identity", ctypes.c_wchar * 256),
("context_len", ctypes.c_uint32),
("context", ctypes.c_uint32 * (256/4)), # is actually 256 bytes of whatever
("description", ctypes.c_wchar * 2048)
]
def Unpack(ctype, buf):
cstring = ctypes.create_string_buffer(buf)
ctype_instance = ctypes.cast(ctypes.pointer(cstring), ctypes.POINTER(ctype)).contents
return ctype_instance
def continent_coords(continent_rect, map_rect, point):
return (
( point[0]-map_rect[0][0])/(map_rect[1][0]-map_rect[0][0])*(continent_rect[1][0]-continent_rect[0][0])+continent_rect[0][0],
(-point[1]-map_rect[0][1])/(map_rect[1][1]-map_rect[0][1])*(continent_rect[1][1]-continent_rect[0][1])+continent_rect[0][1]
)
class Notifier(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.clients = set()
self.running = True
def register(self, client):
self.clients.add(client)
def unregister(self, client):
self.clients.remove(client)
def run(self, ):
current_map = 0
current_map_data = None
memfile = mmap.mmap(0, ctypes.sizeof(Link), "MumbleLink")
while self.running:
memfile.seek(0)
data = memfile.read(ctypes.sizeof(Link))
result = Unpack(Link, data)
if result.context[7] != current_map:
# Map change
current_map = result.context[7]
fp = urllib2.urlopen(_MAP_INFO_URL % current_map)
current_map_data = json.load(fp)["maps"][str(current_map)]
fp.close()
data = {
"name": result.identity,
"map": result.context[7],
"face": -(math.atan2(result.fAvatarFront[2],result.fAvatarFront[0])*180/math.pi)%360
}
if current_map_data:
data.update({"position": continent_coords(current_map_data["continent_rect"], current_map_data["map_rect"], (result.fAvatarPosition[0]*_MULTIPLIER, result.fAvatarPosition[2]*_MULTIPLIER))})
output = json.dumps(data)
for client in self.clients:
try:
client.write_message(output)
except Exception, e:
# Look, effort!
print e
time.sleep(0.1)
class WSHandler(tornado.websocket.WebSocketHandler):
def open(self):
# New connection
_NOTIFIER.register(self)
def on_close(self):
# Connection closed
_NOTIFIER.unregister(self)
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
_NOTIFIER = Notifier()
_NOTIFIER.start()
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
_NOTIFIER.running = False # As if
<!DOCTYPE html>
<html>
<head>
<title>Web Socket Map Thing</title>
<meta charset="utf-8" />
<script src="http://code.jquery.com/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.5.1/leaflet.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.5.1/leaflet.css" />
<style type="text/css">
body {
text-align: center;
min-width: 500px;
}
.leaflet-container {
background: #000;
}
#map {
position: absolute;
top: 50px;
right: 0;
bottom: 0;
left: 0;
}
#message {
position: absolute;
top: 0;
right: 0;
left: 0;
height: 50px;
display: block;
}
</style>
<script>
var map;
var supermarker;
function unproject(coord) {
return map.unproject(coord, map.getMaxZoom());
}
$(function () {
"use strict";
var southWest, northEast;
map = L.map("map", {
minZoom: 0,
maxZoom: 7,
crs: L.CRS.Simple
}).setView([0, 0], 0);
southWest = unproject([0, 32768]);
northEast = unproject([32768, 0]);
map.setMaxBounds(new L.LatLngBounds(southWest, northEast));
L.tileLayer("https://tiles.guildwars2.com/1/1/{z}/{x}/{y}.jpg", {
minZoom: 0,
maxZoom: 7,
continuousWorld: true
}).addTo(map);
var posIcon = L.divIcon({
iconSize: [64, 64],
iconAnchor: [32, 32],
className: 'fancyPlayerPos',
html: '<img src="icons/player_position.png">'
});
supermarker = L.marker(unproject([0, 0]), {
icon: posIcon
}).addTo(map);
$.getJSON("https://api.guildwars2.com/v1/map_floor.json?continent_id=1&floor=1", function (data) {
var region, gameMap, i, il, poi;
for (region in data.regions) {
region = data.regions[region];
for (gameMap in region.maps) {
gameMap = region.maps[gameMap];
for (i = 0, il = gameMap.points_of_interest.length; i < il; i++) {
poi = gameMap.points_of_interest[i];
if (poi.type != "waypoint") {
continue;
}
var waypoint = L.icon({
iconUrl: 'icons/waypoint.png',
iconSize: [20, 20],
iconAnchor: [10, 10],
});
L.marker(unproject(poi.coord), {
icon: waypoint,
title: poi.name
}).addTo(map);
}
}
}
});
});
$(document).ready(function () {
var ws;
var host = "localhost";
var port = "8888";
var uri = "/ws";
ws = new WebSocket("ws://" + host + ":" + port + uri);
ws.onmessage = function (evt) {
$('#message').text(evt.data);
var json = $.parseJSON(evt.data);
if (json.position) {
supermarker.setLatLng(unproject(json.position));
supermarker._icon.title = json.name;
supermarker.update();
$('.fancyPlayerPos img').css({
transform: 'scale(' + 1 / (map.getMaxZoom() - map.getZoom() + 1) + ',' + 1 / (map.getMaxZoom() - map.getZoom() + 1) + ') rotate(' + json.face + 'deg)'
});
}
};
ws.onclose = function (evt) {
console.log("Connection close");
};
ws.onopen = function (evt) {
console.log("Connection open");
};
});
</script>
</head>
<body>
<div id="message"></div>
<div id="map"></div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment