Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions FUTURE_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Future Implementation Steps

The map editor and collision system provide a foundation, but competitive multiplayer requires the server—not the browser—to own the match state. The phases below are ordered to establish trustworthy networking before expanding the game content.

## Phase 5: Server-Authoritative Multiplayer

This is the highest-priority milestone.

- Have clients send destinations or inputs instead of positions.
- Make the server own player positions, movement speed, collisions, health, damage, and cooldowns.
- Run the server simulation at a fixed tick rate.
- Broadcast periodic state snapshots to clients.
- Add client-side prediction, interpolation, and server reconciliation.
- Associate players with their socket or authenticated player ID.
- Send connection IDs only to the corresponding socket.
- Remove players and release match resources when they disconnect.
- Load map boundaries and walls into the server simulation.
- Share collision rules between the editor preview, client, and server without trusting client results.

The current position event must be replaced before the game can be competitive. Accepting coordinates reported by clients allows a modified client to teleport or ignore collisions.

## Phase 6: Matches and Rooms

- Create a separate Socket.IO room and state container for each match.
- Add a lobby, ready state, and match countdown.
- Assign players to teams.
- Add team-specific spawn points.
- Define match start, active, finished, and abandoned states.
- Add a match timer, scoreboard, and win/loss condition.
- Support rematches or returning to the lobby.
- Add a reconnection grace period and restore the player's current match state.
- Add spectator handling after the basic match lifecycle works.

Start with `1v1` or `2v2`. Larger matches will make networking, balancing, and testing substantially harder.

## Phase 7: Minimal MOBA Gameplay

- Add two teams with visually distinct players.
- Add health, death, respawning, and temporary invulnerability after spawning.
- Add a basic attack.
- Add two or three abilities with server-controlled cooldowns.
- Add server-side hit detection and damage validation.
- Add one clear objective, such as destroying a base or reaching a score target.
- Add a HUD for health, cooldowns, score, teams, and match time.
- Establish movement speed, attack range, damage, and cooldown configuration.

Items, minions, progression systems, and multiple heroes should wait until a small match with one character is consistently fun.

## Phase 8: Simple In-Game Chat

- Add match-wide and team chat channels.
- Attach usernames on the server rather than accepting a username with each message.
- Limit message length and validate every message.
- Render messages as text, never executable HTML.
- Add per-user chat rate limits and spam protection.
- Add mute controls.
- Add basic profanity filtering and player reporting.
- Optionally retain a short recent-message history for reconnecting players.
- Keep chat events separate from gameplay events and rate limits.

## Phase 9: Accounts and Competitive Identity

- Add registration and login, or use an external identity provider.
- Give every account a stable, non-sequential public player ID.
- Add unique display names and moderation rules.
- Store match history, wins, losses, and abandonment records.
- Add an initial MMR or Elo-style rating.
- Separate ranked and unranked matchmaking.
- Add leaver penalties only after reconnection is reliable.
- Add password recovery, session expiration, and logout invalidation if passwords are managed locally.

## Phase 10: Persistence and Matchmaking

Use a relational database such as PostgreSQL for:

- Accounts and authentication identities.
- Ratings and match results.
- Map definitions and map versions.
- Reports, mutes, suspensions, and bans.

Redis can be introduced when needed for:

- Matchmaking queues.
- Presence and short-lived session data.
- Active match discovery.
- Coordinating Socket.IO across multiple game-server processes.

Avoid making a database the source of truth for every simulation tick. Active match state should remain in the authoritative game process and persist only meaningful events or final results.

## Phase 11: Security and Anti-Cheat

- Use HTTPS and secure WebSockets in production.
- Authenticate every socket connection.
- Validate the allowed origin during connection setup.
- Authorize every event against the player's identity, team, and current match.
- Treat all client messages as untrusted input.
- Validate message schemas, types, ranges, and sizes.
- Rate-limit movement, abilities, matchmaking, authentication, and chat separately.
- Reject impossible movement, cooldown use, damage, and match-state transitions.
- Limit simultaneous connections per account and IP.
- Add heartbeat and idle-connection cleanup.
- Log authentication failures, invalid commands, rate-limit events, and abnormal disconnects without logging credentials or session tokens.

