Skip to content

Commit

Permalink
feat(combat): add ocean specific combat zones
Browse files Browse the repository at this point in the history
 - add new ocean 1-4 combat zones to wilderness map
 - update combat encounters to take into account boardedShip state
 - when on the ship, new water encounters are available
  • Loading branch information
justindujardin committed Oct 25, 2022
1 parent 7da7ca3 commit 1ec920c
Show file tree
Hide file tree
Showing 18 changed files with 260 additions and 127 deletions.
4 changes: 2 additions & 2 deletions src/app/behaviors/base-player.behavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,15 @@ export class BasePlayerComponent extends MovableBehavior {
* @returns {boolean} True if the passable attribute was found and set to false.
*/
collideWithMap(at: Point, passableAttribute: string): boolean {
let map = <TileMap>this.host.scene.objectByType(TileMap);
let map = <TileMap>this.host.scene?.objectByType(TileMap);
if (map) {
const layers: ITiledLayer[] = map.getLayers();
for (let i = 0; i < layers.length; i++) {
const terrain = map.getTileData(layers[i], at.x, at.y);
if (!terrain) {
continue;
}
if (terrain[passableAttribute] === false) {
if (terrain.properties && terrain.properties[passableAttribute] === false) {
return true;
}
}
Expand Down
13 changes: 11 additions & 2 deletions src/app/models/combat/combat.model.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import * as Immutable from 'immutable';
import { IPoint } from '../../../app/core/point';
import { Rect } from '../../core';
import { CombatantTypes, IEnemy, IPartyMember } from '../base-entity';

/** Valid combat types */
export type CombatType = 'none' | 'fixed' | 'random';

export interface IZoneTarget {
/** The zone name */
name: string;
/** Is ship zone */
water: boolean;
bounds: Rect;
}

/**
* Describe a set of combat zones for a given point on a map.
*/
Expand All @@ -14,9 +23,9 @@ export interface IZoneMatch {
*/
map: string;
/**
* The zone name for the target location on the map
* The zone names for the target location on the map
*/
target: string;
targets: IZoneTarget[];
/**
* The point that target refers to.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/app/models/game-data/random-encounters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export const RANDOM_ENCOUNTERS_DATA: ITemplateRandomEncounter[] = [
zones: ['zone-sewer'],
enemies: ['huge-spider', 'kobold', 'kobold'],
},
{
id: 'world-ocean-1-1',
zones: ['world-ocean-1'],
enemies: ['kobold', 'kobold', 'kobold-shaman'],
},
{
id: 'world-forest-single',
zones: ['world-forest'],
Expand Down
7 changes: 6 additions & 1 deletion src/app/models/game-state/game-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ export class GameStateService {
return this.store.pipe(
first(),
map((state: AppState) => {
const jsonData: string = JSON.stringify(state);
const most = {
...state,
sprites: {},
};
const jsonData: string = JSON.stringify(most);

localStorage.setItem(GameStateService.STATE_KEY, jsonData);
return state;
})
Expand Down
83 changes: 51 additions & 32 deletions src/app/routes/world/behaviors/combat-encounter.behavior.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,25 @@ import { RANDOM_ENCOUNTERS_DATA } from 'app/models/game-data/random-encounters';
import * as Immutable from 'immutable';
import { List } from 'immutable';
import { Observable } from 'rxjs';
import { map, take } from 'rxjs/operators';
import { take, withLatestFrom } from 'rxjs/operators';
import { IEnemy } from '../../../../app/models/base-entity';
import { AppState } from '../../../app.model';
import { IMoveDescription } from '../../../behaviors/movable-behavior';
import { SceneObjectBehavior } from '../../../behaviors/scene-object-behavior';
import { CombatEncounterAction } from '../../../models/combat/combat.actions';
import { CombatEncounter, IZoneMatch } from '../../../models/combat/combat.model';
import {
CombatEncounter,
IZoneMatch,
IZoneTarget,
} from '../../../models/combat/combat.model';
import { Entity } from '../../../models/entity/entity.model';
import { instantiateEntity } from '../../../models/game-data/game-data.model';
import { GameStateSetBattleCounterAction } from '../../../models/game-state/game-state.actions';
import { getGameBattleCounter, getGameParty } from '../../../models/selectors';
import {
getGameBattleCounter,
getGameBoardedShip,
getGameParty,
} from '../../../models/selectors';
import { GameEntityObject } from '../../../scene/objects/game-entity-object';
import { Scene } from '../../../scene/scene';
import { TileMap } from '../../../scene/tile-map';
Expand Down Expand Up @@ -93,37 +101,48 @@ export class CombatEncounterBehaviorComponent extends SceneObjectBehavior {
this.store
.select(getGameParty)
.pipe(
map((party: Immutable.List<Entity>) => {
const viableEncounters = RANDOM_ENCOUNTERS_DATA.filter((enc: any) => {
return (
enc.zones.indexOf(zone.map) !== -1 ||
enc.zones.indexOf(zone.target) !== -1
);
});
if (viableEncounters.length === 0) {
throw new Error('no valid encounters for this zone');
}
const max = viableEncounters.length - 1;
const min = 0;
const encounter =
viableEncounters[Math.floor(Math.random() * (max - min + 1)) + min];
const toCombatant = (id: string): IEnemy => {
const itemTemplate: IEnemy = getEnemyById(id) as any;
return instantiateEntity<IEnemy>(itemTemplate, {
maxhp: itemTemplate.hp,
withLatestFrom(
this.store.select(getGameBoardedShip),
(party: Immutable.List<Entity>, onShip: boolean) => {
const zoneTarget = zone.targets.find((zoneTarget: IZoneTarget) => {
if (onShip) {
return zoneTarget.water;
} else {
return !zoneTarget.water;
}
});

const viableEncounters = RANDOM_ENCOUNTERS_DATA.filter((enc: any) => {
return (
enc.zones.indexOf(zone.map) !== -1 ||
enc.zones.indexOf(zoneTarget.name) !== -1
);
});
};
if (viableEncounters.length === 0) {
throw new Error(`no valid encounters for zone: ${zoneTarget.name}`);
}
const max = viableEncounters.length - 1;
const min = 0;
const encounter =
viableEncounters[Math.floor(Math.random() * (max - min + 1)) + min];
const toCombatant = (id: string): IEnemy => {
const itemTemplate: IEnemy = getEnemyById(id) as any;
return instantiateEntity<IEnemy>(itemTemplate, {
maxhp: itemTemplate.hp,
});
};

const payload: CombatEncounter = {
type: 'random',
id: encounter.id,
enemies: List<IEnemy>(encounter.enemies.map(toCombatant)),
zone: zone.target || zone.map,
message: List<string>(encounter.message),
party: List<Entity>(party),
};
this.store.dispatch(new CombatEncounterAction(payload));
}),
const payload: CombatEncounter = {
type: 'random',
id: encounter.id,
enemies: List<IEnemy>(encounter.enemies.map(toCombatant)),
zone: zoneTarget?.name || zone.map,
message: List<string>(encounter.message),
party: List<Entity>(party),
};
this.store.dispatch(new CombatEncounterAction(payload));
}
),
take(1)
)
.subscribe();
Expand Down
7 changes: 5 additions & 2 deletions src/app/routes/world/map/features/combat-feature.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { List } from 'immutable';
import { map, take } from 'rxjs/operators';
import { IEnemy } from '../../../../../app/models/base-entity';
import { AppState } from '../../../../app.model';
import { Point } from '../../../../core';
import { CombatEncounterAction } from '../../../../models/combat/combat.actions';
import { CombatEncounter, IZoneMatch } from '../../../../models/combat/combat.model';
import { Entity } from '../../../../models/entity/entity.model';
Expand Down Expand Up @@ -76,7 +77,9 @@ export class CombatFeatureComponent extends TiledFeatureComponent {
object.setPoint(object.point);

// Find the combat zone and launch a fixed encounter.
const zone: IZoneMatch = this.party.map.getCombatZones(this.party.host.point);
const zone: IZoneMatch = this.party.map.getCombatZones(
new Point(this.party.host.point)
);

this.store
.select(getGameParty)
Expand All @@ -95,7 +98,7 @@ export class CombatFeatureComponent extends TiledFeatureComponent {
type: 'fixed',
id: encounter.id,
enemies: List<IEnemy>(encounter.enemies.map(toCombatant)),
zone: zone.target || zone.map,
zone: zone.targets[0].name || zone.map,
message: List<string>(encounter.message),
party: List<Entity>(party),
};
Expand Down
7 changes: 2 additions & 5 deletions src/app/routes/world/map/features/ship-feature.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ export class ShipFeatureComponent
.subscribe((p: PointRecord) => {
this.host.setPoint({ x: p.x, y: p.y });
});

return true;
}

Expand Down Expand Up @@ -129,10 +128,8 @@ export class ShipFeatureComponent
this.host.enabled = false;
object.setSprite(this.host.icon, 0);
this._tickInterval = setInterval(() => {
if (
Point.equal(this.partyObject.point, this.party.targetPoint) &&
!this.party.heading.isZero()
) {
const partyTarget = Point.equal(this.partyObject.point, this.party.targetPoint);
if (partyTarget && !this.party.heading.isZero()) {
const from: Point = new Point(this.partyObject.point);
const to: Point = from.clone().add(this.party.heading);
if (
Expand Down
18 changes: 7 additions & 11 deletions src/app/scene/tile-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@
// TODO: TileMap isn't getting added to Spatial DB properly. Can't query for it!
// Scene assuming something about the spatial properties on objects?
import * as _ from 'underscore';
import { ITiledLayer } from '../../app/core/resources/tiled/tiled.model';
import { ITiledLayer, ITiledObject } from '../../app/core/resources/tiled/tiled.model';
import {
IPoint,
ITileInstanceMeta,
Point,
Rect,
TiledTMXResource,
TiledTSXResource,
TilesetTile,
} from '../core';
import { IZoneMatch } from '../models/combat/combat.model';
import { IZoneMatch, IZoneTarget } from '../models/combat/combat.model';
import { GameWorld } from '../services/game-world';
import { Scene } from './scene';
import { SceneObject } from './scene-object';
Expand Down Expand Up @@ -162,10 +161,10 @@ export class TileMap extends SceneObject {
* @param at The position to check for a sub-zone in the map
* @returns {IZoneMatch} The map and target zones that are null if they don't exist
*/
getCombatZones(at: IPoint): IZoneMatch {
getCombatZones(at: Point): IZoneMatch {
const result: IZoneMatch = {
map: null,
target: null,
targets: [],
targetPoint: at,
};
if (this.map?.properties) {
Expand All @@ -175,23 +174,20 @@ export class TileMap extends SceneObject {
}
// Determine which zone and combat type
const invTileSize = 1 / this.map.tilewidth;
const zones: any[] = _.map(this.zones?.objects, (z: any) => {
const zones: IZoneTarget[] = _.map(this.zones?.objects, (z: ITiledObject) => {
const x = z.x * invTileSize;
const y = z.y * invTileSize;
const w = z.width * invTileSize;
const h = z.height * invTileSize;
return {
bounds: new Rect(x, y, w, h),
name: z.name,
water: z.properties?.water || false,
};
});
// TODO: This will always get the first zone. What about overlapping zones?
const zone = _.find(zones, (z: any) => {
result.targets = zones.filter((z: IZoneTarget) => {
return z.bounds.pointInRect(at) && z.name;
});
if (zone) {
result.target = zone.name;
}
return result;
}

Expand Down
10 changes: 5 additions & 5 deletions src/assets/maps/castle.tmx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.9" tiledversion="1.9.2" orientation="orthogonal" renderorder="right-down" width="33" height="17" tilewidth="16" tileheight="16" infinite="0" nextlayerid="3" nextobjectid="23">
<map version="1.9" tiledversion="1.9.2" orientation="orthogonal" renderorder="right-down" width="33" height="17" tilewidth="16" tileheight="16" infinite="0" nextlayerid="4" nextobjectid="23">
<tileset firstgid="1" source="tiles/environment.tsx"/>
<tileset firstgid="72" source="tiles/creatures.tsx"/>
<tileset firstgid="157" source="tiles/objects.tsx"/>
<layer id="1" name="Terrain" width="33" height="17">
<data encoding="csv">
9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
9,61,61,61,39,61,61,61,9,9,33,33,33,9,30,33,33,33,30,9,33,33,33,9,9,39,39,39,39,39,39,39,9,
9,61,61,39,39,39,61,61,9,33,33,33,33,6,33,33,33,33,33,6,33,33,33,33,9,39,39,39,39,39,39,39,9,
9,61,61,39,39,39,61,61,9,33,33,33,33,163,33,33,33,33,33,163,33,33,33,33,9,39,39,39,39,39,39,39,9,
9,61,39,39,33,39,39,61,9,9,33,33,33,9,17,33,33,33,17,9,33,33,33,9,9,39,39,39,33,39,39,39,9,
9,61,61,61,33,61,61,61,61,9,9,9,6,9,9,17,33,17,9,9,6,9,9,9,39,39,39,33,33,33,39,39,9,
9,61,61,61,33,61,61,61,61,9,9,9,163,9,9,17,33,17,9,9,163,9,9,9,39,39,39,33,33,33,39,39,9,
9,61,61,39,33,39,61,61,61,9,9,33,33,33,9,33,33,33,9,33,33,33,9,9,39,39,39,39,33,39,39,39,9,
9,61,61,39,33,39,39,39,61,61,9,9,33,33,9,9,24,9,9,33,33,9,9,39,39,39,39,39,33,39,39,39,9,
9,61,61,39,33,39,39,39,61,61,9,9,33,33,9,9,33,9,9,33,33,9,9,39,39,39,39,39,33,39,39,39,9,
9,61,61,33,33,33,39,61,61,39,61,9,9,9,9,9,33,9,9,9,9,9,39,61,39,61,39,33,33,33,39,39,9,
9,61,61,33,33,33,33,33,33,33,33,6,33,33,33,33,33,33,33,33,33,6,33,33,33,33,33,33,33,33,39,61,9,
9,61,61,33,33,33,33,33,33,33,33,163,33,33,33,33,33,33,33,33,33,163,33,33,33,33,33,33,33,33,39,61,9,
9,61,61,33,33,33,39,61,61,39,61,9,9,9,9,9,33,9,9,9,9,9,39,61,39,61,39,33,33,33,39,39,9,
9,61,61,61,61,61,39,39,39,39,39,9,9,9,9,30,33,30,9,9,9,9,39,39,39,39,39,39,39,39,39,39,9,
9,61,61,61,39,61,61,61,61,61,39,9,9,9,9,9,24,9,9,9,9,9,39,39,39,39,39,39,61,39,39,61,9,
Expand Down
Loading

0 comments on commit 1ec920c

Please sign in to comment.