Stephanie Gredell 4 months ago
parent
commit
a7b63ada07
  1. 51
      internal/eventsub/eventsub.go
  2. 4
      static/character.js
  3. 49
      static/physics.js
  4. 31
      static/playerstatemachine.js
  5. 18
      static/script.js
  6. 20
      static/states/airborne.js
  7. 15
      static/states/idle.js
  8. 11
      static/states/walk.js
  9. 30
      static/stickfigure.js
  10. 2
      templates/index.html
  11. 1
      tmp/build-errors.log
  12. BIN
      tmp/main

51
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
}
}
}

4
static/character.js

@ -46,7 +46,7 @@ export class StickFigureRenderer {
// legs // legs
ctx.beginPath(); ctx.beginPath();
if (player.vx !== 0) { if (player.state === 'ground' && player.vx !== 0) {
const step = Math.sin(time * 0.75); const step = Math.sin(time * 0.75);
const stride = 14; const stride = 14;
const lift = 0; const lift = 0;
@ -70,7 +70,7 @@ export class StickFigureRenderer {
// speech bubble // speech bubble
if (player.talking) { if (player.talking) {
const padding = 12; const padding = 0;
const maxMessageWidth = 100; const maxMessageWidth = 100;
// set font BEFORE measuring // set font BEFORE measuring

49
static/physics.js

@ -4,41 +4,52 @@ export class PhysicsSystems {
const halfFootW = 12; const halfFootW = 12;
const EPS = 0.5; const EPS = 0.5;
if (player._prevY === undefined) { const feetLeft = player.x - halfFootW;
player._prevY = player.y; const feetRight = player.x + halfFootW;
}
const prevFeetY = (player._prevY ?? player.y) + totalHeight;
const currFeetY = player.y + totalHeight;
let landedOnPlatform = false; let landedOnPlatform = false;
let landed = false;
// one-way platforms (land only when falling and crossing from above) // one-way platforms (land only when falling and crossing from above)
if (player.vy >= 0) { if (player.vy >= 0) {
for (const pf of platforms) { for (const pf of platforms) {
const feetLeft = player.x - halfFootW; const pfTop = pf.y;
const feetRight = player.x + halfFootW; const pfLeft = pf.x;
const pfLeft = pf.x, pfRight = pf.x + pf.w; const pfRight = pf.x + pf.w
const horizOverlap = feetRight > pfLeft && feetLeft < pfRight; const horizOverlap = feetRight > pfLeft && feetLeft < pfRight;
const wasAbove = (player._prevY + totalHeight) <= pf.y + EPS; const crossedTop = (prevFeetY <= pfTop + EPS) && (currFeetY >= pfTop - EPS);
const nowBelowTop = (player.y + totalHeight) >= pf.y - EPS; const dropping = player.crouching === true;
const dropping = player.crouching === true; // hold 's' to drop through
if (horizOverlap && wasAbove && nowBelowTop && !dropping) { if (horizOverlap && crossedTop && !dropping) {
player.y = pf.y - totalHeight; player.y = pfTop - totalHeight;
player.vy = 0; player.vy = 0;
landedOnPlatform = true; player.onGround = true;
landed = true;
player.state = 'ground';
break; break;
} }
} }
} }
// --- ground after platforms --- // --- ground after platforms ---
if (!landedOnPlatform && player.y + totalHeight >= groundY) { if (!landedOnPlatform) {
player.y = groundY - totalHeight; const crossedGround = (prevFeetY <= groundY + EPS) && (currFeetY >= groundY - EPS);
player.vy = 0; if (crossedGround) {
player.onGround = true; player.y = groundY - totalHeight;
} else if (landedOnPlatform) { player.vy = 0;
player.onGround = true; player.onGround = true;
} else { player.state = 'ground';
landed = true
}
}
if (!landed) {
player.onGround = false; player.onGround = false;
player.state = 'air';
} }
} }
} }

31
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);
}
}
}

18
static/script.js