Relevant guidance: [OWASP WebSocket Security](https://cheatsheetseries.owasp.org/cheatsheets/WebSocket_Security_Cheat_Sheet.html).

## Phase 12: Reliability and Competitive Quality

- Display network latency and connection state.
- Recover gracefully from temporary disconnections.
- Add AFK detection.
- Handle server shutdowns without silently losing match results.
- Add structured logs and production error reporting.
- Monitor simulation tick duration, active matches, message rates, memory, and disconnects.
- Add automated load tests with simulated players.
- Test under latency, jitter, packet loss, and reconnect scenarios.
- Add regional servers only when player latency measurements justify them.
- Add replay or command-history storage for debugging disputed matches and cheating reports.

## Testing Requirements

Automated tests should cover at least:

- Movement and collision agreement between client and server.
- Map boundaries, individual walls, intersections, endpoints, and sliding.
- Invalid or malicious movement commands.
- Ability range, damage, cooldowns, death, and respawn.
- Match state transitions and win conditions.
- Joining, leaving, disconnecting, and reconnecting.
- Team and match chat isolation.
- Chat validation, escaping, muting, and rate limiting.
- Concurrent matches without state leaking between rooms.
- Server behavior under expected and excessive message rates.

## Immediate Server Cleanup

Before implementing new game content:

- Replace the global players array with per-match state.
- Stop accepting client-provided positions as truth.
- Replace sequential broadcast IDs with socket-scoped or authenticated IDs.
- Remove players on the Socket.IO `disconnect` event.
- Validate every incoming socket payload.
- Remove continuous position logging from production.
- Keep map editing endpoints restricted to authorized users before public deployment.

## Recommended Delivery Order

1. Authoritative movement and server-side map collision.
2. Client snapshots, interpolation, prediction, and reconciliation.
3. Match rooms, teams, lifecycle, and reconnect support.
4. One complete combat objective with health and respawning.
5. Simple team and match chat.
6. Accounts, match history, and unranked matchmaking.
7. Rating, ranked matchmaking, moderation, and operations.

Socket.IO remains suitable for this stage because it provides bidirectional low-latency communication, automatic reconnection, and options for scaling across servers. See the [Socket.IO documentation](https://socket.io/) for its current deployment and adapter guidance.
45 changes: 42 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,49 @@


## Todo List
- [ ] Harita Editoru
- [X] Map Editor
- [ ] [Future implementation steps](FUTURE_IMPLEMENTATION.md)
- [ ] Path Finding
- [ ] Chat to build backend for sockets. [Chat location](https://raw.githubusercontent.com/pegasuspect/simpleMOBA/main/screenshots/2023-04-25_17-08-40.png)
- [ ] If chat is working use the logic to build mulltiplayer moving circles
- [ ] In-game Chat
- [Chat location](https://raw.githubusercontent.com/pegasuspect/simpleMOBA/main/screenshots/2023-04-25_17-08-40.png)

### Map Editor TODOS
#### Phase-1: Create the Editor Page & Preview
1. Implement the same UI with camera movement, with a different route for map editor.
1. Place a default spawn point Indicated as (X) where the player spawns in the actual game.
1. Allow moving the map with arrow keys.
1. Put a toggle button wth label; Preview, to run the game with the map in the editor. Essentially, starting the game on the editor's page.
1. Once the toggle is switched back, ensure the game stops, player(s) are removed and previos state of the editor is shown with the spawn point.

#### Phase-2: Create Endpoints for Map Data
1. Create `GET /map-state` that reads and returns the contents of `map-state.json`
1. Create an enpoint for `POST /save-map` which overrides `map-state.json`

#### Phase-3: Add Editor Toolbar with Default Objects; Spawn, Map Area
1. Add a toolbar on the top, outside the game area.
1. Toolbar should have 1 button and an input for map area, initially. There will be about 4-5 buttons/controls at least later on so account for that space.
1. Read the map state from the server. Place the map's objects on the editor. There will be spawn locaiton and map size initially. If they don't exist set them to whatever is used in the game.
1. Add a button called [Spawn].
1. If spawn data was returned from `GET /map-state` replace this button's text with 'Remove Spawn'. If "Remove Spawn" is pressed:
1. Remove the spawn location from current map state.
1. Disable all other controls.
1. Replace button label "Remove Spawn" with "Place Spawn".
1. While moving the mouse over the map area move the spawn location with mouse.
1. Once clicked place spaw location and restore the button label as "Remove Spawn" again.
1. Otherwise, if there wasn't spawn data from the GET endpoint, and follow steps at Phase-3 4.1.1-5
1. Add a text input.
1. If map size data is returned from `GET /map-state` then set the inputs value to it. Otherwise, set it's value to a default. Set the default to whatever current game runs in pixels.
1. Validate that it always has 2 integers devided by 'x'.
1. The size reads `${length}x${width}`.

#### Phase-4: Add Wall Control
1. Add a button caller [Walls]
1. If this button is pushed swith to wall placement mode, and keep the button pressed.
1. Wall Placement Mode:
1. This mode should add walls to the map by drawing a line. When moving the mouse nothing happens. After one click it places a start location, then moving the mouse around should draw a line between start location and the current mouse location until a second click is pressed which sets the end location. Save this onto map state object as a new wall between start location and end location.
1. Save the new wall object to the server with `POST /save-map`
1. Once the Preview is toggled make sure the player cannot go over the wall boundaries. So update the collision logic for that if it needs update.


# Next Meeting
Create a chat client for the game using sockets on
Expand Down
155 changes: 155 additions & 0 deletions editor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>simpleMOBA Map Editor</title>
<style>
:root {
color-scheme: light;
font-family: system-ui, sans-serif;
}

body {
margin: 0;
background: #f3f4f6;
color: #17202a;
}

main {
width: min(800px, calc(100% - 32px));
margin: 32px auto;
}

.editor-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 12px;
}

h1 {
margin: 0;
font-size: 1.35rem;
}

.toolbar {
display: flex;
align-items: end;
gap: 12px;
min-height: 52px;
margin-bottom: 10px;
padding: 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
background: #fff;
}

.toolbar button,
.toolbar input {
box-sizing: border-box;
height: 34px;
border: 1px solid #9ca3af;
border-radius: 4px;
font: inherit;
}

.toolbar button {
padding: 0 14px;
background: #f8fafc;
cursor: pointer;
}

.toolbar button:hover:not(:disabled) {
background: #e5e7eb;
}

.toolbar button[aria-pressed="true"] {
border-color: #2563eb;
background: #2563eb;
color: #fff;
}

.toolbar label {
display: grid;
gap: 4px;
color: #4b5563;
font-size: .78rem;
font-weight: 600;
}

.toolbar input {
width: 130px;
padding: 0 9px;
}

.toolbar input.invalid {
border-color: #dc2626;
outline-color: #dc2626;
}

:disabled {
cursor: not-allowed;
opacity: .55;
}

.preview-control {
display: inline-flex;
align-items: center;
gap: 9px;
cursor: pointer;
font-weight: 600;
user-select: none;
}

.preview-control input {
width: 42px;
height: 22px;
accent-color: #2563eb;
cursor: pointer;
}

#status {
min-height: 24px;
margin: 0 0 8px;
color: #52606d;
font-size: .9rem;
}

canvas {
display: block;
width: 100%;
height: auto;
border: 1px solid #111827;
background: #fff;
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
}
</style>
</head>
<body>
<main>
<div class="editor-header">
<h1>Map Editor</h1>
<label class="preview-control" for="preview-toggle">
<span>Preview</span>
<input id="preview-toggle" type="checkbox" role="switch" disabled>
</label>
</div>
<div class="toolbar" aria-label="Map editor toolbar">
<button id="spawn-button" type="button" disabled>Remove Spawn</button>
<button id="walls-button" type="button" aria-pressed="false" disabled>Walls</button>
<button id="eraser-button" type="button" aria-pressed="false" disabled>Eraser</button>
<label for="map-size">
Map boundaries
<input id="map-size" type="text" value="800x600" inputmode="numeric" pattern="[0-9]+x[0-9]+" disabled>
</label>
</div>
<p id="status">Editor mode · use the arrow keys to move the camera</p>
<canvas id="editor-canvas" width="800" height="600" tabindex="0" aria-label="Map editor canvas"></canvas>
</main>

<script src="/lib.js"></script>
<script src="/editor.js"></script>
</body>
</html>
10 changes: 9 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
var socket = io();
window.game = new Game(ctx, socket, -1);

fetch('/map-state')
.then(response => {
if(!response.ok) throw new Error(`Map state request failed (${response.status})`);
return response.json();
})
.then(mapState => window.game.applyMapState(mapState))
.catch(error => console.error('Unable to load map boundaries:', error));

socket.on('id', (id) => {
if (window.game && window.game.id === -1) window.game.id = id;
});
Expand Down Expand Up @@ -70,4 +78,4 @@
</div>
</body>

</html>
</html>
Loading