diff --git a/internal/eventsub/eventsub.go b/internal/eventsub/eventsub.go
new file mode 100644
index 0000000..ce2fb51
--- /dev/null
+++ b/internal/eventsub/eventsub.go
@@ -0,0 +1,51 @@
+package eventsub
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/gorilla/websocket"
+)
+
+type WelcomeMessage struct {
+ Metadata struct {
+ MessageType string `json:"message_type"`
+ MessageID string `json:"message_id"`
+ } `json:"metadata"`
+ Payload struct {
+ Session struct {
+ ID string `json:"id"`
+ Status string `json:"status"`
+ } `json:"session"`
+ } `json:"payload"`
+}
+
+func Connect() (*websocket.Conn, error) {
+ conn, _, err := websocket.DefaultDialer.Dial("wss://eventsub.wss.twitch.tv/ws", nil)
+ return conn, err
+}
+
+func HandleMessages(conn *websocket.Conn) {
+ for {
+ _, message, err := conn.ReadMessage()
+ if err != nil {
+ fmt.Printf("Read error: %v", err)
+ return
+ }
+
+ var msg WelcomeMessage
+ if err := json.Unmarshal(message, &msg); err != nil {
+ fmt.Printf("JSON error: %v", err)
+ continue
+ }
+
+ switch msg.Metadata.MessageType {
+ case "session_welcome":
+ sessionID := msg.Payload.Session.ID
+ fmt.Printf("Session ID: %s", sessionID)
+ // Use this session ID to create subscriptions
+ case "notification":
+ // Handle actual events here
+ }
+ }
+}
diff --git a/static/character.js b/static/character.js
index a07fc7d..c62d128 100644
--- a/static/character.js
+++ b/static/character.js
@@ -46,7 +46,7 @@ export class StickFigureRenderer {
// legs
ctx.beginPath();
- if (player.vx !== 0) {
+ if (player.state === 'ground' && player.vx !== 0) {
const step = Math.sin(time * 0.75);
const stride = 14;
const lift = 0;
@@ -70,7 +70,7 @@ export class StickFigureRenderer {
// speech bubble
if (player.talking) {
- const padding = 12;
+ const padding = 0;
const maxMessageWidth = 100;
// set font BEFORE measuring
diff --git a/static/physics.js b/static/physics.js
index a2b00c9..f539eaa 100644
--- a/static/physics.js
+++ b/static/physics.js
@@ -4,41 +4,52 @@ export class PhysicsSystems {
const halfFootW = 12;
const EPS = 0.5;
- if (player._prevY === undefined) {
- player._prevY = player.y;
- }
+ const feetLeft = player.x - halfFootW;
+ const feetRight = player.x + halfFootW;
+
+ const prevFeetY = (player._prevY ?? player.y) + totalHeight;
+ const currFeetY = player.y + totalHeight;
let landedOnPlatform = false;
+ let landed = false;
+
// one-way platforms (land only when falling and crossing from above)
if (player.vy >= 0) {
for (const pf of platforms) {
- const feetLeft = player.x - halfFootW;
- const feetRight = player.x + halfFootW;
- const pfLeft = pf.x, pfRight = pf.x + pf.w;
+ const pfTop = pf.y;
+ const pfLeft = pf.x;
+ const pfRight = pf.x + pf.w
const horizOverlap = feetRight > pfLeft && feetLeft < pfRight;
- const wasAbove = (player._prevY + totalHeight) <= pf.y + EPS;
- const nowBelowTop = (player.y + totalHeight) >= pf.y - EPS;
- const dropping = player.crouching === true; // hold 's' to drop through
+ const crossedTop = (prevFeetY <= pfTop + EPS) && (currFeetY >= pfTop - EPS);
+ const dropping = player.crouching === true;
- if (horizOverlap && wasAbove && nowBelowTop && !dropping) {
- player.y = pf.y - totalHeight;
+ if (horizOverlap && crossedTop && !dropping) {
+ player.y = pfTop - totalHeight;
player.vy = 0;
- landedOnPlatform = true;
+ player.onGround = true;
+ landed = true;
+ player.state = 'ground';
break;
}
}
}
// --- ground after platforms ---
- if (!landedOnPlatform && player.y + totalHeight >= groundY) {
- player.y = groundY - totalHeight;
- player.vy = 0;
- player.onGround = true;
- } else if (landedOnPlatform) {
- player.onGround = true;
- } else {
+ if (!landedOnPlatform) {
+ const crossedGround = (prevFeetY <= groundY + EPS) && (currFeetY >= groundY - EPS);
+ if (crossedGround) {
+ player.y = groundY - totalHeight;
+ player.vy = 0;
+ player.onGround = true;
+ player.state = 'ground';
+ landed = true
+ }
+ }
+
+ if (!landed) {
player.onGround = false;
+ player.state = 'air';
}
}
}
diff --git a/static/playerstatemachine.js b/static/playerstatemachine.js
new file mode 100644
index 0000000..646540e
--- /dev/null
+++ b/static/playerstatemachine.js
@@ -0,0 +1,31 @@
+export class StateMachine {
+ constructor(owner) {
+ this.owner = owner;
+ this.state = null;
+ }
+
+ get() {
+ return this.state;
+ }
+
+ set(next) {
+ if (next === this.state) return;
+
+ const prev = this.state;
+
+ if (prev && typeof prev.exit === 'function') {
+ prev.exit(this.owner, next);
+ }
+
+ this.state = next || null;
+
+ if (this.state && typeof this.state.enter === 'function') {
+ this.state.enter(this.owner, prev);
+ }
+ }
+ update(dt) {
+ if (this.state && typeof this.state.update === 'function') {
+ this.state?.update?.(this.owner, dt);
+ }
+ }
+}
diff --git a/static/script.js b/static/script.js
index 3e7b3ca..be19b62 100644
--- a/static/script.js
+++ b/static/script.js
@@ -27,6 +27,7 @@ class Game {
this._onKeyUp = this._onKeyUp.bind(this);
this._onSendClick = this._onSendClick.bind(this);
this._leaveGame = this._leaveGame.bind(this);
+ this._onBlur = this._onBlur.bind(this);
this._started = false;
this._sentJoin = false;
@@ -45,14 +46,13 @@ class Game {
this.pickups = [
{ id: 'p1', x: 160, y: this.canvas.height - 15, r: 15, type: 'atk', amount: 10 },
-
{ id: 'p2', x: 350, y: this.canvas.height - 15, r: 15, type: 'def', amount: 20 },
]
}
start() {
if (this._started) return;
- this._started = true; // FIXED: was "this.started = true"
+ this._started = true;
this._stopped = false;
document.addEventListener("keydown", this._onKeyDown, { passive: false });
@@ -105,6 +105,7 @@ class Game {
_getPlayerArray() {
if (!this._cachedPlayerArray || this._playersChanged) {
+ console.log('players changed...')
this._cachedPlayerArray = Object.values(this.players);
this._playersChanged = false;
}
@@ -112,9 +113,15 @@ class Game {
return this._cachedPlayerArray;
}
- loop() {
+ loop(now = performance.now()) {
if (this._stopped) return;
- this.time += 0.1;
+
+ if (!this._last) this._last = now;
+
+ const dt = (now - this._last) / 100;
+ this._last = now;
+
+ this.time += dt;
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
const playerArray = this._getPlayerArray()
@@ -126,7 +133,7 @@ class Game {
}
playerArray.forEach(player => {
- player.update(); // Call the update method for each player
+ player.update(dt); // Call the update method for each player
if (player.isLocal) {
this.physics.resolveCollisions(player, this.platform, this.canvas.height);
}
@@ -293,6 +300,7 @@ class Game {
this.players[id] = new StickFigure(250, 1, id, this.canvas)
this._playersChanged = true;
this.players[id].isLocal = (id === this.pageData.username)
+ this.players['dummy'] = new StickFigure(200, 1, 'dummy', this.canvas)
}
}
diff --git a/static/states/airborne.js b/static/states/airborne.js
new file mode 100644
index 0000000..4eb2019
--- /dev/null
+++ b/static/states/airborne.js
@@ -0,0 +1,20 @@
+import { Idle } from "./idle.js"
+
+export const Airborne = {
+ enter(p) {
+ p.onGround = false;
+ },
+ update(p, dt) {
+ p.vy += p.gravity * dt;
+ p.y += p.vy * dt;
+
+ const landed = p.y >= p.groundY;
+
+ if (landed) {
+ p.y = p.canvas.height - 60;
+ p.vy = 0;
+ p.statemachine.set(Idle)
+ }
+ },
+ exit() { }
+}
diff --git a/static/states/idle.js b/static/states/idle.js
new file mode 100644
index 0000000..7574943
--- /dev/null
+++ b/static/states/idle.js
@@ -0,0 +1,15 @@
+import { Walk } from './walk.js'
+import { Airborne } from './airborne.js'
+export const Idle = {
+ enter(p) {
+ p.vx = 0;
+ },
+ update(p) {
+ if (p.keys.a || p.keys.d) {
+ p.statemachine.set(Walk);
+ if (!p.onground) {
+ p.statemachine.set(Airborne);
+ }
+ }
+ }
+}
diff --git a/static/states/walk.js b/static/states/walk.js
new file mode 100644
index 0000000..dfed447
--- /dev/null
+++ b/static/states/walk.js
@@ -0,0 +1,11 @@
+export const Walk = {
+ update(p) {
+ p.vx = (p.keys.d - p.keys.a) * p.speed;
+ if (!p.keys.a && !p.keys.d) {
+ p.statemachine.set(Idle);
+ if (!p.onGround) {
+ p.statemachine.set(Airborne);
+ }
+ }
+ }
+}
diff --git a/static/stickfigure.js b/static/stickfigure.js
index 2921d63..3562453 100644
--- a/static/stickfigure.js
+++ b/static/stickfigure.js
@@ -1,3 +1,6 @@
+import { StateMachine } from "./playerstatemachine.js";
+import { Idle } from "./states/idle.js";
+
export class StickFigure {
constructor(x, facing, id, canvas) {
this.canvas = canvas;
@@ -5,10 +8,12 @@ export class StickFigure {
this.x = x;
this.y = canvas.height - 60;
this.facing = facing;
+ this.state = "ground";
this._lastTx = x;
this._lastTy = this.y;
this._lastUpdateTime = 0;
- this._walkTTL = 0;
+ this.statemachine = new StateMachine(this);
+ this.statemachine.set(Idle)
this.action = "idle";
this.hitFrame = 0;
this.speed = 2;
@@ -41,23 +46,18 @@ export class StickFigure {
this.baseDefense = 14;
}
- update() {
- const prevX = this.x;
+ update(dt) {
+ let prevX = this.x;
this._prevY = this.y;
this.vx = 0;
+ this.statemachine.update(dt)
if (this.keys.a) {
this.vx = -this.speed;
this.facing = -1;
}
- if (this.crouching && this.keys.d) {
- this.speed = 0.5;
- this.vx = this.speed;
- }
- if (this.crouching && this.keys.a) {
- this.vx = -this.speed;
- }
+ this.speed = this.crouching ? 0.5 : 2;
if (this.keys.d) {
this.vx = this.speed;
@@ -93,6 +93,10 @@ export class StickFigure {
if (!this.isLocal
&& typeof this._tx === "number"
&& typeof this._ty === "number") {
+
+ if (this.id === 'codegirl007') {
+ console.log(this.x, prevX)
+ }
const now = Date.now();
const timeSinceUpdate = now - this._lastUpdateTime;
@@ -114,10 +118,13 @@ export class StickFigure {
this.facing = this._tFacing ?? this.facing;
this.vx = this.x - prevX;
+
if (Math.abs(this.vx) < 0.5) {
this.vx = 0;
}
}
+
+ this.state = this.onGround ? 'ground' : 'air';
}
lerp(a, b, t) {
@@ -146,7 +153,7 @@ export class StickFigure {
talk(message) {
this.talking = true;
- this.talkTimer = 120;
+ this.talkTimer = 600; // milliseconds
this.message = message;
}
@@ -154,6 +161,7 @@ export class StickFigure {
if (this.onGround) {
this.vy = this.jumpStrength;
this.onGround = false;
+ this.state = 'air';
}
}
diff --git a/templates/index.html b/templates/index.html
index f54283f..85270c0 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -90,7 +90,7 @@
-
+