@ -27,6 +27,7 @@ class Game {
this._onKeyUp = this._onKeyUp.bind(this); this._onKeyUp = this._onKeyUp.bind(this);
this._onSendClick = this._onSendClick.bind(this); this._onSendClick = this._onSendClick.bind(this);
this._leaveGame = this._leaveGame.bind(this); this._leaveGame = this._leaveGame.bind(this);
this._onBlur = this._onBlur.bind(this);
this._started = false; this._started = false;
this._sentJoin = false; this._sentJoin = false;
@ -45,14 +46,13 @@ class Game {
this.pickups = [ this.pickups = [
{ id: 'p1', x: 160, y: this.canvas.height - 15, r: 15, type: 'atk', amount: 10 }, { 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 }, { id: 'p2', x: 350, y: this.canvas.height - 15, r: 15, type: 'def', amount: 20 },
] ]
} }
start() { start() {
if (this._started) return; if (this._started) return;
this._started = true; // FIXED: was "this.started = true" this._started = true;
this._stopped = false; this._stopped = false;
document.addEventListener("keydown", this._onKeyDown, { passive: false }); document.addEventListener("keydown", this._onKeyDown, { passive: false });
@ -105,6 +105,7 @@ class Game {
_getPlayerArray() { _getPlayerArray() {
if (!this._cachedPlayerArray || this._playersChanged) { if (!this._cachedPlayerArray || this._playersChanged) {
console.log('players changed...')
this._cachedPlayerArray = Object.values(this.players); this._cachedPlayerArray = Object.values(this.players);
this._playersChanged = false; this._playersChanged = false;
} }
@ -112,9 +113,15 @@ class Game {
return this._cachedPlayerArray; return this._cachedPlayerArray;
} }
loop() { loop(now = performance.now()) {
if (this._stopped) return; 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); this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
const playerArray = this._getPlayerArray() const playerArray = this._getPlayerArray()
@ -126,7 +133,7 @@ class Game {
} }
playerArray.forEach(player => { 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) { if (player.isLocal) {
this.physics.resolveCollisions(player, this.platform, this.canvas.height); 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.players[id] = new StickFigure(250, 1, id, this.canvas)
this._playersChanged = true; this._playersChanged = true;
this.players[id].isLocal = (id === this.pageData.username) this.players[id].isLocal = (id === this.pageData.username)
this.players['dummy'] = new StickFigure(200, 1, 'dummy', this.canvas)
} }
} }

20
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() { }
}

15
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);
}
}
}
}

11
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);
}
}
}
}

30
static/stickfigure.js

@ -1,3 +1,6 @@
import { StateMachine } from "./playerstatemachine.js";
import { Idle } from "./states/idle.js";
export class StickFigure { export class StickFigure {
constructor(x, facing, id, canvas) { constructor(x, facing, id, canvas) {
this.canvas = canvas; this.canvas = canvas;
@ -5,10 +8,12 @@ export class StickFigure {
this.x = x; this.x = x;
this.y = canvas.height - 60; this.y = canvas.height - 60;
this.facing = facing; this.facing = facing;
this.state = "ground";
this._lastTx = x; this._lastTx = x;
this._lastTy = this.y; this._lastTy = this.y;
this._lastUpdateTime = 0; this._lastUpdateTime = 0;
this._walkTTL = 0; this.statemachine = new StateMachine(this);
this.statemachine.set(Idle)
this.action = "idle"; this.action = "idle";
this.hitFrame = 0; this.hitFrame = 0;
this.speed = 2; this.speed = 2;
@ -41,23 +46,18 @@ export class StickFigure {
this.baseDefense = 14; this.baseDefense = 14;
} }
update() { update(dt) {
const prevX = this.x; let prevX = this.x;
this._prevY = this.y; this._prevY = this.y;
this.vx = 0; this.vx = 0;
this.statemachine.update(dt)
if (this.keys.a) { if (this.keys.a) {
this.vx = -this.speed; this.vx = -this.speed;
this.facing = -1; this.facing = -1;
} }
if (this.crouching && this.keys.d) { this.speed = this.crouching ? 0.5 : 2;
this.speed = 0.5;
this.vx = this.speed;
}
if (this.crouching && this.keys.a) {
this.vx = -this.speed;
}
if (this.keys.d) { if (this.keys.d) {
this.vx = this.speed; this.vx = this.speed;
@ -93,6 +93,10 @@ export class StickFigure {
if (!this.isLocal if (!this.isLocal
&& typeof this._tx === "number" && typeof this._tx === "number"
&& typeof this._ty === "number") { && typeof this._ty === "number") {
if (this.id === 'codegirl007') {
console.log(this.x, prevX)
}
const now = Date.now(); const now = Date.now();
const timeSinceUpdate = now - this._lastUpdateTime; const timeSinceUpdate = now - this._lastUpdateTime;
@ -114,10 +118,13 @@ export class StickFigure {
this.facing = this._tFacing ?? this.facing; this.facing = this._tFacing ?? this.facing;
this.vx = this.x - prevX; this.vx = this.x - prevX;
if (Math.abs(this.vx) < 0.5) { if (Math.abs(this.vx) < 0.5) {
this.vx = 0; this.vx = 0;
} }
} }
this.state = this.onGround ? 'ground' : 'air';
} }
lerp(a, b, t) { lerp(a, b, t) {
@ -146,7 +153,7 @@ export class StickFigure {
talk(message) { talk(message) {
this.talking = true; this.talking = true;
this.talkTimer = 120; this.talkTimer = 600; // milliseconds
this.message = message; this.message = message;
} }
@ -154,6 +161,7 @@ export class StickFigure {
if (this.onGround) { if (this.onGround) {
this.vy = this.jumpStrength; this.vy = this.jumpStrength;
this.onGround = false; this.onGround = false;
this.state = 'air';
} }
} }

2
templates/index.html

@ -90,7 +90,7 @@
<input type="text" id="msg" placeholder="Type in a message to chat" /> <input type="text" id="msg" placeholder="Type in a message to chat" />
<button type="button" id="send">Send</button> <button type="button" id="send">Send</button>
</div> </div>
<script src="/static/script.js"></script> <script src="/static/script.js" type="module"></script>
</body> </body>

1
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

BIN
tmp/main

Binary file not shown.
Loading…
Cancel
Save