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 @@ - + diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 1abbe85..e69de29 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +0,0 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main index d092a3c..0ea22f2 100755 Binary files a/tmp/main and b/tmp/main differ