UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

580 lines (506 loc) • 23.5 kB
import Vector3 from "../../src/core/geom/Vector3js"; import { NumericInterval } from "../../src/core/math/interval/NumericInterval.js"; import { CellFilterCellMatcher } from "../../src/generation/filtering/CellFilterCellMatcher.js"; import { CellFilterCache } from "../../src/generation/filtering/numeric/CellFilterCache.js"; import { CellFilterLiteralFloat } from "../../src/generation/filtering/numeric/CellFilterLiteralFloat.js"; import { CellFilterAngleToNormal } from "../../src/generation/filtering/numeric/complex/CellFilterAngleToNormal.js"; import { CellFilterGaussianBlur } from "../../src/generation/filtering/numeric/complex/CellFilterGaussianBlur.js"; import { CellFilterSimplexNoise } from "../../src/generation/filtering/numeric/complex/CellFilterSimplexNoise.js"; import { CellFilterAdd } from "../../src/generation/filtering/numeric/math/algebra/CellFilterAdd.js"; import { CellFilterDivide } from "../../src/generation/filtering/numeric/math/algebra/CellFilterDivide.js"; import { CellFilterMultiply } from "../../src/generation/filtering/numeric/math/algebra/CellFilterMultiply.js"; import { CellFilterSubtract } from "../../src/generation/filtering/numeric/math/algebra/CellFilterSubtract.js"; import { CellFilterClamp } from "../../src/generation/filtering/numeric/math/CellFilterClamp.js"; import { CellFilterInverseLerp } from "../../src/generation/filtering/numeric/math/CellFilterInverseLerp.js"; import { CellFilterLerp } from "../../src/generation/filtering/numeric/math/CellFilterLerp.js"; import { CellFilterMax2 } from "../../src/generation/filtering/numeric/math/CellFilterMax2.js"; import { CellFilterOneMinus } from "../../src/generation/filtering/numeric/math/CellFilterOneMinus.js"; import { CellFilterSmoothStep } from "../../src/generation/filtering/numeric/math/CellFilterSmoothStep.js"; import { CellFilterStep } from "../../src/generation/filtering/numeric/math/CellFilterStep.js"; import { CellFilterCubicFunction } from "../../src/generation/filtering/numeric/math/poly/CellFilterCubicFunction.js"; import { CellFilterSampleLayerLinear } from "../../src/generation/filtering/numeric/sampling/CellFilterSampleLayerLinear.js"; import { GridTaskActionRuleSet } from "../../src/generation/grid/generation/discrete/GridTaskActionRuleSet.js"; import { GridTaskCellularAutomata } from "../../src/generation/grid/generation/discrete/GridTaskCellularAutomata.js"; import { GridTaskConnectRooms } from "../../src/generation/grid/generation/discrete/GridTaskConnectRooms.js"; import { GridTaskBuildSourceDistanceMap } from "../../src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js"; import { GridTaskDensityMarkerDistribution } from "../../src/generation/grid/generation/GridTaskDensityMarkerDistribution.js"; import { GridTaskSequence } from "../../src/generation/grid/generation/GridTaskSequence.js"; import { GridTaskGenerateRoads } from "../../src/generation/grid/generation/road/GridTaskGenerateRoads.js"; import { GridTaskGroup } from "../../src/generation/GridTaskGroup.js"; import { GridActionRuleSet } from "../../src/generation/markers/GridActionRuleSet.js"; import { GridCellActionPlaceMarker } from "../../src/generation/markers/GridCellActionPlaceMarker.js"; import { MarkerNodeMatcherByType } from "../../src/generation/markers/matcher/MarkerNodeMatcherByType.js"; import { MarkerNodeTransformerRecordProperty } from "../../src/generation/markers/transform/MarkerNodeTransformerRecordProperty.js"; import { MarkerNodeTransformerYRotateByFilter } from "../../src/generation/markers/transform/MarkerNodeTransformerYRotateByFilter.js"; import { GridCellActionPlaceTags } from "../../src/generation/placement/action/GridCellActionPlaceTags.js"; import { GridCellActionWriteFilterToLayer } from "../../src/generation/placement/action/GridCellActionWriteFilterToLayer.js"; import { GridCellActionSequence } from "../../src/generation/placement/action/util/GridCellActionSequence.js"; import { GridCellPlacementRule } from "../../src/generation/placement/GridCellPlacementRule.js"; import { CellMatcherContainsMarkerWithinRadius } from "../../src/generation/rules/cell/CellMatcherContainsMarkerWithinRadius.js"; import { CellMatcherGridPattern } from "../../src/generation/rules/cell/CellMatcherGridPattern.js"; import { GridPatternMatcherCell } from "../../src/generation/rules/cell/GridPatternMatcherCell.js"; import { CellMatcherAny } from "../../src/generation/rules/CellMatcherAny.js"; import { CellMatcherLayerBitMaskTest } from "../../src/generation/rules/CellMatcherLayerBitMaskTest.js"; import { CellMatcherAnd } from "../../src/generation/rules/logic/CellMatcherAnd.js"; import { CellMatcherNot } from "../../src/generation/rules/logic/CellMatcherNot.js"; import { SampleGroundMoistureFilter } from "./filters/SampleGroundMoistureFilter.js"; import { SampleNoise20_0 } from "./filters/SampleNoise20_0.js"; import { mir_generator_place_bases } from "./generators/mir_generator_place_bases.js"; import { mir_generator_place_road_decorators } from "./generators/mir_generator_place_road_decorators.js"; import { mir_generator_place_starting_point } from "./generators/mir_generator_place_starting_point.js"; import { GridTags } from "./grid/GridTags.js"; import { MirGridLayers } from "./grid/MirGridLayers.js"; import { matcher_not_play_area } from "./rules/matcher_not_play_area.js"; import { matcher_tag_traversable } from "./rules/matcher_tag_traversable.js"; import { matcher_tag_traversable_unoccupied } from "./rules/matcher_tag_traversable_unoccupied.js"; import { mir_matcher_attack_corridor } from "./rules/mir_matcher_attack_corridor.js"; export const SampleGenerator0 = new GridTaskGroup(); const pTreasureCorner = new CellMatcherGridPattern(); const MATCH_EMPTY = CellMatcherLayerBitMaskTest.from(GridTags.Traversable, MirGridLayers.Tags); const MATCH_TREASURE = CellMatcherLayerBitMaskTest.from(GridTags.Treasure, MirGridLayers.Tags); const MATCH_NOT_EMPTY = CellMatcherNot.from(MATCH_EMPTY); const MATCH_STARTING_POINT = CellMatcherLayerBitMaskTest.from(GridTags.StartingPoint, MirGridLayers.Tags); const MATCH_ENEMY = CellMatcherLayerBitMaskTest.from(GridTags.Enemy, MirGridLayers.Tags); pTreasureCorner.addRule(1, 0, MATCH_NOT_EMPTY); pTreasureCorner.addRule(0, 1, MATCH_NOT_EMPTY); pTreasureCorner.addRule(0, 0, matcher_tag_traversable_unoccupied); const pNoTreasureIn3 = new CellMatcherGridPattern(); pNoTreasureIn3.addRule(0, 0, CellMatcherNot.from(CellMatcherContainsMarkerWithinRadius.from( MarkerNodeMatcherByType.from('Treasure'), 3 ))); const chestPlacementRule = GridCellPlacementRule.from({ matcher: CellMatcherAnd.from(pTreasureCorner, pNoTreasureIn3), action: GridCellActionSequence.from([ GridCellActionPlaceMarker.from({ type: 'Treasure', size: 0.5 }), GridCellActionPlaceTags.from(GridTags.Treasure, MirGridLayers.Tags) ]), probability: CellFilterLiteralFloat.from(0.5) }); const aMakePlayArea = GridCellActionSequence.from([ GridCellActionPlaceTags.from(GridTags.Traversable | GridTags.PlayArea, MirGridLayers.Tags) ]); const gMakeEmpty = GridTaskCellularAutomata.from({ action: aMakePlayArea, margin: 3 }); const gConnectRooms = GridTaskConnectRooms.from({ matcher: matcher_tag_traversable, action: aMakePlayArea }); gConnectRooms.addDependency(gMakeEmpty); const gRuleSet1 = GridTaskActionRuleSet.from({ rules: GridActionRuleSet.from({ rules: [chestPlacementRule] }) }); const pNearTreasure = new CellMatcherGridPattern(); pNearTreasure.addRule(0, 0, CellMatcherAnd.from( matcher_tag_traversable_unoccupied, CellMatcherNot.from(MATCH_ENEMY) ) ); pNearTreasure.addRule(1, 1, MATCH_TREASURE); pNearTreasure.addRule(2, 2, MATCH_NOT_EMPTY); const MATCH_ENEMY_IN_3 = CellMatcherContainsMarkerWithinRadius.from(MarkerNodeMatcherByType.from('Enemy'), 3); const MATCH_NO_ENEMY_IN_3 = CellMatcherNot.from(MATCH_ENEMY_IN_3); const pNoEnemyIn3 = new CellMatcherGridPattern(); pNoEnemyIn3.addRule(0, 0, MATCH_NO_ENEMY_IN_3); const ACTION_PLACE_ENEMY_MARKER = GridCellActionPlaceMarker.from({ type: 'Enemy', size: 0.5, transformers: [ MarkerNodeTransformerRecordProperty.from( 'power', CellFilterAdd.from( CellFilterMultiply.from( CellFilterCubicFunction.from( CellFilterMax2.from( CellFilterSubtract.from( CellFilterMultiply.from( CellFilterDivide.from( CellFilterSampleLayerLinear.from(MirGridLayers.DistanceFromStart), //increase level by 1 for each X tiles distance CellFilterLiteralFloat.from(20) ), //add a bit of noise to difficulty distribution CellFilterMultiply.from( CellFilterSmoothStep.from( CellFilterLiteralFloat.from(20), CellFilterLiteralFloat.from(50), CellFilterSampleLayerLinear.from(MirGridLayers.DistanceFromStart) ), CellFilterLerp.from( CellFilterLiteralFloat.from(0.9), CellFilterLiteralFloat.from(2), CellFilterMultiply.from( CellFilterSimplexNoise.from(111.1134, 111.1134, 12319518), CellFilterSimplexNoise.from(25.4827, 25.4827, 4512371) ) ) ) ), CellFilterLiteralFloat.from(1) ), CellFilterLiteralFloat.from(0) ), 0, 1, //linear factor 0.05, //quadratic factor (good for estimating branching) 0.01 //cubic factor ), CellFilterLiteralFloat.from(100) ), CellFilterLiteralFloat.from(50) ) ) ] }); const ACTION_PLACE_ENEMY_TAG = GridCellActionPlaceTags.from(GridTags.Enemy | GridTags.Occupied, MirGridLayers.Tags); const prTreasureGuards = GridCellPlacementRule.from( { matcher: pNearTreasure, action: GridCellActionSequence.from([ ACTION_PLACE_ENEMY_MARKER, ACTION_PLACE_ENEMY_TAG ]) } ); const gRuleSetTreasureGuards = GridTaskActionRuleSet.from({ rules: GridActionRuleSet.from({ rules: [prTreasureGuards] }) }); gRuleSetTreasureGuards.addDependency(gRuleSet1); const prEnemyTreasureGuard = GridCellPlacementRule.from( { matcher: CellMatcherAnd.from( pNearTreasure, pNoEnemyIn3 ), action: GridCellActionSequence.from([ ACTION_PLACE_ENEMY_MARKER, ACTION_PLACE_ENEMY_TAG ]) } ); const prEnemyCorridorGuard = GridCellPlacementRule.from( { matcher: CellMatcherAnd.from( mir_matcher_attack_corridor, pNoEnemyIn3 ), action: GridCellActionSequence.from([ ACTION_PLACE_ENEMY_MARKER, ACTION_PLACE_ENEMY_TAG ]), probability: CellFilterLiteralFloat.from(0.5) } ); const gRuleSet2 = GridTaskActionRuleSet.from({ rules: GridActionRuleSet.from({ rules: [prEnemyTreasureGuard, prEnemyCorridorGuard] }) }); gRuleSet2.addDependency(gRuleSetTreasureGuards); // Place starting point tag const gPlaceStartingPoint = mir_generator_place_starting_point(); gPlaceStartingPoint.addDependency(gConnectRooms); const gBuildDistanceMap = GridTaskBuildSourceDistanceMap.from({ source: MATCH_STARTING_POINT, pass: MATCH_EMPTY, layer: MirGridLayers.DistanceFromStart }); gBuildDistanceMap.addDependency(gPlaceStartingPoint); gRuleSet1.addDependency(gBuildDistanceMap); const gBases = mir_generator_place_bases(); gBases.addDependency(gMakeEmpty); gConnectRooms.addDependency(gBases); gPlaceStartingPoint.addDependency(gBases); gRuleSet1.addDependency(gBases); const gRoads = new GridTaskGenerateRoads(); gRoads.addDependency(gConnectRooms); gRoads.addDependency(gBases); const gRoadDecorators = mir_generator_place_road_decorators(); gRoadDecorators.addDependency(gRoads); /** * * @type {GridTaskActionRuleSet} */ const gDrawLayerMoisture = GridTaskActionRuleSet.from({ rules: GridActionRuleSet.from({ rules: [ GridCellPlacementRule.from( { matcher: CellMatcherAny.INSTANCE, action: GridCellActionWriteFilterToLayer.from(MirGridLayers.Moisture, SampleGroundMoistureFilter) } ) ] }) }); //trees const fReadHeight = CellFilterSampleLayerLinear.from(MirGridLayers.Heights); const fTreeArea = CellFilterCache.from( CellFilterMultiply.from( CellFilterMultiply.from( SampleNoise20_0, CellFilterClamp.from( CellFilterInverseLerp.from( CellFilterLiteralFloat.from(0.1), CellFilterLiteralFloat.from(0.5), CellFilterSampleLayerLinear.from(MirGridLayers.Moisture) ), CellFilterLiteralFloat.from(0), CellFilterLiteralFloat.from(1) ) ), CellFilterMultiply.from( CellFilterMultiply.from( //filter out areas that are below height of 0 CellFilterStep.from( CellFilterLiteralFloat.from(0), fReadHeight ), //filter areas that are playable CellFilterCellMatcher.from( matcher_not_play_area ) ), // Filter areas with sharp slopes CellFilterOneMinus.from( CellFilterSmoothStep.from( CellFilterLiteralFloat.from(Math.PI / 9), CellFilterLiteralFloat.from(Math.PI / 5), CellFilterAngleToNormal.from(fReadHeight, Vector3.forward) ) ) ) ) ); const fFlatlandTrees = CellFilterCache.from( CellFilterMultiply.from( fTreeArea, CellFilterClamp.from( CellFilterInverseLerp.from( CellFilterLiteralFloat.from(0.2), CellFilterLiteralFloat.from(0), CellFilterCache.from( CellFilterGaussianBlur.from( CellFilterAngleToNormal.from(fReadHeight), 2.3, 2.3 ) ) ), CellFilterLiteralFloat.from(0), CellFilterLiteralFloat.from(1) ) ) ); const matcher_non_play_area_3x3 = CellMatcherGridPattern.from([ GridPatternMatcherCell.from(matcher_not_play_area, -1, -1), GridPatternMatcherCell.from(matcher_not_play_area, 0, -1), GridPatternMatcherCell.from(matcher_not_play_area, 1, -1), GridPatternMatcherCell.from(matcher_not_play_area, -1, 0), GridPatternMatcherCell.from(matcher_not_play_area, 0, 0), GridPatternMatcherCell.from(matcher_not_play_area, 1, 0), GridPatternMatcherCell.from(matcher_not_play_area, -1, 1), GridPatternMatcherCell.from(matcher_not_play_area, 0, 1), GridPatternMatcherCell.from(matcher_not_play_area, 1, 1), ]); const filterNonPlayableArea_3x3 = CellFilterCellMatcher.from(matcher_non_play_area_3x3); const gFoliageLarge = GridTaskSequence.from([ GridTaskDensityMarkerDistribution.from( CellFilterCache.from( CellFilterMultiply.from( CellFilterMultiply.from( fFlatlandTrees, //trees take up quite a bit of space, make sure they are far enough from play area filterNonPlayableArea_3x3 ), CellFilterLiteralFloat.from(10) ) ), GridCellActionPlaceMarker.from({ type: 'Tree-Flatland-Large', size: 0.5, transformers: [ MarkerNodeTransformerYRotateByFilter.from( CellFilterMultiply.from( CellFilterSimplexNoise.from( 3.1234, 3.1234, 90127151 ), CellFilterLiteralFloat.from(123421) ) ) ] }), new NumericInterval(2.7, 3.4) ), GridTaskDensityMarkerDistribution.from( CellFilterMultiply.from( fFlatlandTrees, CellFilterLiteralFloat.from(20) ), GridCellActionPlaceMarker.from({ type: 'Tree-Flatland-Small', size: 0.5, transformers: [ MarkerNodeTransformerYRotateByFilter.from( CellFilterMultiply.from( CellFilterSimplexNoise.from( 3.1234, 3.1234, 90127151 ), CellFilterLiteralFloat.from(123421) ) ) ] }), new NumericInterval(1.7, 2) ) ]); const fSharpSlope = CellFilterCache.from( CellFilterSmoothStep.from( CellFilterLiteralFloat.from(Math.PI / 5), CellFilterLiteralFloat.from(Math.PI / 3.5), CellFilterAngleToNormal.from( CellFilterSampleLayerLinear.from(MirGridLayers.Heights), Vector3.forward ) ) ); const filterMushroom = fTreeArea; const gFoliageSmall = GridTaskSequence.from([ GridTaskDensityMarkerDistribution.from( CellFilterMultiply.from( filterMushroom, CellFilterLiteralFloat.from(0.02) ), GridCellActionPlaceMarker.from({ type: 'Mushroom-1', size: 0.5, transformers: [] }), new NumericInterval(0.3, 0.37), 42 ), GridTaskDensityMarkerDistribution.from( CellFilterMultiply.from( filterMushroom, CellFilterLiteralFloat.from(0.05) ), GridCellActionPlaceMarker.from({ type: 'Mushroom-0', size: 0.5, transformers: [] }), new NumericInterval(0.17, 0.25), 9000234 ), GridTaskDensityMarkerDistribution.from( CellFilterCache.from( CellFilterMultiply.from( CellFilterMultiply.from( CellFilterSubtract.from( CellFilterGaussianBlur.from( fSharpSlope, 3, 3 ), fSharpSlope ), CellFilterCellMatcher.from(matcher_not_play_area) ), CellFilterLiteralFloat.from(0.15) ) ), GridCellActionPlaceMarker.from({ type: 'Stone-0', size: 0.5, transformers: [] }), new NumericInterval(0.15, 0.35), 9000234 ) ]); //heights const mHeightArea = new CellMatcherGridPattern(); mHeightArea.addRule(0, -2, matcher_not_play_area); mHeightArea.addRule(-1, -1, matcher_not_play_area); mHeightArea.addRule(0, -1, matcher_not_play_area); mHeightArea.addRule(1, -1, matcher_not_play_area); mHeightArea.addRule(-2, 0, matcher_not_play_area); mHeightArea.addRule(-1, 0, matcher_not_play_area); mHeightArea.addRule(0, 0, matcher_not_play_area); mHeightArea.addRule(1, 0, matcher_not_play_area); mHeightArea.addRule(2, 0, matcher_not_play_area); mHeightArea.addRule(-1, 1, matcher_not_play_area); mHeightArea.addRule(0, 1, matcher_not_play_area); mHeightArea.addRule(1, 1, matcher_not_play_area); mHeightArea.addRule(0, 2, matcher_not_play_area); const gHeights = GridTaskActionRuleSet.from({ rules: GridActionRuleSet.from( { rules: [ GridCellPlacementRule.from( { matcher: CellMatcherAny.INSTANCE, actions: GridCellActionWriteFilterToLayer.from( MirGridLayers.Heights, CellFilterLerp.from( CellFilterLiteralFloat.from(0), CellFilterLerp.from( CellFilterLiteralFloat.from(-2), CellFilterLiteralFloat.from(7), CellFilterMultiply.from( CellFilterSimplexNoise.from(30, 30), CellFilterSimplexNoise.from(13, 13) ) ), CellFilterGaussianBlur.from( CellFilterCache.from( CellFilterCellMatcher.from( mHeightArea ) ), 1.5, 1.5 ) ) ) } ) ] } ), resolution: 1 }); gHeights.addDependency(gConnectRooms); gFoliageLarge.addDependencies([ gMakeEmpty, gConnectRooms, gHeights, gDrawLayerMoisture, gRuleSet1, gRuleSet2, gRuleSetTreasureGuards, gBases ]); gFoliageSmall.addDependencies([gFoliageLarge]); gDrawLayerMoisture.addDependencies([gHeights]); SampleGenerator0.addGenerator(gHeights); SampleGenerator0.addGenerator(gDrawLayerMoisture); SampleGenerator0.addGenerator(gMakeEmpty); SampleGenerator0.addGenerator(gConnectRooms); SampleGenerator0.addGenerator(gRoads); SampleGenerator0.addGenerator(gRoadDecorators); SampleGenerator0.addGenerator(gBases); SampleGenerator0.addGenerator(gPlaceStartingPoint); SampleGenerator0.addGenerator(gBuildDistanceMap); SampleGenerator0.addGenerator(gRuleSet1); SampleGenerator0.addGenerator(gRuleSet2); SampleGenerator0.addGenerator(gRuleSetTreasureGuards); SampleGenerator0.addGenerator(gFoliageLarge); SampleGenerator0.addGenerator(gFoliageSmall);