Skip to content

Commit

Permalink
Merge pull request #453 from leia-uwu/fix-river-obstacles
Browse files Browse the repository at this point in the history
fix: fix obstacle generation on rivers
  • Loading branch information
hsanger authored Jan 3, 2025
2 parents f9b8b7a + c81787d commit 3545986
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 30 deletions.
1 change: 0 additions & 1 deletion common/src/utils/objectDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,6 @@ export enum MapObjectSpawnMode {
* Grass, beach and river banks.
*/
GrassAndSand,
RiverBank,
River,
Beach,
Trail
Expand Down
21 changes: 19 additions & 2 deletions common/src/utils/terrain.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { GameConstants, Layer } from "../constants";
import { PolygonHitbox, RectangleHitbox, type Hitbox } from "./hitbox";
import { Collision, Numeric } from "./math";
import { SeededRandom } from "./random";
import { randomBoolean, randomFloat, SeededRandom } from "./random";
import { Vec, type Vector } from "./vector";

export interface FloorDefinition {
Expand Down Expand Up @@ -257,7 +257,7 @@ export class Terrain {
/**
* Get rivers near a hitbox
*/
getRiversInHitbox(hitbox: Hitbox): River[] {
getRiversInHitbox(hitbox: Hitbox, ignoreTrails = false): River[] {
const rivers = new Set<River>();

const rect = hitbox.toRectangle();
Expand All @@ -267,6 +267,7 @@ export class Terrain {
for (let x = min.x; x <= max.x; x++) {
for (let y = min.y; y <= max.y; y++) {
for (const river of this._grid[x][y].rivers) {
if (ignoreTrails && river.isTrail) continue;
rivers.add(river);
}
}
Expand Down Expand Up @@ -310,6 +311,9 @@ export class River {

readonly isTrail: boolean;

readonly waterWidths: number[] = [];
readonly bankWidths: number[] = [];

constructor(
readonly width: number,
readonly points: readonly Vector[],
Expand Down Expand Up @@ -377,9 +381,11 @@ export class River {
};

if (isRiver) {
this.waterWidths.push(width);
calculatePoints(width, collidingRiver?.waterHitbox, waterPoints);
}

this.bankWidths.push(width + bankWidth);
calculatePoints(width + bankWidth, collidingRiver?.bankHitbox, bankPoints);
}

Expand Down Expand Up @@ -483,4 +489,15 @@ export class River {

return nearestT;
}

getRandomPosition(onBank?: boolean): Vector {
const t = Math.random();
// river width is not consistent so map t to a point indexing the width at that point
const pointIdx = Numeric.clamp(Math.floor(t * this.points.length), 0, this.points.length);
const waterWidth = this[onBank ? "bankWidths" : "waterWidths"][pointIdx];
const dist = randomFloat(0, waterWidth) * (randomBoolean() ? 1 : -1);
// add a random offset that's between river center and river border on either directions
const normal = this.getNormal(t);
return Vec.add(this.getPosition(t), Vec.scale(normal, dist));
}
}
59 changes: 42 additions & 17 deletions server/src/data/maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,19 @@ export interface RiverDefinition {
readonly maxWidth: number
readonly minWideWidth: number
readonly maxWideWidth: number
/**
* The number is the amount of the specified obstacle each river will have
* Multiplied by the river width and amount of nodes and divided by a magic number (500)
* (division done so the numbers here don't need to be too small / decimals)
* so its kinda of a "density" of obstacles inside a river
* a better way would be to calculate the polygon area of the river
* but that's slower and we don't need to be that accurate
*
* NOTE: obstacles in this object still need `River` or `Trail` spawn mode
* for river obstacles that have a fixed amount per game (like river chests)
* you should use `MapDefinition.obstacles`
*/
readonly obstacles: Record<ReferenceTo<ObstacleDefinition>, number>
}

export interface MapDefinition {
Expand Down Expand Up @@ -94,7 +107,11 @@ const maps = {
minWidth: 12,
maxWidth: 18,
minWideWidth: 25,
maxWideWidth: 30
maxWideWidth: 30,
obstacles: {
river_rock: 16,
lily_pad: 6
}
},
buildings: {
large_bridge: 2,
Expand Down Expand Up @@ -149,10 +166,8 @@ const maps = {
grenade_crate: 35,
rock: 150,
river_chest: 1,
river_rock: 45,
bush: 110,
// birthday_cake: 100, // birthday mode
lily_pad: 20,
blueberry_bush: 30,
barrel: 80,
viking_chest: 1,
Expand Down Expand Up @@ -219,7 +234,11 @@ const maps = {
maxWidth: 18,
minWideWidth: 25,
maxWideWidth: 28,
maxWideAmount: 1
maxWideAmount: 1,
obstacles: {
river_rock: 16,
lily_pad: 6
}
},
trails: {
minAmount: 2,
Expand All @@ -229,7 +248,10 @@ const maps = {
maxWidth: 4,
minWideWidth: 3,
maxWideWidth: 5,
maxWideAmount: 1
maxWideAmount: 1,
obstacles: {
pebble: 300
}
},
clearings: {
minWidth: 200,
Expand Down Expand Up @@ -303,10 +325,8 @@ const maps = {
rock: 220,
clearing_boulder: 15,
river_chest: 1,
river_rock: 60,
vibrant_bush: 200,
oak_leaf_pile: 200,
lily_pad: 50,
barrel: 90,
viking_chest: 1,
super_barrel: 35,
Expand All @@ -316,8 +336,7 @@ const maps = {
loot_barrel: 1,
flint_stone: 1,
pumpkin: 200,
large_pumpkin: 5,
pebble: 110
large_pumpkin: 5
},
obstacleClumps: [
{
Expand Down Expand Up @@ -386,7 +405,11 @@ const maps = {
maxWidth: 18,
minWideWidth: 25,
maxWideWidth: 28,
maxWideAmount: 1
maxWideAmount: 1,
obstacles: {
river_rock: 16,
lily_pad: 6
}
},
trails: {
minAmount: 4,
Expand All @@ -396,7 +419,10 @@ const maps = {
maxWidth: 4,
minWideWidth: 3,
maxWideWidth: 5,
maxWideAmount: 1
maxWideAmount: 1,
obstacles: {
pebble: 300
}
},
clearings: {
minWidth: 200,
Expand Down Expand Up @@ -472,10 +498,8 @@ const maps = {
rock: 220,
clearing_boulder: 15,
river_chest: 1,
river_rock: 60,
vibrant_bush: 200,
oak_leaf_pile: 200,
lily_pad: 50,
barrel: 90,
jack_o_lantern: 75,
viking_chest: 1,
Expand All @@ -487,8 +511,7 @@ const maps = {
flint_stone: 3,
pumpkin: 300,
large_pumpkin: 40,
plumpkin: 5,
pebble: 110
plumpkin: 5
},
obstacleClumps: [
{
Expand Down Expand Up @@ -547,7 +570,10 @@ const maps = {
minWidth: 12,
maxWidth: 18,
minWideWidth: 25,
maxWideWidth: 30
maxWideWidth: 30,
obstacles: {
river_rock: 16
}
},
buildings: {
large_bridge: 2,
Expand Down Expand Up @@ -602,7 +628,6 @@ const maps = {
grenade_crate_winter: 35,
rock: 150,
river_chest: 1,
river_rock: 45,
bush: 110,
// birthday_cake: 100, // birthday mode
blueberry_bush: 30,
Expand Down
52 changes: 42 additions & 10 deletions server/src/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,13 @@ export class GameMap {
rivers
);

if (mapDef.rivers) {
this._generateRiverObstacles(mapDef.rivers, false);
}
if (mapDef.trails) {
this._generateRiverObstacles(mapDef.trails, true);
}

this._generateClearings(mapDef.clearings);

Object.entries(mapDef.buildings ?? {}).forEach(([building, count]) => this._generateBuildings(building, count));
Expand Down Expand Up @@ -340,6 +347,35 @@ export class GameMap {
}
}

private _generateRiverObstacles(riverDef: RiverDefinition, onTrails: boolean): void {
for (const river of this.terrain.rivers) {
if (onTrails !== river.isTrail) continue;
for (const obstacle in riverDef.obstacles) {
const amount = riverDef.obstacles[obstacle] * river.width * river.points.length / 500;

const definition = Obstacles.reify(obstacle);

const hitbox = definition.spawnHitbox ?? definition.hitbox;

for (let i = 0; i < amount; i++) {
const position = this.getRandomPosition(hitbox, {
getPosition: () => {
return river.getRandomPosition(
definition.spawnMode === MapObjectSpawnMode.Trail
);
},
spawnMode: definition.spawnMode,
ignoreClearings: true
});

if (position) {
this.generateObstacle(definition, position);
}
}
}
}
}

private _generateClearings(clearingDef: MapDefinition["clearings"]): void {
if (!clearingDef) return;

Expand Down Expand Up @@ -838,11 +874,7 @@ export class GameMap {
// TODO: evenly distribute objects based on river size
case MapObjectSpawnMode.River: {
// rivers that aren't trails must have a waterHitbox
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return () => pickRandomInArray(this.terrain.rivers.filter(({ isTrail }) => !isTrail))?.waterHitbox!.randomPoint();
}
case MapObjectSpawnMode.RiverBank: {
return () => pickRandomInArray(this.terrain.rivers.filter(({ isTrail }) => !isTrail)).bankHitbox.randomPoint();
return () => pickRandomInArray(this.terrain.rivers.filter(({ isTrail }) => !isTrail))?.getRandomPosition();
}
case MapObjectSpawnMode.Beach: {
return () => {
Expand Down Expand Up @@ -870,7 +902,7 @@ export class GameMap {
};
}
case MapObjectSpawnMode.Trail: {
return () => pickRandomInArray(this.terrain.rivers.filter(({ isTrail }) => isTrail)).bankHitbox.randomPoint();
return () => pickRandomInArray(this.terrain.rivers.filter(({ isTrail }) => isTrail)).getRandomPosition(true);
}
}
})();
Expand Down Expand Up @@ -964,9 +996,10 @@ export class GameMap {
Vec.addComponent(position, radius, 0)
]
) {
for (const river of this.terrain.getRiversInHitbox(hitbox)) {
if (!river.waterHitbox?.isPointInside(point)) {
collided = true;
collided = true;
for (const river of this.terrain.getRiversInHitbox(hitbox, true)) {
if (river.waterHitbox?.isPointInside(point)) {
collided = false;
break;
}
}
Expand All @@ -976,7 +1009,6 @@ export class GameMap {
// TODO add code for other hitbox types
break;
}
case MapObjectSpawnMode.RiverBank:
case MapObjectSpawnMode.Trail: {
if (this.isInRiver(hitbox)) {
collided = true;
Expand Down

0 comments on commit 3545986

Please sign in to comment.