main.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. let canvas;
  2. let context;
  3. let username = localStorage.getItem("username");
  4. let userData = {
  5. name: "",
  6. coordinates: { x: 0, y: 0 },
  7. inventory: [],
  8. position: { x: 0.5, y: 0.5 },
  9. size: {
  10. width: 0.03,
  11. height: 0.07
  12. },
  13. speed: 0.005,
  14. sprite: 'medievalUnit_01.png'
  15. };
  16. let lockUserMovement = false;
  17. let loadingDelayTimeout;
  18. let showLoadingMessage;
  19. let otherPlayerCache = {};
  20. let otherPlayers = {};
  21. let roomData = { name: "", walls: [], tiles: [], decals: [] };
  22. let keys = [];
  23. let sendMovement = false;
  24. let userChanged = false;
  25. let atlas = new SpriteAtlas('assets', 'medievalRTS_spritesheet@2.xml');
  26. const apidataurl = `http://jsonapi.eyeofmidas.net/data`;
  27. //const websocketurl = `ws://${location.host}/ws`;
  28. const websocketurl = `ws://jsonapi.eyeofmidas.net:8080`;
  29. let webSocket;
  30. function ajaxGet(url) {
  31. return new Promise((resolve, reject) => {
  32. var r = new XMLHttpRequest();
  33. r.open("GET", url, true);
  34. r.onreadystatechange = function (response) {
  35. if (r.readyState == 4 && r.status == 200) {
  36. resolve(JSON.parse(r.responseText));
  37. }
  38. if (r.readyState == 4 && r.status != 200) {
  39. reject(JSON.parse(r.response));
  40. }
  41. return;
  42. }
  43. r.onerror = function (response) {
  44. console.error("onerror", response, r);
  45. reject(r.responseText);
  46. }
  47. r.setRequestHeader("Content-Type", "application/json");
  48. r.send();
  49. });
  50. }
  51. function ajaxPost(url, data) {
  52. return new Promise((resolve, reject) => {
  53. var r = new XMLHttpRequest();
  54. r.open("POST", url, true);
  55. r.onreadystatechange = function (response) {
  56. if (r.readyState == 4 && r.status == 200) {
  57. resolve(JSON.parse(r.responseText));
  58. }
  59. if (r.readyState == 4 && r.status != 200) {
  60. reject(r.responseText);
  61. }
  62. return;
  63. }
  64. r.onerror = function (response) {
  65. reject(r.responseText);
  66. }
  67. r.setRequestHeader("Content-Type", "application/json");
  68. let jsonData = JSON.stringify(data);
  69. r.send(jsonData);
  70. });
  71. }
  72. function websocketSafeSend(message) {
  73. //console.log("%c" + message, "color: #22FF22");
  74. try {
  75. webSocket.send(message);
  76. } catch (error) {
  77. console.error("websocket", error);
  78. }
  79. }
  80. function handleMessage(message) {
  81. //console.log("%c" + message, "color: #FF2222");
  82. splitMessage = message.split(" ");
  83. switch (splitMessage[0]) {
  84. case "join":
  85. addOtherPlayer(splitMessage[1], splitMessage[2], splitMessage[3]);
  86. break;
  87. case "leave":
  88. removeOtherPlayer(splitMessage[1]);
  89. break;
  90. case "move":
  91. moveOtherPlayer(splitMessage[1], splitMessage[2], splitMessage[3]);
  92. break;
  93. case "population":
  94. splitMessage.shift();
  95. setRoomPopulation(splitMessage);
  96. break;
  97. default:
  98. console.log(message);
  99. break;
  100. }
  101. }
  102. async function getPlayerData(playerKey) {
  103. if (otherPlayerCache[playerKey]) {
  104. return otherPlayerCache[playerKey];
  105. }
  106. let result = await ajaxGet(`${apidataurl}/${playerKey}`);
  107. otherPlayerCache[playerKey] = result;
  108. return otherPlayerCache[playerKey];
  109. }
  110. function addOtherPlayer(playerKey, x, y) {
  111. let cleanName = userData.name.replace(/\s/g, '');
  112. if (playerKey == `user-${cleanName}` || playerKey == "") {
  113. return;
  114. }
  115. otherPlayers[playerKey] = { name: "", position: { x: x, y: y }, size: { width: 0.0125, height: 0.0167 }, sprite: 'medievalUnit_01.png' };
  116. getPlayerData(playerKey).then((response) => {
  117. otherPlayers[playerKey].name = response.name;
  118. otherPlayers[playerKey].size.width = response.size.width;
  119. otherPlayers[playerKey].size.height = response.size.height;
  120. otherPlayers[playerKey].sprite = response.sprite;
  121. //TODO: populate other data
  122. });
  123. }
  124. function removeOtherPlayer(playerKey) {
  125. let cleanName = userData.name.replace(/\s/g, '');
  126. if (playerKey == `user-${cleanName}`) {
  127. return;
  128. }
  129. delete otherPlayers[playerKey];
  130. }
  131. function moveOtherPlayer(playerKey, x, y) {
  132. let cleanName = userData.name.replace(/\s/g, '');
  133. if (playerKey == `user-${cleanName}`) {
  134. return;
  135. }
  136. if (!otherPlayers[playerKey]) {
  137. addOtherPlayer(playerKey, x, y);
  138. }
  139. otherPlayers[playerKey].position.x = parseFloat(x);
  140. otherPlayers[playerKey].position.y = parseFloat(y);
  141. }
  142. function setRoomPopulation(otherUserData) {
  143. for (let i = 0; i < otherUserData.length; i += 3) {
  144. addOtherPlayer(otherUserData[i], otherUserData[i + 1], otherUserData[i + 2]);
  145. }
  146. }
  147. document.addEventListener("DOMContentLoaded", () => {
  148. for (let i = 0; i < 256; i++) {
  149. keys[i] = false;
  150. }
  151. canvas = document.getElementsByTagName("canvas")[0];
  152. context = canvas.getContext("2d");
  153. context.canvas.width = canvas.clientWidth;
  154. context.canvas.height = canvas.clientHeight;
  155. atlas.load().then(() => {
  156. animateLoop();
  157. getRoom(0, 0);
  158. webSocket = new WebSocket(websocketurl);
  159. webSocket.onmessage = function (event) {
  160. handleMessage(event.data);
  161. }
  162. webSocket.onopen = function (event) {
  163. }
  164. if (!username) {
  165. username = prompt("Please enter your username");
  166. }
  167. if (!username) {
  168. return;
  169. }
  170. let cleanName = username.replace(/\s/g, '');
  171. ajaxGet(`${apidataurl}/user-${cleanName}`).then(result => {
  172. userData = result;
  173. localStorage.setItem("username", cleanName);
  174. getRoom(userData.coordinates.x, userData.coordinates.y);
  175. }, (error) => {
  176. if (error.errorCode == 404) {
  177. userData.name = username;
  178. let create = confirm("Would you like to create the new user '" + userData.name + "'?");
  179. if (!create) {
  180. localStorage.removeItem("username");
  181. return;
  182. }
  183. let validStartingUnits = [
  184. 'medievalUnit_01.png',
  185. // 'medievalUnit_02.png',
  186. // 'medievalUnit_03.png',
  187. // 'medievalUnit_04.png',
  188. // 'medievalUnit_05.png',
  189. 'medievalUnit_06.png',
  190. 'medievalUnit_07.png',
  191. // 'medievalUnit_08.png',
  192. // 'medievalUnit_09.png',
  193. // 'medievalUnit_10.png',
  194. // 'medievalUnit_11.png',
  195. 'medievalUnit_12.png',
  196. 'medievalUnit_13.png',
  197. // 'medievalUnit_14.png',
  198. // 'medievalUnit_15.png',
  199. // 'medievalUnit_16.png',
  200. // 'medievalUnit_17.png',
  201. 'medievalUnit_18.png',
  202. 'medievalUnit_19.png',
  203. // 'medievalUnit_20.png',
  204. // 'medievalUnit_21.png',
  205. // 'medievalUnit_22.png',
  206. // 'medievalUnit_23.png',
  207. 'medievalUnit_24.png',
  208. ];
  209. userData.sprite = validStartingUnits[Math.floor(validStartingUnits.length * Math.random())];
  210. saveUser().then(() => {
  211. localStorage.setItem("username", cleanName);
  212. getRoom(userData.coordinates.x, userData.coordinates.y);
  213. });
  214. } else {
  215. console.error(error);
  216. }
  217. });
  218. });
  219. });
  220. let delta = { x: 0, y: 0 };
  221. function draw(context) {
  222. delta.x = 0;
  223. delta.y = 0;
  224. if (keys[38] || keys[87]) {
  225. delta.y = -userData.speed;
  226. sendMovement = true;
  227. }
  228. if (keys[37] || keys[65]) {
  229. delta.x = -userData.speed;
  230. sendMovement = true;
  231. }
  232. if (keys[40] || keys[83]) {
  233. delta.y = userData.speed;
  234. sendMovement = true;
  235. }
  236. if (keys[39] || keys[68]) {
  237. delta.x = +userData.speed;
  238. sendMovement = true;
  239. }
  240. let userCurrentBounds = {
  241. x: userData.position.x,
  242. y: userData.position.y,
  243. width: userData.size.width,
  244. height: userData.size.height,
  245. };
  246. let userFutureBounds = {
  247. x: userData.position.x + delta.x,
  248. y: userData.position.y + delta.y,
  249. width: userData.size.width,
  250. height: userData.size.height,
  251. };
  252. for (let i = 0; i < roomData.walls.length; i++) {
  253. let wall = roomData.walls[i];
  254. if (boxCollision(wall, userFutureBounds) && !boxCollision(wall, userCurrentBounds)) {
  255. let userFutureBoundsY = {
  256. x: userData.position.x,
  257. y: userData.position.y + delta.y,
  258. width: userData.size.width,
  259. height: userData.size.height,
  260. };
  261. let userFutureBoundsX = {
  262. x: userData.position.x + delta.x,
  263. y: userData.position.y,
  264. width: userData.size.width,
  265. height: userData.size.height,
  266. };
  267. if (boxCollision(wall, userFutureBoundsX)) {
  268. delta.x = 0;
  269. }
  270. if (boxCollision(wall, userFutureBoundsY)) {
  271. delta.y = 0;
  272. }
  273. if (delta.x == 0 && delta.y == 0) {
  274. sendMovement = false;
  275. }
  276. }
  277. }
  278. if (!lockUserMovement) {
  279. userData.position.x += delta.x;
  280. userData.position.y += delta.y;
  281. if (userData.position.x * canvas.width > canvas.width) {
  282. lockUserMovement = true;
  283. loadingDelayTimeout = setTimeout(() => { showLoadingMessage = true; }, 50);
  284. getRoom(userData.coordinates.x + 1, userData.coordinates.y).then(() => {
  285. userData.position.x = 0;
  286. userData.coordinates.x++;
  287. saveUser();
  288. lockUserMovement = false;
  289. });
  290. }
  291. if (userData.position.x * canvas.width < -userData.size.width) {
  292. lockUserMovement = true;
  293. loadingDelayTimeout = setTimeout(() => { showLoadingMessage = true; }, 50);
  294. getRoom(userData.coordinates.x - 1, userData.coordinates.y).then(() => {
  295. userData.position.x = 1 - userData.size.width;
  296. userData.coordinates.x--;
  297. saveUser();
  298. lockUserMovement = false;
  299. });
  300. }
  301. if (userData.position.y * canvas.height > canvas.height) {
  302. lockUserMovement = true;
  303. loadingDelayTimeout = setTimeout(() => { showLoadingMessage = true; }, 50);
  304. getRoom(userData.coordinates.x, userData.coordinates.y + 1).then(() => {
  305. userData.position.y = 0;
  306. userData.coordinates.y++;
  307. saveUser();
  308. lockUserMovement = false;
  309. });
  310. }
  311. if (userData.position.y * canvas.height < -userData.size.height) {
  312. lockUserMovement = true;
  313. loadingDelayTimeout = setTimeout(() => { showLoadingMessage = true; }, 50);
  314. getRoom(userData.coordinates.x, userData.coordinates.y - 1).then(() => {
  315. userData.position.y = 1 - userData.size.height;
  316. userData.coordinates.y--;
  317. saveUser();
  318. lockUserMovement = false;
  319. });
  320. }
  321. }
  322. context.save();
  323. context.translate(-0.5, -0.5);
  324. drawRoom(context);
  325. drawOtherPlayers(context);
  326. drawPlayer(context);
  327. if (showLoadingMessage) {
  328. context.font = "30px Arial";
  329. context.textAlign = "center";
  330. context.fillStyle = "#FFFFFF";
  331. context.fillText("Loading... please wait", 0.5 * canvas.width, 0.5 * canvas.height);
  332. } else {
  333. clearTimeout(loadingDelayTimeout);
  334. showLoadingMessage = false;
  335. }
  336. context.restore();
  337. if (drawEditor != undefined) {
  338. drawEditor(context);
  339. }
  340. }
  341. function boxCollision(box1, box2) {
  342. return box1.x < box2.x + box2.width &&
  343. box1.x + box1.width > box2.x &&
  344. box1.y < box2.y + box2.height &&
  345. box1.y + box1.height > box2.y;
  346. }
  347. function pointCollision(box, point) {
  348. return box.x < point.x && box.x + box.width > point.x &&
  349. box.y < point.y && box.y + box.height > point.y;
  350. }
  351. async function saveUser() {
  352. if (!userData.name) {
  353. return;
  354. }
  355. let cleanName = userData.name.replace(/\s/g, '');
  356. let result = await ajaxPost(`${apidataurl}/user-${cleanName}`, userData);
  357. userData = result;
  358. }
  359. async function getRoom(x, y) {
  360. try {
  361. let result = await ajaxGet(`${apidataurl}/room-${x},${y}`);
  362. roomData.name = result.name ?? "";
  363. roomData.walls = result.walls ?? [];
  364. roomData.tiles = result.tiles ?? [];
  365. roomData.decals = result.decals ?? [];
  366. } catch (error) {
  367. let emptyRoom = { name: `${x},${y}`, walls: [], tiles: [], decals: [] };
  368. addRandomTiles(emptyRoom);
  369. let result = await ajaxPost(`${apidataurl}/room-${x},${y}`, emptyRoom);
  370. roomData = result;
  371. }
  372. let cleanName = userData.name.replace(/\s/g, '');
  373. otherPlayers = {};
  374. if (userData.name) {
  375. websocketSafeSend(`subscribe user-${cleanName} room-${x},${y} ${userData.position.x} ${userData.position.y}`);
  376. }
  377. }
  378. const colors = [
  379. "red",
  380. "green",
  381. "blue",
  382. "black",
  383. "purple",
  384. "orange"
  385. ];
  386. function addRandomItems(room) {
  387. let items = [];
  388. for (let i = Math.floor(10 * Math.random()); i > 0; i--) {
  389. let newitem = { coordinates: { x: Math.random(), y: Math.random() }, color: colors[Math.floor(colors.length * Math.random())] };
  390. items.push(newitem);
  391. }
  392. room.items = items;
  393. }
  394. function addRandomTiles(room) {
  395. let tileSize = 8 * 5;
  396. room.tiles = [];
  397. for (let i = 0; i < tileSize; i++) {
  398. room.tiles.push(57 + (Math.floor(2 * Math.random())));
  399. }
  400. }
  401. function drawPlayer(context) {
  402. context.font = "14px Arial";
  403. context.textAlign = "center";
  404. let pos = inScreenSpace(userData.position);
  405. let size = inScreenSize(userData.size);
  406. let centerPos = { x: pos.x + (size.width / 2), y: pos.y + (size.height / 2) };
  407. atlas.drawCentered(context, userData.sprite, centerPos);
  408. context.fillStyle = "#000000";
  409. context.fillText(userData.name, centerPos.x - 1, (pos.y - 0.02 * canvas.height) - 1);
  410. context.fillText(userData.name, centerPos.x + 1, (pos.y - 0.02 * canvas.height) - 1);
  411. context.fillText(userData.name, centerPos.x - 1, (pos.y - 0.02 * canvas.height) + 1);
  412. context.fillText(userData.name, centerPos.x + 1, (pos.y - 0.02 * canvas.height) + 1);
  413. context.fillStyle = "#FFFFFF";
  414. context.fillText(userData.name, centerPos.x, pos.y - 0.02 * canvas.height);
  415. }
  416. function inScreenSpace(serverPosition) {
  417. return { x: serverPosition.x * canvas.width, y: serverPosition.y * canvas.height };
  418. }
  419. function inScreenSize(serverSize) {
  420. return { width: serverSize.width * canvas.width, height: serverSize.height * canvas.height };
  421. }
  422. function drawOtherPlayers(context) {
  423. context.font = "14px Arial";
  424. context.textAlign = "center";
  425. for (let index in otherPlayers) {
  426. let player = otherPlayers[index];
  427. let pos = inScreenSpace(player.position);
  428. let size = inScreenSize(player.size);
  429. let centerPos = { x: pos.x + (size.width / 2), y: pos.y + (size.height / 2) };
  430. atlas.drawCentered(context, player.sprite, centerPos);
  431. context.fillStyle = "#000000";
  432. context.fillText(player.name, centerPos.x - 1, (pos.y - 0.02 * canvas.height) - 1);
  433. context.fillText(player.name, centerPos.x + 1, (pos.y - 0.02 * canvas.height) - 1);
  434. context.fillText(player.name, centerPos.x - 1, (pos.y - 0.02 * canvas.height) + 1);
  435. context.fillText(player.name, centerPos.x + 1, (pos.y - 0.02 * canvas.height) + 1);
  436. context.fillStyle = "#FFFFFF";
  437. context.fillText(player.name, centerPos.x, pos.y - 0.02 * canvas.height);
  438. }
  439. }
  440. function drawRoom(context) {
  441. let width = 126;
  442. let height = 126;
  443. for (var i = 0; i < roomData.tiles.length; i++) {
  444. let tile = roomData.tiles[i];
  445. try {
  446. atlas.getSprite(tile);
  447. atlas.draw(context, tile, { x: ((i % 8) * width), y: (Math.floor(i / 8) * height) });
  448. } catch (e) {}
  449. if (tile < 10) {
  450. tile = "0" + tile;
  451. }
  452. let tileName = `medievalTile_${tile}.png`;
  453. try {
  454. atlas.getSprite(tileName);
  455. atlas.draw(context, tileName, { x: ((i % 8) * width), y: (Math.floor(i / 8) * height) });
  456. } catch (e) {
  457. // console.error("Room data error: tile index ", i, " is not defined");
  458. continue;
  459. }
  460. }
  461. // for (var i = 0; i < roomData.items.length; i++) {
  462. // let item = roomData.items[i];
  463. // context.beginPath();
  464. // context.strokeStyle = item.color;
  465. // context.rect(item.coordinates.x * canvas.width, item.coordinates.y * canvas.height, 10, 10);
  466. // context.stroke();
  467. // }
  468. // for (var i = 0; i < roomData.walls.length; i++) {
  469. // let wall = roomData.walls[i];
  470. // context.beginPath();
  471. // context.strokeStyle = wall.color ?? "red";
  472. // context.rect(wall.x * canvas.width, wall.y * canvas.height, wall.width * canvas.width, wall.height * canvas.height);
  473. // context.stroke();
  474. // }
  475. for (var i = 0; i < roomData.decals.length; i++) {
  476. let decal = roomData.decals[i];
  477. atlas.draw(context, decal.spriteName, { x: decal.x * canvas.width, y: decal.y * canvas.height });
  478. }
  479. context.font = "30px Arial";
  480. context.fillText(roomData.name, 20, 40);
  481. }
  482. function animateLoop() {
  483. canvas.width = canvas.width;
  484. draw(context);
  485. window.requestAnimationFrame(animateLoop);
  486. }
  487. document.addEventListener("keydown", event => {
  488. keys[event.keyCode] = true;
  489. });
  490. document.addEventListener("keyup", event => {
  491. keys[event.keyCode] = false;
  492. });
  493. setInterval(() => {
  494. if (!sendMovement) {
  495. return;
  496. }
  497. userChanged = true;
  498. sendMovement = false;
  499. let cleanName = userData.name.replace(/\s/g, '');
  500. websocketSafeSend(`move user-${cleanName} ${userData.position.x} ${userData.position.y}`);
  501. }, Math.floor(1000 / 30));
  502. setInterval(() => {
  503. if (!userChanged) {
  504. return;
  505. }
  506. saveUser();
  507. userChanged = false;
  508. }, Math.floor(60000));