Compare commits
1 commit
feature/bo
...
master
Author | SHA1 | Date | |
---|---|---|---|
9969561af1 |
16 changed files with 599 additions and 421 deletions
|
@ -1,18 +1,22 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
<link rel="stylesheet" href="//cdn.materialdesignicons.com/2.0.46/css/materialdesignicons.min.css">
|
<link rel="stylesheet" href="//cdn.materialdesignicons.com/2.0.46/css/materialdesignicons.min.css">
|
||||||
<title>mcgvue</title>
|
<title>MLPCARDGAME</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<noscript>
|
<noscript>
|
||||||
<strong>We're sorry but mcgvue doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
<strong>We're sorry but mcgvue doesn't work properly without JavaScript enabled. Please enable it to
|
||||||
|
continue.</strong>
|
||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,86 +0,0 @@
|
||||||
<template>
|
|
||||||
<section
|
|
||||||
:class="zoneClass"
|
|
||||||
@dragenter.prevent="dragenter"
|
|
||||||
@dragleave.prevent="dragleave"
|
|
||||||
>
|
|
||||||
<CardImage
|
|
||||||
class="ccgcard"
|
|
||||||
v-for="(card, i) in cards"
|
|
||||||
:key="i + card.ID"
|
|
||||||
:id="card.ID"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.zone {
|
|
||||||
border: 2px solid rgba(255, 255, 255, 0.2);
|
|
||||||
margin: 2px;
|
|
||||||
padding: 4px;
|
|
||||||
border-radius: 5px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ccgcard {
|
|
||||||
cursor: grab;
|
|
||||||
transition: all 100ms;
|
|
||||||
max-width: none;
|
|
||||||
width: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
margin: 0 10px;
|
|
||||||
&:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dragging {
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Component, Vue, Prop } from "vue-property-decorator";
|
|
||||||
import { cardImageURL, Card } from "@/mlpccg";
|
|
||||||
import CardImage from "@/components/Cards/CardImage.vue";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
components: {
|
|
||||||
CardImage
|
|
||||||
}
|
|
||||||
})
|
|
||||||
export default class Zone extends Vue {
|
|
||||||
@Prop({
|
|
||||||
default: []
|
|
||||||
})
|
|
||||||
private cards!: Card[];
|
|
||||||
|
|
||||||
private dragging!: boolean;
|
|
||||||
|
|
||||||
private data() {
|
|
||||||
return {
|
|
||||||
dragging: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private imageURL(id: string) {
|
|
||||||
return cardImageURL(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private dragenter(e: DragEvent) {
|
|
||||||
this.dragging = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private dragleave(e: DragEvent) {
|
|
||||||
this.dragging = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private get zoneClass() {
|
|
||||||
return {
|
|
||||||
zone: true,
|
|
||||||
dragging: this.dragging
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -19,6 +19,23 @@ export const allSets = [
|
||||||
"Promo"
|
"Promo"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const setNames = {
|
||||||
|
PR: "Premiere",
|
||||||
|
CN: "Canterlot Nights",
|
||||||
|
RR: "Rock and Rave",
|
||||||
|
CS: "Celestial Solstice",
|
||||||
|
CG: "Crystal Games",
|
||||||
|
AD: "Absolute Discord",
|
||||||
|
EO: "Equestrian Odysseys",
|
||||||
|
HM: "High Magic",
|
||||||
|
MT: "Marks In Time",
|
||||||
|
DE: "Defenders of Equestria",
|
||||||
|
SB: "Seaquestria and Beyond",
|
||||||
|
FF: "Friends Forever",
|
||||||
|
GF: "Promotional",
|
||||||
|
ST: "Sands of Time"
|
||||||
|
};
|
||||||
|
|
||||||
export async function loadSets() {
|
export async function loadSets() {
|
||||||
if (Database == null) {
|
if (Database == null) {
|
||||||
throw new Error("Database was not initialized, init with 'initDB()'");
|
throw new Error("Database was not initialized, init with 'initDB()'");
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import {
|
|
||||||
PeerMetadata,
|
|
||||||
NetworkMessage,
|
|
||||||
PasswordResponse,
|
|
||||||
RoomInfo
|
|
||||||
} from "./types";
|
|
||||||
import EventEmitter from "eventemitter3";
|
import EventEmitter from "eventemitter3";
|
||||||
|
import Vue from "vue";
|
||||||
|
|
||||||
|
import { NetworkMessage, PasswordResponse, PeerMetadata, RoomInfo } from "./types";
|
||||||
|
|
||||||
export abstract class Client extends EventEmitter {
|
export abstract class Client extends EventEmitter {
|
||||||
public metadata: PeerMetadata;
|
public metadata: PeerMetadata;
|
||||||
|
@ -23,13 +20,19 @@ export abstract class Client extends EventEmitter {
|
||||||
case "room-info":
|
case "room-info":
|
||||||
this.roomInfo = data.room;
|
this.roomInfo = data.room;
|
||||||
this.players = data.players;
|
this.players = data.players;
|
||||||
|
this.emit("handshake");
|
||||||
break;
|
break;
|
||||||
// Someone changed name (or was forced to)
|
// Someone changed name (or was forced to)
|
||||||
case "rename":
|
case "rename":
|
||||||
if (data.oldname == this.metadata.name) {
|
if (data.oldname == this.metadata.name) {
|
||||||
// We got a name change!
|
// We got a name change!
|
||||||
this.metadata.name = data.newname;
|
this.metadata.name = data.newname;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// Only mutate player list if we have one
|
||||||
|
// This is because rename messages can be received during the initial
|
||||||
|
// handshake, to signal a forced name change before joining.
|
||||||
|
if (this.players) {
|
||||||
let idx = this.players.indexOf(data.oldname);
|
let idx = this.players.indexOf(data.oldname);
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
// Weird
|
// Weird
|
||||||
|
@ -39,7 +42,7 @@ export abstract class Client extends EventEmitter {
|
||||||
this.players.push(data.newname);
|
this.players.push(data.newname);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.players[idx] = data.newname;
|
Vue.set(this.players, idx, data.newname);
|
||||||
}
|
}
|
||||||
this.emit("rename", data.oldname, data.newname);
|
this.emit("rename", data.oldname, data.newname);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
|
import EventEmitter from "eventemitter3";
|
||||||
import Peer, { DataConnection } from "peerjs";
|
import Peer, { DataConnection } from "peerjs";
|
||||||
|
|
||||||
|
import { LocalClient } from ".";
|
||||||
import {
|
import {
|
||||||
RoomInfo,
|
AckMessage,
|
||||||
PasswordRequest,
|
ChatMessage,
|
||||||
Room,
|
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
|
JoinMessage,
|
||||||
|
LeaveMessage,
|
||||||
|
NetworkMessage,
|
||||||
|
NetworkPlayer,
|
||||||
|
PasswordRequest,
|
||||||
PasswordResponse,
|
PasswordResponse,
|
||||||
PeerMetadata,
|
PeerMetadata,
|
||||||
JoinMessage,
|
|
||||||
RoomInfoMessage,
|
|
||||||
Player,
|
Player,
|
||||||
NetworkMessage,
|
|
||||||
RenameMessage,
|
RenameMessage,
|
||||||
LeaveMessage,
|
Room,
|
||||||
NetworkPlayer,
|
RoomInfo,
|
||||||
AckMessage,
|
RoomInfoMessage,
|
||||||
ChatMessage
|
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { LocalClient } from ".";
|
|
||||||
import EventEmitter from "eventemitter3";
|
|
||||||
|
|
||||||
// Increment name, add number at the end if not present
|
// Increment name, add number at the end if not present
|
||||||
// Examples:
|
// Examples:
|
||||||
|
@ -34,6 +35,12 @@ function nextName(name: string): string {
|
||||||
return name.substr(0, i) + (Number(name.slice(i)) + 1);
|
return name.substr(0, i) + (Number(name.slice(i)) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function connectionOpen(conn: DataConnection): Promise<void> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
conn.on("open", () => resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export class PeerServer extends EventEmitter {
|
export class PeerServer extends EventEmitter {
|
||||||
protected peer: Peer;
|
protected peer: Peer;
|
||||||
private room: Room;
|
private room: Room;
|
||||||
|
@ -64,17 +71,21 @@ export class PeerServer extends EventEmitter {
|
||||||
|
|
||||||
// Setup peer
|
// Setup peer
|
||||||
this.peer = customPeer ? customPeer : new Peer();
|
this.peer = customPeer ? customPeer : new Peer();
|
||||||
this.peer.on("open", function(id) {
|
this.peer.on("open", id => {
|
||||||
console.info("Peer ID assigned: %s", id);
|
console.info("Peer ID assigned: %s", id);
|
||||||
|
this.emit("open", id);
|
||||||
});
|
});
|
||||||
this.peer.on("connection", conn => {
|
this.peer.on("connection", conn => {
|
||||||
this._connection(conn);
|
this._connection(conn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _connection(conn: DataConnection) {
|
private async _connection(conn: DataConnection) {
|
||||||
const metadata = conn.metadata as PeerMetadata;
|
const metadata = conn.metadata as PeerMetadata;
|
||||||
|
|
||||||
|
// Wait for connection to be open
|
||||||
|
await connectionOpen(conn);
|
||||||
|
|
||||||
let player: NetworkPlayer = {
|
let player: NetworkPlayer = {
|
||||||
kind: "remote",
|
kind: "remote",
|
||||||
name: metadata.name,
|
name: metadata.name,
|
||||||
|
@ -140,6 +151,12 @@ export class PeerServer extends EventEmitter {
|
||||||
|
|
||||||
private addPlayer(player: NetworkPlayer) {
|
private addPlayer(player: NetworkPlayer) {
|
||||||
const playerName = player.name;
|
const playerName = player.name;
|
||||||
|
|
||||||
|
// Hacky: Give player list before this player was added, so join
|
||||||
|
// message doesn't mess things up later
|
||||||
|
const players = Object.keys(this.room.players);
|
||||||
|
|
||||||
|
// Add player to room
|
||||||
this.room.players[playerName] = player;
|
this.room.players[playerName] = player;
|
||||||
|
|
||||||
// Start listening for new messages
|
// Start listening for new messages
|
||||||
|
@ -148,6 +165,10 @@ export class PeerServer extends EventEmitter {
|
||||||
this._received.bind(this, this.room.players[playerName])
|
this._received.bind(this, this.room.players[playerName])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
player.conn.on("error", err => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
// Send the player info about the room
|
// Send the player info about the room
|
||||||
this.send<RoomInfoMessage>(player, {
|
this.send<RoomInfoMessage>(player, {
|
||||||
kind: "room-info",
|
kind: "room-info",
|
||||||
|
@ -155,7 +176,7 @@ export class PeerServer extends EventEmitter {
|
||||||
...this.room.info,
|
...this.room.info,
|
||||||
password: ""
|
password: ""
|
||||||
},
|
},
|
||||||
players: Object.keys(this.room.players)
|
players
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify other players
|
// Notify other players
|
||||||
|
@ -172,6 +193,9 @@ export class PeerServer extends EventEmitter {
|
||||||
// Close connection with player
|
// Close connection with player
|
||||||
player.conn.close();
|
player.conn.close();
|
||||||
|
|
||||||
|
// Remove player from player list
|
||||||
|
delete this.room.players[player.name];
|
||||||
|
|
||||||
// Notify other players
|
// Notify other players
|
||||||
this.broadcast<LeaveMessage>({
|
this.broadcast<LeaveMessage>({
|
||||||
kind: "player-left",
|
kind: "player-left",
|
||||||
|
@ -189,16 +213,30 @@ export class PeerServer extends EventEmitter {
|
||||||
this.broadcast(data);
|
this.broadcast(data);
|
||||||
} else {
|
} else {
|
||||||
// Player is telling someone specifically
|
// Player is telling someone specifically
|
||||||
if (data.to in this.players) {
|
if (!(data.to in this.players)) {
|
||||||
this.send<ChatMessage>(this.players[data.to], data);
|
|
||||||
} else {
|
|
||||||
this.send<ErrorMessage>(player, {
|
this.send<ErrorMessage>(player, {
|
||||||
kind: "error",
|
kind: "error",
|
||||||
error: `player not found: ${data.to}`
|
error: `player not found: ${data.to}`
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
this.send<ChatMessage>(this.players[data.to], data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
// Player wants to change name
|
||||||
|
case "rename":
|
||||||
|
// Make sure new name is valid
|
||||||
|
data.oldname = player.name;
|
||||||
|
if (data.newname in this.players) {
|
||||||
|
this.send<ErrorMessage>(player, {
|
||||||
|
kind: "error",
|
||||||
|
error: "name not available"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.name = data.newname;
|
||||||
|
this.broadcast<RenameMessage>(data);
|
||||||
|
break;
|
||||||
// Player is leaving!
|
// Player is leaving!
|
||||||
case "leave-req":
|
case "leave-req":
|
||||||
// If we're leaving, end the server
|
// If we're leaving, end the server
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
import DeckBuilder from "@/views/DeckBuilder.vue";
|
||||||
|
import DraftView from "@/views/Draft.vue";
|
||||||
|
import GameView from "@/views/Game.vue";
|
||||||
|
import Home from "@/views/Home.vue";
|
||||||
|
import Lobby from "@/views/Lobby.vue";
|
||||||
|
import SettingsView from "@/views/Settings.vue";
|
||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import Router from "vue-router";
|
import Router from "vue-router";
|
||||||
import Home from "@/views/Home.vue";
|
|
||||||
import DeckBuilder from "@/views/DeckBuilder.vue";
|
|
||||||
import GameView from "@/views/Game.vue";
|
|
||||||
import DraftView from "@/views/Draft.vue";
|
|
||||||
import Lobby from "@/views/Lobby.vue";
|
|
||||||
import RoomView from "@/views/Room.vue";
|
|
||||||
import SettingsView from "@/views/Settings.vue";
|
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
|
@ -46,9 +45,12 @@ export default new Router({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/room",
|
path: "/join/:id",
|
||||||
name: "room",
|
name: "lobby-join",
|
||||||
component: RoomView
|
component: Lobby,
|
||||||
|
meta: {
|
||||||
|
topnav: "Lobby"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
|
|
|
@ -1,28 +1,58 @@
|
||||||
import { ActionTree } from "vuex";
|
import { ChatMessage, Client, LocalClient, NetworkMessage, PeerClient, PeerServer } from "@/network";
|
||||||
|
import { ActionTree, Commit } from "vuex";
|
||||||
|
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { NetworkState, StartServerOptions, ConnectOptions } from "./types";
|
import { ConnectOptions, NetworkState, StartServerOptions } from "./types";
|
||||||
import { PeerServer, LocalClient, PeerClient } from "@/network";
|
|
||||||
|
function bindClientEvents(commit: Commit, client: Client) {
|
||||||
|
client.on("handshake", () => {
|
||||||
|
commit("playerListChanged", client.players);
|
||||||
|
});
|
||||||
|
client.on("player-joined", () => commit("playerListChanged", client.players));
|
||||||
|
client.on("player-left", () => commit("playerListChanged", client.players));
|
||||||
|
client.on("rename", () => commit("playerListChanged", client.players));
|
||||||
|
}
|
||||||
|
|
||||||
const actions: ActionTree<NetworkState, AppState> = {
|
const actions: ActionTree<NetworkState, AppState> = {
|
||||||
startServer({ commit }, options: StartServerOptions) {
|
startServer({ commit }, options: StartServerOptions) {
|
||||||
const local = new LocalClient(options.playerInfo);
|
const local = new LocalClient(options.playerInfo);
|
||||||
const server = new PeerServer(options.roomInfo, local, options._customPeer);
|
const server = new PeerServer(options.roomInfo, local, options._customPeer);
|
||||||
|
server.once("open", id => {
|
||||||
|
commit("serverAssignedID", id);
|
||||||
|
});
|
||||||
|
bindClientEvents(commit, local);
|
||||||
commit("becomeServer", { local, server });
|
commit("becomeServer", { local, server });
|
||||||
},
|
},
|
||||||
|
|
||||||
connect({ commit }, options: ConnectOptions) {
|
connect({ commit }, options: ConnectOptions) {
|
||||||
const client = new PeerClient(options.playerInfo, options._customPeer);
|
const client = new PeerClient(options.playerInfo, options._customPeer);
|
||||||
commit("becomeClient", { peer: client });
|
commit("becomeClient", { peer: client, id: options.serverID });
|
||||||
client.on("connected", () => {
|
client.on("connected", () => {
|
||||||
commit("connected");
|
commit("connectionStatusChanged", "connected");
|
||||||
});
|
});
|
||||||
client.on("disconnected", () => {
|
client.on("disconnected", () => {
|
||||||
commit("disconnected");
|
commit("connectionStatusChanged", "disconnected");
|
||||||
});
|
});
|
||||||
client.on("error", err => {
|
client.on("error", err => {
|
||||||
commit("connectionError", err);
|
commit("connectionError", err);
|
||||||
});
|
});
|
||||||
|
bindClientEvents(commit, client);
|
||||||
client.connect(options.serverID);
|
client.connect(options.serverID);
|
||||||
|
},
|
||||||
|
|
||||||
|
sendChatMessage({ commit, dispatch, getters }, message: ChatMessage) {
|
||||||
|
if (getters.connectionType == "none") {
|
||||||
|
throw new Error("not connected");
|
||||||
|
}
|
||||||
|
dispatch("sendMessage", message);
|
||||||
|
commit("receivedChatMessage", message);
|
||||||
|
},
|
||||||
|
|
||||||
|
sendMessage({ getters }, message: NetworkMessage) {
|
||||||
|
if (getters.connectionType == "none") {
|
||||||
|
throw new Error("not connected");
|
||||||
|
}
|
||||||
|
(getters.client as Client).send(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import { Client } from "@/network";
|
||||||
import { GetterTree } from "vuex";
|
import { GetterTree } from "vuex";
|
||||||
|
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
import { NetworkState } from "./types";
|
import { NetworkState } from "./types";
|
||||||
|
|
||||||
|
@ -13,8 +15,45 @@ const getters: GetterTree<NetworkState, AppState> = {
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sessionID(state): string | null {
|
||||||
|
return state.serverID;
|
||||||
|
},
|
||||||
|
|
||||||
|
client(state): Client | null {
|
||||||
|
switch (state.peerType) {
|
||||||
|
case "server":
|
||||||
|
return state.local;
|
||||||
|
case "client":
|
||||||
|
return state.peer;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
connectionType(state): "client" | "server" | "none" {
|
connectionType(state): "client" | "server" | "none" {
|
||||||
return state.peerType;
|
return state.peerType;
|
||||||
|
},
|
||||||
|
|
||||||
|
busy(state): boolean {
|
||||||
|
if (state.peerType == "client") {
|
||||||
|
if (state.connectionStatus == "connecting") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
inRoom(state): boolean {
|
||||||
|
if (state.peerType == "client") {
|
||||||
|
return state.connectionStatus == "connected";
|
||||||
|
}
|
||||||
|
if (state.peerType == "server") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
players(state): string[] {
|
||||||
|
return state.players;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,12 @@ const namespaced = true;
|
||||||
|
|
||||||
export const state: NetworkState = {
|
export const state: NetworkState = {
|
||||||
peerType: "none",
|
peerType: "none",
|
||||||
|
connectionStatus: null,
|
||||||
|
peer: null,
|
||||||
|
server: null,
|
||||||
|
local: null,
|
||||||
|
serverID: null,
|
||||||
|
players: [],
|
||||||
chatLog: []
|
chatLog: []
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,43 @@
|
||||||
|
import { ChatMessage, LocalClient, PeerClient, PeerServer } from "@/network";
|
||||||
|
import Vue from "vue";
|
||||||
import { MutationTree } from "vuex";
|
import { MutationTree } from "vuex";
|
||||||
import { NetworkState, ServerNetworkState, ClientNetworkState } from "./types";
|
|
||||||
import { LocalClient, PeerServer, PeerClient } from "@/network";
|
import { ClientNetworkState, ConnectionStatus, NetworkState, ServerNetworkState } from "./types";
|
||||||
|
|
||||||
const mutations: MutationTree<NetworkState> = {
|
const mutations: MutationTree<NetworkState> = {
|
||||||
becomeServer(state, payload: { local: LocalClient; server: PeerServer }) {
|
becomeServer(state, payload: { local: LocalClient; server: PeerServer }) {
|
||||||
state = {
|
state.peerType = "server";
|
||||||
...state,
|
state.players = [payload.local.name];
|
||||||
peerType: "server",
|
(state as ServerNetworkState).local = payload.local;
|
||||||
local: payload.local,
|
(state as ServerNetworkState).server = payload.server;
|
||||||
server: payload.server
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
becomeClient(state, payload: { peer: PeerClient }) {
|
becomeClient(state, payload: { peer: PeerClient; id: string }) {
|
||||||
state = {
|
state.peerType = "client";
|
||||||
...state,
|
(state as ClientNetworkState).connectionStatus = "connecting";
|
||||||
connectionStatus: "connecting",
|
(state as ClientNetworkState).peer = payload.peer;
|
||||||
peerType: "client",
|
(state as ClientNetworkState).serverID = payload.id;
|
||||||
peer: payload.peer
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
connected(state) {
|
connectionStatusChanged(state, status: ConnectionStatus) {
|
||||||
(state as ClientNetworkState).connectionStatus = "connected";
|
(state as ClientNetworkState).connectionStatus = status;
|
||||||
},
|
|
||||||
|
|
||||||
disconnected(state) {
|
|
||||||
(state as ClientNetworkState).connectionStatus = "disconnected";
|
|
||||||
},
|
},
|
||||||
|
|
||||||
connectionError(state, error) {
|
connectionError(state, error) {
|
||||||
(state as ClientNetworkState).connectionStatus = "error";
|
(state as ClientNetworkState).connectionStatus = "error";
|
||||||
(state as ClientNetworkState).connectionError = error;
|
(state as ClientNetworkState).connectionError = error;
|
||||||
|
},
|
||||||
|
|
||||||
|
receivedChatMessage(state, message: ChatMessage) {
|
||||||
|
state.chatLog.push(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
serverAssignedID(state, id: string) {
|
||||||
|
state.serverID = id;
|
||||||
|
},
|
||||||
|
|
||||||
|
playerListChanged(state, players: string[]) {
|
||||||
|
Vue.set(state, "players", players);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,30 @@
|
||||||
import {
|
import { ChatMessage, LocalClient, PeerClient, PeerMetadata, PeerServer, RoomInfo } from "@/network";
|
||||||
PeerClient,
|
|
||||||
PeerServer,
|
|
||||||
LocalClient,
|
|
||||||
RoomInfo,
|
|
||||||
PeerMetadata
|
|
||||||
} from "@/network";
|
|
||||||
import Peer from "peerjs";
|
import Peer from "peerjs";
|
||||||
|
|
||||||
export interface ChatMessage {
|
export type ConnectionStatus =
|
||||||
who: string;
|
| "connecting"
|
||||||
to: string;
|
| "connected"
|
||||||
message: string;
|
| "disconnected"
|
||||||
}
|
| "error";
|
||||||
|
|
||||||
export interface SharedNetworkState {
|
export interface SharedNetworkState {
|
||||||
chatLog: ChatMessage[];
|
chatLog: ChatMessage[];
|
||||||
|
serverID: string | null;
|
||||||
|
players: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NoNetworkState extends SharedNetworkState {
|
export interface NoNetworkState extends SharedNetworkState {
|
||||||
peerType: "none";
|
peerType: "none";
|
||||||
|
connectionStatus: null;
|
||||||
|
connectionError?: Error;
|
||||||
|
peer: null;
|
||||||
|
server: null;
|
||||||
|
local: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientNetworkState extends SharedNetworkState {
|
export interface ClientNetworkState extends SharedNetworkState {
|
||||||
peerType: "client";
|
peerType: "client";
|
||||||
connectionStatus: "connecting" | "connected" | "disconnected" | "error";
|
connectionStatus: ConnectionStatus;
|
||||||
connectionError?: Error;
|
connectionError?: Error;
|
||||||
peer: PeerClient;
|
peer: PeerClient;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,8 +54,6 @@ $border-opacity: 0.6;
|
||||||
|
|
||||||
.draftview {
|
.draftview {
|
||||||
background: url("../assets/images/backgrounds/draftbg.webp") center;
|
background: url("../assets/images/backgrounds/draftbg.webp") center;
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
display: grid;
|
display: grid;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
|
@ -1,235 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<section class="game">
|
<section class="game"></section>
|
||||||
<section class="topbar"></section>
|
|
||||||
<section class="board">
|
|
||||||
<Zone class="opp-zone" :cards="zones['opp-home']" id="opp-home" />
|
|
||||||
<section class="problems">
|
|
||||||
<section class="problem own-problem">
|
|
||||||
<Zone
|
|
||||||
class="opp-zone"
|
|
||||||
:cards="zones['own-problem-opp-zone']"
|
|
||||||
id="pown-opp-zone"
|
|
||||||
/>
|
|
||||||
<section class="problem-card opp-zone">
|
|
||||||
<CardImage
|
|
||||||
v-if="problems['own-problem']"
|
|
||||||
:id="problems['own-problem'].ID"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
<Zone
|
|
||||||
class="own-zone"
|
|
||||||
:cards="zones['own-problem-own-zone']"
|
|
||||||
id="pown-own-zone"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
<section class="problem opp-problem">
|
|
||||||
<Zone
|
|
||||||
class="opp-zone"
|
|
||||||
:cards="zones['opp-problem-opp-zone']"
|
|
||||||
id="popp-opp-zone"
|
|
||||||
/>
|
|
||||||
<section class="problem-card">
|
|
||||||
<CardImage
|
|
||||||
v-if="problems['opp-problem']"
|
|
||||||
:id="problems['opp-problem'].ID"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
<Zone
|
|
||||||
class="own-zone"
|
|
||||||
:cards="zones['opp-problem-own-zone']"
|
|
||||||
id="popp-own-zone"
|
|
||||||
/>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<Zone class="own-zone" :cards="zones['own-home']" id="own-home" />
|
|
||||||
</section>
|
|
||||||
<section class="hand">
|
|
||||||
<article
|
|
||||||
draggable="true"
|
|
||||||
v-for="(card, i) in hand"
|
|
||||||
:key="i + card.ID"
|
|
||||||
@dragstart="dragStartCard.bind(this, card)"
|
|
||||||
>
|
|
||||||
<CardImage :id="card.ID" />
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped></style>
|
||||||
$zone-gap: 5px;
|
|
||||||
$card-gap: 5px;
|
|
||||||
$side-min-size: 250px;
|
|
||||||
$top-bar-height: 50px;
|
|
||||||
$hand-min-height: 100px;
|
|
||||||
$hand-max-height: 150px;
|
|
||||||
|
|
||||||
.game {
|
|
||||||
background: url("../assets/images/backgrounds/boardbg.webp") center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
height: 100vh;
|
|
||||||
display: grid;
|
|
||||||
gap: $zone-gap;
|
|
||||||
grid:
|
|
||||||
$top-bar-height 2fr minmax($hand-min-height, $hand-max-height) / minmax(
|
|
||||||
$side-min-size,
|
|
||||||
1fr
|
|
||||||
)
|
|
||||||
5fr minmax($side-min-size, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar {
|
|
||||||
grid-column: 1/4;
|
|
||||||
grid-row: 1;
|
|
||||||
background: linear-gradient(
|
|
||||||
to bottom,
|
|
||||||
rgba(0, 0, 30, 0.9),
|
|
||||||
rgba(0, 0, 50, 0.6)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
.board {
|
|
||||||
grid-column: 2/3;
|
|
||||||
grid-row: 2;
|
|
||||||
display: grid;
|
|
||||||
grid: 1fr 2.5fr 1fr / 1fr;
|
|
||||||
max-height: 100vh;
|
|
||||||
|
|
||||||
.problems {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-flow: row;
|
|
||||||
|
|
||||||
.problem {
|
|
||||||
display: grid;
|
|
||||||
grid: 1.5fr 0.2fr 1fr 0.2fr 1.5fr / 1fr 1.5fr 1fr;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.own-zone {
|
|
||||||
grid-row: 4/6;
|
|
||||||
grid-column: 1/4;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
.opp-zone {
|
|
||||||
grid-row: 1/3;
|
|
||||||
grid-column: 1/4;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
.problem-card {
|
|
||||||
grid-row: 2/5;
|
|
||||||
grid-column: 2;
|
|
||||||
z-index: 1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
img {
|
|
||||||
height: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.hand {
|
|
||||||
grid-column: 2;
|
|
||||||
grid-row: 3;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(10px, max-content));
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
padding-right: 100px;
|
|
||||||
article {
|
|
||||||
margin: $card-gap;
|
|
||||||
margin-bottom: 0;
|
|
||||||
overflow-y: hidden;
|
|
||||||
cursor: grab;
|
|
||||||
transition: all 100ms;
|
|
||||||
min-width: 150px;
|
|
||||||
max-width: 200px;
|
|
||||||
&:hover {
|
|
||||||
margin-top: $card-gap - 30px;
|
|
||||||
}
|
|
||||||
&:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.opp-zone {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
.game {
|
|
||||||
grid-template-columns: $side-min-size 5fr;
|
|
||||||
}
|
|
||||||
.hand {
|
|
||||||
grid-column: 1/3;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
import { Card, cardImageURL, getCards } from "@/mlpccg";
|
|
||||||
import Zone from "@/components/GameBoard/Zone.vue";
|
|
||||||
import CardImage from "@/components/Cards/CardImage.vue";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {}
|
||||||
CardImage,
|
|
||||||
Zone
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
export default class GameView extends Vue {
|
export default class GameView extends Vue {}
|
||||||
private hand!: Card[];
|
|
||||||
private zones!: Record<string, Card[]>;
|
|
||||||
private problems!: Record<string, Card | null>;
|
|
||||||
|
|
||||||
private data() {
|
|
||||||
return {
|
|
||||||
hand: [],
|
|
||||||
zones: {
|
|
||||||
"own-home": [],
|
|
||||||
"opp-home": [],
|
|
||||||
"own-problem-own-zone": [],
|
|
||||||
"own-problem-opp-zone": [],
|
|
||||||
"opp-problem-own-zone": [],
|
|
||||||
"opp-problem-opp-zone": []
|
|
||||||
},
|
|
||||||
problems: {
|
|
||||||
"own-problem": null,
|
|
||||||
"opp-problem": null
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async mounted() {
|
|
||||||
const cards = await getCards({ Sets: ["FF"] });
|
|
||||||
this.hand = cards.slice(0, 9);
|
|
||||||
this.zones["own-home"] = cards.slice(0, 3);
|
|
||||||
this.zones["own-problem-opp-zone"] = cards.slice(10, 11);
|
|
||||||
this.zones["opp-home"] = cards.slice(20, 23);
|
|
||||||
console.log(cards);
|
|
||||||
const problem = cards.find(c => c.ID == "ff130");
|
|
||||||
this.problems["own-problem"] = problem!;
|
|
||||||
this.problems["opp-problem"] = problem!;
|
|
||||||
}
|
|
||||||
|
|
||||||
private imageURL(id: string) {
|
|
||||||
return cardImageURL(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private dragStartCard(card: Card, e: DragEvent) {
|
|
||||||
if (e.dataTransfer) {
|
|
||||||
e.dataTransfer.setData("id", card.ID);
|
|
||||||
e.dataTransfer.dropEffect = "move";
|
|
||||||
e.dataTransfer.effectAllowed = "all";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,26 +1,374 @@
|
||||||
<template>
|
<template>
|
||||||
<section class="lobby">
|
<section class="lobby">
|
||||||
<TopNav />
|
<TopNav />
|
||||||
|
<section v-if="!inRoom" class="body">
|
||||||
|
<section id="info">
|
||||||
|
<b-field label="Your name">
|
||||||
|
<b-input :disabled="busy" v-model="playerName"></b-input>
|
||||||
|
</b-field>
|
||||||
|
</section>
|
||||||
|
<section id="join">
|
||||||
|
<header>
|
||||||
|
<h1>Join someone's session</h1>
|
||||||
|
</header>
|
||||||
|
<b-field label="Session ID" class="only-full">
|
||||||
|
<b-input :disabled="busy" v-model="joinSessionID"></b-input>
|
||||||
|
</b-field>
|
||||||
|
<div class="center submit only-full">
|
||||||
|
<b-button
|
||||||
|
type="is-primary"
|
||||||
|
@click="join"
|
||||||
|
class="wide"
|
||||||
|
:disabled="busy || !canJoin"
|
||||||
|
>
|
||||||
|
Join
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
<div class="only-mobile">
|
||||||
|
<b-field class="full">
|
||||||
|
<b-input
|
||||||
|
:disabled="busy"
|
||||||
|
placeholder="Session ID"
|
||||||
|
v-model="joinSessionID"
|
||||||
|
></b-input>
|
||||||
|
<p class="control">
|
||||||
|
<b-button
|
||||||
|
type="is-primary"
|
||||||
|
@click="join"
|
||||||
|
:disabled="busy || !canJoin"
|
||||||
|
>
|
||||||
|
Join
|
||||||
|
</b-button>
|
||||||
|
</p>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="host">
|
||||||
|
<header>
|
||||||
|
<h1>Host a new session</h1>
|
||||||
|
</header>
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column">
|
||||||
|
<b-field label="Max players">
|
||||||
|
<b-numberinput
|
||||||
|
:disabled="busy"
|
||||||
|
controls-position="compact"
|
||||||
|
v-model="hostMaxPlayers"
|
||||||
|
min="2"
|
||||||
|
max="8"
|
||||||
|
></b-numberinput>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
<div class="column">
|
||||||
|
<b-field label="Password">
|
||||||
|
<b-input :disabled="busy" v-model="hostPassword"></b-input>
|
||||||
|
</b-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center">
|
||||||
|
<b-button
|
||||||
|
:disabled="busy"
|
||||||
|
type="is-primary"
|
||||||
|
@click="create"
|
||||||
|
class="wide"
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</b-button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="room" v-else>
|
||||||
|
<section class="info">
|
||||||
|
Invite your friends:
|
||||||
|
<span class="selectable"
|
||||||
|
><a :href="inviteLink">{{ inviteLink }}</a></span
|
||||||
|
>
|
||||||
|
</section>
|
||||||
|
<section class="chat"></section>
|
||||||
|
<section class="players">
|
||||||
|
<header>Players</header>
|
||||||
|
<ul>
|
||||||
|
<li class="selectable" v-for="player in players" :key="player">
|
||||||
|
{{ player }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section class="player-options">
|
||||||
|
<b-field>
|
||||||
|
<b-input :disabled="busy" v-model="wantedName"></b-input>
|
||||||
|
<p class="control">
|
||||||
|
<b-button
|
||||||
|
@click="changeName"
|
||||||
|
type="is-primary"
|
||||||
|
:disabled="!nameAvailable"
|
||||||
|
>Change name</b-button
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</b-field>
|
||||||
|
<b-button type="is-danger">Leave room</b-button>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import "@/assets/scss/_variables.scss";
|
||||||
|
|
||||||
.lobby {
|
.lobby {
|
||||||
background: url("../assets/images/backgrounds/menubg.webp") center;
|
background: url("../assets/images/backgrounds/menubg.webp") center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
padding: 10px;
|
||||||
|
padding-top: 0;
|
||||||
|
grid-template: 150px 1fr / 1fr 1fr;
|
||||||
|
|
||||||
|
#info {
|
||||||
|
grid-row: 1;
|
||||||
|
grid-column: 1 / end;
|
||||||
|
}
|
||||||
|
|
||||||
|
#join,
|
||||||
|
#host {
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#join {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
#host {
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin: 10px;
|
||||||
|
border: 1px solid rgba($white, 20%);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
header {
|
||||||
|
font-family: $fantasy;
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18pt;
|
||||||
|
}
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wide {
|
||||||
|
min-width: 30%;
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full {
|
||||||
|
width: 100%;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.center {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-btn {
|
||||||
|
flex: 1;
|
||||||
|
:global(.button) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.room {
|
||||||
|
padding: 10px 20px;
|
||||||
|
margin: 10px;
|
||||||
|
border: 1px solid rgba($white, 20%);
|
||||||
|
border-radius: 10px;
|
||||||
|
display: grid;
|
||||||
|
grid-template: 50px 1fr / 200px 1fr 300px;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
grid-column: 1 / max;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.players {
|
||||||
|
header {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
grid-row: 2 / max;
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat {
|
||||||
|
grid-row: 2 / max;
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-options {
|
||||||
|
grid-row: 2 / max;
|
||||||
|
grid-column: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.only-mobile {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectable {
|
||||||
|
user-select: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 500px) {
|
||||||
|
.only-full {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.only-mobile {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
section {
|
||||||
|
padding: 10px;
|
||||||
|
header h1 {
|
||||||
|
font-size: 14pt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.room {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
& > * {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
import TopNav from "@/components/Navigation/TopNav.vue";
|
import TopNav from "@/components/Navigation/TopNav.vue";
|
||||||
|
import { StartServerOptions, ConnectOptions } from "@/store/network/types";
|
||||||
|
import { Action, Getter } from "vuex-class";
|
||||||
|
import { Client, NetworkMessage } from "@/network";
|
||||||
|
|
||||||
|
const networkNS = { namespace: "network" };
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
TopNav
|
TopNav
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
export default class Lobby extends Vue {}
|
export default class Lobby extends Vue {
|
||||||
|
private playerName!: string;
|
||||||
|
private hostMaxPlayers!: number;
|
||||||
|
private hostPassword!: string;
|
||||||
|
private joinSessionID!: string;
|
||||||
|
private wantedName!: string;
|
||||||
|
|
||||||
|
@Action("startServer", networkNS)
|
||||||
|
private startServer!: (options: StartServerOptions) => void;
|
||||||
|
|
||||||
|
@Action("sendMessage", networkNS)
|
||||||
|
private sendMessage!: (message: NetworkMessage) => void;
|
||||||
|
|
||||||
|
@Action("connect", networkNS)
|
||||||
|
private connect!: (options: ConnectOptions) => void;
|
||||||
|
|
||||||
|
@Getter("inRoom", networkNS)
|
||||||
|
private inRoom!: boolean;
|
||||||
|
|
||||||
|
@Getter("busy", networkNS)
|
||||||
|
private busy!: boolean;
|
||||||
|
|
||||||
|
@Getter("sessionID", networkNS)
|
||||||
|
private sessionID!: string | null;
|
||||||
|
|
||||||
|
@Getter("players", networkNS)
|
||||||
|
private players!: string[];
|
||||||
|
|
||||||
|
private data() {
|
||||||
|
const playerName =
|
||||||
|
"Guest-" +
|
||||||
|
Math.random()
|
||||||
|
.toString()
|
||||||
|
.slice(2, 8);
|
||||||
|
return {
|
||||||
|
playerName,
|
||||||
|
hostMaxPlayers: 8,
|
||||||
|
hostPassword: "",
|
||||||
|
joinSessionID: "",
|
||||||
|
wantedName: playerName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private mounted() {
|
||||||
|
if ("id" in this.$route.params) {
|
||||||
|
this.joinSessionID = this.$route.params.id;
|
||||||
|
this.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async create() {
|
||||||
|
this.wantedName = this.playerName;
|
||||||
|
this.startServer({
|
||||||
|
playerInfo: {
|
||||||
|
name: this.playerName
|
||||||
|
},
|
||||||
|
roomInfo: {
|
||||||
|
max_players: this.hostMaxPlayers,
|
||||||
|
password: this.hostPassword
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async join() {
|
||||||
|
this.wantedName = this.playerName;
|
||||||
|
this.connect({
|
||||||
|
serverID: this.joinSessionID,
|
||||||
|
playerInfo: {
|
||||||
|
name: this.playerName
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async changeName() {
|
||||||
|
this.sendMessage({
|
||||||
|
kind: "rename",
|
||||||
|
oldname: this.playerName,
|
||||||
|
newname: this.wantedName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private get canJoin(): boolean {
|
||||||
|
return this.joinSessionID != "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private get inviteLink(): string {
|
||||||
|
let subpath = "";
|
||||||
|
const joinIndex = location.pathname.indexOf("/join");
|
||||||
|
if (joinIndex > 0) {
|
||||||
|
subpath = location.pathname.substring(0, joinIndex);
|
||||||
|
}
|
||||||
|
const lobbyIndex = location.pathname.indexOf("/lobby");
|
||||||
|
if (lobbyIndex > 0) {
|
||||||
|
subpath = location.pathname.substring(0, lobbyIndex);
|
||||||
|
}
|
||||||
|
return `${location.origin}${subpath}/join/${this.sessionID}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get nameAvailable(): boolean {
|
||||||
|
return this.wantedName != "" && !this.players.includes(this.wantedName);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
<template>
|
|
||||||
<section class="room"></section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Component, Vue } from "vue-property-decorator";
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
components: {}
|
|
||||||
})
|
|
||||||
export default class RoomView extends Vue {}
|
|
||||||
</script>
|
|
|
@ -1,5 +1,12 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
publicPath: process.env.SUBPATH ? process.env.SUBPATH : "",
|
configureWebpack: {
|
||||||
|
devServer: {
|
||||||
|
disableHostCheck: true,
|
||||||
|
host: "0.0.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
publicPath: process.env.SUBPATH ? process.env.SUBPATH : "/",
|
||||||
|
|
||||||
pluginOptions: {
|
pluginOptions: {
|
||||||
gitDescribe: {
|
gitDescribe: {
|
||||||
|
|
Loading…
Reference in a new issue