diff --git a/app.js b/app.js new file mode 100644 index 0000000..e4eb348 --- /dev/null +++ b/app.js @@ -0,0 +1,145 @@ +const express = require('express'); // Import Express framework +const http = require('http'); // Import HTTP module +const socketIo = require('socket.io'); // Import Socket.IO for WebSocket functionality +const fs = require('fs'); // Import file system module +const path = require('path'); // Import path module for file path handling + +// Initialize the Express application +const app = express(); + +// Set the view engine to EJS for rendering dynamic content +app.set('view engine', 'ejs'); + + +// Serve static files from the 'public' directory +app.use(express.static(path.join(__dirname, 'public'))); + +// Create HTTP server +const httpServer = http.createServer(app); + +// Set keep-alive timeout for HTTP server +httpServer.keepAliveTimeout = 120000; // 120 seconds + +// Initialize Socket.IO with options for WebSocket +const httpIo = socketIo(httpServer, { + transports: ['websocket'], // Only use WebSocket transport + allowUpgrades: false, // Disable upgrades + pingInterval: 5000, // Ping every 5 seconds + pingTimeout: 10000, // Timeout for ping response after 10 seconds + cors: { + origin: '*', // Allow all origins for CORS + } +}); + +// Player management +const players = {}; // Object to store connected players +let connectionCount = 0; // Count of active connections + +// Handle new socket connections +httpIo.on('connection', handleConnection); + +// Serve the main EJS page for Game Dev Central Hub +app.get('/', (req, res) => { + // Load metadata from a JSON file (if needed) or set dynamically for game dev context + const metaTags = { + title: 'Game Dev Central Hub', + description: 'Create your own pixel-based side scroller games and share them with others!', + keywords: 'game dev, pixel art, side scroller, multiplayer, socket.io' + }; + + // Render the main EJS page + res.render('index', { metaTags }); +}); + +// Start HTTP server listening on port 80 +httpServer.listen(80, () => console.log('HTTP Server listening on port 80')); + +/** + * Handle new socket connections + * @param {Socket} socket - The connected socket instance + */ +function handleConnection(socket) { + console.log(`Player ${socket.id} connected`); // Log new connection + + // Initialize new player and add to the players object + const currentPlayer = initializePlayer(socket); + players[socket.id] = currentPlayer; + + // Emit player data to the newly connected player + emitPlayerData(socket); + + // Set up event listeners for player actions + socket.on('playerMovement', handlePlayerMovement); + socket.on('playerChat', handlePlayerChat); + socket.on('disconnect', handlePlayerDisconnect); +} + +/** + * Initialize a new player object + * @param {Socket} socket - The socket instance for the connected player + * @returns {Object} - The initialized player object + */ +function initializePlayer(socket) { + return { + playerId: socket.id, // Unique ID for the player + connectionNum: ++connectionCount, // Increment connection count + gameData: { pos: [0, 0] }, // Initial player position + chatMessage: '', // Store last chat message + }; +} + +/** + * Emit current player data to the client + * @param {Socket} socket - The socket instance for the connected player + */ +function emitPlayerData(socket) { + socket.emit('currentPlayer', players[socket.id]); // Send current player data + + // Send existing players data + const existingPlayers = Object.values(players); + socket.emit('existingPlayers', { existingPlayers }); + + // Notify other clients of the new player + socket.broadcast.emit('newPlayer', players[socket.id]); +} + +/** + * Handle player movement events + * @param {Object} data - The movement data from the client + */ +function handlePlayerMovement(data) { + // Update player's position based on the received data + players[this.id].gameData.pos = data.pos; + + // Broadcast the new position to other players + this.broadcast.emit('playerMoved', { player: players[this.id] }); +} + +/** + * Handle player chat events + * @param {string} message - The chat message sent by the player + */ +function handlePlayerChat(message) { + // Update player's last chat message + players[this.id].chatMessage = message; + + // Clear chat message after 30 seconds + setTimeout(() => { + players[this.id].chatMessage = ''; + this.emit('chatUpdate', { playerId: this.id, chatMessage: '' }); + }, 30000); + + // Broadcast the chat message to other players + this.broadcast.emit('chatUpdate', { playerId: this.id, chatMessage: message }); +} + +/** + * Handle player disconnection events + */ +function handlePlayerDisconnect() { + console.log(`Player ${this.id} disconnected`); + delete players[this.id]; // Remove player from the object + + // Notify other clients of the player's disconnection + this.broadcast.emit('playerDisconnected', { playerId: this.id }); +} \ No newline at end of file diff --git a/js/bcoin.js b/js/bcoin.js deleted file mode 100644 index 34fe1cb..0000000 --- a/js/bcoin.js +++ /dev/null @@ -1,45 +0,0 @@ -(function() { - if (typeof Mario === 'undefined') - window.Mario = {}; - - var Bcoin = Mario.Bcoin = function(pos) { - Mario.Entity.call(this, { - pos: pos, - sprite: level.bcoinSprite(), - hitbox: [0,0,16,16] - }); - } - - Mario.Util.inherits(Bcoin, Mario.Entity); - - //I'm not sure whether it makes sense to use an array for vel and acc here - //in order to keep with convention, or to just use a single value, since - //it's literally impossible for these to move left or right. - Bcoin.prototype.spawn = function() { - sounds.coin.currentTime = 0.05; - sounds.coin.play(); - this.idx = level.items.length; - level.items.push(this); - this.active = true; - this.vel = -12; - this.targetpos = this.pos[1] - 32; - } - - Bcoin.prototype.update = function(dt) { - if (!this.active) return; - - if (this.vel > 0 && this.pos[1] >= this.targetpos) { - player.coins += 1; - //spawn a score thingy. - delete level.items[this.idx]; - } - - this.acc = 0.75; - this.vel += this.acc; - this.pos[1] += this.vel; - this.sprite.update(dt); - } - - Bcoin.prototype.checkCollisions = function() {;} - -})(); diff --git a/js/block.js b/js/block.js deleted file mode 100644 index e695f04..0000000 --- a/js/block.js +++ /dev/null @@ -1,81 +0,0 @@ -(function() { - if (typeof Mario === 'undefined') - window.Mario = {}; - - //TODO: clean up the logic for sprite switching. - //TODO: There's a weird bug with the collision logic. Look into it. - - var Block = Mario.Block = function(options) { - this.item = options.item; - this.usedSprite = options.usedSprite; - this.bounceSprite = options.bounceSprite; - this.breakable = options.breakable; - - Mario.Entity.call(this, { - pos: options.pos, - sprite: options.sprite, - hitbox: [0,0,16,16] - }); - - this.standing = true; - } - - Mario.Util.inherits(Block, Mario.Floor); - - Block.prototype.break = function() { - sounds.breakBlock.play(); - (new Mario.Rubble()).spawn(this.pos); - var x = this.pos[0] / 16, y = this.pos[1] / 16; - delete level.blocks[y][x]; - } - - Block.prototype.bonk = function(power) { - sounds.bump.play(); - if (power > 0 && this.breakable) { - this.break(); - } else if (this.standing){ - this.standing = false; - if (this.item) { - this.item.spawn(); - this.item = null; - } - this.opos = []; - this.opos[0] = this.pos[0]; - this.opos[1] = this.pos[1]; - if (this.bounceSprite) { - this.osprite = this.sprite; - this.sprite = this.bounceSprite; - } else { - this.sprite = this.usedSprite; - } - - this.vel[1] = -2; - } - } - - Block.prototype.update = function(dt, gameTime) { - if (!this.standing) { - if (this.pos[1] < this.opos[1] - 8) { - this.vel[1] = 2; - } - if (this.pos[1] > this.opos[1]) { - this.vel[1] = 0; - this.pos = this.opos; - if (this.osprite) { - this.sprite = this.osprite; - } - this.standing = true; - } - } else { - if (this.sprite === this.usedSprite) { - var x = this.pos[0] / 16, y = this.pos[1] / 16; - level.statics[y][x] = new Mario.Floor(this.pos, this.usedSprite); - delete level.blocks[y][x]; - } - } - - this.pos[1] += this.vel[1]; - this.sprite.update(dt, gameTime); - } - -})(); diff --git a/js/coin.js b/js/coin.js deleted file mode 100644 index 28f1758..0000000 --- a/js/coin.js +++ /dev/null @@ -1,47 +0,0 @@ -(function() { - if (typeof Mario === 'undefined') - window.Mario = {}; - - var Coin = Mario.Coin = function(pos, sprite) { - Mario.Entity.call(this, { - pos: pos, - sprite: sprite, - hitbox: [0,0,16,16] - }); - this.idx = level.items.length - } - - Mario.Util.inherits(Coin, Mario.Entity); - - Coin.prototype.isPlayerCollided = function() { - //the first two elements of the hitbox array are an offset, so let's do this now. - var hpos1 = [this.pos[0] + this.hitbox[0], this.pos[1] + this.hitbox[1]]; - var hpos2 = [player.pos[0] + player.hitbox[0], player.pos[1] + player.hitbox[1]]; - - //if the hitboxes actually overlap - if (!(hpos1[0] > hpos2[0]+player.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) { - if (!(hpos1[1] > hpos2[1]+player.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) { - this.collect(); - } - } - } - - Coin.prototype.render = function(ctx, vX, vY) { - this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY); - } - - //money is not affected by gravity, you see. - Coin.prototype.update = function(dt) { - this.sprite.update(dt); - } - Coin.prototype.checkCollisions = function() { - this.isPlayerCollided(); - } - - Coin.prototype.collect = function() { - sounds.coin.currentTime = 0.05; - sounds.coin.play(); - player.coins += 1; - delete level.items[this.idx] - } -})(); diff --git a/js/floor.js b/js/floor.js deleted file mode 100644 index 016d678..0000000 --- a/js/floor.js +++ /dev/null @@ -1,56 +0,0 @@ -(function() { - if (typeof Mario === 'undefined') - window.Mario = {}; - - var Floor = Mario.Floor = function(pos, sprite) { - - Mario.Entity.call(this, { - pos: pos, - sprite: sprite, - hitbox: [0,0,16,16] - }); - } - - Mario.Util.inherits(Floor, Mario.Entity); - - Floor.prototype.isCollideWith = function (ent) { - //the first two elements of the hitbox array are an offset, so let's do this now. - var hpos1 = [Math.floor(this.pos[0] + this.hitbox[0]), Math.floor(this.pos[1] + this.hitbox[1])]; - var hpos2 = [Math.floor(ent.pos[0] + ent.hitbox[0]), Math.floor(ent.pos[1] + ent.hitbox[1])]; - - //if the hitboxes actually overlap - if (!(hpos1[0] > hpos2[0]+ent.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) { - if (!(hpos1[1] > hpos2[1]+ent.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) { - if (!this.standing) { - ent.bump(); - } else { - //if the entity is over the block, it's basically floor - var center = hpos2[0] + ent.hitbox[2] / 2; - if (Math.abs(hpos2[1] + ent.hitbox[3] - hpos1[1]) <= ent.vel[1]) { - if (level.statics[(this.pos[1] / 16) - 1][this.pos[0] / 16]) {return}; - ent.vel[1] = 0; - ent.pos[1] = hpos1[1] - ent.hitbox[3] - ent.hitbox[1]; - ent.standing = true; - if (ent instanceof Mario.Player) { - ent.jumping = 0; - } - } else if (Math.abs(hpos2[1] - hpos1[1] - this.hitbox[3]) > ent.vel[1] && - center + 2 >= hpos1[0] && center - 2 <= hpos1[0] + this.hitbox[2]) { - //ent is under the block. - ent.vel[1] = 0; - ent.pos[1] = hpos1[1] + this.hitbox[3]; - if (ent instanceof Mario.Player) { - this.bonk(ent.power); - ent.jumping = 0; - } - } else { - //entity is hitting it from the side, we're a wall - ent.collideWall(this); - } - } - } - } - } - - Floor.prototype.bonk = function() {;} -})(); diff --git a/js/goomba.js b/js/goomba.js deleted file mode 100644 index 78f420a..0000000 --- a/js/goomba.js +++ /dev/null @@ -1,129 +0,0 @@ -(function() { - if (typeof Mario === 'undefined') - window.Mario = {}; - - //TODO: On console the hitbox is smaller. Measure it and edit this. - - var Goomba = Mario.Goomba = function(pos, sprite) { - this.dying = false; - Mario.Entity.call(this, { - pos: pos, - sprite: sprite, - hitbox: [0,0,16,16] - }); - this.vel[0] = -0.5; - this.idx = level.enemies.length; - }; - - Goomba.prototype.render = function(ctx, vX, vY) { - this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY); - }; - - Goomba.prototype.update = function(dt, vX) { - if (this.pos[0] - vX > 336) { //if we're too far away, do nothing. - return; - } else if (this.pos[0] - vX < -32) { - delete level.enemies[this.idx]; - } - - if (this.dying) { - this.dying -= 1; - if (!this.dying) { - delete level.enemies[this.idx]; - } - } - this.acc[1] = 0.2; - this.vel[1] += this.acc[1]; - this.pos[0] += this.vel[0]; - this.pos[1] += this.vel[1]; - this.sprite.update(dt); - }; - - Goomba.prototype.collideWall = function() { - this.vel[0] = -this.vel[0]; - }; - - Goomba.prototype.checkCollisions = function() { - if (this.flipping) { - return; - } - - var h = this.pos[1] % 16 === 0 ? 1 : 2; - var w = this.pos[0] % 16 === 0 ? 1 : 2; - - var baseX = Math.floor(this.pos[0] / 16); - var baseY = Math.floor(this.pos[1] / 16); - - if (baseY + h > 15) { - delete level.enemies[this.idx]; - return; - } - - for (var i = 0; i < h; i++) { - for (var j = 0; j < w; j++) { - if (level.statics[baseY + i][baseX + j]) { - level.statics[baseY + i][baseX + j].isCollideWith(this); - } - if (level.blocks[baseY + i][baseX + j]) { - level.blocks[baseY + i][baseX + j].isCollideWith(this); - } - } - } - var that = this; - level.enemies.forEach(function(enemy){ - if (enemy === that) { //don't check collisions with ourselves. - return; - } else if (enemy.pos[0] - vX > 336){ //stop checking once we get to far away dudes. - return; - } else { - that.isCollideWith(enemy); - } - }); - this.isCollideWith(player); - }; - - Goomba.prototype.isCollideWith = function(ent) { - if (ent instanceof Mario.Player && (this.dying || ent.invincibility)) { - return; - } - - //the first two elements of the hitbox array are an offset, so let's do this now. - var hpos1 = [this.pos[0] + this.hitbox[0], this.pos[1] + this.hitbox[1]]; - var hpos2 = [ent.pos[0] + ent.hitbox[0], ent.pos[1] + ent.hitbox[1]]; - - //if the hitboxes actually overlap - if (!(hpos1[0] > hpos2[0]+ent.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) { - if (!(hpos1[1] > hpos2[1]+ent.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) { - if (ent instanceof Mario.Player) { //if we hit the player - if (ent.vel[1] > 0) { //then the goomba dies - this.stomp(); - } else if (ent.starTime) { - this.bump(); - } else { //or the player gets hit - ent.damage(); - } - } else { - this.collideWall(); - } - } - } - }; - - Goomba.prototype.stomp = function() { - sounds.stomp.play(); - player.bounce = true; - this.sprite.pos[0] = 32; - this.sprite.speed = 0; - this.vel[0] = 0; - this.dying = 10; - }; - - Goomba.prototype.bump = function() { - sounds.kick.play(); - this.sprite.img = 'sprites/enemyr.png'; - this.flipping = true; - this.pos[1] -= 1; - this.vel[0] = 0; - this.vel[1] = -2.5; - }; -})(); diff --git a/js/input.js b/js/input.js deleted file mode 100644 index 79ae296..0000000 --- a/js/input.js +++ /dev/null @@ -1,54 +0,0 @@ -(function() { - var pressedKeys = {}; - - function setKey(event, status) { - var code = event.keyCode; - var key; - - switch(code) { - case 32: - key = 'SPACE'; break; - case 37: - key = 'LEFT'; break; - case 38: - key = 'UP'; break; - case 39: - key = 'RIGHT'; break; - case 40: - key = 'DOWN'; break; - case 88: - key = 'JUMP'; break; - case 90: - key = 'RUN'; break; - default: - key = String.fromCharCode(code); - } - - pressedKeys[key] = status; - } - - document.addEventListener('keydown', function(e) { - setKey(e, true); - }); - - document.addEventListener('keyup', function(e) { - setKey(e, false); - }); - - window.addEventListener('blur', function() { - pressedKeys = {}; - }); - - window.input = { - isDown: function(key) { - return pressedKeys[key.toUpperCase()]; - }, - reset: function() { - pressedKeys['RUN'] = false; - pressedKeys['LEFT'] = false; - pressedKeys['RIGHT'] = false; - pressedKeys['DOWN'] = false; - pressedKeys['JUMP'] = false; - } - }; -})(); diff --git a/js/mushroom.js b/js/mushroom.js deleted file mode 100644 index 97e7233..0000000 --- a/js/mushroom.js +++ /dev/null @@ -1,118 +0,0 @@ -(function() { - if (typeof Mario === 'undefined') - window.Mario = {}; - - var Mushroom = Mario.Mushroom = function(pos) { - this.spawning = false; - this.waiting = 0; - - Mario.Entity.call(this, { - pos: pos, - sprite: level.superShroomSprite, - hitbox: [0,0,16,16] - }); - } - - Mario.Util.inherits(Mushroom, Mario.Entity); - - Mushroom.prototype.render = function(ctx, vX, vY) { - if (this.spawning > 1) return; - this.sprite.render(ctx, this.pos[0], this.pos[1], vX, vY); - } - - Mushroom.prototype.spawn = function() { - if (player.power > 0) { - //replace this with a fire flower - var ff = new Mario.Fireflower(this.pos) - ff.spawn(); - return; - } - sounds.itemAppear.play(); - this.idx = level.items.length; - level.items.push(this); - this.spawning = 12; - this.targetpos = []; - this.targetpos[0] = this.pos[0]; - this.targetpos[1] = this.pos[1] - 16; - } - - Mushroom.prototype.update = function(dt) { - if (this.spawning > 1) { - this.spawning -= 1; - if (this.spawning == 1) this.vel[1] = -.5; - return; - } - if (this.spawning) { - if (this.pos[1] <= this.targetpos[1]) { - this.pos[1] = this.targetpos[1]; - this.vel[1] = 0; - this.waiting = 5; - this.spawning = 0; - this.vel[0] = 1; - } - } else { - this.acc[1] = 0.2; - } - - if (this.waiting) { - this.waiting -= 1; - } else { - this.vel[1] += this.acc[1]; - this.pos[0] += this.vel[0]; - this.pos[1] += this.vel[1]; - this.sprite.update(dt); - } - } - - Mushroom.prototype.collideWall = function() { - this.vel[0] = -this.vel[0]; - } - - Mushroom.prototype.checkCollisions = function() { - if(this.spawning) { - return; - } - var h = this.pos[1] % 16 == 0 ? 1 : 2; - var w = this.pos[0] % 16 == 0 ? 1 : 2; - - var baseX = Math.floor(this.pos[0] / 16); - var baseY = Math.floor(this.pos[1] / 16); - - if (baseY + h > 15) { - delete level.items[this.idx]; - return; - } - - for (var i = 0; i < h; i++) { - for (var j = 0; j < w; j++) { - if (level.statics[baseY + i][baseX + j]) { - level.statics[baseY + i][baseX + j].isCollideWith(this); - } - if (level.blocks[baseY + i][baseX + j]) { - level.blocks[baseY + i][baseX + j].isCollideWith(this); - } - } - } - - this.isPlayerCollided(); - } - - //we have access to player everywhere, so let's just do this. - Mushroom.prototype.isPlayerCollided = function() { - //the first two elements of the hitbox array are an offset, so let's do this now. - var hpos1 = [this.pos[0] + this.hitbox[0], this.pos[1] + this.hitbox[1]]; - var hpos2 = [player.pos[0] + player.hitbox[0], player.pos[1] + player.hitbox[1]]; - - //if the hitboxes actually overlap - if (!(hpos1[0] > hpos2[0]+player.hitbox[2] || (hpos1[0]+this.hitbox[2] < hpos2[0]))) { - if (!(hpos1[1] > hpos2[1]+player.hitbox[3] || (hpos1[1]+this.hitbox[3] < hpos2[1]))) { - player.powerUp(this.idx); - } - } - } - - Mushroom.prototype.bump = function() { - this.vel[1] = -2; - } - -})(); diff --git a/js/sprite.js b/js/sprite.js deleted file mode 100644 index 94b8d6c..0000000 --- a/js/sprite.js +++ /dev/null @@ -1,47 +0,0 @@ -(function() { - if (typeof Mario === 'undefined') - window.Mario = {}; - - var Sprite = Mario.Sprite = function(img, pos, size, speed, frames, once) { - this.pos = pos; - this.size = size; - this.speed = speed; - this._index = 0; - this.img = img; - this.once = once; - this.frames = frames; - } - - Sprite.prototype.update = function(dt, gameTime) { - if (gameTime && gameTime == this.lastUpdated) return; - this._index += this.speed*dt; - if (gameTime) this.lastUpdated = gameTime; - } - - Sprite.prototype.setFrame = function(frame) { - this._index = frame; - } - - Sprite.prototype.render = function(ctx, posx, posy, vX, vY) { - var frame; - - if (this.speed > 0) { - var max = this.frames.length; - var idx = Math.floor(this._index); - frame = this.frames[idx % max]; - - if (this.once && idx >= max) { - this.done = true; - return; - } - } else { - frame = 0; - } - - var x = this.pos[0]; - var y = this.pos[1]; - - x += frame*this.size[0]; - ctx.drawImage(resources.get(this.img), x + (1/3),y + (1/3), this.size[0] - (2/3), this.size[1] - (2/3), Math.round(posx - vX), Math.round(posy - vY), this.size[0],this.size[1]); - } -})(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..eb065ef --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "Crazy Runner MMORPG", + "version": "1.0.0", + "description": "A MMORPG where you are free to be your character!", + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "dependencies": { + "ejs": "^3.1.10", + "express": "^4.18.2", + "openssl": "^2.0.0", + "openssl-nodejs": "^1.0.5", + "socket.io": "^4.5.0" + } +} diff --git a/css/game.css b/public/css/game.css similarity index 100% rename from css/game.css rename to public/css/game.css diff --git a/index.html b/public/index.html similarity index 57% rename from index.html rename to public/index.html index c1b06bf..8685871 100644 --- a/index.html +++ b/public/index.html @@ -2,16 +2,59 @@ - - Super Mario Bros - + +
+ +
+
+ + + + + + +
+ +
+ + +
+
+
+ @@ -36,9 +79,5 @@ -
-

Move: Arrow Keys, Jump: X, Run: Z

-