Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
a2999657a8 | |||
003f4972cb | |||
34723db31c | |||
58c30c1059 | |||
08f539bf42 | |||
c945cc453b | |||
40e392e003 | |||
278c7c5112 | |||
389f325618 | |||
16888b9fdb | |||
5f70116da2 | |||
30188a1a76 | |||
35a65cfa1f | |||
e3708066e6 | |||
ed48de1c38 | |||
589782365b | |||
9279e6388a | |||
c38474614d | |||
7994119d66 | |||
ac21ac1387 | |||
d9e6e664f2 | |||
8c27095ad1 | |||
85a104caa5 | |||
24b4f7db9f | |||
711f1dc845 | |||
598dda2aae | |||
acec248f4d | |||
e5d9230a74 | |||
d4927e2e1b | |||
d73ff6692a | |||
90d85ed4af | |||
064ec75ed3 | |||
f3f111060b | |||
d6d827fe8d | |||
1447b2fa6e | |||
61b0066f0a | |||
3396c36529 | |||
21d9f46d03 | |||
2d1430ca87 | |||
dd2ca2f786 |
7
Build.PL
7
Build.PL
@ -27,6 +27,13 @@ my $build = Module::Build->new(
|
|||||||
'Module::Pluggable' => 0,
|
'Module::Pluggable' => 0,
|
||||||
'List::AllUtils' => 0,
|
'List::AllUtils' => 0,
|
||||||
'Lingua::Stem::Snowball' => 0,
|
'Lingua::Stem::Snowball' => 0,
|
||||||
|
'Mojo::Redis' => 0,
|
||||||
|
'DBIx::Class' => 0,
|
||||||
|
'UUID::URandom' => 0,
|
||||||
|
'Crypt::Bcrypt' => 0,
|
||||||
|
'DBIx::Class::TimeStamp' => 0,
|
||||||
|
'DateTime::Format::HTTP' => 0,
|
||||||
|
'GIS::Distance' => 0,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
$build->create_build_script;
|
$build->create_build_script;
|
||||||
|
11
generate_proto.sh
Normal file
11
generate_proto.sh
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
rm -rf ./js-src/generated/
|
||||||
|
mkdir ./js-src/generated/
|
||||||
|
protoc --plugin="protoc-gen-ts=./node_modules/.bin/protoc-gen-ts" \
|
||||||
|
--plugin="protoc-gen-js=./node_modules/.bin/protoc-gen-js" \
|
||||||
|
--ts_opt=esModuleInterop=true \
|
||||||
|
--js_out="import_style=commonjs,binary:./js-src/generated" \
|
||||||
|
--ts_out="./js-src/generated" \
|
||||||
|
--proto_path="proto" \
|
||||||
|
$(find proto/ -name '*.proto')
|
67
js-src/conquer/create-node.ts
Normal file
67
js-src/conquer/create-node.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
import MapState from '@burguillosinfo/conquer/map-state'
|
||||||
|
|
||||||
|
export default class CreateNode {
|
||||||
|
private conquer: Conquer
|
||||||
|
private createNodeSlide: HTMLElement
|
||||||
|
|
||||||
|
constructor(conquer: Conquer) {
|
||||||
|
this.conquer = conquer
|
||||||
|
this.getCreateNodeCancel().addEventListener('click', () => {
|
||||||
|
this.conquer.removeState(MapState.SELECT_WHERE_TO_CREATE_NODE)
|
||||||
|
this.conquer.removeState(MapState.CREATE_NODE)
|
||||||
|
this.conquer.addState(MapState.NORMAL)
|
||||||
|
})
|
||||||
|
this.getCreateNodeNewNodeElement().addEventListener('click', () => {
|
||||||
|
const state = this.conquer.getState()
|
||||||
|
if (state & MapState.SELECT_WHERE_TO_CREATE_NODE) {
|
||||||
|
this.conquer.removeState(MapState.SELECT_WHERE_TO_CREATE_NODE)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.conquer.addState(MapState.SELECT_WHERE_TO_CREATE_NODE)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCreateNodeCancel(): HTMLElement {
|
||||||
|
const createNodeCancel = document.querySelector('#create-node-exit')
|
||||||
|
if (createNodeCancel === null || !(createNodeCancel instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('Unable to find #create-node-exit.')
|
||||||
|
}
|
||||||
|
return createNodeCancel
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCreateNodeNewNodeElement(): HTMLElement {
|
||||||
|
const createNodeNewElement = document.querySelector('#create-node-new-node')
|
||||||
|
if (createNodeNewElement === null || !(createNodeNewElement instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('Unable to find #create-node-slide.')
|
||||||
|
}
|
||||||
|
return createNodeNewElement
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCreateNodeSlide(): HTMLElement {
|
||||||
|
const createNodeSlide = document.querySelector('#create-node-slide')
|
||||||
|
if (createNodeSlide === null || !(createNodeSlide instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('Unable to find #create-node-slide.')
|
||||||
|
}
|
||||||
|
return createNodeSlide
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public refreshState() {
|
||||||
|
if (!(this.conquer.getState() & MapState.CREATE_NODE)) {
|
||||||
|
this.getCreateNodeSlide().classList.add('conquer-display-none')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.refreshCreateNodeNewNodeState()
|
||||||
|
this.getCreateNodeSlide().classList.remove('conquer-display-none')
|
||||||
|
|
||||||
|
}
|
||||||
|
private refreshCreateNodeNewNodeState(): void {
|
||||||
|
const createNodeNewNode = this.getCreateNodeNewNodeElement()
|
||||||
|
if (this.conquer.getState() & MapState.SELECT_WHERE_TO_CREATE_NODE) {
|
||||||
|
createNodeNewNode.innerText = 'Cancelar.'
|
||||||
|
} else {
|
||||||
|
createNodeNewNode.innerText = 'Crear nodo.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
js-src/conquer/fight-selector-slide.ts
Normal file
53
js-src/conquer/fight-selector-slide.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
export default class FightSelectorSlide {
|
||||||
|
private callbacks: Record<string, Array<() => void>> = {}
|
||||||
|
|
||||||
|
public on(eventName: string, callback: () => void): void {
|
||||||
|
if (this.callbacks[eventName] === undefined) {
|
||||||
|
this.callbacks[eventName] = []
|
||||||
|
}
|
||||||
|
this.callbacks[eventName].push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private runCallbacks(eventName: string) {
|
||||||
|
const callbacks = this.callbacks[eventName];
|
||||||
|
if (callbacks === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const callback of callbacks) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSelectorSlide(): HTMLElement {
|
||||||
|
const selectorSlide = document.querySelector('#fight-battle-selector-slide');
|
||||||
|
if (!(selectorSlide instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('selectorSlide is not HTMLElement');
|
||||||
|
}
|
||||||
|
return selectorSlide;
|
||||||
|
}
|
||||||
|
public startHook(): void {
|
||||||
|
this.createEventListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGlobalBattleButton(): HTMLElement {
|
||||||
|
const globalBattleButton = this.getSelectorSlide().querySelector('button.fight-global-button');
|
||||||
|
if (!(globalBattleButton instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('globalBattleButton is not HTMLElement');
|
||||||
|
}
|
||||||
|
return globalBattleButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createEventListeners(): void {
|
||||||
|
const globalBattleButton = this.getGlobalBattleButton();
|
||||||
|
globalBattleButton.addEventListener('click', () => {
|
||||||
|
this.runCallbacks('global-battle');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public show(): void {
|
||||||
|
this.getSelectorSlide().classList.remove('conquer-display-none');
|
||||||
|
}
|
||||||
|
public hide(): void {
|
||||||
|
this.getSelectorSlide().classList.add('conquer-display-none');
|
||||||
|
}
|
||||||
|
}
|
584
js-src/conquer/index.ts
Normal file
584
js-src/conquer/index.ts
Normal file
@ -0,0 +1,584 @@
|
|||||||
|
import Map from "ol/Map"
|
||||||
|
import MapEvent from "ol/MapEvent"
|
||||||
|
import MapBrowserEvent from "ol/MapBrowserEvent"
|
||||||
|
import View from "ol/View"
|
||||||
|
import Projection from "ol/proj/Projection.js"
|
||||||
|
import TileLayer from 'ol/layer/Tile'
|
||||||
|
import OSM from 'ol/source/OSM'
|
||||||
|
import * as olProj from "ol/proj"
|
||||||
|
import Feature from 'ol/Feature'
|
||||||
|
import Point from 'ol/geom/Point'
|
||||||
|
import VectorLayer from 'ol/layer/Vector'
|
||||||
|
import VectorSource from 'ol/source/Vector'
|
||||||
|
import Stroke from 'ol/style/Stroke'
|
||||||
|
import Fill from 'ol/style/Fill'
|
||||||
|
import CircleStyle from 'ol/style/Circle'
|
||||||
|
import Icon from 'ol/style/Icon'
|
||||||
|
import Style from 'ol/style/Style'
|
||||||
|
import ConquerLogin from '@burguillosinfo/conquer/login'
|
||||||
|
import InterfaceManager from '@burguillosinfo/conquer/interface-manager'
|
||||||
|
import SelfPlayerUI from '@burguillosinfo/conquer/interface/self-player'
|
||||||
|
import CreateNode from '@burguillosinfo/conquer/create-node'
|
||||||
|
import MapState from '@burguillosinfo/conquer/map-state'
|
||||||
|
import MapNode from '@burguillosinfo/conquer/map-node'
|
||||||
|
import NewNodeUI from '@burguillosinfo/conquer/interface/new-node'
|
||||||
|
import NewTeamUI from '@burguillosinfo/conquer/interface/new-team'
|
||||||
|
import WebSocket from '@burguillosinfo/conquer/websocket'
|
||||||
|
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||||
|
import ConquerUser from '@burguillosinfo/conquer/user'
|
||||||
|
import FightSelectorSlide from '@burguillosinfo/conquer/fight-selector-slide';
|
||||||
|
import SelectFightUI from '@burguillosinfo/conquer/interface/select-fight';
|
||||||
|
import ConquerUserCurrentEnemy from '@burguillosinfo/conquer/user-current-enemy'
|
||||||
|
|
||||||
|
type StylesInterface = Record<string, Style>
|
||||||
|
|
||||||
|
export default class Conquer {
|
||||||
|
private conquerContainer: HTMLDivElement
|
||||||
|
private map: Map
|
||||||
|
private enabledOnRotate = true
|
||||||
|
private rotation = 0;
|
||||||
|
private currentLongitude: number
|
||||||
|
private intervalSendCoordinates: number | null = null;
|
||||||
|
private currentLatitude: number
|
||||||
|
private rotationOffset = 0
|
||||||
|
private heading = 0
|
||||||
|
private disableSetRotationOffset = false
|
||||||
|
private currentPositionFeature: Feature | null
|
||||||
|
private vectorLayer: VectorLayer<VectorSource> | null = null
|
||||||
|
private alpha = 0
|
||||||
|
private beta = 0
|
||||||
|
private gamma = 0
|
||||||
|
private conquerLogin: ConquerLogin
|
||||||
|
private selfPlayerUI: SelfPlayerUI | null = null
|
||||||
|
private interfaceManager: InterfaceManager
|
||||||
|
private firstSetCenter = true
|
||||||
|
private firstSetRotation = true
|
||||||
|
private state: MapState = MapState.NOTHING
|
||||||
|
private createNodeObject: CreateNode
|
||||||
|
private serverNodes: Record<string, MapNode> = {}
|
||||||
|
private coordinate_1 = 0;
|
||||||
|
private coordinate_2 = 0;
|
||||||
|
private fightSelectorSlide: FightSelectorSlide;
|
||||||
|
private loggedIn = false;
|
||||||
|
|
||||||
|
public getServerNodes(): Record<string, MapNode> {
|
||||||
|
return this.serverNodes
|
||||||
|
}
|
||||||
|
public getState(): MapState {
|
||||||
|
return this.state
|
||||||
|
}
|
||||||
|
|
||||||
|
public setState(state: MapState) {
|
||||||
|
this.state = state
|
||||||
|
this.refreshState()
|
||||||
|
}
|
||||||
|
public removeState(state: MapState) {
|
||||||
|
this.state &= ~state
|
||||||
|
this.refreshState()
|
||||||
|
}
|
||||||
|
public addState(state: MapState) {
|
||||||
|
this.state |= state
|
||||||
|
this.refreshState()
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshFightSlide(): void {
|
||||||
|
if (this.loggedIn && (this.getState() & MapState.NORMAL) !== 0) {
|
||||||
|
this.fightSelectorSlide.show();
|
||||||
|
}
|
||||||
|
if (!this.loggedIn) {
|
||||||
|
this.fightSelectorSlide.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private refreshState(): void {
|
||||||
|
this.refreshFightSlide();
|
||||||
|
this.createNodeObject.refreshState()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
static start() {
|
||||||
|
const conquerContainer = document.querySelector(".conquer-container")
|
||||||
|
if (conquerContainer === null || !(conquerContainer instanceof HTMLDivElement)) {
|
||||||
|
Conquer.fail('.conquer-container is not a div.')
|
||||||
|
}
|
||||||
|
const conquer = new Conquer(conquerContainer)
|
||||||
|
conquer.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
setCenterDisplaced(lat: number, lon: number) {
|
||||||
|
if (this.firstSetCenter || !(this.state & MapState.FREE_MOVE)) {
|
||||||
|
this.coordinate_1 = lon;
|
||||||
|
this.coordinate_2 = lat;
|
||||||
|
const olCoordinates = this.realCoordinatesToOl(lat, lon)
|
||||||
|
const size = this.map.getSize()
|
||||||
|
if (size === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.map.getView().centerOn(olCoordinates, size, [size[0]/2, size[1]-60])
|
||||||
|
this.firstSetCenter = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static fail(error: string): never {
|
||||||
|
alert('Error de interfaz')
|
||||||
|
throw new Error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
public isStateCreatingNode(): boolean {
|
||||||
|
return !!(this.getState() & MapState.CREATE_NODE)
|
||||||
|
}
|
||||||
|
public isStateSelectWhereToCreateNode(): boolean {
|
||||||
|
return !!(this.getState() & MapState.SELECT_WHERE_TO_CREATE_NODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private createNodeCounter = 0
|
||||||
|
|
||||||
|
async onClickWhereToCreateNode(event: MapEvent) {
|
||||||
|
if (!(event instanceof MapBrowserEvent)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const pixel = event.pixel
|
||||||
|
const coordinates = this.map.getCoordinateFromPixel(pixel)
|
||||||
|
const newNodeUI = new NewNodeUI(coordinates)
|
||||||
|
const oldState = this.getState();
|
||||||
|
newNodeUI.on('close', () => {
|
||||||
|
this.interfaceManager.remove(newNodeUI)
|
||||||
|
this.setState(oldState);
|
||||||
|
})
|
||||||
|
this.interfaceManager.push(newNodeUI)
|
||||||
|
this.removeState(MapState.SELECT_WHERE_TO_CREATE_NODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
private isStateFillingFormCreateNode(): boolean {
|
||||||
|
return !!(this.getState() & MapState.FILLING_FORM_CREATE_NODE)
|
||||||
|
}
|
||||||
|
|
||||||
|
async onClickMap(event: MapEvent): Promise<void> {
|
||||||
|
if (this.isStateCreatingNode() && this.isStateSelectWhereToCreateNode()) {
|
||||||
|
this.onClickWhereToCreateNode(event)
|
||||||
|
}
|
||||||
|
if (!(this.getState() & MapState.NORMAL)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.vectorLayer === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!(event instanceof MapBrowserEvent)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.dragging) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const pixel = event.pixel
|
||||||
|
const features = this.map.getFeaturesAtPixel(pixel)
|
||||||
|
const feature = features.length ? features[0] : undefined
|
||||||
|
if (feature === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!(feature instanceof Feature)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.onClickFeature(feature)
|
||||||
|
}
|
||||||
|
|
||||||
|
async onClickSelf(): Promise<void> {
|
||||||
|
if (!(this.state & MapState.NORMAL)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selfPlayerUI = new SelfPlayerUI(!!(this.getState() & (MapState.FREE_MOVE)))
|
||||||
|
selfPlayerUI.on('close', () => {
|
||||||
|
this.interfaceManager.remove(selfPlayerUI)
|
||||||
|
})
|
||||||
|
selfPlayerUI.on('enable-explorer-mode', () => {
|
||||||
|
this.addState(MapState.FREE_MOVE);
|
||||||
|
});
|
||||||
|
selfPlayerUI.on('disable-explorer-mode', () => {
|
||||||
|
this.removeState(MapState.FREE_MOVE);
|
||||||
|
});
|
||||||
|
selfPlayerUI.on('createNodeStart', () => {
|
||||||
|
this.addState(MapState.CREATE_NODE)
|
||||||
|
this.removeState(MapState.NORMAL)
|
||||||
|
})
|
||||||
|
selfPlayerUI.on('open-create-team', () => {
|
||||||
|
this.onOpenCreateTeam();
|
||||||
|
});
|
||||||
|
this.interfaceManager.push(selfPlayerUI)
|
||||||
|
this.selfPlayerUI = selfPlayerUI
|
||||||
|
}
|
||||||
|
|
||||||
|
private onOpenCreateTeam(): void {
|
||||||
|
const newTeamUI = new NewTeamUI();
|
||||||
|
newTeamUI.on('close', () => {
|
||||||
|
this.interfaceManager.remove(newTeamUI);
|
||||||
|
});
|
||||||
|
this.interfaceManager.push(newTeamUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
private isFeatureEnabledMap: Record<string, boolean> = {}
|
||||||
|
|
||||||
|
async onClickFeature(feature: Feature): Promise<void> {
|
||||||
|
if (this.isFeatureEnabledMap[feature.getProperties().type] === undefined) {
|
||||||
|
this.isFeatureEnabledMap[feature.getProperties().type] = true
|
||||||
|
}
|
||||||
|
if (!this.isFeatureEnabledMap[feature.getProperties().type]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.isFeatureEnabledMap[feature.getProperties().type] = false
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.isFeatureEnabledMap[feature.getProperties().type] = true
|
||||||
|
}, 100);
|
||||||
|
const candidateNode = this.getServerNodes()[feature.getProperties().type];
|
||||||
|
if (candidateNode !== undefined) {
|
||||||
|
candidateNode.click(this.interfaceManager);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (feature === this.currentPositionFeature) {
|
||||||
|
this.onClickSelf()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async onLoginSuccess(): Promise<void> {
|
||||||
|
this.loggedIn = true;
|
||||||
|
this.refreshFightSlide();
|
||||||
|
this.clearIntervalSendCoordinates();
|
||||||
|
this.createIntervalSendCoordinates();
|
||||||
|
this.clearIntervalPollNearbyNodes();
|
||||||
|
this.createIntervalPollNearbyNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private intervalPollNearbyNodes: number | null = null;
|
||||||
|
|
||||||
|
private clearIntervalPollNearbyNodes(): void {
|
||||||
|
if (this.intervalPollNearbyNodes !== null) {
|
||||||
|
window.clearInterval(this.intervalPollNearbyNodes)
|
||||||
|
this.intervalPollNearbyNodes = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getNearbyNodes(): Promise<void> {
|
||||||
|
const urlNodes = new URL('/conquer/node/near', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
let response;
|
||||||
|
try {
|
||||||
|
response = await fetch(urlNodes);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let responseBody;
|
||||||
|
try {
|
||||||
|
responseBody = await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parseando json: ' + responseBody);
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.error(responseBody.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const serverNodes: Record<string, MapNode> = {};
|
||||||
|
const nodes = JsonSerializer.deserialize(responseBody, MapNode);
|
||||||
|
if (!(nodes instanceof Array)) {
|
||||||
|
console.error('Received null instead of node list.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (!(node instanceof MapNode)) {
|
||||||
|
console.error('Received node is not a MapNode.');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
node.on('update-nodes', async () => {
|
||||||
|
await this.sendCoordinatesToServer();
|
||||||
|
this.getNearbyNodes();
|
||||||
|
});
|
||||||
|
serverNodes[node.getId()] = node;
|
||||||
|
}
|
||||||
|
this.serverNodes = serverNodes;
|
||||||
|
this.refreshLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createIntervalPollNearbyNodes(): void {
|
||||||
|
this.getNearbyNodes();
|
||||||
|
this.intervalPollNearbyNodes = window.setInterval(() => {
|
||||||
|
this.getNearbyNodes();
|
||||||
|
}, 40000)
|
||||||
|
}
|
||||||
|
|
||||||
|
private createIntervalSendCoordinates(): void {
|
||||||
|
this.intervalSendCoordinates = window.setInterval(() => {
|
||||||
|
this.sendCoordinatesToServer();
|
||||||
|
}, 40000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendCoordinatesToServer(): Promise<void> {
|
||||||
|
const urlLog = new URL('/conquer/user/coordinates', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
let res;
|
||||||
|
try {
|
||||||
|
res = await fetch(urlLog, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify([
|
||||||
|
this.coordinate_1,
|
||||||
|
this.coordinate_2,
|
||||||
|
])});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let responseBody;
|
||||||
|
try {
|
||||||
|
responseBody = await res.json();
|
||||||
|
} catch(error) {
|
||||||
|
console.error('Error parseando json: ' + responseBody);
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res.status !== 200) {
|
||||||
|
console.error(responseBody.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private runPreStartState(): void {
|
||||||
|
const createNodeObject = new CreateNode(this)
|
||||||
|
this.createNodeObject = createNodeObject
|
||||||
|
const interfaceManager = new InterfaceManager()
|
||||||
|
this.interfaceManager = interfaceManager
|
||||||
|
const conquerLogin = new ConquerLogin(interfaceManager)
|
||||||
|
conquerLogin.on('login', () => {
|
||||||
|
this.onLoginSuccess();
|
||||||
|
});
|
||||||
|
conquerLogin.on('logout', () => {
|
||||||
|
this.onLogout();
|
||||||
|
});
|
||||||
|
conquerLogin.start()
|
||||||
|
this.conquerLogin = conquerLogin
|
||||||
|
this.fightSelectorSlide = new FightSelectorSlide();
|
||||||
|
this.fightSelectorSlide.on('global-battle', () => {
|
||||||
|
this.startGlobalBattleSelector();
|
||||||
|
});
|
||||||
|
this.fightSelectorSlide.startHook();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startGlobalBattleSelector(): Promise<void> {
|
||||||
|
const enemies = await ConquerUserCurrentEnemy.getGlobalEnemies();
|
||||||
|
if (enemies !== null) {
|
||||||
|
const selectFightUI = new SelectFightUI(enemies);
|
||||||
|
selectFightUI.on('close', () => {
|
||||||
|
this.interfaceManager.remove(selectFightUI);
|
||||||
|
});
|
||||||
|
this.interfaceManager.push(selectFightUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private onLogout(): void {
|
||||||
|
this.loggedIn = false;
|
||||||
|
this.refreshFightSlide();
|
||||||
|
this.clearIntervalSendCoordinates();
|
||||||
|
this.clearIntervalPollNearbyNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearIntervalSendCoordinates(): void {
|
||||||
|
if (this.intervalSendCoordinates !== null) {
|
||||||
|
window.clearInterval(this.intervalSendCoordinates);
|
||||||
|
this.intervalSendCoordinates = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
this.runPreStartState()
|
||||||
|
this.setState(MapState.NORMAL | MapState.FREE_ROTATION)
|
||||||
|
const conquerContainer = this.conquerContainer
|
||||||
|
//layer.on('prerender', (evt) => {
|
||||||
|
// // return
|
||||||
|
// if (evt.context) {
|
||||||
|
// const context = evt.context as CanvasRenderingContext2D
|
||||||
|
// context.filter = 'grayscale(80%) invert(100%) '
|
||||||
|
// context.globalCompositeOperation = 'source-over'
|
||||||
|
// }
|
||||||
|
//})
|
||||||
|
|
||||||
|
//layer.on('postrender', (evt) => {
|
||||||
|
// if (evt.context) {
|
||||||
|
// const context = evt.context as CanvasRenderingContext2D
|
||||||
|
// context.filter = 'none'
|
||||||
|
// }
|
||||||
|
//})
|
||||||
|
olProj.useGeographic()
|
||||||
|
const osm = new OSM()
|
||||||
|
osm.setUrls([`${window.location.protocol}//${window.location.hostname}:${window.location.port}/conquer/tile/{z}/{x}/{y}.png`])
|
||||||
|
this.map = new Map({
|
||||||
|
target: conquerContainer,
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: osm
|
||||||
|
})
|
||||||
|
],
|
||||||
|
view: new View({
|
||||||
|
zoom: 19,
|
||||||
|
maxZoom: 22,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
this.setLocationChangeTriggers()
|
||||||
|
this.setRotationChangeTriggers()
|
||||||
|
}
|
||||||
|
|
||||||
|
setRotationChangeTriggers(): void {
|
||||||
|
if (window.DeviceOrientationEvent) {
|
||||||
|
window.addEventListener("deviceorientation", (event) => {
|
||||||
|
if (event.alpha !== null && event.beta !== null && event.gamma !== null) {
|
||||||
|
this.onRotate(event.alpha, event.beta, event.gamma)
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCurrentLocationMarkerToMap(currentLatitude: number,
|
||||||
|
currentLongitude: number) {
|
||||||
|
const currentPositionFeature = new Feature({
|
||||||
|
type: 'currentPositionFeature',
|
||||||
|
geometry: new Point(this.realCoordinatesToOl(currentLatitude, currentLongitude))
|
||||||
|
})
|
||||||
|
this.currentPositionFeature = currentPositionFeature
|
||||||
|
}
|
||||||
|
|
||||||
|
processLocation(location: GeolocationPosition) {
|
||||||
|
this.currentLatitude = location.coords.latitude
|
||||||
|
this.currentLongitude = location. coords.longitude
|
||||||
|
if (location.coords.heading !== null && (this.alpha != 0 || this.beta != 0 || this.gamma != 0) && !this.disableSetRotationOffset) {
|
||||||
|
this.disableSetRotationOffset = true
|
||||||
|
this.heading = location.coords.heading
|
||||||
|
this.rotationOffset = this.compassHeading(this.alpha, this.beta, this.gamma) + (location.coords.heading*Math.PI*2)/360
|
||||||
|
}
|
||||||
|
this.setCenterDisplaced(this.currentLatitude, this.currentLongitude)
|
||||||
|
this.addCurrentLocationMarkerToMap(this.currentLatitude, this.currentLongitude)
|
||||||
|
this.refreshLayers()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async refreshLayers(): Promise<void> {
|
||||||
|
if (this.currentPositionFeature === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const user = await ConquerUser.getSelfUser()
|
||||||
|
let color = 'white';
|
||||||
|
if (user !== null) {
|
||||||
|
const team = await user.getTeam();
|
||||||
|
if (team !== null) {
|
||||||
|
color = team.getColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const styles: StylesInterface = {
|
||||||
|
currentPositionFeature: new Style({
|
||||||
|
image: new Icon({
|
||||||
|
crossOrigin: 'anonymous',
|
||||||
|
src: '/img/arrow-player.svg',
|
||||||
|
color: color,
|
||||||
|
scale: 0.2,
|
||||||
|
rotation: this.rotation,
|
||||||
|
rotateWithView: true,
|
||||||
|
}),
|
||||||
|
zIndex: 4,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
const features = [];
|
||||||
|
features.push(this.currentPositionFeature);
|
||||||
|
for (const key in this.getServerNodes()) {
|
||||||
|
styles[key] = await this.getServerNodes()[key].getStyle()
|
||||||
|
features.push(this.getServerNodes()[key].getFeature())
|
||||||
|
}
|
||||||
|
const vectorLayer = new VectorLayer<VectorSource>({
|
||||||
|
source: new VectorSource({
|
||||||
|
features: features
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
if (this.vectorLayer !== null) {
|
||||||
|
this.map.removeLayer(this.vectorLayer)
|
||||||
|
this.vectorLayer = null;
|
||||||
|
}
|
||||||
|
vectorLayer.setStyle((feature) => {
|
||||||
|
return styles[feature.getProperties().type]
|
||||||
|
})
|
||||||
|
this.map.addLayer(vectorLayer)
|
||||||
|
this.vectorLayer = vectorLayer
|
||||||
|
this.map.on('click', (event: MapEvent) => {
|
||||||
|
this.onClickMap(event)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setLocationChangeTriggers(): void {
|
||||||
|
window.setInterval(() => {
|
||||||
|
this.disableSetRotationOffset = false
|
||||||
|
}, 10000)
|
||||||
|
this.currentPositionFeature = null
|
||||||
|
window.setTimeout(() => {
|
||||||
|
window.setInterval(() => {
|
||||||
|
navigator.geolocation.getCurrentPosition((location) => {
|
||||||
|
this.processLocation(location)
|
||||||
|
}, () => {
|
||||||
|
return
|
||||||
|
}, {
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
})
|
||||||
|
}, 3000)
|
||||||
|
}, 1000)
|
||||||
|
// const initialLatitude = 37.58237
|
||||||
|
//const initialLongitude = -5.96766
|
||||||
|
const initialLongitude = 2.500845037550267
|
||||||
|
const initialLatitude = 48.81050698635832
|
||||||
|
|
||||||
|
this.setCenterDisplaced(initialLatitude, initialLongitude)
|
||||||
|
this.addCurrentLocationMarkerToMap(initialLatitude, initialLongitude)
|
||||||
|
this.refreshLayers()
|
||||||
|
navigator.geolocation.watchPosition((location) => {
|
||||||
|
this.processLocation(location)
|
||||||
|
}, (err) => {
|
||||||
|
return
|
||||||
|
}, {
|
||||||
|
enableHighAccuracy: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
realCoordinatesToOl(lat: number, lon: number): number[] {
|
||||||
|
return olProj.transform(
|
||||||
|
[lon, lat],
|
||||||
|
new Projection({ code: "WGS84" }),
|
||||||
|
new Projection({ code: "EPSG:900913" }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
compassHeading(alpha:number, beta:number, gamma:number): number {
|
||||||
|
const alphaRad = alpha * (Math.PI / 180)
|
||||||
|
return alphaRad
|
||||||
|
}
|
||||||
|
|
||||||
|
logToServer(logValue: string) {
|
||||||
|
const urlLog = new URL('/conquer/log', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
urlLog.searchParams.append('log', logValue)
|
||||||
|
fetch(urlLog).then(() => {
|
||||||
|
return
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onRotate(alpha: number, beta: number, gamma: number) {
|
||||||
|
if (this.enabledOnRotate) {
|
||||||
|
this.alpha = alpha
|
||||||
|
this.beta = beta
|
||||||
|
this.gamma = gamma
|
||||||
|
this.enabledOnRotate = false
|
||||||
|
this.rotation = -(this.compassHeading(alpha, beta, gamma) - this.rotationOffset);
|
||||||
|
if (this.currentPositionFeature !== null) {
|
||||||
|
this.currentPositionFeature.changed();
|
||||||
|
}
|
||||||
|
if (this.firstSetRotation || !(this.state & MapState.FREE_ROTATION)) {
|
||||||
|
this.map.getView().setRotation((this.compassHeading(alpha, beta, gamma) - this.rotationOffset))
|
||||||
|
this.firstSetRotation = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.enabledOnRotate = true
|
||||||
|
}, 10)
|
||||||
|
}
|
||||||
|
this.setCenterDisplaced(this.currentLatitude, this.currentLongitude)
|
||||||
|
}
|
||||||
|
constructor(conquerContainer: HTMLDivElement) {
|
||||||
|
this.conquerContainer = conquerContainer
|
||||||
|
}
|
||||||
|
}
|
61
js-src/conquer/interface-manager.ts
Normal file
61
js-src/conquer/interface-manager.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
import ConquerInterface from '@burguillosinfo/conquer/interface'
|
||||||
|
|
||||||
|
export default class ConquerInterfaceManager {
|
||||||
|
private interfaces: ConquerInterface[] = []
|
||||||
|
|
||||||
|
public push(conquerInterface: ConquerInterface) {
|
||||||
|
const nodesForInterface = conquerInterface.getNodes()
|
||||||
|
for (const nodeInInterface of nodesForInterface) {
|
||||||
|
nodeInInterface.id = ""
|
||||||
|
document.body.appendChild(nodeInInterface)
|
||||||
|
}
|
||||||
|
this.interfaces.push(conquerInterface)
|
||||||
|
conquerInterface.run()
|
||||||
|
let startInterface = this.interfaces.length - 2;
|
||||||
|
if (startInterface < 0) {
|
||||||
|
startInterface = 0
|
||||||
|
}
|
||||||
|
this.recalculateAllZIndexes(startInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
public remove(conquerInterface: ConquerInterface) {
|
||||||
|
for (let i = this.interfaces.length - 1; i >= 0; i--) {
|
||||||
|
if (conquerInterface !== this.interfaces[i]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
this.interfaces.splice(i, 1)
|
||||||
|
for (const nodeToDelete of conquerInterface.getNodes()) {
|
||||||
|
document.body.removeChild(nodeToDelete)
|
||||||
|
}
|
||||||
|
conquerInterface.prune()
|
||||||
|
this.recalculateAllZIndexes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private recalculateAllZIndexes(start = 0) : void {
|
||||||
|
let currentZindex = 5;
|
||||||
|
if (start < 0) {
|
||||||
|
Conquer.fail('ConquerInterfaceManager.recalculateAllZIndexes must not be passed negative values.')
|
||||||
|
}
|
||||||
|
if (start > 0) {
|
||||||
|
const lastInterface = this.interfaces[start-1];
|
||||||
|
if (lastInterface === undefined) {
|
||||||
|
Conquer.fail('Last interface should not be null, dying...')
|
||||||
|
}
|
||||||
|
const lastInterfaceNodes = lastInterface.getNodes()
|
||||||
|
const lastInterfaceLastNode = lastInterfaceNodes[lastInterfaceNodes.length-1]
|
||||||
|
if (lastInterfaceLastNode === undefined) {
|
||||||
|
Conquer.fail('Last interface last node should not be null, dying...')
|
||||||
|
}
|
||||||
|
currentZindex = parseInt(lastInterfaceLastNode.style.zIndex)
|
||||||
|
}
|
||||||
|
for (let i = start; i < this.interfaces.length; i++) {
|
||||||
|
const conquerInterface = this.interfaces[i]
|
||||||
|
for (const node of conquerInterface.getNodes()) {
|
||||||
|
node.style.zIndex = currentZindex + ''
|
||||||
|
currentZindex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
js-src/conquer/interface.ts
Normal file
53
js-src/conquer/interface.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
export default abstract class ConquerInterface {
|
||||||
|
private alreadyGenerated = false
|
||||||
|
private nodes: HTMLElement[]
|
||||||
|
private callbacks: Record<string, Array<() => void>> = {}
|
||||||
|
public getNodes(): HTMLElement[] {
|
||||||
|
if (!this.alreadyGenerated) {
|
||||||
|
this.nodes = this.generateNodes()
|
||||||
|
this.alreadyGenerated = true
|
||||||
|
}
|
||||||
|
return this.nodes
|
||||||
|
}
|
||||||
|
protected abstract generateNodes(): HTMLElement[]
|
||||||
|
|
||||||
|
public run(): void {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
public prune(): void {
|
||||||
|
this.callbacks = {};
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
protected getNodeFromTemplateId(id: string): HTMLElement {
|
||||||
|
let template = document.getElementById(id)
|
||||||
|
if (template === null) {
|
||||||
|
Conquer.fail(`Unable to find template id ${id}.`)
|
||||||
|
}
|
||||||
|
const finalNode = template.cloneNode(true)
|
||||||
|
if (!(finalNode instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('The node is not an Element.')
|
||||||
|
}
|
||||||
|
finalNode.classList.remove('conquer-display-none')
|
||||||
|
return finalNode
|
||||||
|
}
|
||||||
|
|
||||||
|
public on(eventName: string, callback: () => void): void {
|
||||||
|
if (this.callbacks[eventName] === undefined) {
|
||||||
|
this.callbacks[eventName] = []
|
||||||
|
}
|
||||||
|
this.callbacks[eventName].push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected runCallbacks(eventName: string) {
|
||||||
|
const callbacks = this.callbacks[eventName];
|
||||||
|
if (callbacks === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const callback of callbacks) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
js-src/conquer/interface/abstract-top-bar-interface.ts
Normal file
29
js-src/conquer/interface/abstract-top-bar-interface.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
import ConquerInterface from '@burguillosinfo/conquer/interface'
|
||||||
|
|
||||||
|
export default abstract class AbstractTopBarInterface extends ConquerInterface {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
const exitButton = this.getExitButton()
|
||||||
|
exitButton.addEventListener('click', () => {
|
||||||
|
this.runCallbacks('close')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
protected generateNodes(): HTMLElement[] {
|
||||||
|
const newNode = this.getNodeFromTemplateId('conquer-interface-with-top-bar-template')
|
||||||
|
return [newNode]
|
||||||
|
}
|
||||||
|
protected getMainNode(): HTMLElement {
|
||||||
|
return this.getNodes()[0]
|
||||||
|
}
|
||||||
|
protected getExitButton(): HTMLElement {
|
||||||
|
const maybeExitButton = this.getMainNode().querySelector('.conquer-exit-button')
|
||||||
|
if (maybeExitButton === null || !(maybeExitButton instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('No exit button.')
|
||||||
|
}
|
||||||
|
return maybeExitButton
|
||||||
|
}
|
||||||
|
public generateInterfaceElementCentered(): HTMLElement {
|
||||||
|
return this.getNodeFromTemplateId('conquer-interface-element-padded-template')
|
||||||
|
}
|
||||||
|
}
|
209
js-src/conquer/interface/login.ts
Normal file
209
js-src/conquer/interface/login.ts
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
import ConquerLogin from '@burguillosinfo/conquer/login'
|
||||||
|
import ConquerInterface from '@burguillosinfo/conquer/interface'
|
||||||
|
|
||||||
|
export default class LoginUI extends ConquerInterface {
|
||||||
|
private conquerLogin: ConquerLogin
|
||||||
|
|
||||||
|
private conquerLoginGoToRegister: HTMLAnchorElement
|
||||||
|
private conquerLoginError: HTMLParagraphElement
|
||||||
|
private conquerLoginSuccess: HTMLParagraphElement
|
||||||
|
private conquerLoginUsername: HTMLInputElement
|
||||||
|
private conquerLoginPassword: HTMLInputElement
|
||||||
|
private conquerLoginSubmit: HTMLButtonElement
|
||||||
|
|
||||||
|
private conquerRegisterGoToLogin: HTMLAnchorElement
|
||||||
|
private conquerRegisterUsername: HTMLInputElement
|
||||||
|
private conquerRegisterPassword: HTMLInputElement
|
||||||
|
private conquerRegisterRepeatPassword: HTMLInputElement
|
||||||
|
private conquerRegisterSubmit: HTMLButtonElement
|
||||||
|
private conquerRegisterError: HTMLParagraphElement
|
||||||
|
|
||||||
|
constructor(conquerLogin: ConquerLogin) {
|
||||||
|
super()
|
||||||
|
this.conquerLogin = conquerLogin
|
||||||
|
}
|
||||||
|
|
||||||
|
public run() {
|
||||||
|
this.conquerLogin.on('login', () => {
|
||||||
|
this.runCallbacks('close');
|
||||||
|
});
|
||||||
|
this.storeRegisterElements()
|
||||||
|
this.storeLoginElements()
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLoginDiv(): HTMLDivElement {
|
||||||
|
const element = this.getNodes()[1];
|
||||||
|
if (element === undefined || !(element instanceof HTMLDivElement)) {
|
||||||
|
Conquer.fail('Login is not a div.')
|
||||||
|
}
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOverlayDiv(): HTMLDivElement {
|
||||||
|
const element = this.getNodes()[0]
|
||||||
|
if (element === undefined || !(element instanceof HTMLDivElement)) {
|
||||||
|
Conquer.fail('Overlay transparent is not a div.')
|
||||||
|
}
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRegisterDiv(): HTMLDivElement {
|
||||||
|
const element = this.getNodes()[2]
|
||||||
|
if (element === undefined || !(element instanceof HTMLDivElement)) {
|
||||||
|
Conquer.fail('Register is not a div.')
|
||||||
|
}
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateNodes(): HTMLElement[] {
|
||||||
|
const resultArray = []
|
||||||
|
const overlay = this.getNodeFromTemplateId('conquer-overlay-transparent-template')
|
||||||
|
overlay.classList.remove('conquer-display-none')
|
||||||
|
resultArray.push(overlay)
|
||||||
|
const login = this.getNodeFromTemplateId('conquer-login-template')
|
||||||
|
login.classList.remove('conquer-display-none')
|
||||||
|
if (!(login instanceof HTMLDivElement)) {
|
||||||
|
Conquer.fail('Login is required to be a Div.')
|
||||||
|
}
|
||||||
|
resultArray.push(login)
|
||||||
|
const register = this.getNodeFromTemplateId('conquer-register-template')
|
||||||
|
resultArray.push(register)
|
||||||
|
return resultArray
|
||||||
|
}
|
||||||
|
private async storeRegisterElements() {
|
||||||
|
const registerElement = this.getRegisterDiv()
|
||||||
|
const conquerRegisterGoToLogin = registerElement.querySelector('.conquer-register-go-to-login')
|
||||||
|
if (conquerRegisterGoToLogin === null || !(conquerRegisterGoToLogin instanceof HTMLAnchorElement)) {
|
||||||
|
Conquer.fail('Link to go to login from register is invalid.')
|
||||||
|
}
|
||||||
|
this.conquerRegisterGoToLogin = conquerRegisterGoToLogin
|
||||||
|
this.conquerRegisterGoToLogin.addEventListener('click', () => {
|
||||||
|
this.goToLogin()
|
||||||
|
})
|
||||||
|
const conquerRegisterUsername = registerElement.querySelector('.conquer-register-username')
|
||||||
|
if (conquerRegisterUsername === null || !(conquerRegisterUsername instanceof HTMLInputElement)) {
|
||||||
|
Conquer.fail('No username field in conquer register.')
|
||||||
|
}
|
||||||
|
this.conquerRegisterUsername = conquerRegisterUsername
|
||||||
|
const conquerRegisterPassword = registerElement.querySelector('.conquer-register-password')
|
||||||
|
if (conquerRegisterPassword === null || !(conquerRegisterPassword instanceof HTMLInputElement)) {
|
||||||
|
Conquer.fail('No password field in conquer register.')
|
||||||
|
}
|
||||||
|
this.conquerRegisterPassword = conquerRegisterPassword
|
||||||
|
const conquerRegisterRepeatPassword = registerElement.querySelector('.conquer-register-repeat-password')
|
||||||
|
if (conquerRegisterRepeatPassword === null || !(conquerRegisterRepeatPassword instanceof HTMLInputElement)) {
|
||||||
|
Conquer.fail('No repeat password field in conquer register.')
|
||||||
|
}
|
||||||
|
this.conquerRegisterRepeatPassword = conquerRegisterRepeatPassword
|
||||||
|
const conquerRegisterSubmit = registerElement.querySelector('.conquer-register-submit')
|
||||||
|
if (conquerRegisterSubmit === null || !(conquerRegisterSubmit instanceof HTMLButtonElement)) {
|
||||||
|
Conquer.fail('No register submit button found.')
|
||||||
|
}
|
||||||
|
this.conquerRegisterSubmit = conquerRegisterSubmit
|
||||||
|
this.conquerRegisterSubmit.addEventListener('click', (event: Event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
const username = this.conquerRegisterUsername.value
|
||||||
|
const password = this.conquerRegisterPassword.value
|
||||||
|
const repeatPassword = this.conquerRegisterRepeatPassword.value
|
||||||
|
this.conquerLogin.onRegisterRequest(this, username, password, repeatPassword)
|
||||||
|
})
|
||||||
|
const conquerRegisterError = registerElement.querySelector('.conquer-register-error')
|
||||||
|
if (conquerRegisterError === null || !(conquerRegisterError instanceof HTMLParagraphElement)) {
|
||||||
|
Conquer.fail('Unable to find the conquer error element.')
|
||||||
|
}
|
||||||
|
this.conquerRegisterError = conquerRegisterError
|
||||||
|
|
||||||
|
}
|
||||||
|
private storeLoginElements() {
|
||||||
|
const loginElement = this.getLoginDiv()
|
||||||
|
const conquerLoginGoToRegister = loginElement.querySelector('.conquer-login-go-to-register')
|
||||||
|
if (conquerLoginGoToRegister === null || !(conquerLoginGoToRegister instanceof HTMLAnchorElement)) {
|
||||||
|
Conquer.fail('Link to go to register from login is invalid.')
|
||||||
|
}
|
||||||
|
this.conquerLoginGoToRegister = conquerLoginGoToRegister
|
||||||
|
this.conquerLoginGoToRegister.addEventListener('click', () => {
|
||||||
|
this.goToRegister()
|
||||||
|
})
|
||||||
|
const conquerLoginError = loginElement.querySelector('.conquer-login-error')
|
||||||
|
if (conquerLoginError === null || !(conquerLoginError instanceof HTMLParagraphElement)) {
|
||||||
|
Conquer.fail('Unable to find conquer login error.')
|
||||||
|
}
|
||||||
|
this.conquerLoginError = conquerLoginError
|
||||||
|
const conquerLoginSuccess = loginElement.querySelector('.conquer-login-success')
|
||||||
|
if (conquerLoginSuccess === null || !(conquerLoginSuccess instanceof HTMLParagraphElement)) {
|
||||||
|
Conquer.fail('Unable to find conquer login success.')
|
||||||
|
}
|
||||||
|
this.conquerLoginSuccess = conquerLoginSuccess
|
||||||
|
const conquerLoginUsername = loginElement.querySelector('.conquer-login-username')
|
||||||
|
if (conquerLoginUsername === null || !(conquerLoginUsername instanceof HTMLInputElement)) {
|
||||||
|
Conquer.fail('Unable to find conquer login username field.')
|
||||||
|
}
|
||||||
|
this.conquerLoginUsername = conquerLoginUsername
|
||||||
|
const conquerLoginPassword = loginElement.querySelector('.conquer-login-password')
|
||||||
|
if (conquerLoginPassword === null || !(conquerLoginPassword instanceof HTMLInputElement)) {
|
||||||
|
Conquer.fail('Unable to find conquer login password field.')
|
||||||
|
}
|
||||||
|
this.conquerLoginPassword = conquerLoginPassword
|
||||||
|
const conquerLoginSubmit = loginElement.querySelector('.conquer-login-submit')
|
||||||
|
if (conquerLoginSubmit === null || !(conquerLoginSubmit instanceof HTMLButtonElement)) {
|
||||||
|
Conquer.fail('Unable to find the submit button for the login.')
|
||||||
|
}
|
||||||
|
this.conquerLoginSubmit = conquerLoginSubmit
|
||||||
|
this.conquerLoginSubmit.addEventListener('click', (event: Event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
const username = this.conquerLoginUsername.value
|
||||||
|
const password = this.conquerLoginPassword.value
|
||||||
|
this.conquerLogin.onLoginRequested(this, username, password)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async goToRegister(): Promise<void> {
|
||||||
|
await this.removeLoginRegisterCombo()
|
||||||
|
const registerElement = this.getRegisterDiv()
|
||||||
|
registerElement.classList.remove('conquer-display-none')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeLoginRegisterCombo(): Promise<void> {
|
||||||
|
const registerElement = this.getRegisterDiv()
|
||||||
|
const overlayElement = this.getOverlayDiv()
|
||||||
|
overlayElement.classList.add('conquer-display-none')
|
||||||
|
const loginElement = this.getLoginDiv()
|
||||||
|
loginElement.classList.add('conquer-display-none')
|
||||||
|
registerElement.classList.add('conquer-display-none')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addNewLoginSuccessText(message: string): Promise<void> {
|
||||||
|
this.unsetLoginAndRegisterErrors()
|
||||||
|
this.conquerLoginSuccess.innerText = message
|
||||||
|
this.conquerLoginSuccess.classList.remove('conquer-display-none')
|
||||||
|
|
||||||
|
}
|
||||||
|
public async addNewLoginError(error: string): Promise<void> {
|
||||||
|
this.unsetLoginAndRegisterErrors()
|
||||||
|
this.conquerLoginSuccess.classList.add('conquer-display-none')
|
||||||
|
this.conquerLoginError.innerText = error
|
||||||
|
this.conquerLoginError.classList.remove('conquer-display-none')
|
||||||
|
}
|
||||||
|
public async addNewRegisterError(error: string): Promise<void> {
|
||||||
|
this.unsetLoginAndRegisterErrors()
|
||||||
|
this.conquerLoginSuccess.classList.add('conquer-display-none')
|
||||||
|
this.conquerRegisterError.innerText = error
|
||||||
|
this.conquerRegisterError.classList.remove('conquer-display-none')
|
||||||
|
}
|
||||||
|
public async unsetLoginAndRegisterErrors() {
|
||||||
|
this.conquerRegisterError.classList.add('conquer-display-none')
|
||||||
|
this.conquerLoginError.classList.add('conquer-display-none')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async goToLogin(): Promise<void> {
|
||||||
|
await this.removeLoginRegisterCombo()
|
||||||
|
const loginElement = this.getLoginDiv()
|
||||||
|
loginElement.classList.remove('conquer-display-none')
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addNewLoginRegisterError(message: string): Promise<void> {
|
||||||
|
this.addNewRegisterError(message)
|
||||||
|
this.addNewLoginError(message)
|
||||||
|
}
|
||||||
|
}
|
104
js-src/conquer/interface/new-node.ts
Normal file
104
js-src/conquer/interface/new-node.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
|
||||||
|
export default class NewNodeUI extends AbstractTopBarInterface {
|
||||||
|
private coordinates: number[];
|
||||||
|
public getSubmitButton(): HTMLElement {
|
||||||
|
const submitButton = this.getMainNode().querySelector('button.new-node-form-submit')
|
||||||
|
if (submitButton === null || !(submitButton instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('SubmitButton is null');
|
||||||
|
}
|
||||||
|
return submitButton;
|
||||||
|
}
|
||||||
|
public getErrorElement(): HTMLElement {
|
||||||
|
const errorElement = this.getMainNode().querySelector('p.conquer-error');
|
||||||
|
if (errorElement === null || !(errorElement instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('No error element set');
|
||||||
|
}
|
||||||
|
return errorElement;
|
||||||
|
}
|
||||||
|
public getSelectNodeType(): HTMLSelectElement {
|
||||||
|
const selectElement = this.getMainNode().querySelector('select.conquer-node-type');
|
||||||
|
if (selectElement === null || !(selectElement instanceof HTMLSelectElement)) {
|
||||||
|
Conquer.fail('SelectElementNodeType is null');
|
||||||
|
}
|
||||||
|
return selectElement
|
||||||
|
}
|
||||||
|
public getInputNodeName(): HTMLInputElement {
|
||||||
|
const nodeName = this.getMainNode().querySelector('input.conquer-node-name')
|
||||||
|
if (nodeName === null || !(nodeName instanceof HTMLInputElement)) {
|
||||||
|
Conquer.fail('NodeName is null');
|
||||||
|
}
|
||||||
|
return nodeName
|
||||||
|
}
|
||||||
|
public getTextAreaNodeDescription(): HTMLTextAreaElement {
|
||||||
|
const nodeDescription = this.getMainNode().querySelector('textarea.conquer-node-description')
|
||||||
|
if (nodeDescription === null || !(nodeDescription instanceof HTMLTextAreaElement)) {
|
||||||
|
Conquer.fail('NodeDescription is null');
|
||||||
|
}
|
||||||
|
return nodeDescription
|
||||||
|
}
|
||||||
|
constructor(coordinates: number[]) {
|
||||||
|
super()
|
||||||
|
this.coordinates = coordinates
|
||||||
|
}
|
||||||
|
public run() {
|
||||||
|
const mainNode = this.getMainNode()
|
||||||
|
const form = this.getNodeFromTemplateId('conquer-new-node-form-creation-template')
|
||||||
|
mainNode.append(form)
|
||||||
|
this.getSubmitButton().addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.onSubmit();
|
||||||
|
});
|
||||||
|
form.classList.remove('conquer-display-none')
|
||||||
|
mainNode.classList.remove('conquer-display-none')
|
||||||
|
}
|
||||||
|
|
||||||
|
private setError(error: string): void {
|
||||||
|
const errorElement = this.getErrorElement();
|
||||||
|
errorElement.classList.remove('conquer-display-none')
|
||||||
|
errorElement.innerText = error
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSubmit(): void {
|
||||||
|
const selectNodeType = this.getSelectNodeType();
|
||||||
|
const inputNodeName = this.getInputNodeName();
|
||||||
|
const textAreaNodeDescription = this.getTextAreaNodeDescription();
|
||||||
|
const description = textAreaNodeDescription.value;
|
||||||
|
const nodeName = inputNodeName.value;
|
||||||
|
const selectedOptionsNodeType = selectNodeType.selectedOptions;
|
||||||
|
if (selectedOptionsNodeType.length < 1) {
|
||||||
|
this.setError('Debes selecionar un tipo de nodo.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const selectedOptionNodeType = selectedOptionsNodeType[0];
|
||||||
|
const nodeType = selectedOptionNodeType.value;
|
||||||
|
if (nodeName.length < 5) {
|
||||||
|
this.setError('Todos los nodos deben tener un nombre mayor a 4 caracteres.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const urlNode = new URL('/conquer/node', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
fetch(urlNode, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({
|
||||||
|
description: description,
|
||||||
|
name: nodeName,
|
||||||
|
type: nodeType,
|
||||||
|
coordinates: this.coordinates,
|
||||||
|
}),
|
||||||
|
}).then(async (res) => {
|
||||||
|
let responseBody;
|
||||||
|
try {
|
||||||
|
responseBody = await res.json();
|
||||||
|
} catch (error) {
|
||||||
|
this.setError( 'Respuesta erronea del servidor.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res.status !== 200) {
|
||||||
|
this.setError(responseBody.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.runCallbacks('close')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
97
js-src/conquer/interface/new-team.ts
Normal file
97
js-src/conquer/interface/new-team.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
|
||||||
|
export default class NewTeamUI extends AbstractTopBarInterface {
|
||||||
|
public getSubmitButton(): HTMLElement {
|
||||||
|
const submitButton = this.getMainNode().querySelector('button.new-team-form-submit')
|
||||||
|
if (submitButton === null || !(submitButton instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('SubmitButton is null');
|
||||||
|
}
|
||||||
|
return submitButton;
|
||||||
|
}
|
||||||
|
public getErrorElement(): HTMLElement {
|
||||||
|
const errorElement = this.getMainNode().querySelector('p.conquer-error');
|
||||||
|
if (errorElement === null || !(errorElement instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('No error element set');
|
||||||
|
}
|
||||||
|
return errorElement;
|
||||||
|
}
|
||||||
|
public getInputTeamName(): HTMLInputElement {
|
||||||
|
const teamName = this.getMainNode().querySelector('input.conquer-team-name');
|
||||||
|
if (teamName === null || !(teamName instanceof HTMLInputElement)) {
|
||||||
|
Conquer.fail('TeamName is null');
|
||||||
|
}
|
||||||
|
return teamName;
|
||||||
|
}
|
||||||
|
public getInputTeamColor(): HTMLInputElement {
|
||||||
|
const teamColor = this.getMainNode().querySelector('input.conquer-team-color');
|
||||||
|
if (teamColor === null || !(teamColor instanceof HTMLInputElement)) {
|
||||||
|
Conquer.fail('TeamColor is null');
|
||||||
|
}
|
||||||
|
return teamColor;
|
||||||
|
}
|
||||||
|
public getTextareaTeamDescription(): HTMLTextAreaElement {
|
||||||
|
const teamDescription = this.getMainNode().querySelector('textarea.conquer-team-description')
|
||||||
|
if (teamDescription === null || !(teamDescription instanceof HTMLTextAreaElement)) {
|
||||||
|
Conquer.fail('TeamDescription is null');
|
||||||
|
}
|
||||||
|
return teamDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
public run() {
|
||||||
|
const mainNode = this.getMainNode()
|
||||||
|
const form = this.getNodeFromTemplateId('conquer-new-team-form-creation-template')
|
||||||
|
mainNode.append(form)
|
||||||
|
this.getSubmitButton().addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.onSubmit();
|
||||||
|
});
|
||||||
|
form.classList.remove('conquer-display-none')
|
||||||
|
mainNode.classList.remove('conquer-display-none')
|
||||||
|
}
|
||||||
|
|
||||||
|
private setError(error: string): void {
|
||||||
|
const errorElement = this.getErrorElement();
|
||||||
|
errorElement.classList.remove('conquer-display-none')
|
||||||
|
errorElement.innerText = error
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSubmit(): void {
|
||||||
|
const inputTeamName = this.getInputTeamName();
|
||||||
|
const textareaTeamDescription = this.getTextareaTeamDescription();
|
||||||
|
const inputTeamColor = this.getInputTeamColor();
|
||||||
|
const name = inputTeamName.value;
|
||||||
|
const description = textareaTeamDescription.value;
|
||||||
|
const color = inputTeamColor.value;
|
||||||
|
if (name.length < 5) {
|
||||||
|
this.setError('Todos los equipos deben tener un nombre mayor a 4 caracteres.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const urlTeam = new URL('/conquer/team', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
fetch(urlTeam, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({
|
||||||
|
description : description,
|
||||||
|
name : name,
|
||||||
|
color : color,
|
||||||
|
}),
|
||||||
|
}).then(async (res) => {
|
||||||
|
let responseBody;
|
||||||
|
try {
|
||||||
|
responseBody = await res.json();
|
||||||
|
} catch (error) {
|
||||||
|
this.setError( 'Respuesta erronea del servidor.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (res.status !== 200) {
|
||||||
|
this.setError(responseBody.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.runCallbacks('close')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
157
js-src/conquer/interface/node-view.ts
Normal file
157
js-src/conquer/interface/node-view.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
import MapNode from '@burguillosinfo/conquer/map-node'
|
||||||
|
import ConquerUser from '@burguillosinfo/conquer/user'
|
||||||
|
|
||||||
|
export default class NodeView extends AbstractTopBarInterface {
|
||||||
|
private node: MapNode;
|
||||||
|
private user: ConquerUser;
|
||||||
|
private view: HTMLElement | null = null;
|
||||||
|
|
||||||
|
public getNode(): MapNode {
|
||||||
|
return this.node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getNodeNameH2(): HTMLElement {
|
||||||
|
const element = this.getMainNode().querySelector('h2.node-name');
|
||||||
|
if (!(element instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('h2.node-name is not a H2 or does not exist.');
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getView(): HTMLElement {
|
||||||
|
if (this.view === null) {
|
||||||
|
const view = this.getNodeFromTemplateId('conquer-view-node-template')
|
||||||
|
this.view = view;
|
||||||
|
}
|
||||||
|
return this.view;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private getNodeDescriptionParagraph(): HTMLElement {
|
||||||
|
const element = this.getMainNode().querySelector('p.node-description');
|
||||||
|
if (!(element instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('p.node-description is not a P or does not exist.');
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(node: MapNode) {
|
||||||
|
super()
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run() {
|
||||||
|
const user = await ConquerUser.getSelfUser();
|
||||||
|
if (user === null) {
|
||||||
|
this.runCallbacks('close');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.user = user;
|
||||||
|
const mainNode = this.getMainNode()
|
||||||
|
this.runCallbacks('update-nodes');
|
||||||
|
try {
|
||||||
|
this.node = await this.node.fetch();
|
||||||
|
} catch (error) {
|
||||||
|
this.runCallbacks('close');
|
||||||
|
}
|
||||||
|
mainNode.append(this.getView())
|
||||||
|
this.getNodeNameH2().innerText = this.node.getName();
|
||||||
|
this.getNodeDescriptionParagraph().innerText = this.node.getDescription()
|
||||||
|
+ "\n"
|
||||||
|
+ (this.node.isNear()
|
||||||
|
? 'Estas cerca y puedes interactuar con este sitio.'
|
||||||
|
: 'Estás demasiado lejos para hacer nada aquí.');
|
||||||
|
this.populateTeamData();
|
||||||
|
if (this.node.isNear()) {
|
||||||
|
await this.runIfNear();
|
||||||
|
}
|
||||||
|
this.getView().classList.remove('conquer-display-none')
|
||||||
|
mainNode.classList.remove('conquer-display-none')
|
||||||
|
}
|
||||||
|
|
||||||
|
private async populateTeamData() {
|
||||||
|
const element = document.createElement('p');
|
||||||
|
const team = await this.node.getTeam();
|
||||||
|
(() => {
|
||||||
|
if (team === null) {
|
||||||
|
element.innerText = 'El nodo no pertenece a ningún equipo todavía.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const spanText = document.createElement('span');
|
||||||
|
spanText.innerText = 'Equipo: ';
|
||||||
|
element.append(spanText);
|
||||||
|
const spanCircle = document.createElement('span');
|
||||||
|
spanCircle.classList.add('conquer-team-circle');
|
||||||
|
spanCircle.style.backgroundColor = team.getColor();
|
||||||
|
element.append(spanCircle);
|
||||||
|
const spanTeamName = document.createElement('span');
|
||||||
|
spanTeamName.style.color = team.getColor();
|
||||||
|
spanTeamName.innerText = ' ' + team.getName();
|
||||||
|
element.append(spanTeamName);
|
||||||
|
})();
|
||||||
|
this.getView().append(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runIfNear(): Promise<void> {
|
||||||
|
const userTeam = await this.user.getTeam();
|
||||||
|
const nodeTeam = await this.node.getTeam();
|
||||||
|
if (userTeam === null) {
|
||||||
|
const paragraphNoTeam = document.createElement('p');
|
||||||
|
paragraphNoTeam.innerText = 'Parece que no has seleccionado equipo aun,'
|
||||||
|
+ ' pulsa el botón de seleccionar equipo para comenzar tu aventura,'
|
||||||
|
+ ' si quieres cambiar de equipo en el futuro puedes hacerlo sin problemas.';
|
||||||
|
this.getView().append(paragraphNoTeam);
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectTeamButton = document.createElement('button');
|
||||||
|
selectTeamButton.innerText = 'Seleccionar equipo';
|
||||||
|
selectTeamButton.addEventListener('click', () => {
|
||||||
|
this.runCallbacks('open-select-team');
|
||||||
|
this.runCallbacks('close');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getView().append(selectTeamButton);
|
||||||
|
if (await this.isOpposingNode()) {
|
||||||
|
const conquerForTeamButton = document.createElement('button');
|
||||||
|
conquerForTeamButton.innerText = 'Conquistar';
|
||||||
|
conquerForTeamButton.addEventListener('click', () => {
|
||||||
|
this.conquerThisNodeForTeam();
|
||||||
|
});
|
||||||
|
this.getView().append(conquerForTeamButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async conquerThisNodeForTeam() {
|
||||||
|
const urlNode = new URL('/conquer/node/' + this.node.getUUID() + '/try-conquer',
|
||||||
|
window.location.protocol + '//'
|
||||||
|
+ window.location.hostname + ':'
|
||||||
|
+ window.location.port)
|
||||||
|
const response = await fetch(urlNode, {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
this.runCallbacks('update-nodes');
|
||||||
|
this.runCallbacks('close');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async isOpposingNode(): Promise<boolean> {
|
||||||
|
const userTeam = await this.user.getTeam();
|
||||||
|
const nodeTeam = await this.node.getTeam();
|
||||||
|
if (userTeam === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nodeTeam === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (nodeTeam.getUUID() !== userTeam.getUUID()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async isNodeFree(): Promise<boolean> {
|
||||||
|
return await this.node.getTeam() === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
72
js-src/conquer/interface/select-fight.ts
Normal file
72
js-src/conquer/interface/select-fight.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer';
|
||||||
|
import ConquerUser from '@burguillosinfo/conquer/user';
|
||||||
|
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface';
|
||||||
|
import ConquerUserCurrentEnemy from '@burguillosinfo/conquer/user-current-enemy'
|
||||||
|
|
||||||
|
export default class SelectFightUI extends AbstractTopBarInterface {
|
||||||
|
private enemies: ConquerUserCurrentEnemy[];
|
||||||
|
private form: HTMLElement | null = null;
|
||||||
|
|
||||||
|
constructor(enemies: ConquerUserCurrentEnemy[]) {
|
||||||
|
super();
|
||||||
|
this.enemies = enemies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(): Promise<void> {
|
||||||
|
const user = await ConquerUser.getSelfUser()
|
||||||
|
if (user === null) {
|
||||||
|
this.runCallbacks('close')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.getMainNode().append(this.getForm());
|
||||||
|
this.populateEnemies();
|
||||||
|
this.getMainNode().classList.remove('conquer-display-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateEnemies(): void {
|
||||||
|
for (const enemy of this.enemies) {
|
||||||
|
this.appendEnemy(enemy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private appendEnemy(enemy: ConquerUserCurrentEnemy) {
|
||||||
|
const form = this.getForm();
|
||||||
|
const enemyNode = this.getNodeFromTemplateId('conquer-select-fight-item-template');
|
||||||
|
this.getNameEnemyNodeElement(enemyNode).innerText = enemy.getSpecies().getName();
|
||||||
|
this.getLevelEnemyNodeElement(enemyNode).innerText = '' + enemy.getLevel();
|
||||||
|
this.getImageEnemyNodeElement(enemyNode).src = enemy.getSpecies().getImage();
|
||||||
|
form.append(enemyNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getImageEnemyNodeElement(enemyNode: HTMLElement): HTMLImageElement {
|
||||||
|
const conquerImage = enemyNode.querySelector('.conquer-image');
|
||||||
|
if (!(conquerImage instanceof HTMLImageElement)) {
|
||||||
|
Conquer.fail('conquerImage is not HTMLImageElement.')
|
||||||
|
}
|
||||||
|
return conquerImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLevelEnemyNodeElement(enemyNode: HTMLElement): HTMLElement {
|
||||||
|
const conquerLevel = enemyNode.querySelector('.conquer-level');
|
||||||
|
if (!(conquerLevel instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('conquerLevel is not HTMLElement.')
|
||||||
|
}
|
||||||
|
return conquerLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getNameEnemyNodeElement(enemyNode: HTMLElement): HTMLElement {
|
||||||
|
const conquerName = enemyNode.querySelector('.conquer-name');
|
||||||
|
if (!(conquerName instanceof HTMLElement)) {
|
||||||
|
Conquer.fail('conquerName is not HTMLElement.')
|
||||||
|
}
|
||||||
|
return conquerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getForm(): HTMLElement {
|
||||||
|
if (this.form === null) {
|
||||||
|
const form = this.getNodeFromTemplateId('conquer-select-fight-list-template')
|
||||||
|
this.form = form;
|
||||||
|
}
|
||||||
|
return this.form;
|
||||||
|
}
|
||||||
|
}
|
89
js-src/conquer/interface/select-team.ts
Normal file
89
js-src/conquer/interface/select-team.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
import ConquerUser from '@burguillosinfo/conquer/user'
|
||||||
|
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||||
|
import MapNode from '@burguillosinfo/conquer/map-node'
|
||||||
|
import ConquerTeam from '@burguillosinfo/conquer/team';
|
||||||
|
|
||||||
|
export default class SelectTeamUI extends AbstractTopBarInterface {
|
||||||
|
private node: MapNode;
|
||||||
|
private user: ConquerUser;
|
||||||
|
private form: HTMLElement | null = null;
|
||||||
|
|
||||||
|
constructor(node: MapNode) {
|
||||||
|
super();
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(): Promise<void> {
|
||||||
|
const user = await ConquerUser.getSelfUser()
|
||||||
|
if (user === null) {
|
||||||
|
this.runCallbacks('close')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.user = user
|
||||||
|
await this.populateTeams();
|
||||||
|
this.getForm().classList.remove('conquer-display-none');
|
||||||
|
this.getMainNode().append(this.getForm());
|
||||||
|
this.getMainNode().classList.remove('conquer-display-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async populateTeams() {
|
||||||
|
const teams = await ConquerTeam.getTeams();
|
||||||
|
for (const team of teams) {
|
||||||
|
this.populateTeam(team);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateTeam(team: ConquerTeam) {
|
||||||
|
const teamDiv = this.getNodeFromTemplateId('conquer-team-to-select-template')
|
||||||
|
const nameParagraph = teamDiv.querySelector('p.conquer-name');
|
||||||
|
const descriptionParagraph = teamDiv.querySelector('p.conquer-description');
|
||||||
|
const submit = teamDiv.querySelector('button.conquer-submit');
|
||||||
|
if (!(nameParagraph instanceof HTMLParagraphElement)
|
||||||
|
|| !(descriptionParagraph instanceof HTMLParagraphElement)
|
||||||
|
|| !(submit instanceof HTMLButtonElement)) {
|
||||||
|
Conquer.fail('Select team name inclusive or description container are not correctly defined in template.');
|
||||||
|
}
|
||||||
|
nameParagraph.innerText = team.getName();
|
||||||
|
descriptionParagraph.innerText = team.getDescription();
|
||||||
|
nameParagraph.style.color = team.getColor();
|
||||||
|
submit.addEventListener('click', async () => {
|
||||||
|
this.onSelectTeam(team);
|
||||||
|
});
|
||||||
|
this.getForm().append(teamDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onSelectTeam(team: ConquerTeam) {
|
||||||
|
const urlTeam = new URL('/conquer/user/team',
|
||||||
|
window.location.protocol + '//'
|
||||||
|
+ window.location.hostname + ':'
|
||||||
|
+ window.location.port);
|
||||||
|
const response = await fetch(urlTeam, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
team: team.getUUID(),
|
||||||
|
node: this.node.getUUID(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
let responseBody;
|
||||||
|
try {
|
||||||
|
responseBody = await response.json();
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.error(responseBody.error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.runCallbacks('update-nodes');
|
||||||
|
this.runCallbacks('close')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing json', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getForm(): HTMLElement {
|
||||||
|
if (this.form === null) {
|
||||||
|
const form = this.getNodeFromTemplateId('conquer-select-team-list-template')
|
||||||
|
this.form = form;
|
||||||
|
}
|
||||||
|
return this.form;
|
||||||
|
}
|
||||||
|
}
|
147
js-src/conquer/interface/self-player.ts
Normal file
147
js-src/conquer/interface/self-player.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
import ConquerUser from '@burguillosinfo/conquer/user'
|
||||||
|
import AbstractTopBarInterface from '@burguillosinfo/conquer/interface/abstract-top-bar-interface'
|
||||||
|
|
||||||
|
export default class SelfPlayerUI extends AbstractTopBarInterface {
|
||||||
|
private selfPlayer: ConquerUser | null = null
|
||||||
|
private userWelcome: HTMLElement | null = null
|
||||||
|
private isExplorerModeEnabled: boolean;
|
||||||
|
private userTeamData: HTMLElement | null = null;
|
||||||
|
|
||||||
|
constructor(isExplorerModeEnabled: boolean) {
|
||||||
|
super();
|
||||||
|
this.isExplorerModeEnabled = isExplorerModeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run(): Promise<void> {
|
||||||
|
const selfPlayerNode = this.getMainNode()
|
||||||
|
const user = await ConquerUser.getSelfUser()
|
||||||
|
if (user === null) {
|
||||||
|
this.runCallbacks('close')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.selfPlayer = user
|
||||||
|
this.populateWelcome()
|
||||||
|
this.populateCreateNodeOption()
|
||||||
|
this.populateToggleExplorerModeOption();
|
||||||
|
this.populateCreateTeamButton();
|
||||||
|
await this.populateUserTeamData();
|
||||||
|
selfPlayerNode.classList.remove('conquer-display-none')
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateToggleExplorerModeOption(): void {
|
||||||
|
const toggleExplorerModeButton = document.createElement('button');
|
||||||
|
this.setTextToggleExplorerModeButton(toggleExplorerModeButton);
|
||||||
|
toggleExplorerModeButton.addEventListener('click', () => {
|
||||||
|
(() => {
|
||||||
|
if (this.isExplorerModeEnabled) {
|
||||||
|
this.runCallbacks('disable-explorer-mode');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.runCallbacks('enable-explorer-mode');
|
||||||
|
})();
|
||||||
|
this.runCallbacks('close');
|
||||||
|
});
|
||||||
|
const toggleExplorerModeInterface = this.generateInterfaceElementCentered()
|
||||||
|
toggleExplorerModeInterface.appendChild(toggleExplorerModeButton)
|
||||||
|
this.getMainNode().appendChild(toggleExplorerModeInterface)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateCreateTeamButton(): void {
|
||||||
|
// Only admins can create teams.
|
||||||
|
if (!this.selfPlayer?.isAdmin()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const createTeamButton = document.createElement('button');
|
||||||
|
createTeamButton.innerText = 'Crea un nuevo equipo';
|
||||||
|
createTeamButton.addEventListener('click', () => {
|
||||||
|
this.runCallbacks('open-create-team');
|
||||||
|
this.runCallbacks('close');
|
||||||
|
});
|
||||||
|
const createTeamButtonInterface = this.generateInterfaceElementCentered()
|
||||||
|
createTeamButtonInterface.append(createTeamButton);
|
||||||
|
this.getMainNode().appendChild(createTeamButtonInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setTextToggleExplorerModeButton(button: HTMLElement): void {
|
||||||
|
if (this.isExplorerModeEnabled) {
|
||||||
|
button.innerText = 'Desactivar movimiento libre en el mapa.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
button.innerText = 'Activar movimiento libre en el mapa.';
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateCreateNodeOption() {
|
||||||
|
// Only admins can create nodes.
|
||||||
|
if (!this.selfPlayer?.isAdmin()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const createNodeButton = document.createElement('button')
|
||||||
|
createNodeButton.innerText = 'Crear Nuevo Nodo'
|
||||||
|
createNodeButton.addEventListener('click', () => {
|
||||||
|
this.runCallbacks('createNodeStart')
|
||||||
|
// We close because it is a sensible thing to do.
|
||||||
|
this.runCallbacks('close')
|
||||||
|
})
|
||||||
|
const createNodeButtonInterface = this.generateInterfaceElementCentered()
|
||||||
|
createNodeButtonInterface.appendChild(createNodeButton)
|
||||||
|
this.getMainNode().appendChild(createNodeButtonInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getUserTeamData(): Promise<HTMLElement> {
|
||||||
|
if (this.userTeamData !== null) {
|
||||||
|
return this.userTeamData;
|
||||||
|
}
|
||||||
|
const element = document.createElement('p');
|
||||||
|
this.userTeamData = element;
|
||||||
|
if (this.selfPlayer === null) {
|
||||||
|
throw new Error('User still not set')
|
||||||
|
}
|
||||||
|
const team = await this.selfPlayer.getTeam();
|
||||||
|
if (team === null) {
|
||||||
|
element.innerText = 'No tienes equipo aun,'
|
||||||
|
+ ' ve al nodo más cercano para unirte a un equipo.';
|
||||||
|
return this.userTeamData;
|
||||||
|
}
|
||||||
|
const spanText = document.createElement('span');
|
||||||
|
spanText.innerText = 'Equipo: ';
|
||||||
|
element.append(spanText);
|
||||||
|
const spanCircle = document.createElement('span');
|
||||||
|
spanCircle.classList.add('conquer-team-circle');
|
||||||
|
spanCircle.style.backgroundColor = team.getColor();
|
||||||
|
element.append(spanCircle);
|
||||||
|
const spanTeamName = document.createElement('span');
|
||||||
|
spanTeamName.style.color = team.getColor();
|
||||||
|
spanTeamName.innerText = ' ' + team.getName();
|
||||||
|
element.append(spanTeamName);
|
||||||
|
return this.userTeamData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async populateUserTeamData(): Promise<void> {
|
||||||
|
const userTeamData = await this.getUserTeamData();
|
||||||
|
const userTeamDataInterface = this.generateInterfaceElementCentered();
|
||||||
|
userTeamDataInterface.append(userTeamData);
|
||||||
|
this.getMainNode().append(userTeamDataInterface);
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateWelcome(): void {
|
||||||
|
const userWelcome = this.getUserWelcome()
|
||||||
|
const userWelcomeInterface = this.generateInterfaceElementCentered();
|
||||||
|
userWelcomeInterface.appendChild(userWelcome)
|
||||||
|
this.getMainNode().appendChild(userWelcomeInterface)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUserWelcome(): HTMLElement {
|
||||||
|
if (this.userWelcome !== null) {
|
||||||
|
return this.userWelcome
|
||||||
|
}
|
||||||
|
const element = document.createElement('h2')
|
||||||
|
if (this.selfPlayer === null) {
|
||||||
|
throw new Error('User still not set')
|
||||||
|
}
|
||||||
|
element.innerText = `¡Hola, ${this.selfPlayer.getUsername()}!`
|
||||||
|
this.userWelcome = element
|
||||||
|
return this.userWelcome
|
||||||
|
}
|
||||||
|
}
|
136
js-src/conquer/login.ts
Normal file
136
js-src/conquer/login.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
import ConquerInterfaceManager from '@burguillosinfo/conquer/interface-manager'
|
||||||
|
import LoginUI from '@burguillosinfo/conquer/interface/login'
|
||||||
|
|
||||||
|
export type ConquerLoginEventCallback = () => void
|
||||||
|
|
||||||
|
export default class Login {
|
||||||
|
private conquerLogin: HTMLDivElement
|
||||||
|
private conquerInterfaceManager: ConquerInterfaceManager
|
||||||
|
private cachedIsLoggedIn: boolean | null = null
|
||||||
|
|
||||||
|
constructor(conquerInterfaceManager: ConquerInterfaceManager) {
|
||||||
|
this.conquerInterfaceManager = conquerInterfaceManager
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(): Promise<void> {
|
||||||
|
this.loopCheckLogin()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onRegisterRequest(loginUI: LoginUI, username: string, password: string, repeatPassword: string): Promise<void> {
|
||||||
|
const urlUser = new URL('/conquer/user', window.location.protocol +
|
||||||
|
'//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
let responseJson
|
||||||
|
let status
|
||||||
|
try {
|
||||||
|
const response = await fetch(urlUser, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
repeat_password: repeatPassword
|
||||||
|
})
|
||||||
|
})
|
||||||
|
responseJson = await response.json()
|
||||||
|
status = response.status
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
loginUI.addNewRegisterError('El servidor ha enviado datos inesperados.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (status !== 200) {
|
||||||
|
loginUI.addNewRegisterError(responseJson.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loginUI.addNewLoginSuccessText(`Usuario registrado ${username}.`)
|
||||||
|
loginUI.goToLogin()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loopCheckLogin(): Promise<void> {
|
||||||
|
window.setInterval(() => {
|
||||||
|
this.isLogged().then((isLogged) => {
|
||||||
|
if (isLogged) {
|
||||||
|
if (this.cachedIsLoggedIn !== true) {
|
||||||
|
this.cachedIsLoggedIn = true;
|
||||||
|
this.onLoginSuccess();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.cachedIsLoggedIn !== false) {
|
||||||
|
this.cachedIsLoggedIn = false;
|
||||||
|
this.onLogout()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onLogout(): Promise<void> {
|
||||||
|
const interfaceManager = this.conquerInterfaceManager
|
||||||
|
const loginUI = new LoginUI(this)
|
||||||
|
for (const callback of this.callbacks.logout) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
loginUI.on('close', () => {
|
||||||
|
interfaceManager.remove(loginUI);
|
||||||
|
})
|
||||||
|
interfaceManager.push(loginUI)
|
||||||
|
}
|
||||||
|
|
||||||
|
private callbacks: Record<string, Array<ConquerLoginEventCallback>> = {}
|
||||||
|
public async on(name: string, callback: ConquerLoginEventCallback) {
|
||||||
|
if (this.callbacks[name] === undefined) {
|
||||||
|
this.callbacks[name] = []
|
||||||
|
}
|
||||||
|
this.callbacks[name].push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onLoginSuccess(): Promise<void> {
|
||||||
|
this.cachedIsLoggedIn = true
|
||||||
|
for (const callback of this.callbacks.login) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onLoginRequested(loginUI: LoginUI, username: string, password: string): Promise<void> {
|
||||||
|
const urlUser = new URL('/conquer/user/login', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
let responseJson
|
||||||
|
let status
|
||||||
|
try {
|
||||||
|
const response = await fetch(urlUser, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
responseJson = await response.json()
|
||||||
|
status = response.status
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e)
|
||||||
|
loginUI.addNewLoginError('El servidor ha enviado datos inesperados.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (status !== 200) {
|
||||||
|
loginUI.addNewLoginError(responseJson.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loginUI.unsetLoginAndRegisterErrors()
|
||||||
|
const isLogged = await this.isLogged()
|
||||||
|
if (isLogged) {
|
||||||
|
this.onLoginSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async isLogged(): Promise<boolean> {
|
||||||
|
const urlUser = new URL('/conquer/user', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
let status
|
||||||
|
try {
|
||||||
|
const response = await fetch(urlUser)
|
||||||
|
status = response.status
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return status === 200
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
163
js-src/conquer/map-node.ts
Normal file
163
js-src/conquer/map-node.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||||
|
import Style from 'ol/style/Style'
|
||||||
|
import Feature from 'ol/Feature'
|
||||||
|
import CircleStyle from 'ol/style/Circle'
|
||||||
|
import Point from 'ol/geom/Point'
|
||||||
|
import Fill from 'ol/style/Fill'
|
||||||
|
import Stroke from 'ol/style/Stroke'
|
||||||
|
import InterfaceManager from '@burguillosinfo/conquer/interface-manager'
|
||||||
|
import NodeView from '@burguillosinfo/conquer/interface/node-view'
|
||||||
|
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||||
|
import SelectTeamUI from '@burguillosinfo/conquer/interface/select-team';
|
||||||
|
import ConquerTeam from '@burguillosinfo/conquer/team';
|
||||||
|
|
||||||
|
@JsonObject()
|
||||||
|
export default class MapNode {
|
||||||
|
private feature: Feature | null = null;
|
||||||
|
private callbacks: Record<string, Array<() => void>> = {}
|
||||||
|
private cachedTeam: ConquerTeam | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@JsonProperty() private uuid: string,
|
||||||
|
@JsonProperty() private coordinate_1: number,
|
||||||
|
@JsonProperty() private coordinate_2: number,
|
||||||
|
@JsonProperty() private type: string,
|
||||||
|
@JsonProperty() private name: string,
|
||||||
|
@JsonProperty() private description: string,
|
||||||
|
@JsonProperty() private kind: string,
|
||||||
|
@JsonProperty() private is_near: boolean,
|
||||||
|
@JsonProperty() private team: string,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTeam(): Promise<ConquerTeam | null> {
|
||||||
|
if (this.cachedTeam === null) {
|
||||||
|
if (this.team === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.cachedTeam = await ConquerTeam.getTeam(this.team);
|
||||||
|
}
|
||||||
|
return this.cachedTeam;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async fetch(): Promise<MapNode> {
|
||||||
|
const urlNode = new URL('/conquer/node/' + this.uuid, window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
const response = await fetch(urlNode);
|
||||||
|
let responseBody;
|
||||||
|
const errorThrow = new Error('Unable to fetch node updated.');
|
||||||
|
try {
|
||||||
|
responseBody = await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parseando json: ' + responseBody);
|
||||||
|
console.error(error);
|
||||||
|
throw errorThrow;
|
||||||
|
}
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.error(responseBody.error);
|
||||||
|
throw errorThrow;
|
||||||
|
}
|
||||||
|
const node = JsonSerializer.deserialize(responseBody, MapNode);
|
||||||
|
if (!(node instanceof MapNode)) {
|
||||||
|
console.error('Unexpected JSON value for MapNode.');
|
||||||
|
throw errorThrow;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public click(interfaceManager: InterfaceManager): void {
|
||||||
|
const viewNodeInterface = new NodeView(this);
|
||||||
|
viewNodeInterface.on('close', () => {
|
||||||
|
interfaceManager.remove(viewNodeInterface);
|
||||||
|
});
|
||||||
|
viewNodeInterface.on('update-nodes', () => {
|
||||||
|
this.runCallbacks('update-nodes');
|
||||||
|
});
|
||||||
|
viewNodeInterface.on('open-select-team', () => {
|
||||||
|
this.openSelectTeam(interfaceManager);
|
||||||
|
});
|
||||||
|
interfaceManager.push(viewNodeInterface);
|
||||||
|
this.runCallbacks('click');
|
||||||
|
}
|
||||||
|
|
||||||
|
public openSelectTeam(interfaceManager: InterfaceManager): void {
|
||||||
|
const selectTeamUI = new SelectTeamUI(this);
|
||||||
|
selectTeamUI.on('update-nodes', () => {
|
||||||
|
this.runCallbacks('update-nodes');
|
||||||
|
});
|
||||||
|
selectTeamUI.on('close', () => {
|
||||||
|
interfaceManager.remove(selectTeamUI);
|
||||||
|
});
|
||||||
|
interfaceManager.push(selectTeamUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
public on(eventName: string, callback: () => void): void {
|
||||||
|
if (this.callbacks[eventName] === undefined) {
|
||||||
|
this.callbacks[eventName] = []
|
||||||
|
}
|
||||||
|
this.callbacks[eventName].push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected runCallbacks(eventName: string) {
|
||||||
|
const callbacks = this.callbacks[eventName];
|
||||||
|
if (callbacks === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const callback of callbacks) {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getType(): string {
|
||||||
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public isNear(): boolean {
|
||||||
|
return this.is_near;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName(): string {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDescription(): string {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getId(): string {
|
||||||
|
return 'node-' + this.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUUID(): string {
|
||||||
|
return this.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFeature(): Feature {
|
||||||
|
if (this.feature === null) {
|
||||||
|
this.feature = new Feature({
|
||||||
|
geometry: new Point([this.coordinate_1, this.coordinate_2]),
|
||||||
|
type: 'node-' + this.uuid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return this.feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getStyle(): Promise<Style> {
|
||||||
|
const team = await this.getTeam();
|
||||||
|
let color = 'white';
|
||||||
|
if (team !== null) {
|
||||||
|
color = team.getColor();
|
||||||
|
}
|
||||||
|
return new Style({
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 14,
|
||||||
|
fill: new Fill({color: color}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: 'black',
|
||||||
|
width: 5,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
js-src/conquer/map-state.ts
Normal file
11
js-src/conquer/map-state.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
enum MapState {
|
||||||
|
NOTHING = 0x0,
|
||||||
|
NORMAL = 0x1,
|
||||||
|
FREE_MOVE = 0x2,
|
||||||
|
FREE_ROTATION = 0x4,
|
||||||
|
CREATE_NODE = 0x8,
|
||||||
|
SELECT_WHERE_TO_CREATE_NODE = 0x10,
|
||||||
|
FILLING_FORM_CREATE_NODE = 0x20,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MapState
|
6
js-src/conquer/serializer.ts
Normal file
6
js-src/conquer/serializer.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { JsonSerializer, throwError } from 'typescript-json-serializer';
|
||||||
|
|
||||||
|
export default new JsonSerializer({
|
||||||
|
errorCallback: throwError,
|
||||||
|
additionalPropertiesPolicy: 'disallow',
|
||||||
|
});
|
25
js-src/conquer/specie.ts
Normal file
25
js-src/conquer/specie.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||||
|
|
||||||
|
@JsonObject()
|
||||||
|
export default class Specie {
|
||||||
|
@JsonProperty()
|
||||||
|
private id: string;
|
||||||
|
|
||||||
|
@JsonProperty()
|
||||||
|
private name: string;
|
||||||
|
|
||||||
|
@JsonProperty()
|
||||||
|
private image: string;
|
||||||
|
|
||||||
|
public getId(): string {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName(): string {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getImage(): string {
|
||||||
|
return this.image;
|
||||||
|
}
|
||||||
|
}
|
117
js-src/conquer/team.ts
Normal file
117
js-src/conquer/team.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||||
|
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||||
|
import Conquer from '@burguillosinfo/conquer'
|
||||||
|
|
||||||
|
@JsonObject()
|
||||||
|
export default class ConquerTeam {
|
||||||
|
@JsonProperty()
|
||||||
|
private kind: string;
|
||||||
|
@JsonProperty()
|
||||||
|
private uuid: string;
|
||||||
|
@JsonProperty()
|
||||||
|
private name: string;
|
||||||
|
@JsonProperty()
|
||||||
|
private description: string;
|
||||||
|
@JsonProperty()
|
||||||
|
private points: number;
|
||||||
|
@JsonProperty()
|
||||||
|
private color: string;
|
||||||
|
|
||||||
|
public getUUID(): string {
|
||||||
|
return this.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getName(): string {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDescription(): string {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getColor(): string {
|
||||||
|
return this.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(uuid: string, name: string, description: string, points: number, color: string) {
|
||||||
|
this.kind = 'ConquerTeam';
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.name = name;
|
||||||
|
this.description = description;
|
||||||
|
this.points = points;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getTeams(): Promise<ConquerTeam[]> {
|
||||||
|
const urlTeam = new URL('/conquer/teams', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
try {
|
||||||
|
const response = await fetch(urlTeam)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error('Invalid response fetching teams.')
|
||||||
|
}
|
||||||
|
const teamData = await response.json()
|
||||||
|
const teams = JsonSerializer.deserialize(teamData, ConquerTeam);
|
||||||
|
if (teams === undefined || teams === null) {
|
||||||
|
Conquer.fail('Teams cannot be null, server error.');
|
||||||
|
}
|
||||||
|
if (!(teams instanceof Array)) {
|
||||||
|
throw new Error('Unable to parse team.');
|
||||||
|
}
|
||||||
|
const teamsSanitized: ConquerTeam[] = [];
|
||||||
|
for (const team of teams) {
|
||||||
|
if (!(team instanceof ConquerTeam)) {
|
||||||
|
console.error('Received null team from server, fix this error.');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
teamsSanitized.push(team);
|
||||||
|
}
|
||||||
|
return teamsSanitized;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw new Error('Unable to fetch Teams.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static async getTeam(uuid: string): Promise<ConquerTeam> {
|
||||||
|
const urlTeam = new URL('/conquer/team/' + uuid, window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
try {
|
||||||
|
const response = await fetch(urlTeam)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error('Invalid response fetching team.')
|
||||||
|
}
|
||||||
|
const teamData = await response.json()
|
||||||
|
let team = JsonSerializer.deserialize(teamData, ConquerTeam);
|
||||||
|
if (team === undefined) {
|
||||||
|
team = null;
|
||||||
|
}
|
||||||
|
if (!(team instanceof ConquerTeam)) {
|
||||||
|
throw new Error('Unable to parse team.');
|
||||||
|
}
|
||||||
|
return team;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw new Error('Unable to fetch Team.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getSelfTeam(): Promise<ConquerTeam | null> {
|
||||||
|
const urlTeam = new URL('/conquer/user/team', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
try {
|
||||||
|
const response = await fetch(urlTeam)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error('Invalid response fetching team.')
|
||||||
|
}
|
||||||
|
const teamData = await response.json()
|
||||||
|
let team = JsonSerializer.deserialize(teamData, ConquerTeam);
|
||||||
|
if (team === undefined) {
|
||||||
|
team = null;
|
||||||
|
}
|
||||||
|
if (team !== null && !(team instanceof ConquerTeam)) {
|
||||||
|
throw new Error('Unable to parse team.');
|
||||||
|
}
|
||||||
|
return team;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw new Error('Unable to fetch Team.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
js-src/conquer/user-current-enemy.ts
Normal file
66
js-src/conquer/user-current-enemy.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import Conquer from '@burguillosinfo/conquer';
|
||||||
|
|
||||||
|
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||||
|
import Specie from '@burguillosinfo/conquer/specie';
|
||||||
|
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||||
|
|
||||||
|
@JsonObject()
|
||||||
|
export default class ConquerUserCurrentEnemy {
|
||||||
|
@JsonProperty()
|
||||||
|
private uuid: string;
|
||||||
|
|
||||||
|
@JsonProperty()
|
||||||
|
private species: Specie;
|
||||||
|
|
||||||
|
@JsonProperty()
|
||||||
|
private level: number;
|
||||||
|
|
||||||
|
@JsonProperty()
|
||||||
|
private max_health: number;
|
||||||
|
|
||||||
|
public getUUID(): string {
|
||||||
|
return this.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSpecies(): Specie {
|
||||||
|
return this.species;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLevel(): number {
|
||||||
|
return this.level;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getMaxHealth(): number {
|
||||||
|
return this.max_health;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getGlobalEnemies(): Promise<ConquerUserCurrentEnemy[] | null> {
|
||||||
|
const urlEnemies = new URL('/conquer/user/enemies/global', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port);
|
||||||
|
const response = await fetch(urlEnemies);
|
||||||
|
let responseBody;
|
||||||
|
try {
|
||||||
|
responseBody = await response.json();
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.error(responseBody.error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const enemiesRaw = JsonSerializer.deserialize(responseBody, ConquerUserCurrentEnemy);
|
||||||
|
const enemiesReturnArray: ConquerUserCurrentEnemy[] = [];
|
||||||
|
if (!(enemiesRaw instanceof Array)) {
|
||||||
|
console.error('Incorrect type retrieved from ' + urlEnemies);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (const enemy of enemiesRaw) {
|
||||||
|
if (!(enemy instanceof ConquerUserCurrentEnemy)) {
|
||||||
|
console.error('Incorrect type for enemy, maybe null or undef.', enemy);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
enemiesReturnArray.push(enemy);
|
||||||
|
}
|
||||||
|
return enemiesReturnArray;
|
||||||
|
} catch(error) {
|
||||||
|
console.error(error, 'Invalid response from server seeking for possible battles.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
js-src/conquer/user.ts
Normal file
78
js-src/conquer/user.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import JsonSerializer from '@burguillosinfo/conquer/serializer';
|
||||||
|
import { JsonObject, JsonProperty } from 'typescript-json-serializer';
|
||||||
|
import ConquerTeam from '@burguillosinfo/conquer/team';
|
||||||
|
|
||||||
|
export interface UserData {
|
||||||
|
is_admin: number
|
||||||
|
kind: string
|
||||||
|
last_activity?: string
|
||||||
|
registration_date?: string
|
||||||
|
username: string
|
||||||
|
uuid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonObject()
|
||||||
|
export default class ConquerUser {
|
||||||
|
@JsonProperty()
|
||||||
|
private is_admin: boolean;
|
||||||
|
@JsonProperty()
|
||||||
|
private kind: string;
|
||||||
|
@JsonProperty()
|
||||||
|
private last_activity: string | null;
|
||||||
|
@JsonProperty()
|
||||||
|
private registration_date: string | null;
|
||||||
|
@JsonProperty()
|
||||||
|
private username: string;
|
||||||
|
@JsonProperty()
|
||||||
|
private uuid: string;
|
||||||
|
@JsonProperty()
|
||||||
|
private team: string | null;
|
||||||
|
private cachedTeam: ConquerTeam | null = null;
|
||||||
|
|
||||||
|
constructor(kind: string, uuid: string, username: string, is_admin = false, registration_date: string | null = null, last_activity: string | null = null) {
|
||||||
|
this.kind = kind;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.username = username;
|
||||||
|
this.is_admin = is_admin;
|
||||||
|
this.registration_date = registration_date;
|
||||||
|
this.last_activity = last_activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTeam(): Promise<ConquerTeam | null> {
|
||||||
|
if (this.cachedTeam === null) {
|
||||||
|
if (this.team === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.cachedTeam = await ConquerTeam.getTeam(this.team);
|
||||||
|
}
|
||||||
|
return this.cachedTeam;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getSelfUser(): Promise<ConquerUser | null> {
|
||||||
|
const urlUser = new URL('/conquer/user', window.location.protocol + '//' + window.location.hostname + ':' + window.location.port)
|
||||||
|
try {
|
||||||
|
const response = await fetch(urlUser)
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error('Invalid response fetching user.')
|
||||||
|
}
|
||||||
|
const userData = await response.json()
|
||||||
|
const user = JsonSerializer.deserialize(userData, ConquerUser);
|
||||||
|
if (!(user instanceof ConquerUser)) {
|
||||||
|
throw new Error('Unable to parse user.');
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public getUsername(): string {
|
||||||
|
if (this.username === null) {
|
||||||
|
throw new Error('User username cannot be null.')
|
||||||
|
}
|
||||||
|
return this.username
|
||||||
|
}
|
||||||
|
public isAdmin(): boolean {
|
||||||
|
return this.is_admin
|
||||||
|
}
|
||||||
|
}
|
30
js-src/conquer/websocket.ts
Normal file
30
js-src/conquer/websocket.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export default class ConquerWebSocket {
|
||||||
|
private webSocket: WebSocket | null = null
|
||||||
|
private socketReady = false
|
||||||
|
|
||||||
|
private getWebSocket(): WebSocket {
|
||||||
|
if (this.webSocket !== null && this.socketReady) {
|
||||||
|
return this.webSocket
|
||||||
|
}
|
||||||
|
this.webSocket = new WebSocket(`wss://${window.location.hostname}:${window.location.port}/conquer/websocket`)
|
||||||
|
this.webSocket.addEventListener('close', (event) => {
|
||||||
|
this.onSocketClose(event)
|
||||||
|
})
|
||||||
|
this.webSocket.addEventListener('error', (event) => {
|
||||||
|
this.onSocketClose(event)
|
||||||
|
})
|
||||||
|
this.webSocket.addEventListener('open', (event) => {
|
||||||
|
this.onSocketOpen(event)
|
||||||
|
})
|
||||||
|
return this.webSocket
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSocketOpen(event: Event) {
|
||||||
|
this.socketReady = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSocketClose(event: Event) {
|
||||||
|
this.socketReady = false
|
||||||
|
console.error(event)
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,23 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
import Tablesort from 'tablesort';
|
import Tablesort from 'tablesort';
|
||||||
|
import Conquer from '@burguillosinfo/conquer/index';
|
||||||
import CarouselAd from '@burguillosinfo/carousel-ad'
|
import CarouselAd from '@burguillosinfo/carousel-ad'
|
||||||
window.Tablesort = require('tablesort');
|
window.Tablesort = require('tablesort');
|
||||||
require('tablesort/src/sorts/tablesort.number');
|
require('tablesort/src/sorts/tablesort.number');
|
||||||
|
|
||||||
let fakeSearchInput
|
let fakeSearchInput
|
||||||
let searchMobile
|
let searchMobile
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
onDomContentLoaded();
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
function onDomContentLoaded() {
|
||||||
|
const path = window.location.pathname
|
||||||
|
if (path.match(/^(?:\/)?conquer(?:$|\/)/)) {
|
||||||
|
Conquer.start();
|
||||||
|
return
|
||||||
|
}
|
||||||
const menu_expand = document.querySelector('a.menu-expand');
|
const menu_expand = document.querySelector('a.menu-expand');
|
||||||
const mobile_foldable = document.querySelector('nav.mobile-foldable');
|
const mobile_foldable = document.querySelector('nav.mobile-foldable');
|
||||||
const transparentFullscreenHide = document.querySelector('div.transparent-fullscreen-hide');
|
const transparentFullscreenHide = document.querySelector('div.transparent-fullscreen-hide');
|
||||||
@ -64,8 +75,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
fakeSearchInput = searchMobile.querySelector('input')
|
fakeSearchInput = searchMobile.querySelector('input')
|
||||||
addListenersSearch()
|
addListenersSearch()
|
||||||
}
|
}
|
||||||
}, false);
|
}
|
||||||
|
|
||||||
function fillFarmaciaGuardia() {
|
function fillFarmaciaGuardia() {
|
||||||
const farmaciaName = document.querySelector('#farmacia-name');
|
const farmaciaName = document.querySelector('#farmacia-name');
|
||||||
const farmaciaAddress = document.querySelector('#farmacia-address');
|
const farmaciaAddress = document.querySelector('#farmacia-address');
|
||||||
|
@ -7,6 +7,7 @@ use Mojo::Base 'Mojolicious', -signatures;
|
|||||||
# This method will run once at server start
|
# This method will run once at server start
|
||||||
sub startup ($self) {
|
sub startup ($self) {
|
||||||
my $metrics = BurguillosInfo::Controller::Metrics->new;
|
my $metrics = BurguillosInfo::Controller::Metrics->new;
|
||||||
|
$self->sessions->default_expiration(0);
|
||||||
$self->hook(
|
$self->hook(
|
||||||
around_dispatch => sub {
|
around_dispatch => sub {
|
||||||
my $next = shift;
|
my $next = shift;
|
||||||
@ -19,26 +20,66 @@ sub startup ($self) {
|
|||||||
);
|
);
|
||||||
push @{ $self->commands->namespaces }, 'BurguillosInfo::Command';
|
push @{ $self->commands->namespaces }, 'BurguillosInfo::Command';
|
||||||
$self->hook(
|
$self->hook(
|
||||||
before_render => sub($c, $args) {
|
before_render => sub ( $c, $args ) {
|
||||||
my $current_route = $c->url_for;
|
my $current_route = $c->url_for;
|
||||||
$c->stash(current_route => $current_route);
|
$c->stash( current_route => $current_route );
|
||||||
my $is_android = $c->req->headers->user_agent =~ /android/i;
|
my $is_android = $c->req->headers->user_agent =~ /android/i;
|
||||||
$c->stash(is_android => $is_android);
|
$c->stash( is_android => $is_android );
|
||||||
my $onion_base_url = $self->config->{onion_base_url};
|
my $onion_base_url = $self->config->{onion_base_url};
|
||||||
my $base_url = $self->config->{base_url};
|
my $base_url = $self->config->{base_url};
|
||||||
if (!defined $onion_base_url) {
|
if ( !defined $onion_base_url ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$current_route =~ s/^$base_url//;
|
$current_route =~ s/^$base_url//;
|
||||||
$c->res->headers->header('Onion-Location' => $onion_base_url.$current_route);
|
$c->res->headers->header(
|
||||||
|
'Onion-Location' => $onion_base_url . $current_route );
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
my $config = $self->plugin('JSONConfig');
|
my $config = $self->plugin('JSONConfig');
|
||||||
$self->config(
|
$self->config(
|
||||||
hypnotoad => { proxy => 1, listen => [$self->config('listen') // 'http://localhost:3000'] } );
|
hypnotoad => {
|
||||||
|
proxy => 1,
|
||||||
|
listen => [ $self->config('listen') // 'http://localhost:3000' ]
|
||||||
|
}
|
||||||
|
);
|
||||||
$self->config( css_version => int( rand(10000) ) );
|
$self->config( css_version => int( rand(10000) ) );
|
||||||
$self->secrets( $self->config->{secrets} );
|
$self->secrets( $self->config->{secrets} );
|
||||||
|
|
||||||
|
$self->helper(
|
||||||
|
current_user => sub ($c) {
|
||||||
|
use BurguillosInfo::Schema;
|
||||||
|
$self->session(expiration => 0);
|
||||||
|
my $user_uuid = $c->session->{conquer}{user};
|
||||||
|
if ( !defined $user_uuid ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $user_resultset =
|
||||||
|
BurguillosInfo::Schema->Schema->resultset('ConquerUser');
|
||||||
|
my @user_candidates =
|
||||||
|
$user_resultset->search( { uuid => $user_uuid } );
|
||||||
|
my $user = $user_candidates[0];
|
||||||
|
|
||||||
|
# Just to make clear what happens if there is no user we return.
|
||||||
|
if ( !defined $user ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$self->helper(
|
||||||
|
set_current_user => sub ( $c, $user ) {
|
||||||
|
$self->session(expiration => 0);
|
||||||
|
if ( !defined $user
|
||||||
|
|| !$user->can('uuid')
|
||||||
|
|| !$user->can('get_from_storage') )
|
||||||
|
{
|
||||||
|
die "$user is not a valid user for it's usage in a session.";
|
||||||
|
}
|
||||||
|
$user = $user->get_from_storage;
|
||||||
|
$c->session->{conquer}{user} = $user->uuid;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
# Router
|
# Router
|
||||||
my $r = $self->routes;
|
my $r = $self->routes;
|
||||||
|
|
||||||
@ -48,12 +89,29 @@ sub startup ($self) {
|
|||||||
$r->get('/sitemap.xml')->to('Sitemap#sitemap');
|
$r->get('/sitemap.xml')->to('Sitemap#sitemap');
|
||||||
$r->get('/robots.txt')->to('Robots#robots');
|
$r->get('/robots.txt')->to('Robots#robots');
|
||||||
|
|
||||||
# $r->get('/:post')->to('Page#post');
|
|
||||||
$r->get('/stats')->to('Metrics#stats');
|
$r->get('/stats')->to('Metrics#stats');
|
||||||
|
$r->get('/conquer')->to('Conquer#index');
|
||||||
|
$r->put('/conquer/user')->to('UserConquer#create');
|
||||||
|
$r->get('/conquer/user/team')->to('UserConquer#getSelfTeam');
|
||||||
|
$r->post('/conquer/user/team')->to('UserConquer#setTeamForUser');
|
||||||
|
$r->post('/conquer/user/coordinates')->to('UserConquer#setCoordinates');
|
||||||
|
$r->get('/conquer/team/<uuid>')->to('ConquerTeam#get');
|
||||||
|
$r->put('/conquer/team')->to('ConquerTeam#put');
|
||||||
|
$r->get('/conquer/teams')->to('ConquerTeam#getAll');
|
||||||
|
$r->put('/conquer/node')->to('ConquerNode#create');
|
||||||
|
$r->get('/conquer/node/near')->to('ConquerNode#nearbyNodes');
|
||||||
|
$r->get('/conquer/user/enemies/global')->to('ConquerUserCurrentEnemy#listEnemiesGlobal');
|
||||||
|
$r->post('/conquer/user/enemies/fight')->to('ConquerUserCurrentEnemy#fightEnemy');
|
||||||
|
$r->get('/conquer/node/<uuid>')->to('ConquerNode#get');
|
||||||
|
$r->post('/conquer/node/<uuid>/try-conquer')->to('ConquerNode#tryConquer');
|
||||||
|
$r->get('/conquer/user')->to('UserConquer#get_self');
|
||||||
|
$r->post('/conquer/user/login')->to('UserConquer#login');
|
||||||
|
$r->get('/conquer/tile/<zoom>/<x>/<y>.png')->to('ConquerTile#tile');
|
||||||
$r->get('/search.json')->to('Search#search');
|
$r->get('/search.json')->to('Search#search');
|
||||||
$r->get('/farmacia-guardia.json')->to('FarmaciaGuardia#current');
|
$r->get('/farmacia-guardia.json')->to('FarmaciaGuardia#current');
|
||||||
$r->get('/<:category>.rss')->to('Page#category_rss');
|
$r->get('/<:category>.rss')->to('Page#category_rss');
|
||||||
$r->get('/:category_slug/atributo/<:attribute_slug>-preview.png')->to('Attribute#get_attribute_preview');
|
$r->get('/:category_slug/atributo/<:attribute_slug>-preview.png')
|
||||||
|
->to('Attribute#get_attribute_preview');
|
||||||
$r->get('/:category_slug/atributo/:attribute_slug')->to('Attribute#get');
|
$r->get('/:category_slug/atributo/:attribute_slug')->to('Attribute#get');
|
||||||
$r->get('/<:category>-preview.png')->to('Page#get_category_preview');
|
$r->get('/<:category>-preview.png')->to('Page#get_category_preview');
|
||||||
$r->get('/:category')->to('Page#category');
|
$r->get('/:category')->to('Page#category');
|
||||||
|
15
lib/BurguillosInfo/Controller/Conquer.pm
Normal file
15
lib/BurguillosInfo/Controller/Conquer.pm
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package BurguillosInfo::Controller::Conquer;
|
||||||
|
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||||
|
|
||||||
|
sub index($self) {
|
||||||
|
$self->render;
|
||||||
|
}
|
||||||
|
1;
|
185
lib/BurguillosInfo/Controller/ConquerNode.pm
Normal file
185
lib/BurguillosInfo/Controller/ConquerNode.pm
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
package BurguillosInfo::Controller::ConquerNode;
|
||||||
|
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||||
|
use UUID::URandom qw/create_uuid_string/;
|
||||||
|
use BurguillosInfo::Schema;
|
||||||
|
|
||||||
|
sub get($self) {
|
||||||
|
my $uuid = $self->param('uuid');
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if (!defined $uuid || !$uuid) {
|
||||||
|
return $self->render(status => 400, json => {
|
||||||
|
error => 'UUID de nodo invalido.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
my $schema = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||||
|
my @nodes = $schema->search({uuid => $uuid});
|
||||||
|
if (!scalar @nodes) {
|
||||||
|
return $self->render(status => 404, json => {
|
||||||
|
error => 'Nodo no encontrado',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
my $node = $nodes[0];
|
||||||
|
if (defined $user) {
|
||||||
|
return $self->render(json => $node->serialize($user));
|
||||||
|
}
|
||||||
|
return $self->render(json => $node->serialize());
|
||||||
|
}
|
||||||
|
|
||||||
|
sub tryConquer($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
my $schema = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||||
|
if (!defined $user) {
|
||||||
|
return $self->render(status => 401, json => {
|
||||||
|
error => 'You must be logged to conquer a node.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!defined $user->team) {
|
||||||
|
return $self->render(status => 400, json => {
|
||||||
|
error => 'You must belong to a team to conquer a node.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
my $uuid = $self->param('uuid');
|
||||||
|
my ($node) = $schema->search({uuid => $uuid});
|
||||||
|
if (!defined $node) {
|
||||||
|
return $self->render(status => 404, json => {
|
||||||
|
error => 'No existe ese nodo.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$node->team($user->team);
|
||||||
|
$node->update;
|
||||||
|
$self->render(json => {
|
||||||
|
ok => $JSON::true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub create ($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if ( !defined $user ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 401,
|
||||||
|
json => {
|
||||||
|
error => 'No estás autenticado.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !$user->is_admin ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 403,
|
||||||
|
json => {
|
||||||
|
error => 'No tienes permiso para hacer eso.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $input = $self->_expectJson;
|
||||||
|
if ( !defined $input ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $name = $input->{name};
|
||||||
|
my $coordinates = $input->{coordinates};
|
||||||
|
my $type = $input->{type};
|
||||||
|
my $description = $input->{description};
|
||||||
|
if ( ref $coordinates ne 'ARRAY' || scalar $coordinates->@* != 2 ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 400,
|
||||||
|
json => {
|
||||||
|
error => 'Formato erroneo de coordenadas.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my ($coordinate_1, $coordinate_2) = $coordinates->@*;
|
||||||
|
if ( !defined $name || length $name < 5 ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 400,
|
||||||
|
json => {
|
||||||
|
error =>
|
||||||
|
'Número incorrecto de carácteres en el nombre del nodo.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !defined $description ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 400,
|
||||||
|
json => {
|
||||||
|
error => 'La descripción puede estar vacía, '
|
||||||
|
. 'pero debe existir, si ves este error '
|
||||||
|
. 'desde la aplicación es un error de programación.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !defined $type ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 400,
|
||||||
|
json => {
|
||||||
|
error => 'Los nodos deben tener un tipo.'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( $type ne 'normal' ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 400,
|
||||||
|
json => {
|
||||||
|
error => 'Tipo de nodo no soportado.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $uuid_node = create_uuid_string();
|
||||||
|
my $node;
|
||||||
|
eval {
|
||||||
|
$node = BurguillosInfo::Schema->Schema->resultset('ConquerNode')->new(
|
||||||
|
{
|
||||||
|
uuid => $uuid_node,
|
||||||
|
description => $description,
|
||||||
|
name => $name,
|
||||||
|
type => $type,
|
||||||
|
geometry => \['ST_MakePoint(?, ?)', $coordinate_1, $coordinate_2],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$node->insert;
|
||||||
|
$node = $node->get_from_storage;
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
warn $@;
|
||||||
|
return $self->render(
|
||||||
|
status => 500,
|
||||||
|
json => {
|
||||||
|
error => 'El servidor no pudo almacenar el nodo, reporta este error.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $self->render(
|
||||||
|
status => 200,
|
||||||
|
json => $node->serialize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub nearbyNodes($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if (!defined $user) {
|
||||||
|
return $self->render(status => 401, json => {
|
||||||
|
error => 'No estás loggeado.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
my @nodes = BurguillosInfo::Schema->Schema->resultset('ConquerNode')->search({});
|
||||||
|
@nodes = map { $_->serialize($user) } @nodes;
|
||||||
|
return $self->render(json => \@nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _expectJson ($self) {
|
||||||
|
my $input;
|
||||||
|
eval { $input = $self->req->json; };
|
||||||
|
if ($@) {
|
||||||
|
say STDERR $@;
|
||||||
|
$self->_renderError( 400, 'Se esperaba JSON.' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
1;
|
180
lib/BurguillosInfo/Controller/ConquerTeam.pm
Normal file
180
lib/BurguillosInfo/Controller/ConquerTeam.pm
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package BurguillosInfo::Controller::ConquerTeam;
|
||||||
|
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||||
|
|
||||||
|
use UUID::URandom qw/create_uuid_string/;
|
||||||
|
use JSON;
|
||||||
|
|
||||||
|
use BurguillosInfo::Schema;
|
||||||
|
|
||||||
|
sub getAll ($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if ( !defined $user ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 401,
|
||||||
|
json => {
|
||||||
|
error => 'You must be logged to fetch teams.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $uuid = $self->param('uuid');
|
||||||
|
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
|
||||||
|
my @teams = $resultset->search({});
|
||||||
|
return $self->render( json => [ map { $_->serialize } @teams ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get ($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if ( !defined $user ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 401,
|
||||||
|
json => {
|
||||||
|
error => 'You must be logged to fetch a team.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $uuid = $self->param('uuid');
|
||||||
|
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
|
||||||
|
my @teams = $resultset->search(
|
||||||
|
{
|
||||||
|
'uuid' => $uuid,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if ( scalar @teams <= 0 ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 404,
|
||||||
|
json => {
|
||||||
|
error => 'This team does not exist.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $team = $teams[0];
|
||||||
|
return $self->render( json => $team->serialize );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub getSelfTeam ($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if ( !defined $user ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 401,
|
||||||
|
json => {
|
||||||
|
error => 'You must be logged to fetch your Team.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
|
||||||
|
my @teams = $resultset->search(
|
||||||
|
{
|
||||||
|
'players.uuid' => $user->uuid
|
||||||
|
},
|
||||||
|
{
|
||||||
|
join => 'players',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if ( scalar @teams <= 0 ) {
|
||||||
|
return $self->render( json => undef );
|
||||||
|
}
|
||||||
|
my $team = $teams[0];
|
||||||
|
return $self->render( json => $team );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _expectJson ($self) {
|
||||||
|
my $input;
|
||||||
|
eval { $input = $self->req->json; };
|
||||||
|
if ($@) {
|
||||||
|
say STDERR $@;
|
||||||
|
$self->_renderError( 400, 'Se esperaba JSON.' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub put ($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if ( !defined $user ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 401,
|
||||||
|
json => {
|
||||||
|
error => 'No estás autenticado.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !$user->is_admin ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 403,
|
||||||
|
json => {
|
||||||
|
error => 'No tienes permiso para hacer eso.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $input = $self->_expectJson;
|
||||||
|
if ( !defined $input ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $name = $input->{name};
|
||||||
|
my $description = $input->{description};
|
||||||
|
my $color = $input->{color};
|
||||||
|
if ( !defined $name || length $name < 5 ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 400,
|
||||||
|
json => {
|
||||||
|
error =>
|
||||||
|
'Número incorrecto de carácteres en el nombre del equipo..',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !defined $description ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 400,
|
||||||
|
json => {
|
||||||
|
error => 'La descripción puede estar vacía, '
|
||||||
|
. 'pero debe existir, si ves este error '
|
||||||
|
. 'desde la aplicación es un error de programación.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $color_regex_char = qr/[0-9a-fA-F]/;
|
||||||
|
if ( !defined $color || $color !~ /^#(?:${color_regex_char}{6}|${color_regex_char}{3})$/ ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 400,
|
||||||
|
json => {
|
||||||
|
error => 'Formato de color invalido',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $uuid_team = create_uuid_string();
|
||||||
|
my $team;
|
||||||
|
eval {
|
||||||
|
$team = BurguillosInfo::Schema->Schema->resultset('ConquerTeam')->new(
|
||||||
|
{
|
||||||
|
uuid => $uuid_team,
|
||||||
|
description => $description,
|
||||||
|
name => $name,
|
||||||
|
color => $color,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$team->insert;
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
warn $@;
|
||||||
|
return $self->render(
|
||||||
|
status => 500,
|
||||||
|
json => {
|
||||||
|
error =>
|
||||||
|
'El servidor no pudo almacenar el equipo, reporta este error.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $self->render(
|
||||||
|
status => 200,
|
||||||
|
json => $team->serialize,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
1;
|
70
lib/BurguillosInfo/Controller/ConquerTile.pm
Normal file
70
lib/BurguillosInfo/Controller/ConquerTile.pm
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package BurguillosInfo::Controller::ConquerTile;
|
||||||
|
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||||
|
use Path::Tiny;
|
||||||
|
use Mojo::UserAgent;
|
||||||
|
use DateTime::Format::HTTP;
|
||||||
|
use DateTime;
|
||||||
|
|
||||||
|
my $cache_files_dir =
|
||||||
|
path(__FILE__)->parent->parent->parent->parent->child('cache/tiles/');
|
||||||
|
|
||||||
|
sub _cache_response ($self) {
|
||||||
|
my $tomorrow_same_hour_datetime = DateTime->now->add( days => 1 );
|
||||||
|
$self->res->headers->cache_control("max_age=@{[3600*24]}");
|
||||||
|
$self->res->headers->expires(
|
||||||
|
DateTime::Format::HTTP->format_datetime($tomorrow_same_hour_datetime) );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub tile ($self) {
|
||||||
|
my $zoom = $self->stash('zoom');
|
||||||
|
my $x = $self->stash('x');
|
||||||
|
my $y = $self->stash('y');
|
||||||
|
my $candidate_file = $cache_files_dir->child("$zoom-$x-$y.png");
|
||||||
|
if ( -f $candidate_file ) {
|
||||||
|
$self->_cache_response;
|
||||||
|
return $self->_render_png($candidate_file);
|
||||||
|
}
|
||||||
|
if ( !defined $self->current_user ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 401,
|
||||||
|
text => '¡¡No estás loggeado, no puedes cargar mapa nuevo.!!'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$self->_cache_response;
|
||||||
|
my $file_to_write = $candidate_file;
|
||||||
|
my $ua = Mojo::UserAgent->new;
|
||||||
|
my $png_tile =
|
||||||
|
$ua->get("https://tile.openstreetmap.org/$zoom/$x/$y.png")->result->body;
|
||||||
|
open my $fh, '|-', 'convert', '/dev/stdin', '-channel', 'RGB', '-negate',
|
||||||
|
$file_to_write;
|
||||||
|
print $fh $png_tile;
|
||||||
|
close $fh;
|
||||||
|
$self->_render_png($file_to_write);
|
||||||
|
$self->_delete_extra_files();
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _delete_extra_files ($self) {
|
||||||
|
my @files = $cache_files_dir->children;
|
||||||
|
if ( scalar @files < 20001 ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@files = sort { -M $a <=> -M $b } @files;
|
||||||
|
for ( my $i = 0 ; $i < ( scalar @files ) - 20000 ; $i++ ) {
|
||||||
|
system 'rm', '-v', $files[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _render_png ( $self, $file ) {
|
||||||
|
system 'touch', $file;
|
||||||
|
return $self->render( data => $file->slurp_raw, status => 200,
|
||||||
|
format => 'png' );
|
||||||
|
}
|
||||||
|
1;
|
103
lib/BurguillosInfo/Controller/ConquerUserCurrentEnemy.pm
Normal file
103
lib/BurguillosInfo/Controller/ConquerUserCurrentEnemy.pm
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package BurguillosInfo::Controller::ConquerUserCurrentEnemy;
|
||||||
|
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||||
|
|
||||||
|
use UUID::URandom qw/create_uuid_string/;
|
||||||
|
use JSON;
|
||||||
|
|
||||||
|
use BurguillosInfo::Schema;
|
||||||
|
use BurguillosInfo::Species;
|
||||||
|
|
||||||
|
sub listEnemiesGlobal ($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if ( !defined $user ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 401,
|
||||||
|
json => {
|
||||||
|
error => 'Debes estar autenticado.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
my $current_enemies = $self->_get_enemies($user);
|
||||||
|
if ( scalar @$current_enemies ) {
|
||||||
|
return $self->_return_enemies($current_enemies);
|
||||||
|
}
|
||||||
|
$self->_generate_enemies_global($user);
|
||||||
|
$current_enemies = $self->_get_enemies($user);
|
||||||
|
return $self->_return_enemies($current_enemies);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _return_enemies ( $self, $current_enemies ) {
|
||||||
|
return $self->render( json => [ map { $_->serialize } @$current_enemies ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _get_enemies ( $self, $user ) {
|
||||||
|
my $resultset_current_enemies =
|
||||||
|
BurguillosInfo::Schema->Schema->resultset('ConquerUserCurrentEnemy');
|
||||||
|
my @current_enemies = $resultset_current_enemies->search(
|
||||||
|
{
|
||||||
|
'user_object.uuid' => $user->uuid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
join => 'user_object',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return \@current_enemies;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _generate_enemies_global ( $self, $user ) {
|
||||||
|
my $minimum_number_enemies = 2;
|
||||||
|
my $maximum_number_enemies = 6;
|
||||||
|
my $number_enemies =
|
||||||
|
$self->_calculate_number_of_enemies( $minimum_number_enemies,
|
||||||
|
$maximum_number_enemies );
|
||||||
|
for ( my $i = 0 ; $i < $number_enemies ; $i++ ) {
|
||||||
|
$self->_generate_enemy_global($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _generate_enemy_global ( $self, $user ) {
|
||||||
|
my $resultset_current_enemies =
|
||||||
|
BurguillosInfo::Schema->Schema->resultset('ConquerUserCurrentEnemy');
|
||||||
|
my $uuid = create_uuid_string();
|
||||||
|
my $species = BurguillosInfo::Species->new;
|
||||||
|
my @species = @{ $species->list_can_be_global };
|
||||||
|
my $selected_species = $species[int( rand( scalar @species ) )];
|
||||||
|
my $enemy = $resultset_current_enemies->new(
|
||||||
|
{
|
||||||
|
uuid => $uuid,
|
||||||
|
species => $selected_species->id,
|
||||||
|
level => $self->_get_level_enemy($user),
|
||||||
|
user => $user->uuid,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$enemy->insert;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _get_level_enemy ( $self, $user ) {
|
||||||
|
my $max_enemy_level = int( $user->level / 2 ) + 1;
|
||||||
|
if ( $max_enemy_level < $user->level - 10 ) {
|
||||||
|
$max_enemy_level = $user->level - 10;
|
||||||
|
}
|
||||||
|
if ( $max_enemy_level < 3 ) {
|
||||||
|
$max_enemy_level = 3;
|
||||||
|
}
|
||||||
|
my $min_enemy_level = $max_enemy_level - 5;
|
||||||
|
if ( $min_enemy_level < 2 ) {
|
||||||
|
$min_enemy_level = 2;
|
||||||
|
}
|
||||||
|
return $min_enemy_level +
|
||||||
|
int( rand( $max_enemy_level - $min_enemy_level + 1 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _calculate_number_of_enemies ( $self, $min, $max ) {
|
||||||
|
return $min + int( rand( $max - $min + 1 ) );
|
||||||
|
}
|
||||||
|
1;
|
@ -89,8 +89,6 @@ sub submit_login {
|
|||||||
$self->render( text => 'Server error.', status => 500 );
|
$self->render( text => 'Server error.', status => 500 );
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
say $password;
|
|
||||||
say $bcrypted_pass;
|
|
||||||
if ( !bcrypt_check( $password, $bcrypted_pass ) ) {
|
if ( !bcrypt_check( $password, $bcrypted_pass ) ) {
|
||||||
$self->render( text => 'Wrong password', status => 401 );
|
$self->render( text => 'Wrong password', status => 401 );
|
||||||
return;
|
return;
|
||||||
|
235
lib/BurguillosInfo/Controller/UserConquer.pm
Normal file
235
lib/BurguillosInfo/Controller/UserConquer.pm
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
package BurguillosInfo::Controller::UserConquer;
|
||||||
|
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
use Mojo::Base 'Mojolicious::Controller', '-signatures';
|
||||||
|
|
||||||
|
use UUID::URandom qw/create_uuid_string/;
|
||||||
|
use Crypt::Bcrypt qw/bcrypt bcrypt_check/;
|
||||||
|
use Crypt::URandom qw/urandom/;
|
||||||
|
use JSON;
|
||||||
|
|
||||||
|
use BurguillosInfo::Schema;
|
||||||
|
|
||||||
|
my $username_minimum_chars = 3;
|
||||||
|
my $username_maximum_chars = 15;
|
||||||
|
my $password_minimum_chars = 8;
|
||||||
|
my $password_maximum_chars = 4096;
|
||||||
|
|
||||||
|
sub setTeamForUser($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if (!defined $user) {
|
||||||
|
return $self->_renderError(401, 'No estás loggeado.');
|
||||||
|
}
|
||||||
|
my $input = $self->_expectJson;
|
||||||
|
if (!defined $input) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $node_uuid = $input->{node};
|
||||||
|
my $team_uuid = $input->{team};
|
||||||
|
my $resultset_team = BurguillosInfo::Schema->Schema->resultset('ConquerTeam');
|
||||||
|
my $resultset_node = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||||
|
my @teams = $resultset_team->search({uuid => $team_uuid});
|
||||||
|
my @nodes = $resultset_node->search({uuid => $node_uuid});
|
||||||
|
if (scalar @teams < 1) {
|
||||||
|
return $self->render(status => 404, json => {
|
||||||
|
error => 'No se encontró ese equipo.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (scalar @nodes < 1) {
|
||||||
|
return $self->render(status => 404, json => {
|
||||||
|
error => 'No se encontró este nodo.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
my $team = $teams[0];
|
||||||
|
my $node = $nodes[0];
|
||||||
|
if (!$node->is_near($user)) {
|
||||||
|
return $self->render(status => 400, json => {
|
||||||
|
error => 'Estás demasiado lejos del nodo.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$user = $user->get_from_storage;
|
||||||
|
$user->team_object($team);
|
||||||
|
$user->update;
|
||||||
|
return $self->render(json => {
|
||||||
|
ok => $JSON::true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_self ($self) {
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if ( !defined $user ) {
|
||||||
|
return $self->_renderError( 401, 'No estás loggeado.' );
|
||||||
|
}
|
||||||
|
return $self->render( json => $user->serialize_to_owner, status => 200 );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub create ($self) {
|
||||||
|
my $input = $self->_expectJson;
|
||||||
|
if ( !defined $input ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $username = $input->{username};
|
||||||
|
my $password = $input->{password};
|
||||||
|
my $repeat_password = $input->{repeat_password};
|
||||||
|
return
|
||||||
|
unless $self->_createCheckInput( $username, $password, $repeat_password );
|
||||||
|
return $self->_createUser( $username, $password );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _expectJson ($self) {
|
||||||
|
my $input;
|
||||||
|
eval { $input = $self->req->json; };
|
||||||
|
if ($@) {
|
||||||
|
say STDERR $@;
|
||||||
|
$self->_renderError( 400, 'Se esperaba JSON.' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return $input;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub login ($self) {
|
||||||
|
my $input = $self->_expectJson;
|
||||||
|
if ( !defined $input ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $username = $input->{username};
|
||||||
|
my $password = $input->{password};
|
||||||
|
|
||||||
|
my $resultset_conquer_user =
|
||||||
|
BurguillosInfo::Schema->Schema->resultset('ConquerUser');
|
||||||
|
my @tentative_users =
|
||||||
|
$resultset_conquer_user->search( { username => $username } );
|
||||||
|
my $tentative_user = $tentative_users[0];
|
||||||
|
if ( !defined $tentative_user ) {
|
||||||
|
$self->_renderError( 401, 'El usuario especificado no existe.' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( !bcrypt_check( $password, $tentative_user->encrypted_password ) ) {
|
||||||
|
$self->_renderError( 401, 'Contraseña incorrecta.' );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
my $user = $tentative_user;
|
||||||
|
$self->set_current_user($user);
|
||||||
|
$self->render(
|
||||||
|
json => {
|
||||||
|
success => $JSON::true
|
||||||
|
},
|
||||||
|
status => 200
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub setCoordinates ($self) {
|
||||||
|
my $input = $self->_expectJson;
|
||||||
|
my $user = $self->current_user;
|
||||||
|
if ( !defined $user ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 401,
|
||||||
|
json => {
|
||||||
|
error => 'Debes estar loggeado para cambiar tus'
|
||||||
|
. ' coordenadas.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !defined $input ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ( ref $input ne 'ARRAY' && scalar $input->@* == 2 ) {
|
||||||
|
return $self->render(
|
||||||
|
status => 400,
|
||||||
|
json => {
|
||||||
|
error => 'Mal formato de coordenadas, debe ser '
|
||||||
|
. 'un array de exactamente 2 números reales.',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$user->coordinates($input);
|
||||||
|
$user->update;
|
||||||
|
return $self->render(
|
||||||
|
status => 200,
|
||||||
|
json => {
|
||||||
|
ok => $JSON::true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _createUser ( $self, $username, $password ) {
|
||||||
|
my $user;
|
||||||
|
my $uuid = create_uuid_string();
|
||||||
|
my $new_salt = urandom(16);
|
||||||
|
my $encrypted_password = bcrypt $password, '2b', 12, $new_salt;
|
||||||
|
eval {
|
||||||
|
$user = BurguillosInfo::Schema->Schema->resultset('ConquerUser')->new(
|
||||||
|
{
|
||||||
|
uuid => $uuid,
|
||||||
|
encrypted_password => $encrypted_password,
|
||||||
|
username => $username
|
||||||
|
}
|
||||||
|
);
|
||||||
|
$user->coordinates( [ 0, 0 ] );
|
||||||
|
$user->insert;
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
if ( $@ =~ /Key \((.*?)\)=\((.*?)\) already exists\./ ) {
|
||||||
|
return $self->_renderError( 400,
|
||||||
|
"La clave $1 ($2) ya existe en la base de datos.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
say STDERR $@;
|
||||||
|
return $self->_renderError( 400,
|
||||||
|
'No se pudo crear el usuario por razones desconocidas.' );
|
||||||
|
}
|
||||||
|
$self->render( status => 200, json => $user->serialize_to_owner );
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _renderError ( $self, $status, $message ) {
|
||||||
|
$self->render( status => $status, json => { error => $message } );
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _createCheckInput ( $self, $username, $password, $repeat_password ) {
|
||||||
|
if ( !defined $username
|
||||||
|
|| $username !~
|
||||||
|
/^(?:\w|\d|[ÑÁÉÍÓÚñáéíóú ]){$username_minimum_chars,$username_maximum_chars}$/
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return $self->_renderError( 400,
|
||||||
|
"Username invalido, las reglas son tamaño entre $username_minimum_chars y $username_maximum_chars"
|
||||||
|
. ' carácteres y solo se podrán usar letras, números y espacios.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !defined $password
|
||||||
|
|| $password eq $username
|
||||||
|
|| $password !~ /^.{$password_minimum_chars,$password_maximum_chars}$/
|
||||||
|
|| $password =~ /^\d+$/ )
|
||||||
|
{
|
||||||
|
return $self->_renderError(
|
||||||
|
400,
|
||||||
|
'Contraseña invalida, las reglas son la contraseña debe ser'
|
||||||
|
. ' distinta al nombre de usuario, la contraseña debe tener entre'
|
||||||
|
. " $password_minimum_chars y $password_maximum_chars carácteres"
|
||||||
|
. ' (Tu contraseña no se guardará en texto plano, el límite de'
|
||||||
|
. " $password_maximum_chars caracteres es para evitar denegaciones"
|
||||||
|
. ' de servicio), la contraseña no puede estar compuesta solo de números.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ( !defined $repeat_password || $password ne $repeat_password ) {
|
||||||
|
$self->_renderError(
|
||||||
|
400,
|
||||||
|
'El campo de repetir contraseña debe coincidir de forma'
|
||||||
|
. ' totalmente exacta con el campo de contraseña para asegurar'
|
||||||
|
. ' que podrás recordar la contraseña y/o que no has cometido'
|
||||||
|
. ' ningún error, si pierdes el acceso a tu cuenta no podrás'
|
||||||
|
. ' recuperarlo de ningún modo.',
|
||||||
|
);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
1;
|
@ -28,11 +28,11 @@ sub MIGRATIONS {
|
|||||||
path TEXT,
|
path TEXT,
|
||||||
FOREIGN KEY (path) REFERENCES paths(path)
|
FOREIGN KEY (path) REFERENCES paths(path)
|
||||||
)',
|
)',
|
||||||
'ALTER TABLE paths ADD column last_seen TIMESTAMP;',
|
'ALTER TABLE paths ADD COLUMN last_seen TIMESTAMP;',
|
||||||
'ALTER TABLE paths ALTER COLUMN last_seen SET DEFAULT NOW();',
|
'ALTER TABLE paths ALTER COLUMN last_seen SET DEFAULT NOW();',
|
||||||
'ALTER TABLE requests ADD PRIMARY KEY (uuid)',
|
'ALTER TABLE requests ADD PRIMARY KEY (uuid)',
|
||||||
'CREATE INDEX request_extra_index on requests (date, path);',
|
'CREATE INDEX request_extra_index on requests (date, path);',
|
||||||
'ALTER TABLE requests ADD column referer text;',
|
'ALTER TABLE requests ADD COLUMN referer text;',
|
||||||
'CREATE INDEX request_referer_index on requests (referer);',
|
'CREATE INDEX request_referer_index on requests (referer);',
|
||||||
'ALTER TABLE requests ADD COLUMN country TEXT;',
|
'ALTER TABLE requests ADD COLUMN country TEXT;',
|
||||||
'CREATE INDEX request_country_index on requests (country);',
|
'CREATE INDEX request_country_index on requests (country);',
|
||||||
@ -49,18 +49,69 @@ sub MIGRATIONS {
|
|||||||
id_farmacia TEXT NOT NULL
|
id_farmacia TEXT NOT NULL
|
||||||
);',
|
);',
|
||||||
'CREATE INDEX farmacia_guardia_index on farmacia_guardia (date, id_farmacia, uuid);',
|
'CREATE INDEX farmacia_guardia_index on farmacia_guardia (date, id_farmacia, uuid);',
|
||||||
|
'CREATE TABLE conquer_user (
|
||||||
|
uuid UUID NOT NULL PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
encrypted_password TEXT NOT NULL,
|
||||||
|
last_activity TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
is_admin BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
registration_date TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
|
);',
|
||||||
|
'CREATE TABLE conquer_node (
|
||||||
|
uuid UUID NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
coordinate_1 REAL NOT NULL,
|
||||||
|
coordinate_2 REAL NOT NULL,
|
||||||
|
description TEXT NOT NULL
|
||||||
|
);',
|
||||||
|
'CREATE INDEX index_conquer_node_coordinate_1 on conquer_node (coordinate_1);',
|
||||||
|
'CREATE INDEX index_conquer_node_coordinate_2 on conquer_node (coordinate_2);',
|
||||||
|
'ALTER TABLE conquer_user ADD COLUMN last_coordinate_1 REAL NOT NULL DEFAULT 0;',
|
||||||
|
'ALTER TABLE conquer_user ALTER COLUMN last_coordinate_1 DROP DEFAULT;',
|
||||||
|
'ALTER TABLE conquer_user ADD COLUMN last_coordinate_2 REAL NOT NULL DEFAULT 0;',
|
||||||
|
'ALTER TABLE conquer_user ALTER COLUMN last_coordinate_2 DROP DEFAULT;',
|
||||||
|
'CREATE TABLE conquer_teams (
|
||||||
|
uuid UUID NOT NULL PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL DEFAULT \'\',
|
||||||
|
points INTEGER NOT NULL DEFAULT 0
|
||||||
|
);',
|
||||||
|
'ALTER TABLE conquer_user ADD COLUMN team UUID REFERENCES conquer_teams (uuid);',
|
||||||
|
'ALTER TABLE conquer_node ADD COLUMN team UUID REFERENCES conquer_teams (uuid);',
|
||||||
|
'ALTER TABLE conquer_teams ADD COLUMN color TEXT NOT NULL DEFAULT \'#000\';',
|
||||||
|
'ALTER TABLE conquer_teams ALTER COLUMN color SET DEFAULT \'#555\';',
|
||||||
|
'ALTER TABLE conquer_teams ALTER COLUMN color SET DEFAULT \'#aaa\';',
|
||||||
|
'CREATE EXTENSION IF NOT EXISTS postgis;',
|
||||||
|
'ALTER TABLE conquer_node ADD COLUMN geometry GEOMETRY NULL;',
|
||||||
|
'UPDATE conquer_node SET geometry=ST_MakePoint(coordinate_1, coordinate_2);',
|
||||||
|
'ALTER TABLE conquer_node ALTER COLUMN geometry SET NOT NULL;',
|
||||||
|
'ALTER TABLE conquer_node DROP COLUMN coordinate_1;',
|
||||||
|
'ALTER TABLE conquer_node DROP COLUMN coordinate_2;',
|
||||||
|
'ALTER TABLE conquer_user ADD COLUMN experience INTEGER NOT NULL DEFAULT 125;',
|
||||||
|
'ALTER TABLE conquer_user ADD COLUMN current_hp INTEGER NOT NULL DEFAULT 999;',
|
||||||
|
'CREATE TABLE conquer_user_current_enemy (
|
||||||
|
uuid UUID NOT NULL PRIMARY KEY,
|
||||||
|
"user" UUID NOT NULL REFERENCES conquer_user(uuid),
|
||||||
|
species INTEGER NOT NULL,
|
||||||
|
is_battled BOOLEAN DEFAULT false,
|
||||||
|
is_selected_to_battle BOOLEAN DEFAULT false,
|
||||||
|
level INTEGER NOT NULL
|
||||||
|
);',
|
||||||
|
'ALTER TABLE conquer_user_current_enemy ALTER COLUMN species TYPE TEXT;',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _populate_locations ($dbh) {
|
sub _populate_locations ($dbh) {
|
||||||
require BurguillosInfo;
|
# This subroutine crashes the migrations.
|
||||||
require BurguillosInfo::Tracking;
|
# require BurguillosInfo;
|
||||||
my $tracking = BurguillosInfo::Tracking->new( BurguillosInfo->new );
|
# require BurguillosInfo::Tracking;
|
||||||
my $page = 0;
|
# my $tracking = BurguillosInfo::Tracking->new( BurguillosInfo->new );
|
||||||
while (1) {
|
# my $page = 0;
|
||||||
last if !_update_request_page( $dbh, $tracking, $page );
|
# while (1) {
|
||||||
$page += 100;
|
# last if !_update_request_page( $dbh, $tracking, $page );
|
||||||
}
|
# $page += 100;
|
||||||
|
# }
|
||||||
}
|
}
|
||||||
|
|
||||||
sub _update_request_page ( $dbh, $tracking, $page ) {
|
sub _update_request_page ( $dbh, $tracking, $page ) {
|
||||||
|
64
lib/BurguillosInfo/Schema.pm
Normal file
64
lib/BurguillosInfo/Schema.pm
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package BurguillosInfo::Schema;
|
||||||
|
|
||||||
|
use v5.36.0;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use utf8;
|
||||||
|
|
||||||
|
our $VERSION = 1;
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use BurguillosInfo;
|
||||||
|
|
||||||
|
use parent 'DBIx::Class::Schema';
|
||||||
|
|
||||||
|
__PACKAGE__->load_namespaces();
|
||||||
|
|
||||||
|
my $schema;
|
||||||
|
|
||||||
|
sub Schema ($class) {
|
||||||
|
if ( !defined $schema ) {
|
||||||
|
use BurguillosInfo::DB;
|
||||||
|
BurguillosInfo::DB->connect;
|
||||||
|
my $app = BurguillosInfo->new;
|
||||||
|
my $config = $app->{config};
|
||||||
|
my $database_config = $config->{db};
|
||||||
|
my $dbname = $database_config->{database};
|
||||||
|
my $host = $database_config->{host};
|
||||||
|
my $port = $database_config->{port};
|
||||||
|
my $user = $database_config->{user};
|
||||||
|
my $password = $database_config->{password};
|
||||||
|
my $dsn = 'dbi:Pg:';
|
||||||
|
|
||||||
|
if ( !defined $dbname ) {
|
||||||
|
die "The key database/dbname must be configured.";
|
||||||
|
}
|
||||||
|
$dsn .= "dbname=$dbname";
|
||||||
|
if ( defined $host ) {
|
||||||
|
$dsn .= ";host=$host";
|
||||||
|
}
|
||||||
|
if ( defined $port ) {
|
||||||
|
$dsn .= ";port=$port";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Undef is perfectly fine for username and password.
|
||||||
|
$schema = $class->connect(
|
||||||
|
$dsn, $user,
|
||||||
|
$password,
|
||||||
|
{
|
||||||
|
auto_savepoint => 1,
|
||||||
|
Callbacks => {
|
||||||
|
connected => sub {
|
||||||
|
shift->do('set timezone = UTC');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
quote_char => '"',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
1;
|
121
lib/BurguillosInfo/Schema/Result/ConquerNode.pm
Normal file
121
lib/BurguillosInfo/Schema/Result/ConquerNode.pm
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
package BurguillosInfo::Schema::Result::ConquerNode;
|
||||||
|
|
||||||
|
use v5.36.0;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use parent 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use JSON;
|
||||||
|
use GIS::Distance;
|
||||||
|
|
||||||
|
__PACKAGE__->table('conquer_node');
|
||||||
|
__PACKAGE__->load_components("TimeStamp");
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
uuid => {
|
||||||
|
data_type => 'uuid',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
name => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'0',
|
||||||
|
},
|
||||||
|
type => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
description => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
geometry => {
|
||||||
|
data_type => 'geometry',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
team => {
|
||||||
|
data_type => 'uuid',
|
||||||
|
is_nullable => 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
sub coordinate_2 ($self) {
|
||||||
|
require BurguillosInfo::Schema;
|
||||||
|
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||||
|
my ($new_self) = $resultset->search(
|
||||||
|
{ uuid => $self->uuid },
|
||||||
|
{
|
||||||
|
'+select' => {
|
||||||
|
ST_Y => { ST_Centroid => 'geometry' },
|
||||||
|
-as => 'coordinate_2',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return $new_self->get_column('coordinate_2');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub coordinate_1 ($self) {
|
||||||
|
require BurguillosInfo::Schema;
|
||||||
|
my $resultset = BurguillosInfo::Schema->Schema->resultset('ConquerNode');
|
||||||
|
my ($new_self) = $resultset->search(
|
||||||
|
{ uuid => $self->uuid },
|
||||||
|
{
|
||||||
|
'+select' => {
|
||||||
|
ST_X => { ST_Centroid => 'geometry' },
|
||||||
|
-as => 'coordinate_1',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return $new_self->get_column('coordinate_1');
|
||||||
|
}
|
||||||
|
|
||||||
|
sub serialize ( $self, $player = undef ) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
my $return = {
|
||||||
|
kind => 'ConquerNode',
|
||||||
|
uuid => $self->uuid,
|
||||||
|
name => $self->name,
|
||||||
|
description => $self->description,
|
||||||
|
type => $self->type,
|
||||||
|
coordinate_1 => $self->coordinate_1,
|
||||||
|
coordinate_2 => $self->coordinate_2,
|
||||||
|
is_near => $self->is_near($player),
|
||||||
|
team => $self->team,
|
||||||
|
};
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub is_near ( $self, $player ) {
|
||||||
|
if ( !defined $player ) {
|
||||||
|
return $JSON::false;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Meters
|
||||||
|
if ( $self->get_distance_to_player($player) < 100 ) {
|
||||||
|
return $JSON::true;
|
||||||
|
}
|
||||||
|
return $JSON::false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get_distance_to_player ( $self, $player ) {
|
||||||
|
my $longitude_player = $player->last_coordinate_1;
|
||||||
|
my $latitude_player = $player->last_coordinate_2;
|
||||||
|
my $longitude_node = $self->coordinate_1;
|
||||||
|
my $latitude_node = $self->coordinate_2;
|
||||||
|
my $gis = GIS::Distance->new;
|
||||||
|
|
||||||
|
# Setting distance to meters.
|
||||||
|
my $distance = $gis->distance_metal(
|
||||||
|
$latitude_node, $longitude_node,
|
||||||
|
$latitude_player, $longitude_player
|
||||||
|
) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
__PACKAGE__->set_primary_key('uuid');
|
||||||
|
__PACKAGE__->belongs_to( 'team_object',
|
||||||
|
'BurguillosInfo::Schema::Result::ConquerTeam', 'team' );
|
||||||
|
1;
|
55
lib/BurguillosInfo/Schema/Result/ConquerTeam.pm
Normal file
55
lib/BurguillosInfo/Schema/Result/ConquerTeam.pm
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package BurguillosInfo::Schema::Result::ConquerTeam;
|
||||||
|
|
||||||
|
use v5.36.0;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use parent 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use JSON;
|
||||||
|
|
||||||
|
__PACKAGE__->table('conquer_teams');
|
||||||
|
__PACKAGE__->load_components("TimeStamp");
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
uuid => {
|
||||||
|
data_type => 'uuid',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
name => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
description => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
points => {
|
||||||
|
data_type => 'integer',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
color => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
__PACKAGE__->set_primary_key('uuid');
|
||||||
|
__PACKAGE__->has_many( players => 'BurguillosInfo::Schema::Result::ConquerUser', 'team');
|
||||||
|
__PACKAGE__->has_many( nodes => 'BurguillosInfo::Schema::Result::ConquerNode', 'team');
|
||||||
|
|
||||||
|
sub serialize ($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
return {
|
||||||
|
kind => 'ConquerTeam',
|
||||||
|
uuid => $self->uuid,
|
||||||
|
name => $self->name,
|
||||||
|
description => $self->description,
|
||||||
|
points => $self->points,
|
||||||
|
color => $self->color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
1;
|
153
lib/BurguillosInfo/Schema/Result/ConquerUser.pm
Normal file
153
lib/BurguillosInfo/Schema/Result/ConquerUser.pm
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
package BurguillosInfo::Schema::Result::ConquerUser;
|
||||||
|
|
||||||
|
use v5.36.0;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use parent 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use JSON;
|
||||||
|
|
||||||
|
__PACKAGE__->table('conquer_user');
|
||||||
|
__PACKAGE__->load_components("TimeStamp");
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
uuid => {
|
||||||
|
data_type => 'uuid',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
team => {
|
||||||
|
data_type => 'uuid',
|
||||||
|
is_nullable => 1,
|
||||||
|
},
|
||||||
|
username => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
encrypted_password => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
last_activity => {
|
||||||
|
data_type => 'timestamp',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'NOW()',
|
||||||
|
},
|
||||||
|
registration_date => {
|
||||||
|
data_type => 'timestamp',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'NOW()',
|
||||||
|
},
|
||||||
|
is_admin => {
|
||||||
|
data_type => 'boolean',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'0',
|
||||||
|
},
|
||||||
|
last_coordinate_1 => {
|
||||||
|
data_type => 'real',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'0',
|
||||||
|
},
|
||||||
|
last_coordinate_2 => {
|
||||||
|
data_type => 'real',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'0',
|
||||||
|
},
|
||||||
|
experience => {
|
||||||
|
data_type => 'integer',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'125',
|
||||||
|
},
|
||||||
|
current_hp => {
|
||||||
|
data_type => 'integer',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'999',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
sub max_health($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
my $base = 50;
|
||||||
|
my $born_value = 31;
|
||||||
|
return int(
|
||||||
|
(($base * 2 + $born_value) * $self->level)
|
||||||
|
/ 100 + $self->level + 10
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub level($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
return int($self->experience ** (1/3) + 0.0000000000001);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub attack($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
my $base = 50;
|
||||||
|
my $born_value = 31;
|
||||||
|
return int(
|
||||||
|
(($base * 2 + $self->level)*$self->level)
|
||||||
|
/100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub defense($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
my $base = 50;
|
||||||
|
my $born_value = 31;
|
||||||
|
return int(
|
||||||
|
(($base * 2 + $self->level)*$self->level)
|
||||||
|
/100
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
sub health($self, $health = undef) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
my $hp = $self->current_hp;
|
||||||
|
if ($hp > $self->max_health) {
|
||||||
|
$self->current_hp($self->max_health);
|
||||||
|
$self->update;
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
}
|
||||||
|
if (defined $health) {
|
||||||
|
if ($health > $self->max_health) {
|
||||||
|
$health = $self->max_health;
|
||||||
|
}
|
||||||
|
$self->current_hp($health);
|
||||||
|
$self->update;
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
}
|
||||||
|
return $self->current_hp;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub coordinates ( $self, $coordinates = undef ) {
|
||||||
|
if ( defined $coordinates ) {
|
||||||
|
if ( ref $coordinates ne 'ARRAY' || scalar $coordinates->@* != 2 ) {
|
||||||
|
die 'The second parameter of this subroutine '
|
||||||
|
. 'must be an ARRAYREF of exactly two elements.';
|
||||||
|
}
|
||||||
|
$self->last_coordinate_1( $coordinates->[0] );
|
||||||
|
$self->last_coordinate_2( $coordinates->[1] );
|
||||||
|
}
|
||||||
|
return [ $self->last_coordinate_1, $self->last_coordinate_2 ];
|
||||||
|
}
|
||||||
|
|
||||||
|
sub serialize_to_owner ($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
return {
|
||||||
|
kind => 'ConquerUser',
|
||||||
|
uuid => $self->uuid,
|
||||||
|
team => $self->team,
|
||||||
|
username => $self->username,
|
||||||
|
is_admin => $self->is_admin ? $JSON::true : $JSON::false,
|
||||||
|
last_activity => $self->last_activity,
|
||||||
|
registration_date => $self->registration_date,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
__PACKAGE__->set_primary_key('uuid');
|
||||||
|
__PACKAGE__->belongs_to('team_object', 'BurguillosInfo::Schema::Result::ConquerTeam', 'team');
|
||||||
|
__PACKAGE__->add_unique_constraint( "unique_constraint_username",
|
||||||
|
['username'] );
|
||||||
|
1;
|
101
lib/BurguillosInfo/Schema/Result/ConquerUserCurrentEnemy.pm
Normal file
101
lib/BurguillosInfo/Schema/Result/ConquerUserCurrentEnemy.pm
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package BurguillosInfo::Schema::Result::ConquerUserCurrentEnemy;
|
||||||
|
|
||||||
|
use v5.36.0;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use parent 'DBIx::Class::Core';
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use JSON;
|
||||||
|
use BurguillosInfo::Species;
|
||||||
|
|
||||||
|
__PACKAGE__->table('conquer_user_current_enemy');
|
||||||
|
__PACKAGE__->load_components("TimeStamp");
|
||||||
|
|
||||||
|
__PACKAGE__->add_columns(
|
||||||
|
uuid => {
|
||||||
|
data_type => 'uuid',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
user => {
|
||||||
|
data_type => 'uuid',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
species => {
|
||||||
|
data_type => 'text',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
is_selected_to_battle => {
|
||||||
|
data_type => 'boolean',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'false',
|
||||||
|
},
|
||||||
|
is_battled => {
|
||||||
|
data_type => 'boolean',
|
||||||
|
is_nullable => 0,
|
||||||
|
default_value => \'false',
|
||||||
|
},
|
||||||
|
level => {
|
||||||
|
data_type => 'integer',
|
||||||
|
is_nullable => 0,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
sub serialize ($self) {
|
||||||
|
my $species = BurguillosInfo::Species->new;
|
||||||
|
my $specie = $species->get( $self->species );
|
||||||
|
return {
|
||||||
|
uuid => $self->uuid,
|
||||||
|
species => $specie->serialize,
|
||||||
|
level => $self->level,
|
||||||
|
max_health => $self->max_health,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub max_health ($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
my $base = 50;
|
||||||
|
my $born_value = 31;
|
||||||
|
return
|
||||||
|
int( ( ( $base * 2 + $born_value ) * $self->level ) / 100 +
|
||||||
|
$self->level +
|
||||||
|
10 );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub experience_drop ($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
return int( $self->level / 7 * 179 );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub experience ($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
return int( $self->level**(3) );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub attack ($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
my $base = 50;
|
||||||
|
my $born_value = 31;
|
||||||
|
return int( ( ( $base * 2 + $self->level ) * $self->level ) / 100 );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub defense ($self) {
|
||||||
|
$self = $self->get_from_storage();
|
||||||
|
my $base = 50;
|
||||||
|
my $born_value = 31;
|
||||||
|
return int( ( ( $base * 2 + $self->level ) * $self->level ) / 100 );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub health ($self) {
|
||||||
|
|
||||||
|
# Combat result is decided from the start of battle.
|
||||||
|
return $self->max_health;
|
||||||
|
}
|
||||||
|
|
||||||
|
__PACKAGE__->set_primary_key('uuid');
|
||||||
|
__PACKAGE__->belongs_to( 'user_object',
|
||||||
|
'BurguillosInfo::Schema::Result::ConquerUser', 'user' );
|
||||||
|
1;
|
24
lib/BurguillosInfo/Specie.pm
Normal file
24
lib/BurguillosInfo/Specie.pm
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package BurguillosInfo::Specie;
|
||||||
|
|
||||||
|
use v5.36.0;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use feature 'signatures';
|
||||||
|
|
||||||
|
use Moo::Role;
|
||||||
|
|
||||||
|
sub can_be_global {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub serialize ($self) {
|
||||||
|
return {
|
||||||
|
id => $self->id,
|
||||||
|
name => $self->name,
|
||||||
|
image => $self->image,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
requires 'id name image';
|
||||||
|
1;
|
58
lib/BurguillosInfo/Species.pm
Normal file
58
lib/BurguillosInfo/Species.pm
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package BurguillosInfo::Species;
|
||||||
|
|
||||||
|
use v5.36.0;
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use Moo;
|
||||||
|
|
||||||
|
use Module::Pluggable
|
||||||
|
search_path => ['BurguillosInfo::Species'],
|
||||||
|
instantiate => 'new',
|
||||||
|
on_require_error => sub ( $plugin, $error ) {
|
||||||
|
die $error;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
my %hash_species;
|
||||||
|
|
||||||
|
sub _hash ($self) {
|
||||||
|
if ( !scalar keys %hash_species ) {
|
||||||
|
$self->_populate_hash;
|
||||||
|
}
|
||||||
|
return {%hash_species};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _populate_hash ($self) {
|
||||||
|
my @species = $self->plugins();
|
||||||
|
print Data::Dumper::Dumper \@species;
|
||||||
|
for my $specie (@species) {
|
||||||
|
$self->_check_specie_valid($specie);
|
||||||
|
if (exists $hash_species{$specie->id}) {
|
||||||
|
die "Duplicated species id @{[$specie->id]}.";
|
||||||
|
}
|
||||||
|
$hash_species{$specie->id} = $specie;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub _check_specie_valid ( $self, $specie ) {
|
||||||
|
if ( !$specie->does('BurguillosInfo::Specie') ) {
|
||||||
|
die "$specie does not implement BurguillosInfo::Specie.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub get($self, $id) {
|
||||||
|
return $self->_hash->{$id};
|
||||||
|
}
|
||||||
|
|
||||||
|
sub list($self) {
|
||||||
|
my @species_keys = keys %{$self->_hash};
|
||||||
|
my $species = [ sort { $a->id cmp $b->id } map { $self->_hash->{$_} } @species_keys ];
|
||||||
|
return $species;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub list_can_be_global($self) {
|
||||||
|
return [ grep { $_->can_be_global } $self->list->@* ];
|
||||||
|
}
|
||||||
|
1;
|
28
lib/BurguillosInfo/Species/Murcielago.pm
Normal file
28
lib/BurguillosInfo/Species/Murcielago.pm
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package BurguillosInfo::Species::Murcielago;
|
||||||
|
|
||||||
|
use v5.34.1;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
|
||||||
|
use Moo;
|
||||||
|
|
||||||
|
use parent 'BurguillosInfo::Specie';
|
||||||
|
|
||||||
|
sub id {
|
||||||
|
return 'murcielago';
|
||||||
|
}
|
||||||
|
|
||||||
|
sub name {
|
||||||
|
return 'Murcielago';
|
||||||
|
}
|
||||||
|
|
||||||
|
sub image {
|
||||||
|
return '/img/conquer/species/murcielago.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
sub can_be_global {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
1;
|
@ -25,7 +25,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
"ol": "^8.1.0",
|
"ol": "^8.1.0",
|
||||||
|
"protoc-gen-js": "^3.21.2",
|
||||||
"tablesort": "^5.3.0",
|
"tablesort": "^5.3.0",
|
||||||
"ts-loader": "^9.5.0"
|
"ts-loader": "^9.5.0",
|
||||||
|
"ts-protoc-gen": "^0.15.0",
|
||||||
|
"typescript-json-serializer": "^6.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,162 @@ body {
|
|||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%; }
|
height: 100%; }
|
||||||
|
body span.conquer-team-circle {
|
||||||
|
display: inline-block;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
height: 1rem;
|
||||||
|
border-radius: 50%; }
|
||||||
|
body div.conquer-team-to-select {
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: beige;
|
||||||
|
border: solid black; }
|
||||||
|
body div.conquer-team-to-select button {
|
||||||
|
height: 60px; }
|
||||||
|
body p.conquer-register-error, body p.conquer-login-error, body p.conquer-login-success, body p.conquer-error {
|
||||||
|
color: red;
|
||||||
|
margin: 3px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
background: blanchedalmond;
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: solid 1px black;
|
||||||
|
overflow-y: scroll; }
|
||||||
|
body form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%; }
|
||||||
|
body form label {
|
||||||
|
width: 100%; }
|
||||||
|
body form label input, body form label textarea, body form label select {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
background-image: none;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
-moz-box-shadow: none;
|
||||||
|
background: white;
|
||||||
|
box-shadow: none;
|
||||||
|
min-height: 2rem;
|
||||||
|
border-radius: 0.5rem; }
|
||||||
|
body form label textarea {
|
||||||
|
height: 100px; }
|
||||||
|
body div.conquer-interface-element-padded {
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
padding-left: 30px;
|
||||||
|
padding-right: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center; }
|
||||||
|
body div.conquer-interface-element-padded.conquer-display-block {
|
||||||
|
display: block; }
|
||||||
|
body div.conquer-interface-element-padded.conquer-display-none {
|
||||||
|
display: none; }
|
||||||
|
body div.fight-battle-selector-slide {
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
top: 0;
|
||||||
|
height: 100px; }
|
||||||
|
body div.fight-battle-selector-slide.conquer-display-none {
|
||||||
|
display: none; }
|
||||||
|
body div.fight-battle-selector-slide img {
|
||||||
|
height: 50px;
|
||||||
|
aspect-ratio: 1 / 1; }
|
||||||
|
body div.create-node-slide {
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100px; }
|
||||||
|
body div.create-node-slide.conquer-display-none {
|
||||||
|
display: none; }
|
||||||
|
body p.conquer-login-success {
|
||||||
|
color: green; }
|
||||||
|
body a.conquer-exit-button {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
background: darkmagenta;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
width: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: xx-large;
|
||||||
|
border: 2px black solid; }
|
||||||
|
body div.conquer-overlay-transparent {
|
||||||
|
background: black;
|
||||||
|
opacity: 50%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1; }
|
||||||
|
body div.conquer-self-player {
|
||||||
|
border: 1px solid black;
|
||||||
|
position: fixed;
|
||||||
|
color: black;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
border-radius: 30px;
|
||||||
|
background: darkseagreen;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: calc(100% - 12px);
|
||||||
|
height: calc(100% - 22px);
|
||||||
|
margin: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
overflow-y: scroll; }
|
||||||
|
body div.conquer-top-bar {
|
||||||
|
display: flex;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
border-radius: 30px 30px 0 0;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
background: darkcyan;
|
||||||
|
margin-left: 0;
|
||||||
|
border-bottom: 1px black solid;
|
||||||
|
display: flex;
|
||||||
|
justify-content: end; }
|
||||||
|
body div.conquer-login, body div.conquer-register {
|
||||||
|
border: 1px solid black;
|
||||||
|
position: fixed;
|
||||||
|
color: black;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
border-radius: 30px;
|
||||||
|
background: darkseagreen;
|
||||||
|
top: calc( 50% - 200px - 10px);
|
||||||
|
left: calc( 50% - 150px - 10px);
|
||||||
|
padding: 10px;
|
||||||
|
height: 400px;
|
||||||
|
width: 300px;
|
||||||
|
z-index: 1; }
|
||||||
|
body div.conquer-login form, body div.conquer-register form {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center; }
|
||||||
|
body .conquer-display-none {
|
||||||
|
display: none; }
|
||||||
|
body div.conquer-container {
|
||||||
|
background: black;
|
||||||
|
height: 100dvh;
|
||||||
|
width: 100%; }
|
||||||
|
body div.conquer-select-fight div.conquer-image-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center; }
|
||||||
|
body div.conquer-select-fight div.conquer-button-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center; }
|
||||||
|
body div.ol-control {
|
||||||
|
display: none; }
|
||||||
body span.round-center {
|
body span.round-center {
|
||||||
background: blueviolet;
|
background: blueviolet;
|
||||||
color: #FEFEFA;
|
color: #FEFEFA;
|
||||||
|
@ -17,6 +17,194 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
span.conquer-team-circle {
|
||||||
|
display: inline-block;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
height: 1rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
div.conquer-team-to-select {
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: beige;
|
||||||
|
border: solid black;
|
||||||
|
button {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.conquer-register-error, p.conquer-login-error, p.conquer-login-success,p.conquer-error {
|
||||||
|
color: red;
|
||||||
|
margin: 3px;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
background: blanchedalmond;
|
||||||
|
padding: 3px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: solid 1px black;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
label {
|
||||||
|
width: 100%;
|
||||||
|
input, textarea, select {
|
||||||
|
width: 100%;
|
||||||
|
border:none;
|
||||||
|
background-image:none;
|
||||||
|
-webkit-box-shadow: none;
|
||||||
|
-moz-box-shadow: none;
|
||||||
|
background: white;
|
||||||
|
box-shadow: none;
|
||||||
|
min-height: 2rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div.conquer-interface-element-padded {
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
padding-left: 30px;
|
||||||
|
padding-right: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
&.conquer-display-block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
&.conquer-display-none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div.fight-battle-selector-slide {
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
top: 0;
|
||||||
|
height: 100px;
|
||||||
|
&.conquer-display-none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
height: 50px;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div.create-node-slide {
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100px;
|
||||||
|
&.conquer-display-none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.conquer-login-success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
a.conquer-exit-button {
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
background: darkmagenta;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
width: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: xx-large;
|
||||||
|
border: 2px black solid;
|
||||||
|
}
|
||||||
|
div.conquer-overlay-transparent {
|
||||||
|
background: black;
|
||||||
|
opacity: 50%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
div.conquer-self-player {
|
||||||
|
border: 1px solid black;
|
||||||
|
position: fixed;
|
||||||
|
color: black;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
border-radius: 30px;
|
||||||
|
background: darkseagreen;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: calc(100% - 12px);
|
||||||
|
height: calc(100% - 22px);
|
||||||
|
margin: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
div.conquer-top-bar {
|
||||||
|
display: flex;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
border-radius: 30px 30px 0 0;
|
||||||
|
padding-top: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
background: darkcyan;
|
||||||
|
margin-left: 0;
|
||||||
|
border-bottom: 1px black solid;
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conquer-login,div.conquer-register {
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
border: 1px solid black;
|
||||||
|
position: fixed;
|
||||||
|
color: black;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
border-radius: 30px;
|
||||||
|
background: darkseagreen;
|
||||||
|
top: calc( 50% - 200px - 10px );
|
||||||
|
left: calc( 50% - 150px - 10px );
|
||||||
|
padding: 10px;
|
||||||
|
height: 400px;
|
||||||
|
width: 300px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.conquer-display-none {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
div.conquer-container {
|
||||||
|
background: black;
|
||||||
|
height: 100dvh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.conquer-select-fight {
|
||||||
|
div.conquer-image-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
div.conquer-button-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div.ol-control {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
span.round-center {
|
span.round-center {
|
||||||
background: $background_div;
|
background: $background_div;
|
||||||
color: $background_sidebar;
|
color: $background_sidebar;
|
||||||
|
92
public/img/arrow-player-killed.svg
Normal file
92
public/img/arrow-player-killed.svg
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="180"
|
||||||
|
height="180"
|
||||||
|
viewBox="0 0 180 180"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="arrow-player-killed.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="2.1722222"
|
||||||
|
inkscape:cx="114.39898"
|
||||||
|
inkscape:cy="58.925831"
|
||||||
|
inkscape:window-width="1499"
|
||||||
|
inkscape:window-height="991"
|
||||||
|
inkscape:window-x="26"
|
||||||
|
inkscape:window-y="23"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer2" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer3"
|
||||||
|
inkscape:label="Layer 3"
|
||||||
|
style="stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:0.99906439">
|
||||||
|
<ellipse
|
||||||
|
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3.22896;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1514"
|
||||||
|
cy="90"
|
||||||
|
cx="90"
|
||||||
|
rx="87.179558"
|
||||||
|
ry="88.385521" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer2"
|
||||||
|
inkscape:label="Layer 2"
|
||||||
|
style="stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:0.99906439">
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:#000000;fill-opacity:0.999064;stroke:none;stroke-width:3.42195;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 27.1872,145.48336 c -0.325678,6.59573 4.86944,13.81584 14.628956,9.61774 38.996707,-20.08168 63.312444,-21.05373 94.835564,1.87477 9.98034,4.08129 14.61469,-3.036 15.90751,-6.76847 C 153.79696,107.06667 118.11757,6.5875149 90,1.6144791 63.092475,7.5716541 26.88641,91.183358 27.1872,145.48336 Z"
|
||||||
|
id="path1682"
|
||||||
|
sodipodi:nodetypes="cccccc" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:inline">
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.805;stroke-dasharray:none;stroke-opacity:0"
|
||||||
|
d="m 109.99321,42.936903 5.25546,0.250702 -11.87761,18.116623 -5.190631,-0.233775 z"
|
||||||
|
id="path16212"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.805;stroke-dasharray:none;stroke-opacity:0"
|
||||||
|
d="m 98.180429,45.18702 4.515381,-0.518513 12.68756,15.571862 -5.93341,0.608601 z"
|
||||||
|
id="path16538"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.805;stroke-dasharray:none;stroke-opacity:0"
|
||||||
|
d="m 75.170413,42.936903 5.25546,0.250702 -11.87761,18.116623 -5.190631,-0.233775 z"
|
||||||
|
id="path16212-5"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.805;stroke-dasharray:none;stroke-opacity:0"
|
||||||
|
d="m 63.357632,45.18702 4.515381,-0.518513 12.68756,15.571862 -5.93341,0.608601 z"
|
||||||
|
id="path16538-3"
|
||||||
|
sodipodi:nodetypes="ccccc" />
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:2.39555;stroke-dasharray:none;stroke-opacity:0.997527"
|
||||||
|
d="m 60.222542,111.59197 2.432165,2.37521 7.776952,-12.17573 5.48651,12.67675 7.484611,-13.49323 6.616088,13.42642 5.917787,-12.95044 6.241525,15.681 7.16239,-16.32398 3.56976,17.01614 7.0569,-13.92744 -1.49286,-2.20634 -5.24564,9.43495 -3.04735,-15.193902 -7.75217,14.476702 -6.68575,-13.508063 -5.724592,11.749843 -6.278623,-12.941158 -7.663829,12.631268 -5.33589,-11.255318 z"
|
||||||
|
id="path16739" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
107
public/img/arrow-player.svg
Normal file
107
public/img/arrow-player.svg
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="180"
|
||||||
|
height="180"
|
||||||
|
viewBox="0 0 180 180"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||||
|
sodipodi:docname="arrow-player.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview7"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="true"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="4.3444444"
|
||||||
|
inkscape:cx="86.777494"
|
||||||
|
inkscape:cy="90.000001"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1011"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer3" />
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer3"
|
||||||
|
inkscape:label="Layer 3"
|
||||||
|
style="stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:0.99906439">
|
||||||
|
<ellipse
|
||||||
|
style="display:inline;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:10.6795;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path1514"
|
||||||
|
cy="90"
|
||||||
|
cx="90"
|
||||||
|
rx="84.558273"
|
||||||
|
ry="84.660248" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer2"
|
||||||
|
inkscape:label="Layer 2"
|
||||||
|
style="stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:0.99906439">
|
||||||
|
<path
|
||||||
|
style="stroke:none;stroke-opacity:1;fill:#000000;fill-opacity:0.999064;display:inline;stroke-width:3.305;stroke-dasharray:none"
|
||||||
|
d="m 28.658815,141.34251 c -0.325678,6.15261 4.86944,12.88765 14.628956,8.97159 38.996707,-18.73253 63.312439,-19.63928 94.835559,1.74882 9.98035,3.8071 14.6147,-2.83203 15.90752,-6.31374 C 155.26857,105.50676 119.58918,11.778083 91.471615,7.1391499 64.56409,12.696105 28.358025,90.690537 28.658815,141.34251 Z"
|
||||||
|
id="path1682"
|
||||||
|
sodipodi:nodetypes="cccccc" />
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
style="display:inline">
|
||||||
|
<path
|
||||||
|
style="fill:#ffffff;fill-opacity:0.999064;stroke:#000000;stroke-width:2.88197;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path7203"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="74.857635"
|
||||||
|
sodipodi:cy="50.984001"
|
||||||
|
sodipodi:rx="12.305052"
|
||||||
|
sodipodi:ry="10.465668"
|
||||||
|
sodipodi:start="6.0340459"
|
||||||
|
sodipodi:end="6.0280938"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="M 86.782769,48.403481 A 12.305052,10.465668 0 0 1 77.909434,61.12269 12.305052,10.465668 0 0 1 62.941585,53.594694 12.305052,10.465668 0 0 1 71.770374,40.853082 12.305052,10.465668 0 0 1 86.764498,48.343158" />
|
||||||
|
<path
|
||||||
|
style="display:inline;fill:#ffffff;fill-opacity:0.999064;stroke:#000000;stroke-width:2.8579;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path7203-7"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="110.57592"
|
||||||
|
sodipodi:cy="50.984138"
|
||||||
|
sodipodi:rx="12.09339"
|
||||||
|
sodipodi:ry="10.471755"
|
||||||
|
sodipodi:start="6.0340459"
|
||||||
|
sodipodi:end="6.0280938"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="m 122.29593,48.402118 a 12.09339,10.471755 0 0 1 -8.72071,12.726606 12.09339,10.471755 0 0 1 -14.710381,-7.532374 12.09339,10.471755 0 0 1 8.676921,-12.749023 12.09339,10.471755 0 0 1 14.73621,7.494432" />
|
||||||
|
<path
|
||||||
|
style="fill:#000000;fill-opacity:0;stroke:#ffffff;stroke-width:2.26257;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path7803"
|
||||||
|
sodipodi:type="arc"
|
||||||
|
sodipodi:cx="93.89315"
|
||||||
|
sodipodi:cy="100.6772"
|
||||||
|
sodipodi:rx="22.730469"
|
||||||
|
sodipodi:ry="15.978166"
|
||||||
|
sodipodi:start="0.031220556"
|
||||||
|
sodipodi:end="3.1729889"
|
||||||
|
sodipodi:open="true"
|
||||||
|
sodipodi:arc-type="arc"
|
||||||
|
d="M 116.61254,101.17597 A 22.730469,15.978166 0 0 1 104.63719,114.75778 22.730469,15.978166 0 0 1 81.91671,114.25759 22.730469,15.978166 0 0 1 71.173884,100.17563" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/img/conquer/earth-bomb.png
Normal file
BIN
public/img/conquer/earth-bomb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.0 MiB |
BIN
public/img/conquer/species/murcielago.png
Normal file
BIN
public/img/conquer/species/murcielago.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 956 B |
BIN
public/img/missingicon.gif
Normal file
BIN
public/img/missingicon.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 212 B |
2170
public/js/bundle.js
2170
public/js/bundle.js
File diff suppressed because one or more lines are too long
@ -0,0 +1,3 @@
|
|||||||
|
<div id="conquer-select-team-list-template" class="conquer-display-none conquer-interface-element-padded conquer-display-block">
|
||||||
|
<h1>Encuentra tu equipo ideal.</h1>
|
||||||
|
</div>
|
@ -0,0 +1,5 @@
|
|||||||
|
<div id="conquer-team-to-select-template" class="conquer-team-to-select conquer-display-none">
|
||||||
|
<p class="conquer-name"></p>
|
||||||
|
<p class="conquer-description"></p>
|
||||||
|
<button class="conquer-submit">Elegir este equipo.</button>
|
||||||
|
</div>
|
5
templates/conquer/_conquer-view-node-template.html.ep
Normal file
5
templates/conquer/_conquer-view-node-template.html.ep
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<div id="conquer-view-node-template" class="conquer-display-none conquer-interface-element-padded conquer-display-block">
|
||||||
|
<h1>Vista de nodo.</h1>
|
||||||
|
<h2 class="node-name"></h2>
|
||||||
|
<p class="node-description"></p>
|
||||||
|
</div>
|
4
templates/conquer/_create-node-slide.html.ep
Normal file
4
templates/conquer/_create-node-slide.html.ep
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<div class="create-node-slide conquer-display-none" id="create-node-slide">
|
||||||
|
<button id="create-node-new-node">Autogenerado.</button>
|
||||||
|
<button id="create-node-exit">Salir del modo crear nodo.</button>
|
||||||
|
</div>
|
6
templates/conquer/_fight-battle-selector-slide.html.ep
Normal file
6
templates/conquer/_fight-battle-selector-slide.html.ep
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<div class="fight-battle-selector-slide conquer-display-none" id="fight-battle-selector-slide">
|
||||||
|
<button class="fight-global-button">
|
||||||
|
<img alt="Fight global" src="/img/conquer/earth-bomb.png"/>
|
||||||
|
<p>Batalla global</p>
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -0,0 +1,2 @@
|
|||||||
|
<div id="conquer-interface-element-padded-template" class="conquer-interface-element-padded conquer-display-none">
|
||||||
|
</div>
|
@ -0,0 +1,5 @@
|
|||||||
|
<div id="conquer-interface-with-top-bar-template" class="conquer-self-player conquer-display-none">
|
||||||
|
<div class="conquer-top-bar">
|
||||||
|
<a href="#" class="conquer-exit-button">X</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
12
templates/conquer/_login-template.html.ep
Normal file
12
templates/conquer/_login-template.html.ep
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<div id="conquer-login-template" class="conquer-login conquer-display-none">
|
||||||
|
<form>
|
||||||
|
<p class="conquer-login-error conquer-display-none"></p>
|
||||||
|
<p class="conquer-login-success conquer-display-none"></p>
|
||||||
|
<label>Nombre de usuario</label>
|
||||||
|
<input class="conquer-login-username"/>
|
||||||
|
<label>Contraseña</label>
|
||||||
|
<input class="conquer-login-password" type="password"/>
|
||||||
|
<button class="conquer-login-submit">Inicia sesión</button>
|
||||||
|
<p>¿No tienes cuenta aun? <a href="#" class="conquer-login-go-to-register">Registrate</a></p>
|
||||||
|
</form>
|
||||||
|
</div>
|
19
templates/conquer/_new-node-form-creation-template.html.ep
Normal file
19
templates/conquer/_new-node-form-creation-template.html.ep
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<div id="conquer-new-node-form-creation-template" class="conquer-new-node-form-creation conquer-display-none conquer-interface-element-padded">
|
||||||
|
<form>
|
||||||
|
<p class="conquer-error conquer-display-none"></p>
|
||||||
|
<label>Tipo de Nodo<br/>
|
||||||
|
<select class="conquer-node-type">
|
||||||
|
<option value="normal">Normal (Conquistable)</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<label>Nuevo Nombre de Nodo<br/>
|
||||||
|
<input type="text" class="conquer-node-name"/></label>
|
||||||
|
</div>
|
||||||
|
<label>Descripción de la Zona<br/>
|
||||||
|
<textarea class="conquer-node-description"></textarea></label>
|
||||||
|
<div>
|
||||||
|
<button class="new-node-form-submit">Finalizar creación de nodo.</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
18
templates/conquer/_new-team-form-creation-template.html.ep
Normal file
18
templates/conquer/_new-team-form-creation-template.html.ep
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<div id="conquer-new-team-form-creation-template" class="conquer-new-team-form-creation conquer-display-none conquer-interface-element-padded">
|
||||||
|
<form>
|
||||||
|
<p class="conquer-error conquer-display-none"></p>
|
||||||
|
<label>Nombre del equipo.<br/>
|
||||||
|
<input class="conquer-team-name"/>
|
||||||
|
</label>
|
||||||
|
<label>Descripción del equipo.<br/>
|
||||||
|
<textarea class="conquer-team-description"></textarea></label>
|
||||||
|
</label>
|
||||||
|
<label>Color del equipo<br/>
|
||||||
|
<input type="color" class="conquer-team-color"/>
|
||||||
|
</label>
|
||||||
|
<div>
|
||||||
|
<button class="new-team-form-submit">Finalizar creación de nodo.</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
2
templates/conquer/_overlay-transparent-template.html.ep
Normal file
2
templates/conquer/_overlay-transparent-template.html.ep
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<div id="conquer-overlay-transparent-template" class="conquer-overlay-transparent conquer-display-none">
|
||||||
|
</div>
|
13
templates/conquer/_register-template.html.ep
Normal file
13
templates/conquer/_register-template.html.ep
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<div id="conquer-register-template" class="conquer-register conquer-display-none">
|
||||||
|
<form>
|
||||||
|
<p class="conquer-register-error conquer-display-none"></p>
|
||||||
|
<label>Nombre de usuario</label>
|
||||||
|
<input class="conquer-register-username"/>
|
||||||
|
<label>Contraseña</label>
|
||||||
|
<input class="conquer-register-password" type="password"/>
|
||||||
|
<label>Repite la contraseña</label>
|
||||||
|
<input class="conquer-register-repeat-password" type="password"/>
|
||||||
|
<button class="conquer-register-submit">Finaliza el registro</button>
|
||||||
|
<p>¿Ya estás registrado? <a href="#" class="conquer-register-go-to-login">Inicia Sesión</a>.</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
41
templates/conquer/index.html.ep
Normal file
41
templates/conquer/index.html.ep
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
% my $css_version = config 'css_version';
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Conquista Burguillos</title>
|
||||||
|
<script src="/js/bundle.js?v=<%=$css_version%>"></script>
|
||||||
|
<link rel="stylesheet" href="/css/styles.css?v=<%=$css_version%>"/>
|
||||||
|
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0'/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="conquer-select-fight-list-template" class="conquer-display-none conquer-interface-element-padded conquer-display-block">
|
||||||
|
<h1>Elige un rival.</h1>
|
||||||
|
</div>
|
||||||
|
<div id="conquer-select-fight-item-template" class="conquer-display-none conquer-interface-element-padded conquer-select-fight">
|
||||||
|
<div class="conquer-image-container">
|
||||||
|
<img alt="" class="conquer-image"/>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<span class="conquer-name"></span>
|
||||||
|
Nivel: <span class="conquer-level"></span>
|
||||||
|
</p>
|
||||||
|
<div class="conquer-button-container">
|
||||||
|
<button class="conquer-submit">Luchar</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
%= include 'conquer/_fight-battle-selector-slide';
|
||||||
|
%= include 'conquer/_overlay-transparent-template';
|
||||||
|
%= include 'conquer/_new-node-form-creation-template';
|
||||||
|
%= include 'conquer/_create-node-slide';
|
||||||
|
%= include 'conquer/_interface-element-padded-template';
|
||||||
|
%= include 'conquer/_interface-with-top-bar-template';
|
||||||
|
%= include 'conquer/_login-template';
|
||||||
|
%= include 'conquer/_register-template';
|
||||||
|
%= include 'conquer/_new-team-form-creation-template';
|
||||||
|
%= include 'conquer/_conquer-select-team-list-template';
|
||||||
|
%= include 'conquer/_conquer-team-to-select-template';
|
||||||
|
%= include 'conquer/_conquer-view-node-template';
|
||||||
|
<div class="conquer-container">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
"outDir": "./public/js/",
|
"outDir": "./public/js/",
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"module": "es2020",
|
"module": "es2020",
|
||||||
|
Loading…
Reference in New Issue
Block a user