// ==============================================================================
// Copyright (C) 2019 - Philip Paquette, Steven Bocco
//
// This program is free software: you can redistribute it and/or modify it under
// the terms of the GNU Affero General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option) any
// later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
// details.
//
// You should have received a copy of the GNU Affero General Public License along
// with this program. If not, see .
// ==============================================================================
/** Generated using svg_to_react.py with parameters:
Namespace(actionable=['MouseLayer'], input='src/diplomacy/maps/svg/standard.svg', name='SvgStandard', output='src/gui/maps/', remove=None)
**/
import React from 'react';
import PropTypes from 'prop-types';
import "./SvgStandard.css";
import {Game} from "../../../diplomacy/engine/game";
import {MapData} from "../../utils/map_data";
import {Unit} from "./unit";
import {SupplyCenter} from "./supplyCenter";
import {Hold} from "./hold";
import {Move} from "./move";
import {SupportMove} from "./supportMove";
import {SupportHold} from "./supportHold";
import {Convoy} from "./convoy";
import {Build} from "./build";
import {Disband} from "./disband";
import {UTILS} from "../../../diplomacy/utils/utils";
import {Diplog} from "../../../diplomacy/utils/diplog";
import {extendOrderBuilding} from "../../utils/order_building";
function setInfluence(classes, mapData, loc, power_name) {
loc = loc.toUpperCase().substr(0, 3);
if (!['LAND', 'COAST'].includes(mapData.getProvince(loc).type))
return;
const id = '_' + loc.toLowerCase();
if (!classes.hasOwnProperty(id))
throw new Error(`Unable to find SVG path for loc ${id}`);
classes[id] = power_name ? power_name.toLowerCase() : 'nopower';
}
function getClickedID(event) {
let node = event.target;
if (!node.id && node.parentNode.id && node.parentNode.tagName === 'g')
node = node.parentNode;
let id = node.id;
return id ? id.substr(0, 3) : null;
}
function parseLocation(txt) {
if (txt.length > 2 && txt[1] === ' ' && ['A', 'F'].includes(txt[0]))
return txt.substr(2);
return txt;
}
export class SvgStandard extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.onHover = this.onHover.bind(this);
}
onClick(event) {
if (this.props.orderBuilding)
return this.handleClickedID(getClickedID(event));
}
onHover(event) {
return this.handleHoverID(getClickedID(event));
}
handleClickedID(id) {
const orderBuilding = this.props.orderBuilding;
if (!orderBuilding.builder)
return this.props.onError('No orderable locations.');
const province = this.props.mapData.getProvince(id);
if (!province)
return;
const stepLength = orderBuilding.builder.steps.length;
if (orderBuilding.path.length >= stepLength)
throw new Error(`Order building: current steps count (${orderBuilding.path.length}) should be less than` +
` expected steps count (${stepLength}) (${orderBuilding.path.join(', ')}).`);
const lengthAfterClick = orderBuilding.path.length + 1;
let validLocations = [];
const testedPath = [orderBuilding.type].concat(orderBuilding.path);
const value = UTILS.javascript.getTreeValue(this.props.game.ordersTree, testedPath);
if (value !== null) {
const checker = orderBuilding.builder.steps[lengthAfterClick - 1];
try {
const possibleLocations = checker(province, orderBuilding.power);
for (let possibleLocation of possibleLocations) {
possibleLocation = possibleLocation.toUpperCase();
if (value.includes(possibleLocation))
validLocations.push(possibleLocation);
}
} catch (error) {
return this.props.onError(error);
}
}
if (!validLocations.length)
return this.props.onError('Disallowed.');
if (validLocations.length > 1 && orderBuilding.type === 'S' && orderBuilding.path.length >= 2) {
// We are building a support order and we have a multiple choice for a location.
// Let's check if next location to choose is a coast. To have a coast:
// - all possible locations must start with same 3 characters.
// - we expect at least province name in possible locations (e.g. 'SPA' for 'SPA/NC').
// If we have a coast, we will remove province name from possible locations.
let isACoast = true;
let validLocationsNoProvinceName = [];
for (let i = 0; i < validLocations.length; ++i) {
let location = validLocations[i];
if (i > 0) {
// Compare 3 first letters with previous location.
if (validLocations[i - 1].substring(0, 3).toUpperCase() !== validLocations[i].substring(0, 3).toUpperCase()) {
// No same prefix with previous location. We does not have a coast.
isACoast = false;
break;
}
}
if (location.length !== 3)
validLocationsNoProvinceName.push(location);
}
if (validLocations.length === validLocationsNoProvinceName.length) {
// We have not found province name.
isACoast = false;
}
if (isACoast) {
// We want to choose location in a coastal province. Let's remove province name.
validLocations = validLocationsNoProvinceName;
}
}
if (validLocations.length > 1) {
if (this.props.onSelectLocation) {
return this.props.onSelectLocation(validLocations, orderBuilding.power, orderBuilding.type, orderBuilding.path);
} else {
Diplog.warn(`Forced to select first valid location.`);
validLocations = [validLocations[0]];
}
}
let orderBuildingType = orderBuilding.type;
if (lengthAfterClick === stepLength && orderBuildingType === 'M') {
const moveOrderPath = ['M'].concat(orderBuilding.path, validLocations[0]);
const moveTypes = UTILS.javascript.getTreeValue(this.props.game.ordersTree, moveOrderPath);
if (moveTypes !== null) {
if (moveTypes.length === 2 && this.props.onSelectVia) {
// This move can be done either regularly or VIA a fleet. Let user choose.
return this.props.onSelectVia(validLocations[0], orderBuilding.power, orderBuilding.path);
} else {
orderBuildingType = moveTypes[0];
}
}
}
extendOrderBuilding(
orderBuilding.power, orderBuildingType, orderBuilding.path, validLocations[0],
this.props.onOrderBuilding, this.props.onOrderBuilt, this.props.onError
);
}
handleHoverID(id) {
if (this.props.onHover) {
const province = this.props.mapData.getProvince(id);
if (province) {
this.props.onHover(province.name, this.getRelatedOrders(province.name));
}
}
}
getRelatedOrders(name) {
const orders = [];
if (this.props.orders) {
for (let powerOrders of Object.values(this.props.orders)) {
if (powerOrders) {
for (let order of powerOrders) {
const pieces = order.split(/ +/);
if (pieces[1].slice(0, 3) === name.toUpperCase().slice(0, 3))
orders.push(order);
}
}
}
}
return orders;
}
getNeighbors(extraLocation) {
const selectedPath = [this.props.orderBuilding.type].concat(this.props.orderBuilding.path);
if (extraLocation)
selectedPath.push(extraLocation);
const possibleNeighbors = UTILS.javascript.getTreeValue(this.props.game.ordersTree, selectedPath);
const neighbors = possibleNeighbors ? possibleNeighbors.map(neighbor => parseLocation(neighbor)) : [];
return neighbors.length ? neighbors: null;
}
render() {
const classes = {"_ank":"nopower","_arm":"nopower","_con":"nopower","_mos":"nopower","_sev":"nopower","_stp":"nopower","_syr":"nopower","_ukr":"nopower","_lvn":"nopower","_war":"nopower","_pru":"nopower","_sil":"nopower","_ber":"nopower","_kie":"nopower","_ruh":"nopower","_mun":"nopower","_rum":"nopower","_bul":"nopower","_gre":"nopower","_smy":"nopower","_alb":"nopower","_ser":"nopower","_bud":"nopower","_gal":"nopower","_vie":"nopower","_boh":"nopower","_tyr":"nopower","_tri":"nopower","_fin":"nopower","_swe":"nopower","_nwy":"nopower","_den":"nopower","_hol":"nopower","_bel":"nopower","_swi":"impassable","_ven":"nopower","_pie":"nopower","_tus":"nopower","_rom":"nopower","_apu":"nopower","_nap":"nopower","_bur":"nopower","_mar":"nopower","_gas":"nopower","_pic":"nopower","_par":"nopower","_bre":"nopower","_spa":"nopower","_por":"nopower","_naf":"nopower","_tun":"nopower","_lon":"nopower","_wal":"nopower","_lvp":"nopower","_yor":"nopower","_edi":"nopower","_cly":"nopower","unplayable":"neutral","unplayable_water":"water","_nat":"water","_nrg":"water","_bar":"water","_bot":"water","_bal":"water","denmark_water":"water","_ska":"water","_hel":"water","_nth":"water","_eng":"water","_iri":"water","_mid":"water","_wes":"water","_gol":"water","_tyn":"water","_adr":"water","_ion":"water","_aeg":"water","_eas":"water","constantinople_water":"water","_bla":"water","BriefLabelLayer":"labeltext24","CurrentNote":"currentnotetext","CurrentNote2":"currentnotetext","CurrentPhase":"currentphasetext","MouseLayer":"invisibleContent"};
const game = this.props.game;
const mapData = this.props.mapData;
const orders = this.props.orders;
//// Current phase.
const current_phase = (game.phase[0] === '?' || game.phase === 'COMPLETED') ? 'FINAL' : game.phase;
//// Notes.
const nb_centers = [];
for (let power of Object.values(game.powers)) {
if (!power.isEliminated())
nb_centers.push([power.name.substr(0, 3), power.centers.length]);
}
// Sort nb_centers by descending number of centers.
nb_centers.sort((a, b) => {
return -(a[1] - b[1]) || a[0].localeCompare(b[0]);
});
const nb_centers_per_power = nb_centers.map((couple) => (couple[0] + ': ' + couple[1])).join(' ');
const note = game.note;
//// Adding units, supply centers, influence and orders.
const scs = new Set(mapData.supplyCenters);
const renderedUnits = [];
const renderedDislodgedUnits = [];
const renderedSupplyCenters = [];
const renderedOrders = [];
const renderedOrders2 = [];
const renderedHighestOrders = [];
for (let power of Object.values(game.powers)) {
for (let unit of power.units) {
renderedUnits.push();
}
for (let unit of Object.keys(power.retreats)) {
renderedDislodgedUnits.push();
}
for (let center of power.centers) {
renderedSupplyCenters.push();
setInfluence(classes, mapData, center, power.name);
scs.delete(center);
}
if (!power.isEliminated()) {
for (let loc of power.influence) {
if (!mapData.supplyCenters.has(loc))
setInfluence(classes, mapData, loc, power.name);
}
}
if (orders) {
const powerOrders = (orders && orders.hasOwnProperty(power.name) && orders[power.name]) || [];
for (let order of powerOrders) {
const tokens = order.split(/ +/);
if (!tokens || tokens.length < 3)
continue;
const unit_loc = tokens[1];
if (tokens[2] === 'H') {
renderedOrders.push();
} else if (tokens[2] === '-') {
const destLoc = tokens[tokens.length - (tokens[tokens.length - 1] === 'VIA' ? 2 : 1)];
renderedOrders.push();
} else if (tokens[2] === 'S') {
const destLoc = tokens[tokens.length - 1];
if (tokens.includes('-')) {
const srcLoc = tokens[4];
renderedOrders2.push();
} else {
renderedOrders2.push();
}
} else if (tokens[2] === 'C') {
const srcLoc = tokens[4];
const destLoc = tokens[tokens.length - 1];
if ((srcLoc !== destLoc) && (tokens.includes('-'))) {
renderedOrders2.push();
}
} else if (tokens[2] === 'B') {
renderedHighestOrders.push();
} else if (tokens[2] === 'D') {
renderedHighestOrders.push();
} else if (tokens[2] === 'R') {
const srcLoc = tokens[1];
const destLoc = tokens[3];
renderedOrders.push();
} else {
throw new Error(`Unknown error to render (${order}).`);
}
}
}
}
// Adding remaining supply centers.
for (let remainingCenter of scs) {
renderedSupplyCenters.push();
}
if (this.props.orderBuilding && this.props.orderBuilding.path.length) {
const clicked = parseLocation(this.props.orderBuilding.path[0]);
const province = this.props.mapData.getProvince(clicked);
if (!province)
throw new Error(('Unknown clicked province ' + clicked));
const clickedID = province.getID(classes);
if (!clicked)
throw new Error(`Unknown path (${clickedID}) for province (${clicked}).`);
classes[clickedID] = 'provinceRed';
const neighbors = this.getNeighbors();
if (neighbors) {
for (let neighbor of neighbors) {
const neighborProvince = this.props.mapData.getProvince(neighbor);
if (!neighborProvince)
throw new Error('Unknown neighbor province ' + neighbor);
const neighborID = neighborProvince.getID(classes);
if (!neighborID)
throw new Error(`Unknown neoghbor path (${neighborID}) for province (${neighbor}).`);
classes[neighborID] = neighborProvince.isWater() ? 'provinceBlue' : 'provinceGreen';
}
}
}
return (
);
}
}
SvgStandard.propTypes = {
game: PropTypes.instanceOf(Game).isRequired,
mapData: PropTypes.instanceOf(MapData).isRequired,
orders: PropTypes.object,
onHover: PropTypes.func,
onError: PropTypes.func.isRequired,
onSelectLocation: PropTypes.func,
onSelectVia: PropTypes.func,
onOrderBuilding: PropTypes.func,
onOrderBuilt: PropTypes.func,
orderBuilding: PropTypes.object
};