123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- import * as CANNON from './cannon-es.js'
- import Entity from './Entity.js'
- Object.defineProperty(Array.prototype, 'shuffle', {
- value: function () {
- for (let i = this.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [this[i], this[j]] = [this[j], this[i]];
- }
- return this;
- }
- });
- //TODO
- //connect with server
- //download last 20(?) frames
- //simulate next 10(?) frames
- // frames 1-30 exist
- //start drawing frame 23 (last + input delay)
- //each update add new "future" frame
- //when player pressed input, apply change to proper future frames
- //current frame 23
- //player presses go forward - add acceleration to frame 26
- //when server info comes in, look at where entity should be by frame and adjust values
- //current frame 23, simulated out to 30
- //server frame 20 comes in, entity should be moving
- //cannot edit past frames, but can make changes to future frames
- //set entity position as computed for frame 24
- var socket
- var scheme = document.location.protocol === "https:" ? "wss" : "ws"
- var host = document.location.hostname
- var port = document.location.port ? (":" + document.location.port) : ":80"
- const userId = `player` + `${Math.floor(255 * Math.random())}`.padStart(3, "0");
- const playerColor = ["red", "orange", "black", "green", "blue", "violet"].shuffle()[0]
- const gameSocketUrl = scheme + "://" + host + port + "/game"
- // const gameSocketUrl = "ws://game.bubblesocket"
- var canvas = null;
- var context = null;
- var movements = []
- var hasInitialFrame = false
- var sessionId = null;
- var entityId = null;
- // var frictionCoefficient = 0.05;
- const keys = {}
- var simulationData = {
- entityFrames: [],
- inputFrames: [],
- currentFrame: 0,
- displayFrame: 0,
- baseFrame: 0,
- serverFrame: 0,
- }
- const FRAME_INPUT_DELAY = 3;
- const simulationComputeBuffer = 30;
- // var inLockStep = false
- // var lockedFrame = 0
- var cannonWorld
- var lastTime = new Date().getTime()
- var fixedTimeStep = 1.0 / 60.0 //60 fps
- var maxSubSteps = 3
- var playerEntity
- const InputList = {
- THRUST: 1,
- TURNLEFT: 2,
- TURNRIGHT: 3,
- }
- const SimulationActionType = {
- UNKNOWN: "00",
- //sent from client
- Connect: "01", //authenticate and get initial state
- Disconnect: "02", //disconnect socket
- JoinChannel: "03", //request to join channel
- LeaveChannel: "04", //leave channel
- SendInputs: "05", //send inputs to simulation
- //sent to client
- OtherJoinChannel: "06", //received when another joins current channel
- OtherLeaveChannel: "07", //received when another leaves current channel
- OtherSendInputs: "08", //received when another pushes inputs to those in channel
- Resync: "09",//request full channel simulation state
- }
- document.addEventListener("DOMContentLoaded", () => {
- canvas = document.createElement("canvas")
- canvas.style.width = "300px"
- canvas.style.height = "150px"
- canvas.width = 300
- canvas.height = 150
- context = canvas.getContext('2d')
- context.width = 300
- context.height = 150
- const connectButton = document.getElementById("connect")
- document.getElementById("container").appendChild(canvas)
- connectButton.addEventListener("click", () => {
- if (socket) {
- socketSend(SimulationActionType.Disconnect, {})
- socket.close(1000, "Closing from client");
- connectButton.innerHTML = "Connect"
- return
- }
- socket = new WebSocket(gameSocketUrl);
- socket.onopen = (event) => {
- let token = getCookie('token')
- socketSend(SimulationActionType.Connect, [token])
- connectButton.innerHTML = "Disconnect"
- }
- socket.onclose = (event) => {
- if (!event.wasClean) {
- console.log(`socket did not close cleanly:`, event)
- connectButton.innerHTML = "Connect"
- }
- socket = null
- }
- socket.onerror = (event) => {
- console.error(`socket error:`, event)
- socket = null
- }
- socket.onmessage = (event) => {
- socketReceive(event)
- }
- connectButton.innerHTML = "Connecting..."
- })
- document.getElementById("login").addEventListener("click", () => {
- let data = {
- "Username": document.getElementById("username").value,
- "Password": document.getElementById("password").value,
- }
- fetch(`//${host}${port}/jwt/login`, {
- method: 'POST',
- body: JSON.stringify(data),
- headers: {
- 'Content-Type': 'application/json'
- }
- }).then(res => res.json())
- .then(res => {
- setCookie('token', res['token'], res['expiration'])
- document.getElementById("login-section").style.display = "none"
- document.getElementById("canvas-section").style.display = "block"
- });
- })
- function setCookie(key, value, expiration) {
- document.cookie = key + "=" + value + ";" + "expires=" + new Date(expiration).toUTCString() + ";path=/";
- }
- function getCookie(cname) {
- let name = cname + "=";
- let decodedCookie = decodeURIComponent(document.cookie);
- let ca = decodedCookie.split(';');
- for (let i = 0; i < ca.length; i++) {
- let c = ca[i];
- while (c.charAt(0) == ' ') {
- c = c.substring(1);
- }
- if (c.indexOf(name) == 0) {
- return c.substring(name.length, c.length);
- }
- }
- return "";
- }
- window.addEventListener("keydown", (e) => {
- keys[e.code] = true
- })
- window.addEventListener("keyup", (e) => {
- keys[e.code] = false
- })
- window.requestAnimationFrame(animate)
- setInterval(() => {
- let time = new Date().getTime()
- update((time - lastTime) / 1000)
- lastTime = time
- }, parseInt(fixedTimeStep * 1000))
- setInterval(broadcast, 32) //30fps
- })
- function animate() {
- draw(context)
- window.requestAnimationFrame(animate)
- }
- function update(dt) {
- if (!socket || !hasInitialFrame) {
- return
- }
- }
- function broadcast() {
- if (!socket || !hasInitialFrame) {
- return;
- }
- }
- function socketSend(simulationActionType, payload) {
- if (!socket || socket.readyState !== WebSocket.OPEN) {
- return
- }
- let blobData = [simulationActionType, JSON.stringify(payload)]
- let blob = new Blob(blobData, { type: 'application/json' });
- socket.send(blob)
- }
- async function socketReceive(packet) {
- let response = await packet.data.text()
- let simulationActionType = response.substr(0, 2)
- let messageData = response.substr(2, response.length - 2)
- let payload = JSON.parse(messageData);
- switch (simulationActionType) {
- case SimulationActionType.Connect:
- let characterName = payload.characters[0].Name
- let channel = payload.characters[0].Channel
- console.log("connect", characterName, channel)
- socketSend(SimulationActionType.JoinChannel, [characterName, channel])
- break;
- case SimulationActionType.Disconnect:
- console.log("disconnect", payload)
- break;
- case SimulationActionType.JoinChannel:
- console.log("channel join response", payload)
- break;
- case SimulationActionType.Resync:
- console.log("resync", payload)
- break
- default:
- console.warn("unknown message type", simulationActionType, payload)
- break
- }
- }
- // var acceleration = {x: 0, y: 0}
- // var velocity = {x: 0, y: 0}
- // var position = {x: 20, y: 20}
- function drawPlayer(ctx, playerData) {
- ctx.beginPath()
- ctx.arc(playerData.position.x, playerData.position.y, 4, 0, 2 * Math.PI)
- ctx.fill()
- }
- function draw(ctx) {
- context.clearRect(0, 0, canvas.width, canvas.height)
- if (!socket) {
- ctx.fillStyle = "black"
- ctx.fillText("Disconnected", 50, 50)
- } else if (socket && !hasInitialFrame) {
- ctx.fillStyle = "black"
- ctx.fillText("Syncing with server", 50, 50)
- } else if (socket && hasInitialFrame) {
- // playerEntity.draw(ctx)
- // let player = simulationData.entityFrames[simulationData.displayFrame][entityId]
- let currentEntities = Object.values(simulationData.entityFrames[simulationData.serverFrame])
- currentEntities.forEach(entity => {
- drawPlayer(ctx, entity);
- })
- }
- }
|