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 @@ @@ -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 { @@ -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 { @@ -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

49
static/physics.js

@ -4,41 +4,52 @@ export class PhysicsSystems { @@ -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';
}
}
}

31
static/playerstatemachine.js

@ -0,0 +1,31 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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)
}
}

20
static/states/airborne.js

@ -0,0 +1,20 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -154,6 +161,7 @@ export class StickFigure {
if (this.onGround) {
this.vy = this.jumpStrength;
this.onGround = false;
this.state = 'air';
}
}

2
templates/index.html

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

1
tmp/build-errors.log

@ -1 +0,0 @@ @@ -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