diff --git a/packages/backend/scripts/migrate.ts b/packages/backend/scripts/migrate.ts index 1b55a8c..ae8cd0c 100644 --- a/packages/backend/scripts/migrate.ts +++ b/packages/backend/scripts/migrate.ts @@ -7,6 +7,7 @@ import { ResourceType, StorageConfig, StructureConfigs, + UniverseMap, } from '@star-angry/core' /** @@ -55,7 +56,6 @@ const migrate = async () => { ;(structureMap[newStructureId] as ProducerData).produceSpeed = StructureConfigs[newStructureId].getProduceSpeed?.( oldStructureData.level, - data.userDataMap, ) || {} ;(structureMap[newStructureId] as ProducerData).consumeSpeed = StructureConfigs[newStructureId].getConsumeSpeed?.( @@ -111,12 +111,61 @@ const migrate = async () => { structures, }, }, - } + } as any }) data.version = '0.0.1' } + if (data.version === '0.0.1') { + if (!data.config) { + data.config = { + seed: 0.63, + } + } + + const userDatas = Object.values(data.userDataMap) + const userPlanetCount = userDatas.reduce( + (count, userData) => count + Object.keys(userData.planets).length, + 0, + ) + // 一个区块放四个玩家的星球 + const chunkNumber = Math.ceil(userPlanetCount / 4) + // 获取星球 + const universeMap = new UniverseMap(data.config.seed) + // 取地图中心的区块范围半径 + const radius = Math.floor( + (Math.ceil(Math.sqrt(chunkNumber)) * universeMap.chunkSize) / 2, + ) + const chunks = universeMap.getChunks(-radius, -radius, radius, radius) + const planets = chunks + .map((chunkId) => universeMap.getPlanets(chunkId)) + .flat() + const gap = Math.floor(planets.length / userPlanetCount) + let index = 0 + + userDatas.forEach((userData) => { + Object.keys(userData.planets).forEach((planetId) => { + const planet = userData.planets[planetId] + planet.id = planetId + // 重新分配一个 + const newPlanet = planets[index] + planet.id = universeMap.getBlockId(...newPlanet).toString() + planet.position = newPlanet + userData.planets[planet.id] = planet + delete userData.planets[planetId] + index += gap + + if (!(planet.targetPosition instanceof Array)) { + const { x: tx = 0, y: ty = 0 } = planet.targetPosition as any + planet.targetPosition = [tx, ty] + } + }) + }) + + data.version = '0.0.2' + } + await GameDB.getDB().setData(data) console.log('迁移完成') process.exit(0) diff --git a/packages/backend/src/error/ErrorCode.ts b/packages/backend/src/error/ErrorCode.ts index 47cf663..dc89aa4 100644 --- a/packages/backend/src/error/ErrorCode.ts +++ b/packages/backend/src/error/ErrorCode.ts @@ -23,4 +23,7 @@ export class ErrorCode { static ROOM_NOT_EXIST = new ErrorCode(2001, '房间不存在') static MESSAGE_TOO_LONG = new ErrorCode(2002, '消息太长') + + static PLANET_NOT_EXIST = new ErrorCode(2021, '星球不存在') + static PLANET_OCCUPIED = new ErrorCode(2022, '星球已被占领') } diff --git a/packages/backend/src/service/game/index.ts b/packages/backend/src/service/game/index.ts index e406c7c..35eac9e 100644 --- a/packages/backend/src/service/game/index.ts +++ b/packages/backend/src/service/game/index.ts @@ -1,5 +1,6 @@ import { init, loop } from '@star-angry/core' import { GameDB } from '@star-angry/db' +import MapService from '../map' export default class GameService { /** @@ -32,6 +33,7 @@ export default class GameService { */ static async initData() { const data = await GameDB.getDB().getData() + MapService.init(data) init(data.userDataMap) console.log('initGameData') } diff --git a/packages/backend/src/service/map/index.ts b/packages/backend/src/service/map/index.ts new file mode 100644 index 0000000..dae480b --- /dev/null +++ b/packages/backend/src/service/map/index.ts @@ -0,0 +1,121 @@ +import { ErrorCode } from '../../error/ErrorCode' +import { GameError } from '../../error/GameError' +import { + getPlanetBaseData, + initPlanetData, + PlanetData, + UniverseMap, +} from '@star-angry/core' +import { GameDB, GameModel } from '@star-angry/db' + +/** + * 地图缓存对象数据 + */ +export interface ObjectCacheData { + userId?: string + userName?: string + planet: PlanetData +} + +export default class MapService { + static map: UniverseMap + + /** + * 缓存区块中的物体 + */ + static objectCache: { + [chunkId: number]: ObjectCacheData[] + } = {} + + /** + * 区块中原始星球(后来被玩家占领)的id + */ + static originalPlanetIds: Record> = {} + + /** + * 新玩家注册星球 + */ + static async registerPlanet(userId: string, planetId: string) { + const data = await GameDB.getDB().getData() + const userDataMap = data.userDataMap + const userData = userDataMap[userId] + if (!userData || Object.keys(userData.planets).length > 0) { + throw new GameError(ErrorCode.USER_NOT_EXIST) + } + const map = MapService.map + const [x, y] = map.getBlockCoord(+planetId) + // 不是一个星球 + if (!map.isPlanet(x, y)) { + throw new GameError(ErrorCode.PLANET_NOT_EXIST) + } + // 已经被别人占了 + const originChunkId = map.getChunkId(x, y) + if (MapService.originalPlanetIds[originChunkId]?.has(+planetId)) { + throw new GameError(ErrorCode.PLANET_OCCUPIED) + } + + const planet = getPlanetBaseData(planetId) + planet.position = [x, y] + userData.planets[planetId] = planet + initPlanetData(planet, userDataMap) + } + + /** + * 获取区块中的物体 + */ + static getObjectsFromChunks(chunkIds: number[]) { + return chunkIds.reduce( + (prev, chunkId) => { + prev[chunkId] = MapService.objectCache[chunkId] || [] + return prev + }, + {} as { [chunkId: number]: ObjectCacheData[] }, + ) + } + + /** + * 获取区块中已经被占领的星球 + */ + static getOccupiedPlanetsFromChunks(chunkIds: number[]) { + return chunkIds.reduce( + (prev, chunkId) => { + prev[chunkId] = Array.from(MapService.originalPlanetIds[chunkId] || []) + return prev + }, + {} as Record, + ) + } + + /** + * 初始化地图相关数据 + */ + static init(data: GameModel) { + MapService.map = new UniverseMap(data.config.seed) + + Object.keys(data.userDataMap).forEach((userId) => { + const userData = data.userDataMap[userId] + Object.values(userData.planets).forEach((planet) => { + // 缓存现在的坐标 + const [x, y] = planet.position + const chunkId = MapService.map.getChunkId(x, y) + if (!MapService.objectCache[chunkId]) { + MapService.objectCache[chunkId] = [] + } + MapService.objectCache[chunkId].push({ + userId, + userName: + data.user.find((user) => user.id === userId)?.username || '', + planet, + }) + // 缓存原始的坐标 + const originChunkId = MapService.map.getChunkId( + ...MapService.map.getBlockCoord(+planet.id), + ) + if (!MapService.originalPlanetIds[originChunkId]) { + MapService.originalPlanetIds[originChunkId] = new Set() + } + MapService.originalPlanetIds[originChunkId].add(+planet.id) + }) + }) + } +} diff --git a/packages/backend/src/service/structure/index.ts b/packages/backend/src/service/structure/index.ts index e10619b..dcb8f9a 100644 --- a/packages/backend/src/service/structure/index.ts +++ b/packages/backend/src/service/structure/index.ts @@ -1,19 +1,14 @@ -import { processor } from '../../../../core/src' +import { processor, StructureOperationParams } from '@star-angry/core' +import { GameDB } from '@star-angry/db' import { ErrorCode } from '../../error/ErrorCode' import { GameError } from '../../error/GameError' -import { GameDB } from '@star-angry/db' export default class StructureService { /** * 添加操作 */ - static async addOperation(params: { - userId: string - planetId: string - structureId: string - operation: string - }) { - const { userId, planetId, structureId, operation } = params + static async addOperation(params: StructureOperationParams) { + const { userId } = params const data = await GameDB.getDB().getData() const userDataMap = data.userDataMap const userData = userDataMap[userId] diff --git a/packages/backend/src/socket/connection.ts b/packages/backend/src/socket/connection.ts index facbe2d..72d9749 100644 --- a/packages/backend/src/socket/connection.ts +++ b/packages/backend/src/socket/connection.ts @@ -6,6 +6,7 @@ import { userEventHandler, structureEventHandler, gameEventHandler, + mapEventHandler, } from './event' const users: Map> = new Map() @@ -27,6 +28,7 @@ export const createConnection = (io: Server) => { userEventHandler(socket, io) gameEventHandler(socket, io) + mapEventHandler(socket, io) structureEventHandler(socket, io) messageEventHandler(socket, io) diff --git a/packages/backend/src/socket/event/index.ts b/packages/backend/src/socket/event/index.ts index 1b6e018..4b7c45d 100644 --- a/packages/backend/src/socket/event/index.ts +++ b/packages/backend/src/socket/event/index.ts @@ -2,3 +2,4 @@ export * from './message' export * from './user' export * from './game' export * from './structure' +export * from './map' diff --git a/packages/backend/src/socket/event/map/index.ts b/packages/backend/src/socket/event/map/index.ts new file mode 100644 index 0000000..76e5a29 --- /dev/null +++ b/packages/backend/src/socket/event/map/index.ts @@ -0,0 +1,49 @@ +import { Server, Socket } from 'socket.io' +import { Result } from '../../../utils/result' +import { ErrorCode } from '../../../error/ErrorCode' +import { GameError } from '../../../error/GameError' +import MapService from '../../../service/map' + +export const mapEventHandler = (socket: Socket, io: Server) => { + /** + * 新玩家注册星球 + */ + socket.on('registerPlanet', async (planetId: string, callback) => { + const userId = socket.userId + if (!userId) { + return callback(Result.error(ErrorCode.PARAM_ERROR)) + } + + try { + await MapService.registerPlanet(userId, planetId) + return callback(Result.success({})) + } catch (error: unknown) { + console.error(error) + if (error instanceof GameError) { + return callback(Result.error(error.errorCode)) + } + } + }) + + /** + * 获取区块中的物体 + */ + socket.on('getObjectsFromChunks', async (chunkIds: number[], callback) => { + try { + const data = MapService.getObjectsFromChunks(chunkIds) + const occupiedPlanets = MapService.getOccupiedPlanetsFromChunks(chunkIds) + return callback( + Result.success({ + seed: MapService.map.seed, + chunkObjects: data, + occupiedPlanets, + }), + ) + } catch (error: unknown) { + console.error(error) + if (error instanceof GameError) { + return callback(Result.error(error.errorCode)) + } + } + }) +} diff --git a/packages/backend/src/socket/event/structure/index.ts b/packages/backend/src/socket/event/structure/index.ts index bfc7d40..2a7d12a 100644 --- a/packages/backend/src/socket/event/structure/index.ts +++ b/packages/backend/src/socket/event/structure/index.ts @@ -1,4 +1,5 @@ import { Server, Socket } from 'socket.io' +import { StructureOperationParams } from '@star-angry/core' import { Result } from '../../../utils/result' import { ErrorCode } from '../../../error/ErrorCode' import StructureService from '../../../service/structure' @@ -9,14 +10,7 @@ export const structureEventHandler = (socket: Socket, io: Server) => { // 添加操作 socket.on( 'addOperation', - async ( - params: { - planetId: string - structureId: string - operation: string - }, - callback, - ) => { + async (params: Omit, callback) => { const userId = socket.userId if (!userId) { return callback(Result.error(ErrorCode.PARAM_ERROR)) diff --git a/packages/core/src/config/combat.ts b/packages/core/src/config/combat.ts new file mode 100644 index 0000000..e1deeee --- /dev/null +++ b/packages/core/src/config/combat.ts @@ -0,0 +1,38 @@ +/** + * 攻击种类 + */ +export enum AttackType { + /** + * 炮弹攻击 + */ + Ballistic, + /** + * 导弹攻击 + */ + Missile, + /** + * 激光攻击 + */ + Laser, + /** + * 离子攻击 + */ + Ion, + /** + * 等离子攻击 + */ + Plasma, +} + +/** + * 攻击名称 + */ +export const AttackTypeName: { + [key in AttackType]: string +} = { + [AttackType.Ballistic]: '炮弹攻击', + [AttackType.Missile]: '导弹攻击', + [AttackType.Laser]: '激光攻击', + [AttackType.Ion]: '离子攻击', + [AttackType.Plasma]: '等离子攻击', +} diff --git a/packages/core/src/config/structure.ts b/packages/core/src/config/structure.ts index 7f163e4..f064102 100644 --- a/packages/core/src/config/structure.ts +++ b/packages/core/src/config/structure.ts @@ -1,7 +1,11 @@ import { StructureConfig } from '../structure/types' +import { AttackType } from './combat' import { ResourceType } from './resource' -export const StructureConfigs: StructureConfig = { +/** + * 基地建筑 + */ +export const BaseStructureConfigs: StructureConfig = { solarPlant: { id: 'solarPlant', name: '太阳能电站', @@ -132,10 +136,274 @@ export const StructureConfigs: StructureConfig = { }), priority: 120, }, -} as const +} + +/** + * 科技建筑 + */ +export const TechnologyStructureConfigs: StructureConfig = { + 'spy-technology': { + id: 'spy-technology', + name: '间谍技术', + description: '间谍技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + }, + 'computer-technology': { + id: 'computer-technology', + name: '计算机技术', + description: '计算机技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + }, + 'weapons-technology': { + id: 'weapons-technology', + name: '武器技术', + description: '武器技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + }, + 'shield-technology': { + id: 'shield-technology', + name: '护盾技术', + description: '护盾技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + }, + 'armor-technology': { + id: 'armor-technology', + name: '装甲技术', + description: '装甲技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + }, + 'energy-technology': { + id: 'energy-technology', + name: '能量技术', + description: '能量技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + }, + 'hyperspace-technology': { + id: 'hyperspace-technology', + name: '超空间技术', + description: '超空间技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + preDepend: { + 'computer-technology': 5, + }, + }, + 'combustion-technology': { + id: 'combustion-technology', + name: '燃烧技术', + description: '燃烧技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + }, + 'laser-technology': { + id: 'laser-technology', + name: '激光技术', + description: '激光技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + }, + 'ion-technology': { + id: 'ion-technology', + name: '离子技术', + description: '离子技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + }, + 'plasma-technology': { + id: 'plasma-technology', + name: '等离子技术', + description: '等离子技术', + type: 'technology', + getUpgradeCost: (level) => ({ + energy: Math.floor(100 * Math.pow(1.5, level)), + metal: Math.floor(200 * Math.pow(1.5, level)), + }), + preDepend: { + 'combustion-technology': 3, + 'laser-technology': 3, + }, + }, +} + +/** + * 防御建筑 + */ +export const DefenseStructureConfigs: StructureConfig = { + 'standard-tower': { + id: 'standard-tower', + name: '普通炮塔', + description: '普通炮塔', + type: 'defense', + getUpgradeCost: () => ({ + energy: 15000, + metal: 5000, + }), + health: 1000, + shield: 500, + attack: { + [AttackType.Ballistic]: 100, + }, + }, + 'heavy-tower': { + id: 'heavy-tower', + name: '重型炮塔', + description: '重型炮塔', + type: 'defense', + getUpgradeCost: () => ({ + energy: 100000, + metal: 50000, + }), + health: 3000, + shield: 2000, + attack: { + [AttackType.Ballistic]: 1000, + }, + }, + 'missile-launcher': { + id: 'missile-launcher', + name: '导弹发射器', + description: '导弹发射器', + type: 'defense', + getUpgradeCost: () => ({ + energy: 300000, + metal: 200000, + }), + health: 10000, + shield: 10000, + attack: { + [AttackType.Missile]: 5000, + }, + }, + 'ion-cannon': { + id: 'ion-cannon', + name: '离子炮', + description: '离子炮', + type: 'defense', + getUpgradeCost: () => ({ + energy: 50000, + metal: 100000, + }), + health: 2000, + shield: 2000, + attack: { + [AttackType.Ion]: 2000, + }, + }, + 'plasma-cannon': { + id: 'plasma-cannon', + name: '等离子炮', + description: '等离子炮', + type: 'defense', + getUpgradeCost: () => ({ + energy: 80000, + metal: 200000, + }), + health: 8000, + shield: 8000, + attack: { + [AttackType.Plasma]: 3000, + }, + }, + 'light-laser-tower': { + id: 'light-laser-tower', + name: '轻型激光塔', + description: '轻型激光塔', + type: 'defense', + getUpgradeCost: () => ({ + energy: 5000, + metal: 1000, + }), + health: 100, + shield: 50, + attack: { + [AttackType.Laser]: 10, + }, + }, + 'heavy-laser-tower': { + id: 'heavy-laser-tower', + name: '重型激光塔', + description: '重型激光塔', + type: 'defense', + getUpgradeCost: () => ({ + energy: 50000, + metal: 10000, + }), + health: 300, + shield: 200, + attack: { + [AttackType.Laser]: 400, + }, + }, + 'small-shield': { + id: 'small-shield', + name: '小型护盾', + description: '小型护盾', + type: 'defense', + getUpgradeCost: () => ({ + energy: 10000000, + metal: 5000000, + }), + health: 10000, + shield: 50000, + }, + 'large-shield': { + id: 'large-shield', + name: '大型护盾', + description: '大型护盾', + type: 'defense', + getUpgradeCost: () => ({ + energy: 100000000, + metal: 500000000, + }), + health: 100000, + shield: 500000, + }, +} + +export const StructureConfigs: StructureConfig = { + ...BaseStructureConfigs, + ...TechnologyStructureConfigs, + ...DefenseStructureConfigs, +} -// 建筑在前端的展示顺序,数字越小越靠前 -export const StructureOrder: { +// 基地建筑在前端的展示顺序,数字越小越靠前 +export const BaseStructureOrder: { [structureId: string]: number } = { metalStorage: 1, diff --git a/packages/core/src/map/map.ts b/packages/core/src/map/map.ts index 272ef81..e107b45 100644 --- a/packages/core/src/map/map.ts +++ b/packages/core/src/map/map.ts @@ -1,104 +1,46 @@ -import { lcg } from '@star-angry/shared' +import { isClose } from '@star-angry/shared' +import { Noise2D } from './noise' /** * 宇宙地图 */ -export class Universe { +export class UniverseMap { /** - * 伪随机数生成器,种子相同,生成的随机数序列相同 + * 噪声 */ - public random: () => number - - // 区块星系 - public chunkGalaxyMap: Map = new Map() + public noise: Noise2D public constructor( public seed: number, public chunkSize: number = 256, ) { - this.random = lcg(seed) + this.noise = new Noise2D(seed, 10, 1) } /** - * 获取区块中的星系 + * 获取区块中的星球 */ - public getGalaxy(chunkId: number): Galaxy { - if (this.chunkGalaxyMap.has(chunkId)) { - return this.chunkGalaxyMap.get(chunkId)! - } - - // 先找到区块中心 - let [x, y] = this.getChunkCoord(chunkId) - x += Math.floor(this.chunkSize / 2) - y += Math.floor(this.chunkSize / 2) - // 在生成星系中心,即区块中心附近 - const galaxyX = x + Math.floor(((this.random() - 0.5) * this.chunkSize) / 2) - const galaxyY = y + Math.floor(((this.random() - 0.5) * this.chunkSize) / 2) - - // 暂时一个区块只有一个星系,星系整体范围在区块内,半径大约为区块半径的 1/4 - const size = Math.floor((this.random() / 4 + 0.1) * this.chunkSize) - const galaxy = new Galaxy( - this.getBlockId(galaxyX, galaxyY), - galaxyX, - galaxyY, - size, - ) - - // 生成星球 - const planetCount = Math.floor(this.random() * 5 + 5) - const planets: Planet[] = [] - - const genPlanet = () => { - const planetX = galaxyX + Math.floor(this.random() * size) - const planetY = galaxyY + Math.floor(this.random() * size) - const planetSize = Math.floor((this.random() / planetCount + 0.1) * size) - const color = `#${Math.floor(this.random() * 0xffffff).toString(16)}` - return new Planet( - this.getBlockId(planetX, planetY), - planetX, - planetY, - planetSize, - color, - ) - } - - let check = 0 - for (let i = 0; i < planetCount; i++) { - const planet = genPlanet() - // 防止星球重叠 - if ( - planets.some( - (p) => - Math.abs(p.x - planet.x) < p.size + planet.size && - Math.abs(p.y - planet.y) < p.size + planet.size, - ) - ) { - if (check++ > 10) { - check = 0 - continue + public getPlanets(chunkId: number) { + const planets: [x: number, y: number][] = [] + const [lx, ly] = this.getChunkCoord(chunkId) + const rx = lx + this.chunkSize + const ry = ly + this.chunkSize + for (let x = lx; x < rx; x++) { + for (let y = ly; y < ry; y++) { + if (this.isPlanet(x, y)) { + planets.push([x, y]) } - i-- - continue } - planets.push(planet) } - - galaxy.planets = planets - this.chunkGalaxyMap.set(chunkId, galaxy) - return galaxy + return planets } /** - * 获取地图可视区中的星系 + * 检查方块是否是星球 */ - public getGalaxies(lx: number, ly: number, rx: number, ry: number): Galaxy[] { - const chunkIds = this.getChunks(lx, ly, rx, ry) - const galaxies: Galaxy[] = [] - for (const chunkId of chunkIds) { - const galaxy = this.getGalaxy(chunkId) - galaxies.push(galaxy) - } - return galaxies + public isPlanet(x: number, y: number): boolean { + const noise = this.noise.getBuff(x, y) + return isClose(noise, 0, 0.0001) } /** @@ -111,12 +53,13 @@ export class Universe { */ public getChunks(lx: number, ly: number, rx: number, ry: number): number[] { const chunkIds: number[] = [] - for (let x = lx; x <= rx; x += this.chunkSize) { - for (let y = ly; y <= ry; y += this.chunkSize) { + for (let x = lx; x < rx + this.chunkSize; x += this.chunkSize) { + for (let y = ly; y < ry + this.chunkSize; y += this.chunkSize) { const chunkId = this.getChunkId(x, y) chunkIds.push(chunkId) } } + return chunkIds } @@ -153,40 +96,3 @@ export class Universe { return [x * this.chunkSize, y * this.chunkSize] } } - -/** - * 星系 - */ -export class Galaxy { - // 星球 - public planets: Planet[] = [] - - /** - * @param x 星系中心 x 坐标 - * @param y 星系中心 y 坐标 - * @param seed 随机种子 - */ - public constructor( - public id: number, - public x: number, - public y: number, - public size: number, - ) {} - - addPlanet(planet: Planet) { - this.planets.push(planet) - } -} - -/** - * 星球 - */ -export class Planet { - public constructor( - public id: number, - public x: number, - public y: number, - public size: number, - public color: string = '#000', - ) {} -} diff --git a/packages/core/src/processor/init.ts b/packages/core/src/processor/init.ts index 340651c..2ba934e 100644 --- a/packages/core/src/processor/init.ts +++ b/packages/core/src/processor/init.ts @@ -8,15 +8,33 @@ import { PlanetData, UserDataMap } from '../utils' export const init = (userDataMap: UserDataMap) => { Object.values(userDataMap).forEach((user) => { Object.values(user.planets).forEach((planet) => { - initPlanet(planet) + initPlanetData(planet, userDataMap) }) }) } +/** + * 获取星球基础数据 + */ +export const getPlanetBaseData = (planetId: string): PlanetData => { + return { + id: planetId, + name: `星球${planetId}`, + level: 1, + speed: 0, + structures: {}, + resources: {}, + position: [0, 0], + } +} + /** * 初始化星球 */ -export const initPlanet = (planet: PlanetData) => { +export const initPlanetData = ( + planet: PlanetData, + userDataMap: UserDataMap, +) => { // 初始化建筑 const structureIds = Object.keys(StructureConfigs) structureIds.forEach((structureId) => { @@ -32,6 +50,7 @@ export const initPlanet = (planet: PlanetData) => { {} as any, StructureConfigs, planet, + userDataMap, ) } }) diff --git a/packages/core/src/processor/processor.ts b/packages/core/src/processor/processor.ts index dc9ef5c..253f84e 100644 --- a/packages/core/src/processor/processor.ts +++ b/packages/core/src/processor/processor.ts @@ -1,14 +1,9 @@ import { StructureConfigs } from '../config' import { getOperationHandler } from '../structure' -import { UserDataMap } from '../utils' +import { StructureOperationParams, UserDataMap } from '../utils' export const processor = ( - params: { - userId: string - planetId: string - structureId: string - operation: string - }, + params: StructureOperationParams, userDataMap: UserDataMap, ) => { const { userId, planetId, structureId, operation } = params diff --git a/packages/core/src/structure/defense.ts b/packages/core/src/structure/defense.ts new file mode 100644 index 0000000..dc8afc9 --- /dev/null +++ b/packages/core/src/structure/defense.ts @@ -0,0 +1,66 @@ +import { isResourceEnough, StructureOperationObject } from '../utils' +import { StructureBaseOperation } from './base' +import { DefenseData } from './types' + +/** + * 防御设施建筑操作 + */ +export const DefenseOperation: StructureOperationObject = { + /** + * 初始化 + */ + _init(_, data, structureConfigs, planetData, userDataMap) { + if ( + !StructureBaseOperation._init( + _, + data, + structureConfigs, + planetData, + userDataMap, + ) + ) { + return false + } + return true + }, + + /** + * 建造 + */ + build(params, data, structureConfigs, planetData) { + const config = structureConfigs[data.id] + // 想要建造的数量 + const require = Math.floor(params.data?.require) + if (typeof require !== 'number' || require < 1 || !isFinite(require)) { + return false + } + + // 获取升级依赖的前置建筑 + const preStructure = config.preDepend + // 检查这些建筑的等级是否满足 + if (preStructure) { + for (const [id, level] of Object.entries(preStructure)) { + if (planetData.structures[id].level < level) { + return false + } + } + } + + // 获取建造所需资源 + const cost = config.getUpgradeCost(data.level) + Object.keys(cost).forEach((key) => { + cost[key as keyof typeof cost]! *= require + }) + + // 检查资源是否足够并扣除资源 + if (!isResourceEnough(planetData.resources, cost, true)) { + return false + } + + // 更新数量 + const defenseData = data as DefenseData + defenseData.amount = (defenseData.amount || 0) + require + + return true + }, +} diff --git a/packages/core/src/structure/index.ts b/packages/core/src/structure/index.ts index bbf697c..ab69524 100644 --- a/packages/core/src/structure/index.ts +++ b/packages/core/src/structure/index.ts @@ -1,7 +1,9 @@ import { StructureConfigs } from '../config' import { StructureOperationObject } from '../utils/types' +import { DefenseOperation } from './defense' import { ProducerOperation } from './producer' import { StorageOperation } from './storage' +import { TechnologyOperation } from './technology' import { StructureType } from './types' export * from './types' @@ -11,6 +13,8 @@ export const StructureOperationMap: { } = { storage: StorageOperation, producer: ProducerOperation, + technology: TechnologyOperation, + defense: DefenseOperation, } /** diff --git a/packages/core/src/structure/producer.ts b/packages/core/src/structure/producer.ts index b8394d8..0e07a2f 100644 --- a/packages/core/src/structure/producer.ts +++ b/packages/core/src/structure/producer.ts @@ -1,6 +1,5 @@ import { ResourceType } from '../config/resource' -import { isStorageFull } from '../utils' -import { StructureOperationObject } from '../utils/types' +import { isStorageFull, StructureOperationObject } from '../utils' import { StructureBaseOperation } from './base' import { ProducerConfig, ProducerData } from './types' @@ -57,7 +56,7 @@ export const ProducerOperation: StructureOperationObject = { // 升级成功,更新资源产量和消耗 const config = structureConfigs[data.id] as ProducerConfig - const produceSpeed = config.getProduceSpeed?.(data.level, userDataMap) ?? {} + const produceSpeed = config.getProduceSpeed?.(data.level) ?? {} const consumeSpeed = config.getConsumeSpeed?.(data.level) ?? {} const structureData = data as ProducerData structureData.produceSpeed = produceSpeed diff --git a/packages/core/src/structure/storage.ts b/packages/core/src/structure/storage.ts index e4b83af..b0621c4 100644 --- a/packages/core/src/structure/storage.ts +++ b/packages/core/src/structure/storage.ts @@ -1,4 +1,4 @@ -import { StructureOperationObject } from '../utils/types' +import { StructureOperationObject } from '../utils' import { StructureBaseOperation } from './base' import { StorageConfig } from './types' diff --git a/packages/core/src/structure/technology.ts b/packages/core/src/structure/technology.ts new file mode 100644 index 0000000..57e36c6 --- /dev/null +++ b/packages/core/src/structure/technology.ts @@ -0,0 +1,43 @@ +import { StructureOperationObject } from '../utils' +import { StructureBaseOperation } from './base' + +/** + * 科技类型建筑操作 + */ +export const TechnologyOperation: StructureOperationObject = { + /** + * 初始化 + */ + _init(_, data, structureConfigs, planetData, userDataMap) { + if ( + !StructureBaseOperation._init( + _, + data, + structureConfigs, + planetData, + userDataMap, + ) + ) { + return false + } + return true + }, + + /** + * 升级 + */ + upgrade(_, data, structureConfigs, planetData, userDataMap) { + if ( + !StructureBaseOperation.upgrade( + _, + data, + structureConfigs, + planetData, + userDataMap, + ) + ) { + return false + } + return true + }, +} diff --git a/packages/core/src/structure/types.ts b/packages/core/src/structure/types.ts index a265cde..da59553 100644 --- a/packages/core/src/structure/types.ts +++ b/packages/core/src/structure/types.ts @@ -1,3 +1,4 @@ +import { AttackType } from '../config/combat' import { ResourceType } from '../config/resource' import { UserDataMap } from '../utils' @@ -76,20 +77,49 @@ export interface ProducerConfig extends StructureBaseConfig { /** * 获取产出资源速度 */ - getProduceSpeed?: ( - level: number, - userDataMap: UserDataMap, - ) => Partial> + getProduceSpeed?: (level: number) => Partial> /** * 获取消耗资源速度 */ getConsumeSpeed?: (level: number) => Partial> } +/** + * 科技类型建筑配置 + */ +export interface TechnologyConfig extends StructureBaseConfig { + type: 'technology' +} + +/** + * 防御设施建造配置 + */ +export interface DefenseConfig extends StructureBaseConfig { + type: 'defense' + /** + * 生命值 + */ + health: number + /** + * 护盾 + */ + shield: number + /** + * 攻击力 + */ + attack?: { + [type in AttackType]?: number + } +} + /** * 所有类型的建筑配置 */ -export type AllStructureConfig = StorageConfig | ProducerConfig +export type AllStructureConfig = + | StorageConfig + | ProducerConfig + | TechnologyConfig + | DefenseConfig /** * 建筑类型 @@ -122,7 +152,7 @@ export interface StructureData { /** * 上次更新的时间 */ - lastUpdateTime: number + lastUpdateTime?: number } /** @@ -144,7 +174,26 @@ export interface ProducerData extends StructureData { consumeSpeed: Partial> } +/** + * 科技类型建筑数据 + */ +export interface TechnologyData extends StructureData {} + +/** + * 防御设施建筑数据 + */ +export interface DefenseData extends StructureData { + /** + * 数量 + */ + amount: number +} + /** * 所有类型的建筑数据 */ -export type AllStructureData = StorageData | ProducerData +export type AllStructureData = + | StorageData + | ProducerData + | TechnologyData + | DefenseData diff --git a/packages/core/src/utils/types.ts b/packages/core/src/utils/types.ts index 9744b5a..4f06e29 100644 --- a/packages/core/src/utils/types.ts +++ b/packages/core/src/utils/types.ts @@ -32,6 +32,10 @@ export interface UserDataMap { * 星球数据 */ export interface PlanetData { + /** + * 星球 id + */ + id: string /** * 星球名称 */ @@ -47,17 +51,11 @@ export interface PlanetData { /** * 当前坐标 */ - position: { - x: number - y: number - } + position: [x: number, y: number] /** * 目的地坐标 */ - targetPosition?: { - x: number - y: number - } + targetPosition?: [x: number, y: number] /** * 星球资源 */ @@ -115,3 +113,14 @@ export interface StructureOperationObject { export type ResourceNumberMap = { [resourceId in ResourceType]?: number } + +/** + * 建筑操作参数 + */ +export interface StructureOperationParams { + userId: string + planetId: string + structureId: string + operation: string + data?: any +} diff --git a/packages/db/src/gamedb.ts b/packages/db/src/gamedb.ts index 0245c69..b490125 100644 --- a/packages/db/src/gamedb.ts +++ b/packages/db/src/gamedb.ts @@ -1,7 +1,6 @@ import { DB, DBNames } from './db' import { GameModel } from './model/game' import { Mutex } from './AsyncLock' -import { initPlanet } from '@star-angry/core' const mutex = new Mutex() export class GameDB extends DB { @@ -56,25 +55,8 @@ export class GameDB extends DB { money: 0, xnc: 0, }, - planets: { - 0: { - name: '星球1', - level: 1, - speed: 0, - position: { - x: 0, - y: 0, - }, - targetPosition: { - x: 0, - y: 0, - }, - resources: {}, - structures: {}, - }, - }, + planets: {}, } - initPlanet(data.userDataMap[userId].planets[0]) } initData() { @@ -83,6 +65,9 @@ export class GameDB extends DB { user: [], messages: {}, userDataMap: {}, + config: { + seed: 0.63, + }, } as GameModel } diff --git a/packages/db/src/model/config.ts b/packages/db/src/model/config.ts new file mode 100644 index 0000000..bfaf986 --- /dev/null +++ b/packages/db/src/model/config.ts @@ -0,0 +1,6 @@ +export interface GameConfig { + /** + * 种子 + */ + seed: number +} diff --git a/packages/db/src/model/game.ts b/packages/db/src/model/game.ts index 4102a6b..ac495b7 100644 --- a/packages/db/src/model/game.ts +++ b/packages/db/src/model/game.ts @@ -1,10 +1,12 @@ import { UserDataMap } from '@star-angry/core' import { MessageMap } from './message' import { UserModel } from './user' +import { GameConfig } from './config' export interface GameModel { version: string // 游戏数据的版本 user: UserModel[] messages: MessageMap userDataMap: UserDataMap + config: GameConfig } diff --git a/packages/game/components.d.ts b/packages/game/components.d.ts index cc47ccb..96f43bd 100644 --- a/packages/game/components.d.ts +++ b/packages/game/components.d.ts @@ -14,6 +14,7 @@ declare module 'vue' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElInput: typeof import('element-plus/es')['ElInput'] + ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElLink: typeof import('element-plus/es')['ElLink'] ElOption: typeof import('element-plus/es')['ElOption'] ElProgress: typeof import('element-plus/es')['ElProgress'] @@ -24,6 +25,8 @@ declare module 'vue' { ElSwitch: typeof import('element-plus/es')['ElSwitch'] ElTable: typeof import('element-plus/es')['ElTable'] ElTableColumn: typeof import('element-plus/es')['ElTableColumn'] + ElTabPane: typeof import('element-plus/es')['ElTabPane'] + ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] ElText: typeof import('element-plus/es')['ElText'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] diff --git a/packages/game/src/App.vue b/packages/game/src/App.vue index 6f5f713..b465cdb 100644 --- a/packages/game/src/App.vue +++ b/packages/game/src/App.vue @@ -6,12 +6,14 @@ diff --git a/packages/game/src/views/planet/PlanetPage.vue b/packages/game/src/views/planet/PlanetPage.vue index 4d2028a..b8754f9 100644 --- a/packages/game/src/views/planet/PlanetPage.vue +++ b/packages/game/src/views/planet/PlanetPage.vue @@ -1,225 +1,60 @@ - diff --git a/packages/game/src/views/planet/ShowPanel.vue b/packages/game/src/views/planet/ShowPanel.vue new file mode 100644 index 0000000..fb4e762 --- /dev/null +++ b/packages/game/src/views/planet/ShowPanel.vue @@ -0,0 +1,503 @@ + + + + + +