[add] first templates
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
node_modules
 | 
			
		||||
dist
 | 
			
		||||
.DS_STORE
 | 
			
		||||
*.meta
 | 
			
		||||
							
								
								
									
										11
									
								
								.mocharc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.mocharc.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
    require: [
 | 
			
		||||
        'ts-node/register',
 | 
			
		||||
    ],
 | 
			
		||||
    timeout: 999999,
 | 
			
		||||
    exit: true,
 | 
			
		||||
    spec: [
 | 
			
		||||
        './test/**/*.test.ts'
 | 
			
		||||
    ],
 | 
			
		||||
    'preserve-symlinks': true
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
{
 | 
			
		||||
    "configurations": [
 | 
			
		||||
        {
 | 
			
		||||
            "type": "node",
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "name": "mocha current file",
 | 
			
		||||
            "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha",
 | 
			
		||||
            "args": [
 | 
			
		||||
                "${file}"
 | 
			
		||||
            ],
 | 
			
		||||
            "internalConsoleOptions": "openOnSessionStart",
 | 
			
		||||
            "cwd": "${workspaceFolder}"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "type": "node",
 | 
			
		||||
            "request": "launch",
 | 
			
		||||
            "name": "ts-node current file",
 | 
			
		||||
            "protocol": "inspector",
 | 
			
		||||
            "args": [
 | 
			
		||||
                "${relativeFile}"
 | 
			
		||||
            ],
 | 
			
		||||
            "cwd": "${workspaceRoot}",
 | 
			
		||||
            "runtimeArgs": [
 | 
			
		||||
                "-r",
 | 
			
		||||
                "ts-node/register"
 | 
			
		||||
            ],
 | 
			
		||||
            "internalConsoleOptions": "openOnSessionStart"
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
{
 | 
			
		||||
    "typescript.tsdk": "node_modules\\typescript\\lib"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										30
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								Dockerfile
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
			
		||||
FROM node
 | 
			
		||||
 | 
			
		||||
# 使用淘宝 NPM 镜像(国内机器构建推荐启用)
 | 
			
		||||
# RUN npm config set registry https://registry.npm.taobao.org/
 | 
			
		||||
 | 
			
		||||
# npm install
 | 
			
		||||
ADD package*.json /src/
 | 
			
		||||
WORKDIR /src
 | 
			
		||||
RUN npm i
 | 
			
		||||
 | 
			
		||||
# build
 | 
			
		||||
ADD . /src
 | 
			
		||||
RUN npm run build
 | 
			
		||||
 | 
			
		||||
# clean
 | 
			
		||||
RUN npm prune --production
 | 
			
		||||
 | 
			
		||||
# move
 | 
			
		||||
RUN rm -rf /app \
 | 
			
		||||
    && mv dist /app \
 | 
			
		||||
    && mv node_modules /app/ \
 | 
			
		||||
    && rm -rf /src
 | 
			
		||||
 | 
			
		||||
# ENV
 | 
			
		||||
ENV NODE_ENV production
 | 
			
		||||
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
CMD node index.js
 | 
			
		||||
							
								
								
									
										49
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,49 @@
 | 
			
		||||
# TSRPC Server
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
### Local dev server
 | 
			
		||||
 | 
			
		||||
Dev server would restart automatically when code changed.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
npm run dev
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Run unit Test
 | 
			
		||||
Execute `npm run dev` first, then execute:
 | 
			
		||||
```
 | 
			
		||||
npm run test
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Build
 | 
			
		||||
```
 | 
			
		||||
npm run build
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Additional Scripts
 | 
			
		||||
 | 
			
		||||
### Generate API document
 | 
			
		||||
 | 
			
		||||
Generate API document in swagger/openapi and markdown format.
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
npm run doc
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Generate ServiceProto
 | 
			
		||||
```
 | 
			
		||||
npm run proto
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Generate API templates
 | 
			
		||||
```
 | 
			
		||||
npm run api
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Manually sync shared code
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
npm run sync
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										3672
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3672
									
								
								package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										27
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "backend-.",
 | 
			
		||||
  "version": "0.1.0",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "proto": "tsrpc proto",
 | 
			
		||||
    "sync": "tsrpc link",
 | 
			
		||||
    "api": "tsrpc api",
 | 
			
		||||
    "doc": "tsrpc doc",
 | 
			
		||||
    "dev": "tsrpc dev",
 | 
			
		||||
    "test": "mocha test/**/*.test.ts",
 | 
			
		||||
    "build": "tsrpc build"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/mocha": "^8.2.3",
 | 
			
		||||
    "@types/node": "^15.14.9",
 | 
			
		||||
    "mocha": "^9.1.3",
 | 
			
		||||
    "onchange": "^7.1.0",
 | 
			
		||||
    "ts-node": "^10.4.0",
 | 
			
		||||
    "tsrpc-cli": "^2.2.2",
 | 
			
		||||
    "typescript": "^4.5.2"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "tsrpc": "^3.1.3"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/api/ApiJoin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/api/ApiJoin.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import { ApiCallWs } from "tsrpc";
 | 
			
		||||
import { roomInstance } from "..";
 | 
			
		||||
import { ReqJoin, ResJoin } from "../shared/protocols/PtlJoin";
 | 
			
		||||
 | 
			
		||||
export async function ApiJoin(call: ApiCallWs<ReqJoin, ResJoin>) {
 | 
			
		||||
    let playerId = roomInstance.join(call.req, call.conn);
 | 
			
		||||
 | 
			
		||||
    call.succ({
 | 
			
		||||
        playerId: playerId,
 | 
			
		||||
        gameState: roomInstance.gameSystem.state
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import 'k8w-extend-native';
 | 
			
		||||
import * as path from "path";
 | 
			
		||||
import { WsConnection, WsServer } from "tsrpc";
 | 
			
		||||
import { Room } from './models/Room';
 | 
			
		||||
import { serviceProto, ServiceType } from './shared/protocols/serviceProto';
 | 
			
		||||
 | 
			
		||||
// 创建 TSRPC WebSocket Server
 | 
			
		||||
export const server = new WsServer(serviceProto, {
 | 
			
		||||
    port: 3000,
 | 
			
		||||
    json: true
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 断开连接后退出房间
 | 
			
		||||
server.flows.postDisconnectFlow.push(v => {
 | 
			
		||||
    let conn = v.conn as WsConnection<ServiceType>;
 | 
			
		||||
    if (conn.playerId) {
 | 
			
		||||
        roomInstance.leave(conn.playerId, conn);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return v;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const roomInstance = new Room(server);
 | 
			
		||||
 | 
			
		||||
// 初始化
 | 
			
		||||
async function init() {
 | 
			
		||||
    // 挂载 API 接口
 | 
			
		||||
    await server.autoImplementApi(path.resolve(__dirname, 'api'));
 | 
			
		||||
 | 
			
		||||
    // TODO
 | 
			
		||||
    // Prepare something... (e.g. connect the db)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 启动入口点
 | 
			
		||||
async function main() {
 | 
			
		||||
    await init();
 | 
			
		||||
    await server.start();
 | 
			
		||||
}
 | 
			
		||||
main();
 | 
			
		||||
							
								
								
									
										101
									
								
								src/models/Room.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/models/Room.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
			
		||||
import { WsConnection, WsServer } from "tsrpc";
 | 
			
		||||
import { gameConfig } from "../shared/game/gameConfig";
 | 
			
		||||
import { GameSystem, GameSystemInput, PlayerJoin } from "../shared/game/GameSystem";
 | 
			
		||||
import { ReqJoin } from "../shared/protocols/PtlJoin";
 | 
			
		||||
import { ServiceType } from "../shared/protocols/serviceProto";
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 服务端 - 房间 - 逻辑系统
 | 
			
		||||
 */
 | 
			
		||||
export class Room {
 | 
			
		||||
 | 
			
		||||
    // 帧同步频率,次数/秒
 | 
			
		||||
    syncRate = gameConfig.syncRate;
 | 
			
		||||
    nextPlayerId = 1;
 | 
			
		||||
 | 
			
		||||
    gameSystem = new GameSystem();
 | 
			
		||||
 | 
			
		||||
    server: WsServer<ServiceType>;
 | 
			
		||||
    conns: WsConnection<ServiceType>[] = [];
 | 
			
		||||
    pendingInputs: GameSystemInput[] = [];
 | 
			
		||||
    playerLastSn: { [playerId: number]: number | undefined } = {};
 | 
			
		||||
    lastSyncTime?: number;
 | 
			
		||||
 | 
			
		||||
    constructor(server: WsServer<ServiceType>) {
 | 
			
		||||
        this.server = server;
 | 
			
		||||
        setInterval(() => { this.sync() }, 1000 / this.syncRate);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** 加入房间 */
 | 
			
		||||
    join(req: ReqJoin, conn: WsConnection<ServiceType>) {
 | 
			
		||||
        let input: PlayerJoin = {
 | 
			
		||||
            type: 'PlayerJoin',
 | 
			
		||||
            playerId: this.nextPlayerId++,
 | 
			
		||||
            // 初始位置随机
 | 
			
		||||
            pos: {
 | 
			
		||||
                x: Math.random() * 10 - 5,
 | 
			
		||||
                y: Math.random() * 10 - 5
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.applyInput(input);
 | 
			
		||||
 | 
			
		||||
        this.conns.push(conn);
 | 
			
		||||
        conn.playerId = input.playerId;
 | 
			
		||||
        conn.listenMsg('client/ClientInput', call => {
 | 
			
		||||
            this.playerLastSn[input.playerId] = call.msg.sn;
 | 
			
		||||
            call.msg.inputs.forEach(v => {
 | 
			
		||||
                this.applyInput({
 | 
			
		||||
                    ...v,
 | 
			
		||||
                    playerId: input.playerId
 | 
			
		||||
                });
 | 
			
		||||
            })
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return input.playerId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    applyInput(input: GameSystemInput) {
 | 
			
		||||
        this.pendingInputs.push(input);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sync() {
 | 
			
		||||
        let inputs = this.pendingInputs;
 | 
			
		||||
        this.pendingInputs = [];
 | 
			
		||||
 | 
			
		||||
        // Apply inputs
 | 
			
		||||
        inputs.forEach(v => {
 | 
			
		||||
            this.gameSystem.applyInput(v)
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // Apply TimePast
 | 
			
		||||
        let now = process.uptime() * 1000;
 | 
			
		||||
        this.applyInput({
 | 
			
		||||
            type: 'TimePast',
 | 
			
		||||
            dt: now - (this.lastSyncTime ?? now)
 | 
			
		||||
        });
 | 
			
		||||
        this.lastSyncTime = now;
 | 
			
		||||
 | 
			
		||||
        // 发送同步帧
 | 
			
		||||
        this.conns.forEach(v => {
 | 
			
		||||
            v.sendMsg('server/Frame', {
 | 
			
		||||
                inputs: inputs,
 | 
			
		||||
                lastSn: this.playerLastSn[v.playerId!]
 | 
			
		||||
            })
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** 离开房间 */
 | 
			
		||||
    leave(playerId: number, conn: WsConnection<ServiceType>) {
 | 
			
		||||
        this.conns.removeOne(v => v.playerId === playerId);
 | 
			
		||||
        this.applyInput({
 | 
			
		||||
            type: 'PlayerLeave',
 | 
			
		||||
            playerId: playerId
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare module 'tsrpc' {
 | 
			
		||||
    export interface WsConnection {
 | 
			
		||||
        playerId?: number;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										143
									
								
								src/shared/game/GameSystem.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/shared/game/GameSystem.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
import { gameConfig } from "./gameConfig";
 | 
			
		||||
import { ArrowState } from "./state/ArrowState";
 | 
			
		||||
import { PlayerState } from "./state/PlayerState";
 | 
			
		||||
 | 
			
		||||
// 状态定义
 | 
			
		||||
export interface GameSystemState {
 | 
			
		||||
    // 当前的时间(游戏时间)
 | 
			
		||||
    now: number,
 | 
			
		||||
    // 玩家
 | 
			
		||||
    players: PlayerState[],
 | 
			
		||||
    // 飞行中的箭矢
 | 
			
		||||
    arrows: ArrowState[],
 | 
			
		||||
    // 箭矢的 ID 生成
 | 
			
		||||
    nextArrowId: number
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 前后端复用的状态计算模块
 | 
			
		||||
 */
 | 
			
		||||
export class GameSystem {
 | 
			
		||||
 | 
			
		||||
    // 当前状态
 | 
			
		||||
    private _state: GameSystemState = {
 | 
			
		||||
        now: 0,
 | 
			
		||||
        players: [],
 | 
			
		||||
        arrows: [],
 | 
			
		||||
        nextArrowId: 1
 | 
			
		||||
    }
 | 
			
		||||
    get state(): Readonly<GameSystemState> {
 | 
			
		||||
        return this._state
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 重设状态
 | 
			
		||||
    reset(state: GameSystemState) {
 | 
			
		||||
        this._state = Object.merge({}, state);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 应用输入,计算状态变更
 | 
			
		||||
    applyInput(input: GameSystemInput) {
 | 
			
		||||
        if (input.type === 'PlayerMove') {
 | 
			
		||||
            let player = this._state.players.find(v => v.id === input.playerId);
 | 
			
		||||
            if (!player) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (player.dizzyEndTime && player.dizzyEndTime > this._state.now) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            player.pos.x += input.speed.x * input.dt;
 | 
			
		||||
            player.pos.y += input.speed.y * input.dt;
 | 
			
		||||
        }
 | 
			
		||||
        else if (input.type === 'PlayerAttack') {
 | 
			
		||||
            let player = this._state.players.find(v => v.id === input.playerId);
 | 
			
		||||
            if (player) {
 | 
			
		||||
                let newArrow: ArrowState = {
 | 
			
		||||
                    id: this._state.nextArrowId++,
 | 
			
		||||
                    fromPlayerId: input.playerId,
 | 
			
		||||
                    targetPos: { ...input.targetPos },
 | 
			
		||||
                    targetTime: input.targetTime
 | 
			
		||||
                };
 | 
			
		||||
                this._state.arrows.push(newArrow);
 | 
			
		||||
                this.onNewArrow.forEach(v => v(newArrow));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else if (input.type === 'PlayerJoin') {
 | 
			
		||||
            this.state.players.push({
 | 
			
		||||
                id: input.playerId,
 | 
			
		||||
                pos: { ...input.pos }
 | 
			
		||||
            })
 | 
			
		||||
        }
 | 
			
		||||
        else if (input.type === 'PlayerLeave') {
 | 
			
		||||
            this.state.players.remove(v => v.id === input.playerId);
 | 
			
		||||
        }
 | 
			
		||||
        else if (input.type === 'TimePast') {
 | 
			
		||||
            this._state.now += input.dt;
 | 
			
		||||
 | 
			
		||||
            // 落地的 Arrow
 | 
			
		||||
            for (let i = this._state.arrows.length - 1; i > -1; --i) {
 | 
			
		||||
                let arrow = this._state.arrows[i];
 | 
			
		||||
                if (arrow.targetTime <= this._state.now) {
 | 
			
		||||
                    // 伤害判定
 | 
			
		||||
                    let damagedPlayers = this._state.players.filter(v => {
 | 
			
		||||
                        return (v.pos.x - arrow.targetPos.x) * (v.pos.x - arrow.targetPos.x) + (v.pos.y - arrow.targetPos.y) * (v.pos.y - arrow.targetPos.y) <= gameConfig.arrowAttackRadius * gameConfig.arrowAttackRadius
 | 
			
		||||
                    });
 | 
			
		||||
                    damagedPlayers.forEach(p => {
 | 
			
		||||
                        // 设置击晕状态
 | 
			
		||||
                        p.dizzyEndTime = this._state.now + gameConfig.arrowDizzyTime;
 | 
			
		||||
 | 
			
		||||
                        // Event
 | 
			
		||||
                    })
 | 
			
		||||
 | 
			
		||||
                    this._state.arrows.splice(i, 1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * 事件
 | 
			
		||||
     * 某些转瞬即逝的事件,可能不会直观的体现在前后两帧状态的变化中,但表面层又需要知晓。
 | 
			
		||||
     * 例如一颗狙击枪的子弹,在少于一帧的时间内创建和销毁,前后两帧的状态中都不包含这颗子弹;但表现层却需要绘制出子弹的弹道。
 | 
			
		||||
     * 此时,可以通过事件的方式通知表现层。
 | 
			
		||||
     */
 | 
			
		||||
    // 发射箭矢
 | 
			
		||||
    onNewArrow: ((arrow: ArrowState) => void)[] = [];
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PlayerMove {
 | 
			
		||||
    type: 'PlayerMove',
 | 
			
		||||
    playerId: number,
 | 
			
		||||
    speed: { x: number, y: number },
 | 
			
		||||
    // 移动的时间 (秒)
 | 
			
		||||
    dt: number,
 | 
			
		||||
}
 | 
			
		||||
export interface PlayerAttack {
 | 
			
		||||
    type: 'PlayerAttack',
 | 
			
		||||
    playerId: number,
 | 
			
		||||
    // 落点坐标
 | 
			
		||||
    targetPos: { x: number, y: number },
 | 
			
		||||
    // 落点时间(游戏时间)
 | 
			
		||||
    targetTime: number
 | 
			
		||||
}
 | 
			
		||||
export interface PlayerJoin {
 | 
			
		||||
    type: 'PlayerJoin',
 | 
			
		||||
    playerId: number,
 | 
			
		||||
    pos: { x: number, y: number }
 | 
			
		||||
}
 | 
			
		||||
export interface PlayerLeave {
 | 
			
		||||
    type: 'PlayerLeave',
 | 
			
		||||
    playerId: number
 | 
			
		||||
}
 | 
			
		||||
// 时间流逝
 | 
			
		||||
export interface TimePast {
 | 
			
		||||
    type: 'TimePast',
 | 
			
		||||
    dt: number
 | 
			
		||||
}
 | 
			
		||||
// 输入定义
 | 
			
		||||
export type GameSystemInput = PlayerMove
 | 
			
		||||
    | PlayerAttack
 | 
			
		||||
    | PlayerJoin
 | 
			
		||||
    | PlayerLeave
 | 
			
		||||
    | TimePast;
 | 
			
		||||
							
								
								
									
										14
									
								
								src/shared/game/gameConfig.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/shared/game/gameConfig.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
export const gameConfig = {
 | 
			
		||||
    syncRate: 10,
 | 
			
		||||
 | 
			
		||||
    moveSpeed: 10,
 | 
			
		||||
 | 
			
		||||
    // 箭矢飞行时间(毫秒)
 | 
			
		||||
    arrowFlyTime: 500,
 | 
			
		||||
    // 箭矢投掷距离
 | 
			
		||||
    arrowDistance: 8,
 | 
			
		||||
    // 箭矢落地命中判定半径
 | 
			
		||||
    arrowAttackRadius: 2,
 | 
			
		||||
    // 被箭矢几种后的晕眩时间(毫秒)
 | 
			
		||||
    arrowDizzyTime: 1000
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/shared/game/state/ArrowState.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/shared/game/state/ArrowState.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
export type ArrowState = {
 | 
			
		||||
    id: number,
 | 
			
		||||
    // 谁发出的箭
 | 
			
		||||
    fromPlayerId: number,
 | 
			
		||||
    // 落地时间(游戏时间)
 | 
			
		||||
    targetTime: number,
 | 
			
		||||
    // 落点位置(游戏位置)
 | 
			
		||||
    targetPos: { x: number, y: number }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/shared/game/state/PlayerState.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/shared/game/state/PlayerState.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
export interface PlayerState {
 | 
			
		||||
    id: number,
 | 
			
		||||
    // 位置
 | 
			
		||||
    pos: { x: number, y: number },
 | 
			
		||||
    // 晕眩结束时间
 | 
			
		||||
    dizzyEndTime?: number,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/shared/protocols/PtlJoin.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/shared/protocols/PtlJoin.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
import { GameSystemState } from "../game/GameSystem";
 | 
			
		||||
 | 
			
		||||
/** 加入房间 */
 | 
			
		||||
export interface ReqJoin {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ResJoin {
 | 
			
		||||
    /** 加入房间后,自己的 ID */
 | 
			
		||||
    playerId: number,
 | 
			
		||||
    /** 状态同步:一次性同步当前状态 */
 | 
			
		||||
    gameState: GameSystemState
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// export const conf = {}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/shared/protocols/client/MsgClientInput.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/shared/protocols/client/MsgClientInput.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
import { PlayerAttack, PlayerMove } from "../../game/GameSystem";
 | 
			
		||||
 | 
			
		||||
/** 发送自己的输入 */
 | 
			
		||||
export interface MsgClientInput {
 | 
			
		||||
    sn: number,
 | 
			
		||||
    inputs: ClientInput[]
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type ClientInput = Omit<PlayerMove, 'playerId'> | Omit<PlayerAttack, 'playerId'>;
 | 
			
		||||
							
								
								
									
										11
									
								
								src/shared/protocols/server/MsgFrame.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/shared/protocols/server/MsgFrame.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
import { GameSystemInput } from "../../game/GameSystem";
 | 
			
		||||
 | 
			
		||||
/** 
 | 
			
		||||
 * 服务端定期广播的同步帧
 | 
			
		||||
 * 包含了这一段期间所有输入
 | 
			
		||||
 */
 | 
			
		||||
export interface MsgFrame {
 | 
			
		||||
    inputs: GameSystemInput[],
 | 
			
		||||
    /** 当前用户提交的,经服务端确认的最后一条输入的 SN */
 | 
			
		||||
    lastSn?: number
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										496
									
								
								src/shared/protocols/serviceProto.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										496
									
								
								src/shared/protocols/serviceProto.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,496 @@
 | 
			
		||||
import { ServiceProto } from 'tsrpc-proto';
 | 
			
		||||
import { MsgClientInput } from './client/MsgClientInput';
 | 
			
		||||
import { ReqJoin, ResJoin } from './PtlJoin';
 | 
			
		||||
import { MsgFrame } from './server/MsgFrame';
 | 
			
		||||
 | 
			
		||||
export interface ServiceType {
 | 
			
		||||
    api: {
 | 
			
		||||
        "Join": {
 | 
			
		||||
            req: ReqJoin,
 | 
			
		||||
            res: ResJoin
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    msg: {
 | 
			
		||||
        "client/ClientInput": MsgClientInput,
 | 
			
		||||
        "server/Frame": MsgFrame
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const serviceProto: ServiceProto<ServiceType> = {
 | 
			
		||||
    "services": [
 | 
			
		||||
        {
 | 
			
		||||
            "id": 0,
 | 
			
		||||
            "name": "client/ClientInput",
 | 
			
		||||
            "type": "msg"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "id": 1,
 | 
			
		||||
            "name": "Join",
 | 
			
		||||
            "type": "api"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "id": 2,
 | 
			
		||||
            "name": "server/Frame",
 | 
			
		||||
            "type": "msg"
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "types": {
 | 
			
		||||
        "client/MsgClientInput/MsgClientInput": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "sn",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "inputs",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Array",
 | 
			
		||||
                        "elementType": {
 | 
			
		||||
                            "type": "Reference",
 | 
			
		||||
                            "target": "client/MsgClientInput/ClientInput"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "client/MsgClientInput/ClientInput": {
 | 
			
		||||
            "type": "Union",
 | 
			
		||||
            "members": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "target": {
 | 
			
		||||
                            "type": "Reference",
 | 
			
		||||
                            "target": "../game/GameSystem/PlayerMove"
 | 
			
		||||
                        },
 | 
			
		||||
                        "keys": [
 | 
			
		||||
                            "playerId"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "type": "Omit"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "target": {
 | 
			
		||||
                            "type": "Reference",
 | 
			
		||||
                            "target": "../game/GameSystem/PlayerAttack"
 | 
			
		||||
                        },
 | 
			
		||||
                        "keys": [
 | 
			
		||||
                            "playerId"
 | 
			
		||||
                        ],
 | 
			
		||||
                        "type": "Omit"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "../game/GameSystem/PlayerMove": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "type",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "literal": "PlayerMove"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "playerId",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 2,
 | 
			
		||||
                    "name": "speed",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Interface",
 | 
			
		||||
                        "properties": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 0,
 | 
			
		||||
                                "name": "x",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 1,
 | 
			
		||||
                                "name": "y",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 3,
 | 
			
		||||
                    "name": "dt",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "../game/GameSystem/PlayerAttack": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "type",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "literal": "PlayerAttack"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "playerId",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 2,
 | 
			
		||||
                    "name": "targetPos",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Interface",
 | 
			
		||||
                        "properties": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 0,
 | 
			
		||||
                                "name": "x",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 1,
 | 
			
		||||
                                "name": "y",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 3,
 | 
			
		||||
                    "name": "targetTime",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "PtlJoin/ReqJoin": {
 | 
			
		||||
            "type": "Interface"
 | 
			
		||||
        },
 | 
			
		||||
        "PtlJoin/ResJoin": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "playerId",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "gameState",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Reference",
 | 
			
		||||
                        "target": "../game/GameSystem/GameSystemState"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "../game/GameSystem/GameSystemState": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "now",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "players",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Array",
 | 
			
		||||
                        "elementType": {
 | 
			
		||||
                            "type": "Reference",
 | 
			
		||||
                            "target": "../game/state/PlayerState/PlayerState"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 2,
 | 
			
		||||
                    "name": "arrows",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Array",
 | 
			
		||||
                        "elementType": {
 | 
			
		||||
                            "type": "Reference",
 | 
			
		||||
                            "target": "../game/state/ArrowState/ArrowState"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 3,
 | 
			
		||||
                    "name": "nextArrowId",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "../game/state/PlayerState/PlayerState": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "id",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "pos",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Interface",
 | 
			
		||||
                        "properties": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 0,
 | 
			
		||||
                                "name": "x",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 1,
 | 
			
		||||
                                "name": "y",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 2,
 | 
			
		||||
                    "name": "dizzyEndTime",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    },
 | 
			
		||||
                    "optional": true
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "../game/state/ArrowState/ArrowState": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "id",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "fromPlayerId",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 2,
 | 
			
		||||
                    "name": "targetTime",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 3,
 | 
			
		||||
                    "name": "targetPos",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Interface",
 | 
			
		||||
                        "properties": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 0,
 | 
			
		||||
                                "name": "x",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 1,
 | 
			
		||||
                                "name": "y",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "server/MsgFrame/MsgFrame": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "inputs",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Array",
 | 
			
		||||
                        "elementType": {
 | 
			
		||||
                            "type": "Reference",
 | 
			
		||||
                            "target": "../game/GameSystem/GameSystemInput"
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "lastSn",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    },
 | 
			
		||||
                    "optional": true
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "../game/GameSystem/GameSystemInput": {
 | 
			
		||||
            "type": "Union",
 | 
			
		||||
            "members": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Reference",
 | 
			
		||||
                        "target": "../game/GameSystem/PlayerMove"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Reference",
 | 
			
		||||
                        "target": "../game/GameSystem/PlayerAttack"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 2,
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Reference",
 | 
			
		||||
                        "target": "../game/GameSystem/PlayerJoin"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 3,
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Reference",
 | 
			
		||||
                        "target": "../game/GameSystem/PlayerLeave"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 4,
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Reference",
 | 
			
		||||
                        "target": "../game/GameSystem/TimePast"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "../game/GameSystem/PlayerJoin": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "type",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "literal": "PlayerJoin"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "playerId",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 2,
 | 
			
		||||
                    "name": "pos",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Interface",
 | 
			
		||||
                        "properties": [
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 0,
 | 
			
		||||
                                "name": "x",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
                            {
 | 
			
		||||
                                "id": 1,
 | 
			
		||||
                                "name": "y",
 | 
			
		||||
                                "type": {
 | 
			
		||||
                                    "type": "Number"
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        ]
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "../game/GameSystem/PlayerLeave": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "type",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "literal": "PlayerLeave"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "playerId",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        },
 | 
			
		||||
        "../game/GameSystem/TimePast": {
 | 
			
		||||
            "type": "Interface",
 | 
			
		||||
            "properties": [
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 0,
 | 
			
		||||
                    "name": "type",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "literal": "TimePast"
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "id": 1,
 | 
			
		||||
                    "name": "dt",
 | 
			
		||||
                    "type": {
 | 
			
		||||
                        "type": "Number"
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										39
									
								
								test/api/ApiSend.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								test/api/ApiSend.test.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import assert from "assert";
 | 
			
		||||
import { TsrpcError, WsClient } from "tsrpc";
 | 
			
		||||
import { serviceProto, ServiceType } from "../../src/shared/protocols/serviceProto";
 | 
			
		||||
 | 
			
		||||
// 1. EXECUTE `npm run dev` TO START A LOCAL DEV SERVER
 | 
			
		||||
// 2. EXECUTE `npm test` TO START UNIT TEST
 | 
			
		||||
 | 
			
		||||
describe("ApiSend", function (): void {
 | 
			
		||||
    let client: WsClient<ServiceType> = new WsClient(serviceProto, {
 | 
			
		||||
        server: "ws://127.0.0.1:3000",
 | 
			
		||||
        logger: console
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    before(async function (): Promise<void> {
 | 
			
		||||
        let res: any = await client.connect();
 | 
			
		||||
        assert.strictEqual(res.isSucc, true, "Failed to connect to server, have you executed `npm run dev` already?");
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Success", async function (): Promise<void> {
 | 
			
		||||
        let ret: any = await client.callApi("Send", {
 | 
			
		||||
            content: "Test"
 | 
			
		||||
        });
 | 
			
		||||
        assert.ok(ret.isSucc)
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Check content is empty", async function (): Promise<void> {
 | 
			
		||||
        let ret: any = await client.callApi("Send", {
 | 
			
		||||
            content: ""
 | 
			
		||||
        });
 | 
			
		||||
        assert.deepStrictEqual(ret, {
 | 
			
		||||
            isSucc: false,
 | 
			
		||||
            err: new TsrpcError("Content is empty")
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    after(async function () {
 | 
			
		||||
        await client.disconnect();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										15
									
								
								test/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								test/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "es2018"
 | 
			
		||||
    ],
 | 
			
		||||
    "module": "commonjs",
 | 
			
		||||
    "target": "es2018",
 | 
			
		||||
    "outDir": "dist",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "forceConsistentCasingInFileNames": true,
 | 
			
		||||
    "moduleResolution": "node"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
			
		||||
{
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "es2018"
 | 
			
		||||
    ],
 | 
			
		||||
    "module": "commonjs",
 | 
			
		||||
    "target": "es2018",
 | 
			
		||||
    "outDir": "dist",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "forceConsistentCasingInFileNames": true,
 | 
			
		||||
    "moduleResolution": "node"
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "src"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										116
									
								
								tslint.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								tslint.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
{
 | 
			
		||||
    "defaultSeverity": "warning",
 | 
			
		||||
    "rules": {
 | 
			
		||||
        "ban": [
 | 
			
		||||
            true,
 | 
			
		||||
            [
 | 
			
		||||
                "_",
 | 
			
		||||
                "extend"
 | 
			
		||||
            ],
 | 
			
		||||
            [
 | 
			
		||||
                "_",
 | 
			
		||||
                "isNull"
 | 
			
		||||
            ],
 | 
			
		||||
            [
 | 
			
		||||
                "_",
 | 
			
		||||
                "isDefined"
 | 
			
		||||
            ]
 | 
			
		||||
        ],
 | 
			
		||||
        "class-name": false,
 | 
			
		||||
        "comment-format": [
 | 
			
		||||
            true,
 | 
			
		||||
            "check-space"
 | 
			
		||||
        ],
 | 
			
		||||
        "curly": true,
 | 
			
		||||
        "eofline": false,
 | 
			
		||||
        "forin": false,
 | 
			
		||||
        "indent": [
 | 
			
		||||
            true,
 | 
			
		||||
            4
 | 
			
		||||
        ],
 | 
			
		||||
        "interface-name": [
 | 
			
		||||
            true,
 | 
			
		||||
            "never-prefix"
 | 
			
		||||
        ],
 | 
			
		||||
        "jsdoc-format": true,
 | 
			
		||||
        "label-position": true,
 | 
			
		||||
        "label-undefined": true,
 | 
			
		||||
        "max-line-length": [
 | 
			
		||||
            false,
 | 
			
		||||
            140
 | 
			
		||||
        ],
 | 
			
		||||
        "no-arg": true,
 | 
			
		||||
        "no-bitwise": false,
 | 
			
		||||
        "no-console": [
 | 
			
		||||
            true,
 | 
			
		||||
            "debug",
 | 
			
		||||
            "info",
 | 
			
		||||
            "time",
 | 
			
		||||
            "timeEnd",
 | 
			
		||||
            "trace"
 | 
			
		||||
        ],
 | 
			
		||||
        "no-construct": true,
 | 
			
		||||
        "no-debugger": true,
 | 
			
		||||
        "no-duplicate-key": true,
 | 
			
		||||
        "no-duplicate-variable": true,
 | 
			
		||||
        "no-empty": true,
 | 
			
		||||
        // "no-eval": true,
 | 
			
		||||
        "no-string-literal": false,
 | 
			
		||||
        "no-trailing-comma": true,
 | 
			
		||||
        "no-trailing-whitespace": true,
 | 
			
		||||
        "no-unused-expression": false,
 | 
			
		||||
        "no-unused-variable": true,
 | 
			
		||||
        "no-unreachable": true,
 | 
			
		||||
        "no-use-before-declare": false,
 | 
			
		||||
        "one-line": [
 | 
			
		||||
            true,
 | 
			
		||||
            "check-open-brace",
 | 
			
		||||
            "check-catch",
 | 
			
		||||
            "check-else",
 | 
			
		||||
            "check-whitespace"
 | 
			
		||||
        ],
 | 
			
		||||
        "quotemark": [
 | 
			
		||||
            true,
 | 
			
		||||
            "double"
 | 
			
		||||
        ],
 | 
			
		||||
        "radix": true,
 | 
			
		||||
        "semicolon": [
 | 
			
		||||
            true,
 | 
			
		||||
            "always"
 | 
			
		||||
        ],
 | 
			
		||||
        "triple-equals": [
 | 
			
		||||
            true,
 | 
			
		||||
            "allow-null-check"
 | 
			
		||||
        ],
 | 
			
		||||
        "typedef": [
 | 
			
		||||
            true,
 | 
			
		||||
            "call-signature",
 | 
			
		||||
            "parameter",
 | 
			
		||||
            "property-declaration",
 | 
			
		||||
            "variable-declaration"
 | 
			
		||||
        ],
 | 
			
		||||
        "typedef-whitespace": [
 | 
			
		||||
            true,
 | 
			
		||||
            {
 | 
			
		||||
                "call-signature": "nospace"
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                "index-signature": "space"
 | 
			
		||||
            }
 | 
			
		||||
        ],
 | 
			
		||||
        "use-strict": [
 | 
			
		||||
            true,
 | 
			
		||||
            "check-module",
 | 
			
		||||
            "check-function"
 | 
			
		||||
        ],
 | 
			
		||||
        "variable-name": false,
 | 
			
		||||
        "whitespace": [
 | 
			
		||||
            false,
 | 
			
		||||
            "check-branch",
 | 
			
		||||
            "check-decl",
 | 
			
		||||
            "check-operator",
 | 
			
		||||
            "check-separator",
 | 
			
		||||
            "check-type"
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								tsrpc.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								tsrpc.config.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import { TsrpcConfig } from 'tsrpc-cli';
 | 
			
		||||
 | 
			
		||||
const tsrpcConf: TsrpcConfig = {
 | 
			
		||||
    // Generate ServiceProto
 | 
			
		||||
    proto: [
 | 
			
		||||
        {
 | 
			
		||||
            ptlDir: 'src/shared/protocols', // Protocol dir
 | 
			
		||||
            output: 'src/shared/protocols/serviceProto.ts', // Path for generated ServiceProto
 | 
			
		||||
            apiDir: 'src/api',   // API dir
 | 
			
		||||
            docDir: 'docs',     // API documents dir
 | 
			
		||||
            // ptlTemplate: CodeTemplate.getExtendedPtl(),
 | 
			
		||||
            // msgTemplate: CodeTemplate.getExtendedMsg(),
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    // Sync shared code
 | 
			
		||||
    sync: [
 | 
			
		||||
        {
 | 
			
		||||
            from: 'src/shared',
 | 
			
		||||
            to: '../frontend/assets/scripts/shared',
 | 
			
		||||
            type: 'symlink'     // Change this to 'copy' if your environment not support symlink
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    // Dev server
 | 
			
		||||
    dev: {
 | 
			
		||||
        autoProto: true,        // Auto regenerate proto
 | 
			
		||||
        autoSync: true,         // Auto sync when file changed
 | 
			
		||||
        autoApi: true,          // Auto create API when ServiceProto updated
 | 
			
		||||
        watch: 'src',           // Restart dev server when these files changed
 | 
			
		||||
        entry: 'src/index.ts',  // Dev server command: node -r ts-node/register {entry}
 | 
			
		||||
    },
 | 
			
		||||
    // Build config
 | 
			
		||||
    build: {
 | 
			
		||||
        autoProto: true,        // Auto generate proto before build
 | 
			
		||||
        autoSync: true,         // Auto sync before build
 | 
			
		||||
        autoApi: true,          // Auto generate API before build
 | 
			
		||||
        outDir: 'dist',         // Clean this dir before build
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
export default tsrpcConf;
 | 
			
		||||
		Reference in New Issue
	
	Block a user