Skip to content

Commit

Permalink
redfection: base from filters branch
Browse files Browse the repository at this point in the history
  • Loading branch information
le-jeu committed Nov 13, 2022
1 parent 7370d65 commit d8e4843
Show file tree
Hide file tree
Showing 6 changed files with 403 additions and 1 deletion.
2 changes: 1 addition & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const buildName = process.env.BUILD;
const buildPath = "dist";
const pluginsPath = "src";

let pluginsId = ["search-guid", "portals-pictures", "what3words", "dialogs", "player-inventory"];
let pluginsId = ["search-guid", "portals-pictures", "what3words", "dialogs", "player-inventory", "redfection"];

export default pluginsId.map((p) => ({
input: path.join(pluginsPath, p),
Expand Down
147 changes: 147 additions & 0 deletions src/redfection/filters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/* Filters API
Filters work by exclusion, following the old layer system.
A feature that matches a filter is removed from the map.
A filter applies to a combinaison of portal/link/field and is described by
- data properties that must (all) match
- or a predicate for complex filter
{ portal: true, link: true, data: { team: 'E' }}
filters any ENL portal/link
[{ link: true, data: { oGuid: "some guid" }}, { link: true, data: { dGuid: "some guid" }}]
filters any links on portal with guid "some guid"
{ field: true, pred: function (f) { return f.options.timestamp < Date.parse('2021-10-31'); } }
filters any fields made before Halloween 2021
*/

/**
* @type {Object.<string, FilterDesc>}
*/
const _filters = {};

/**
* @callback FilterPredicate
* @param {Object} ent - IITC entity
* @returns {boolean}
*/

/**
* @typedef FilterDesc
* @type {object}
* @property {boolean} filterDesc.portal apply to portal
* @property {boolean} filterDesc.link apply to link
* @property {boolean} filterDesc.field apply to field
* @property {object} [filterDesc.data] entity data properties that must match
* @property {FilterPredicate} [filterDesc.pred] predicate on the entity
*/

/**
* @param {string} name filter name
* @param {FilterDesc | FilterDesc[]} filterDesc filter description (OR)
*/
export function set(name, filterDesc) {
_filters[name] = filterDesc;
}

export function has(name) {
return name in _filters;
}

export function remove(name) {
return delete _filters[name];
}

function simpleFilter(type, entity, filter) {
// type must match
if (!filter[type]) return false;
// use predicate if available
if (typeof filter.pred === 'function') return filter.pred(entity);
// if no constraint, match
if (!filter.data) return true;
// else must match all constraints
for (const prop in filter.data) if (entity.options.data[prop] !== filter.data[prop]) return false;
return true;
}

function arrayFilter(type, entity, filters) {
if (!Array.isArray(filters)) filters = [filters];
filters = filters.flat();
for (let i = 0; i < filters.length; i++) if (simpleFilter(type, entity, filters[i])) return true;
return false;
}

/**
*
* @param {object} portal Portal to test
* @returns {boolean} `true` if the the portal matches one of the filters
*/
export function filterPortal(portal) {
return arrayFilter('portal', portal, Object.values(_filters));
}

/**
*
* @param {object} link Link to test
* @returns {boolean} `true` if the the link matches one of the filters
*/
export function filterLink(link) {
return arrayFilter('link', link, Object.values(_filters));
}

/**
*
* @param {object} field Field to test
* @returns {boolean} `true` if the the field matches one of the filters
*/
export function filterField(field) {
return arrayFilter('field', field, Object.values(_filters));
}

export function filterEntities() {
for (const guid in window.portals) {
const p = window.portals[guid];
if (filterPortal(p)) p.remove();
else p.addTo(window.map);
}
for (const guid in window.links) {
const link = window.links[guid];
if (filterLink(link)) link.remove();
else link.addTo(window.map);
}
for (const guid in window.fields) {
const field = window.fields[guid];
if (filterField(field)) field.remove();
else field.addTo(window.map);
}
}

/**
* @class FilterLayer
* @description Layer abstraction to control with the layer chooser a filter.
* The filter is disabled on layer add, and enabled on layer remove.
* @extends L.Layer
* @param {{name: string, filter: FilterDesc}} options
*/
export const FilterLayer = L.Layer.extend({
options: {
name: null,
filter: {},
},

initialize: function (options) {
L.setOptions(this, options);
set(this.options.name, this.options.filter);
},

onAdd: function () {
remove(this.options.name);
filterEntities();
},

onRemove: function () {
set(this.options.name, this.options.filter);
filterEntities();
},
});
36 changes: 36 additions & 0 deletions src/redfection/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { createDefaultOverlays } from './map';
import {
bringPortalsToFront,
createFieldEntity,
createLinkEntity,
deletePortalEntity,
deleteLinkEntity,
deleteFieldEntity,
addPortalToMapLayer,
removePortalFromMapLayer,
} from './map_data_render';

function setup() {
window.Render.prototype.bringPortalsToFront = bringPortalsToFront;
window.Render.prototype.createLinkEntity = createLinkEntity;
window.Render.prototype.createFieldEntity = createFieldEntity;
window.Render.prototype.deletePortalEntity = deletePortalEntity;
window.Render.prototype.deleteLinkEntity = deleteLinkEntity;
window.Render.prototype.deleteFieldEntity = deleteFieldEntity;
window.Render.prototype.addPortalToMapLayer = addPortalToMapLayer;
window.Render.prototype.removePortalFromMapLayer = removePortalFromMapLayer;

const LayerChooser = window.LayerChooser;
window.LayerChooser = LayerChooser.extend({
initialize: function (baseLayers, overlays, options) {
for (const key in overlays) delete overlays[key];
const newOverlays = createDefaultOverlays();
for (const key in newOverlays) overlays[key] = newOverlays[key];
LayerChooser.prototype.initialize.apply(this, arguments);
},
});
}

setup.priority = 'boot';

export default setup;
62 changes: 62 additions & 0 deletions src/redfection/map.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { FilterLayer } from './filters';

export function createDefaultOverlays() {
var addLayers = {};

var portalsLayers = [];
portalsLayers[0] = new FilterLayer({
name: 'Unclaimed/Placeholder Portals',
filter: [
{ portal: true, data: { team: 'N' } },
{ portal: true, data: { level: undefined } },
],
});
addLayers['Unclaimed/Placeholder Portals'] = portalsLayers[0];
for (var i = 1; i <= 8; i++) {
var t = 'Level ' + i + ' Portals';
portalsLayers[i] = new FilterLayer({
name: t,
filter: [
{ portal: true, data: { level: i, team: 'R' } },
{ portal: true, data: { level: i, team: 'E' } },
],
});
addLayers[t] = portalsLayers[i];
}

var fieldsLayer = new FilterLayer({
name: 'Fields',
filter: { field: true },
});
addLayers['Fields'] = fieldsLayer;

var linksLayer = new FilterLayer({
name: 'Links',
filter: { link: true },
});
addLayers['Links'] = linksLayer;

// faction-specific layers
var resistanceLayer = new FilterLayer({
name: 'Resistance',
filter: { portal: true, link: true, field: true, data: { team: 'R' } },
});
var enlightenedLayer = new FilterLayer({
name: 'Enlightened',
filter: { portal: true, link: true, field: true, data: { team: 'E' } },
});

// to avoid any favouritism, we'll put the player's own faction layer first
if (window.PLAYER.team === 'RESISTANCE') {
addLayers['Resistance'] = resistanceLayer;
addLayers['Enlightened'] = enlightenedLayer;
} else {
addLayers['Enlightened'] = enlightenedLayer;
addLayers['Resistance'] = resistanceLayer;
}

// compatibility
addLayers.Neutral = L.layerGroup();

return addLayers;
}
Loading

0 comments on commit d8e4843

Please sign in to comment.