Skip to content

Commit

Permalink
Merge pull request #37 from keshav2010/feat/tile-ownership
Browse files Browse the repository at this point in the history
Color coded Territory Representation
  • Loading branch information
keshav2010 authored May 17, 2024
2 parents a37ac2f + ce8b938 commit 3ab5ad9
Show file tree
Hide file tree
Showing 8 changed files with 267 additions and 73 deletions.
21 changes: 15 additions & 6 deletions gameserver/commands/OnSpawnPointSelectCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,23 @@ import { Command } from "@colyseus/command";
import { SessionRoom } from "../SessionRoom";
import { Client } from "colyseus";
import { CommandPayload } from "./CommandPayloadType";
import SAT from 'sat';
import SAT from "sat";
import { PacketType } from "../../common/PacketType";
export class OnSpawnPointSelectCommand extends Command<SessionRoom, CommandPayload> {
export class OnSpawnPointSelectCommand extends Command<
SessionRoom,
CommandPayload
> {
execute({
client,
message,
gameManager,
}: CommandPayload<{ spawnX: number; spawnY: number }>) {
const { spawnX, spawnY } = message;
const castleSize = 64;
const requestedPoint = new SAT.Vector(spawnX - castleSize/2, spawnY - castleSize/2);

const requestedPoint = new SAT.Vector(
spawnX - castleSize / 2,
spawnY - castleSize / 2
);

const dimensions = gameManager?.scene.getDimension();
if (!dimensions) return;
Expand All @@ -24,8 +29,11 @@ export class OnSpawnPointSelectCommand extends Command<SessionRoom, CommandPaylo
dimensions.x - castleSize * 2,
dimensions.y - castleSize * 2
);

const pointInPolygon = SAT.pointInPolygon(requestedPoint, sceneBoundingBox.toPolygon())

const pointInPolygon = SAT.pointInPolygon(
requestedPoint,
sceneBoundingBox.toPolygon()
);
if (!pointInPolygon) {
client.send(PacketType.ByServer.SPAWN_POINT_RJCT, {
spawnX,
Expand All @@ -35,5 +43,6 @@ export class OnSpawnPointSelectCommand extends Command<SessionRoom, CommandPaylo
}
const player = this.state.getPlayer(client.id);
player?.updatePosition(requestedPoint.x, requestedPoint.y);
this.state.tilemap.updateOwnershipMap(this.state.getPlayers());
}
}
7 changes: 6 additions & 1 deletion gameserver/schema/PlayerState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,12 @@ export class PlayerState extends Schema implements ISceneItem {
}

public addNewSoldier(type: SoldierType, scene: Scene) {
const newSoldier = new SoldierState(this.id, type, this.pos.x, this.pos.y);
const newSoldier = new SoldierState(
this.id,
type,
this.pos.x + 16,
this.pos.y + 16
);
this.soldiers.set(newSoldier.id, newSoldier);
scene.addSceneItem(newSoldier);
return newSoldier.id;
Expand Down
7 changes: 7 additions & 0 deletions gameserver/schema/SessionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ export class SessionState extends Schema {
public getPlayer(sessionId: string) {
return this.players.get(sessionId);
}
public getPlayers() {
let playersArr= [];
for(let player of this.players.entries()) {
playersArr.push(player[1]);
}
return playersArr;
}
public removePlayer(sessionId: string, gameManager: GameStateManagerType) {
const player = this.players.get(sessionId);
if (!player) {
Expand Down
13 changes: 10 additions & 3 deletions gameserver/schema/SoldierState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,17 @@ export class SoldierState extends Schema implements ISceneItem, IBoidAgent {
const centerPos = this.currentPosition
.getVector()
.add(new SAT.Vector(16, 16));
const currentTile = sessionState.tilemap.getTileTypeAt(
centerPos.x,
centerPos.y

const tileCoord = new SAT.Vector(
Math.floor(centerPos.x / sessionState.tilemap.tilewidth),
Math.floor(centerPos.y / sessionState.tilemap.tileheight)
);

const tileIndex =
tileCoord.x + tileCoord.y * sessionState.tilemap.tilemapWidth;

const currentTile = sessionState.tilemap.getTileTypeAt(tileIndex);

if (currentTile === "water") {
this.speed = SoldierTypeConfig[this.type].speed * 0.4;
} else if (currentTile === "dirt") {
Expand Down
110 changes: 86 additions & 24 deletions gameserver/schema/TilemapState.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,112 @@
import { Schema, type, ArraySchema, MapSchema } from "@colyseus/schema";
import { createNoise2D } from 'simplex-noise';
import { Schema, type, ArraySchema } from "@colyseus/schema";
import { createNoise2D } from "simplex-noise";
import { PlayerState } from "./PlayerState";
import SAT from "sat";

export enum ETileType {
DIRT = "dirt",
GRASS = "grass",
WATER = "water",
}

const TilesType = {
dirt: 16,
grass: 56,
water: 154,
[ETileType.DIRT]: 16,
[ETileType.GRASS]: 56,
[ETileType.WATER]: 154,
};
const TilesTypeById: { [key: number]: string } = {
16: "dirt",
56: "grass",
154: "water",

const TilesTypeById: { [key: number]: ETileType } = {
16: ETileType.DIRT,
56: ETileType.GRASS,
154: ETileType.WATER,
};

export function getTileType(tileValue: number): ETileType {
return TilesTypeById[tileValue];
}

export class TilemapState extends Schema {
// Key : sessionId
static TILE_INFLUENCE_DISTANCE: number = 10;

@type(["number"]) tilemap1D = new ArraySchema<number>();

@type(["string"]) ownershipTilemap1D = new ArraySchema<string>();
@type("number") tileheight = 32;
@type("number") tilewidth = 32;

/** height in tiles */
@type("number") tilemapHeight = 60;

/** width in tiles */
@type("number") tilemapWidth = 60;

@type("boolean") ready = false;
simplex: any;

constructor() {
super();
this.simplex = createNoise2D();

this.generateTilemap();
}

getTileTypeAt(x: number, y: number) {
const col = Math.floor(x/this.tilewidth);
const row = Math.floor(y/this.tileheight);
const index1D = col + row * this.tilemapWidth;
const tileId = this.tilemap1D.at(index1D);
private get2DIndex(index1D: number): SAT.Vector {
const row = Math.floor(index1D / this.tilemapWidth);
const col = index1D % this.tilemapWidth;
return new SAT.Vector(col, row);
}

getTileTypeAt(index: number) {
const tileId = this.tilemap1D.at(index);
return TilesTypeById[tileId];
}

updateOwnershipMap(players: PlayerState[]) {
let tilesAffected = 0;

for (
let tileIndex1D = 0;
tileIndex1D < this.ownershipTilemap1D.length;
tileIndex1D++
) {
const tileType = this.getTileTypeAt(tileIndex1D);
if (tileType === "water") continue;

const currentOwner = this.ownershipTilemap1D.at(tileIndex1D);
const newOwner = this.selectTileOwner(tileIndex1D, players);
if (newOwner === currentOwner) continue;

tilesAffected++;
this.ownershipTilemap1D[tileIndex1D] = newOwner;
}
}

private selectTileOwner(
tileIndex: number,
players: PlayerState[]
): string | "NONE" {
let ownerId = "NONE";
let minDistance = TilemapState.TILE_INFLUENCE_DISTANCE;

for (const player of players) {
const castlePosition = player.pos.getVector();

// Convert to tile coordinates
castlePosition.x = Math.floor(castlePosition.x / this.tilewidth);
castlePosition.y = Math.floor(castlePosition.y / this.tileheight);

const tilePos = this.get2DIndex(tileIndex);

const distanceFromTile = castlePosition.sub(tilePos).len();
if (distanceFromTile >= minDistance) continue;

minDistance = distanceFromTile;
ownerId = player.id;
}
return ownerId;
}

async generateTilemap() {
this.ready = false;
const { tilemapWidth, tilemapHeight } = this;
const perlinScale = Math.max(0.03,0.05*Math.random()); // Lower scale for larger features
const noiseMap = Array.from({ length: tilemapHeight }, () => Array(tilemapWidth).fill(0));
const perlinScale = Math.max(0.03, 0.05 * Math.random()); // Lower scale for larger features
const noiseMap = Array.from({ length: tilemapHeight }, () =>
Array(tilemapWidth).fill(0)
);

for (let y = 0; y < tilemapHeight; y++) {
for (let x = 0; x < tilemapWidth; x++) {
Expand All @@ -61,12 +122,13 @@ export class TilemapState extends Schema {
tileTypeIndex = TilesType.water;
} else if (noiseValue < -0.1 && Math.random() < 0.05) {
tileTypeIndex = TilesType.water;
} else if (noiseValue < 0.5*Math.random()) {
} else if (noiseValue < 0.5 * Math.random()) {
tileTypeIndex = TilesType.dirt;
} else {
tileTypeIndex = TilesType.grass;
}
this.tilemap1D.push(tileTypeIndex);
this.ownershipTilemap1D.push("NONE");
}
}
this.ready = true;
Expand Down
53 changes: 41 additions & 12 deletions public/scenes/BaseScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NetworkManager } from "../NetworkManager";
import { ClientStateManager } from "../ClientStateManager";
import CONSTANT from "../constant";
import { nanoid } from "nanoid";
import { ETileType, getTileType } from "../../gameserver/schema/TilemapState";
interface Destroyable {
destroy: Function;
}
Expand Down Expand Up @@ -53,8 +54,32 @@ export class BaseScene extends Phaser.Scene {
);
}

AddStateChangeListener(cleanupFunction ?: Function, key?: string) {
if(!cleanupFunction) return;
updateTilemap(
networkManager: NetworkManager,
tileOwner: string,
tile1DIndex: number
) {
const GameStateManager = networkManager.getState();
if (!GameStateManager) return;

const map = this.data.get("map1") as Phaser.Tilemaps.Tilemap;
const row = Math.floor(tile1DIndex / map.width);
const col = tile1DIndex % map.width;
const tile = map.getTileAt(col, row, false, "groundLayer");
if (!tile) return;
const tileType = getTileType(
GameStateManager.tilemap.tilemap1D.at(tile1DIndex)
);
if (tileType === ETileType.WATER) return;

// tile.setAlpha(tileOwner !== networkManager.getClientId() ? 1 : 1);
if (tileOwner === "NONE") tile.tint = 0xffffff;
else if (tileOwner === networkManager.getClientId()) tile.tint = 0xffffa0;
else tile.tint = 0xffdddd;
}

AddStateChangeListener(cleanupFunction?: Function, key?: string) {
if (!cleanupFunction) return;
const mKey = key || nanoid();
let existingCbSet = this.networkCallsCleanup.get(mKey) || new Set();
existingCbSet.add(cleanupFunction);
Expand Down Expand Up @@ -106,8 +131,7 @@ export class BaseScene extends Phaser.Scene {

// Recursively destroy an object, including any children if it's a group
DestroyObject<T extends ManagedTypes>(obj: T) {
if(!obj)
return;
if (!obj) return;
if ((obj as any)?.type === "Group") obj.destroy(true);
else obj.destroy();
}
Expand Down Expand Up @@ -137,19 +161,24 @@ export class BaseScene extends Phaser.Scene {
this.registeredInputEvents = new Set();
}

setupSceneTilemap(map2DData: number[][]) {
setupSceneTilemap(
map2DData: number[][],
tileSize: number,
tilemapSize: number
) {
const map = this.make.tilemap({
data: map2DData!,
tileWidth: 32,
tileHeight: 32,
width: 60,
height: 60,
tileWidth: tileSize,
tileHeight: tileSize,
width: tilemapSize,
height: tilemapSize,
});

const tileset = map.addTilesetImage("groundtiles", "img_groundtiles");
const groundLayer = map.createBlankLayer("groundlayer", tileset!);
const groundLayer = map.createBlankLayer("groundLayer", tileset!);
map2DData.forEach((row, y) => {
row.forEach((tileIdInTileset, x) => {
groundLayer?.putTileAt(tileIdInTileset - 1, x, y);
row.forEach((tile, x) => {
groundLayer?.putTileAt(tile - 1, x, y);
});
});
return map;
Expand Down
40 changes: 37 additions & 3 deletions public/scenes/GameScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,15 +207,49 @@ export class GameScene extends BaseScene {

create() {
networkManager = this.registry.get("networkManager") as NetworkManager;
const GameSessionState = networkManager.getState();

if (!GameSessionState) {
networkManager.disconnectGameServer();
return;
}

const parsedMap = networkManager.getMapData();
if(!parsedMap) {
console.error('Failed to parse map');
if (!parsedMap) {
console.error("Failed to parse map");
networkManager.disconnectGameServer();
return;
}
const map = this.setupSceneTilemap(parsedMap!);
const tilemap = networkManager.getState()!.tilemap;
const map = this.setupSceneTilemap(
parsedMap!,
tilemap.tileheight,
tilemap.tilemapHeight
);
this.data.set("map1", map);

// render tilemap with initial data
for (
let tileId = 0;
tileId < GameSessionState.tilemap.ownershipTilemap1D.length;
tileId++
) {
this.updateTilemap(
networkManager,
GameSessionState.tilemap.ownershipTilemap1D[tileId],
tileId
);
}

// update tilemap for every tile update received.
this.AddStateChangeListener(
GameSessionState.tilemap.ownershipTilemap1D.onChange(
(owner, tileIndex) => {
this.updateTilemap(networkManager, owner, tileIndex);
}
)
);

const stylus = this.add.graphics();
stylus.setDepth(9999);

Expand Down
Loading

0 comments on commit 3ab5ad9

Please sign in to comment.