Adding first screen with pj list, log and list of available locations.

This commit is contained in:
Sergiotarxz 2023-06-13 02:43:52 +02:00
parent aa9c652fcb
commit 206f2c48a2
51 changed files with 3102 additions and 301 deletions

37
.eslintrc.js Normal file
View File

@ -0,0 +1,37 @@
module.exports = {
env: {
browser: true,
es2021: true
},
extends: [
'plugin:react/recommended',
'standard-with-typescript'
],
overrides: [
],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: 'tsconfig.json'
},
plugins: [
'react',
'no-relative-import-paths'
],
rules: {
indent: ['error', 4, { SwitchCase: 1 }],
'no-relative-import-paths/no-relative-import-paths': ['warn', { allowSameFolder: true }],
'@typescript-eslint/indent': ['error', 4],
'react/jsx-indent': ['error', 4],
'react/jsx-indent-props': ['error', 4]
},
settings: {
'import/resolver': {
typescript: {
project: [
'tsconfig.json'
]
}
}
}
}

1
.vimrc Normal file
View File

@ -0,0 +1 @@
let g:ale_linters = {'perl': ['perl','perlcritic'] }

View File

@ -20,6 +20,7 @@ my $build = Module::Build->new(
'UUID::URandom' => 0,
'Module::Pluggable' => 0,
'Redis' => 0,
'List::AllUtils' => 0,
},
);
$build->create_build_script;

View File

@ -0,0 +1,292 @@
--
-- Created by SQL::Translator::Producer::PostgreSQL
-- Created on Tue Jun 13 00:53:45 2023
--
;
--
-- Table: equipment
--
CREATE TABLE "equipment" (
"uuid" uuid NOT NULL,
PRIMARY KEY ("uuid")
);
;
--
-- Table: inventories
--
CREATE TABLE "inventories" (
"uuid" uuid NOT NULL,
PRIMARY KEY ("uuid")
);
;
--
-- Table: players
--
CREATE TABLE "players" (
"uuid" uuid NOT NULL,
"username" text NOT NULL,
"encrypted_password" text NOT NULL,
"email" text NOT NULL,
"verified" boolean NOT NULL,
"verification_token" text,
"register_date" timestamp DEFAULT NOW() NOT NULL,
"last_activity" timestamp DEFAULT NOW() NOT NULL,
PRIMARY KEY ("uuid"),
CONSTRAINT "unique_constraint_email" UNIQUE ("email"),
CONSTRAINT "unique_constraint_username" UNIQUE ("username")
);
;
--
-- Table: skill_like_lists
--
CREATE TABLE "skill_like_lists" (
"uuid" uuid DEFAULT uuid_generate_v4() NOT NULL,
PRIMARY KEY ("uuid")
);
;
--
-- Table: stats
--
CREATE TABLE "stats" (
"uuid" uuid NOT NULL,
"health" integer NOT NULL,
"mana" integer NOT NULL,
"strength" integer NOT NULL,
"resistance" integer NOT NULL,
"magic" integer NOT NULL,
"speed" integer NOT NULL,
"intelligence" integer NOT NULL,
PRIMARY KEY ("uuid")
);
;
--
-- Table: equipment_items
--
CREATE TABLE "equipment_items" (
"kind" text NOT NULL,
"equipment" uuid NOT NULL,
"identifier" text NOT NULL,
"quantity" integer NOT NULL,
PRIMARY KEY ("kind", "equipment")
);
CREATE INDEX "equipment_items_idx_equipment" on "equipment_items" ("equipment");
;
--
-- Table: inventory_items
--
CREATE TABLE "inventory_items" (
"uuid" uuid DEFAULT uuid_generate_v4() NOT NULL,
"inventory" uuid NOT NULL,
"identifier" text NOT NULL,
"quantity" integer NOT NULL,
PRIMARY KEY ("uuid")
);
CREATE INDEX "inventory_items_idx_inventory" on "inventory_items" ("inventory");
;
--
-- Table: skill_like_items
--
CREATE TABLE "skill_like_items" (
"identifier" text NOT NULL,
"owner_list" uuid NOT NULL,
"level" integer DEFAULT 1 NOT NULL,
PRIMARY KEY ("identifier", "owner_list")
);
CREATE INDEX "skill_like_items_idx_owner_list" on "skill_like_items" ("owner_list");
;
--
-- Table: teams
--
CREATE TABLE "teams" (
"uuid" uuid NOT NULL,
"leader" uuid,
"name" text NOT NULL,
"planet" text NOT NULL,
"super_area" text NOT NULL,
"area" text NOT NULL,
"location" text NOT NULL,
PRIMARY KEY ("uuid"),
CONSTRAINT "u_name" UNIQUE ("name")
);
CREATE INDEX "teams_idx_leader" on "teams" ("leader");
;
--
-- Table: player_pjs
--
CREATE TABLE "player_pjs" (
"uuid" uuid DEFAULT uuid_generate_v4() NOT NULL,
"owner" uuid NOT NULL,
"full_name" text NOT NULL,
"short_name" text NOT NULL,
"nick" text NOT NULL,
"race" text NOT NULL,
"team" uuid NOT NULL,
"creation_date" timestamp DEFAULT NOW() NOT NULL,
"last_activity" timestamp DEFAULT NOW() NOT NULL,
"experience" integer DEFAULT 1 NOT NULL,
"equipment" uuid NOT NULL,
"born_stats" uuid NOT NULL,
"training_stats" uuid NOT NULL,
"skills" uuid NOT NULL,
"spells" uuid NOT NULL,
"inventory" uuid NOT NULL,
"health" integer NOT NULL,
"mana" integer NOT NULL,
PRIMARY KEY ("uuid")
);
CREATE INDEX "player_pjs_idx_born_stats" on "player_pjs" ("born_stats");
CREATE INDEX "player_pjs_idx_equipment" on "player_pjs" ("equipment");
CREATE INDEX "player_pjs_idx_inventory" on "player_pjs" ("inventory");
CREATE INDEX "player_pjs_idx_owner" on "player_pjs" ("owner");
CREATE INDEX "player_pjs_idx_skills" on "player_pjs" ("skills");
CREATE INDEX "player_pjs_idx_spells" on "player_pjs" ("spells");
CREATE INDEX "player_pjs_idx_team" on "player_pjs" ("team");
CREATE INDEX "player_pjs_idx_training_stats" on "player_pjs" ("training_stats");
;
--
-- Table: player_companion_npcs
--
CREATE TABLE "player_companion_npcs" (
"uuid" uuid DEFAULT uuid_generate_v4() NOT NULL,
"owner" uuid NOT NULL,
"identifier" text NOT NULL,
"nick" text,
"race" text NOT NULL,
"level" integer DEFAULT 1 NOT NULL,
"exp" integer DEFAULT 1 NOT NULL,
"equipment" uuid NOT NULL,
"stats" uuid NOT NULL,
"skills" uuid NOT NULL,
"spells" uuid NOT NULL,
"inventory" uuid NOT NULL,
PRIMARY KEY ("uuid")
);
CREATE INDEX "player_companion_npcs_idx_equipment" on "player_companion_npcs" ("equipment");
CREATE INDEX "player_companion_npcs_idx_inventory" on "player_companion_npcs" ("inventory");
CREATE INDEX "player_companion_npcs_idx_owner" on "player_companion_npcs" ("owner");
CREATE INDEX "player_companion_npcs_idx_skills" on "player_companion_npcs" ("skills");
CREATE INDEX "player_companion_npcs_idx_spells" on "player_companion_npcs" ("spells");
CREATE INDEX "player_companion_npcs_idx_stats" on "player_companion_npcs" ("stats");
;
--
-- Table: player_pjs_flags
--
CREATE TABLE "player_pjs_flags" (
"name" text NOT NULL,
"owner" uuid NOT NULL,
PRIMARY KEY ("name", "owner")
);
CREATE INDEX "player_pjs_flags_idx_owner" on "player_pjs_flags" ("owner");
CREATE INDEX "index_flag" on "player_pjs_flags" ("owner", "name");
;
--
-- Table: player_pjs_log
--
CREATE TABLE "player_pjs_log" (
"uuid" uuid NOT NULL,
"content" jsonb NOT NULL,
"owner" uuid NOT NULL,
"date" timestamp DEFAULT NOW() NOT NULL,
PRIMARY KEY ("uuid")
);
CREATE INDEX "player_pjs_log_idx_owner" on "player_pjs_log" ("owner");
CREATE INDEX "index_log" on "player_pjs_log" ("owner", "date");
;
--
-- Foreign Key Definitions
--
;
ALTER TABLE "equipment_items" ADD CONSTRAINT "equipment_items_fk_equipment" FOREIGN KEY ("equipment")
REFERENCES "equipment" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;
ALTER TABLE "inventory_items" ADD CONSTRAINT "inventory_items_fk_inventory" FOREIGN KEY ("inventory")
REFERENCES "inventories" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;
ALTER TABLE "skill_like_items" ADD CONSTRAINT "skill_like_items_fk_owner_list" FOREIGN KEY ("owner_list")
REFERENCES "skill_like_lists" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;
ALTER TABLE "teams" ADD CONSTRAINT "teams_fk_leader" FOREIGN KEY ("leader")
REFERENCES "player_pjs" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_pjs" ADD CONSTRAINT "player_pjs_fk_born_stats" FOREIGN KEY ("born_stats")
REFERENCES "stats" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_pjs" ADD CONSTRAINT "player_pjs_fk_equipment" FOREIGN KEY ("equipment")
REFERENCES "equipment" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_pjs" ADD CONSTRAINT "player_pjs_fk_inventory" FOREIGN KEY ("inventory")
REFERENCES "inventories" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_pjs" ADD CONSTRAINT "player_pjs_fk_owner" FOREIGN KEY ("owner")
REFERENCES "players" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;
ALTER TABLE "player_pjs" ADD CONSTRAINT "player_pjs_fk_skills" FOREIGN KEY ("skills")
REFERENCES "skill_like_lists" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_pjs" ADD CONSTRAINT "player_pjs_fk_spells" FOREIGN KEY ("spells")
REFERENCES "skill_like_lists" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_pjs" ADD CONSTRAINT "player_pjs_fk_team" FOREIGN KEY ("team")
REFERENCES "teams" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;
ALTER TABLE "player_pjs" ADD CONSTRAINT "player_pjs_fk_training_stats" FOREIGN KEY ("training_stats")
REFERENCES "stats" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_companion_npcs" ADD CONSTRAINT "player_companion_npcs_fk_equipment" FOREIGN KEY ("equipment")
REFERENCES "equipment" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_companion_npcs" ADD CONSTRAINT "player_companion_npcs_fk_inventory" FOREIGN KEY ("inventory")
REFERENCES "inventories" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_companion_npcs" ADD CONSTRAINT "player_companion_npcs_fk_owner" FOREIGN KEY ("owner")
REFERENCES "player_pjs" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;
ALTER TABLE "player_companion_npcs" ADD CONSTRAINT "player_companion_npcs_fk_skills" FOREIGN KEY ("skills")
REFERENCES "skill_like_lists" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_companion_npcs" ADD CONSTRAINT "player_companion_npcs_fk_spells" FOREIGN KEY ("spells")
REFERENCES "skill_like_lists" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_companion_npcs" ADD CONSTRAINT "player_companion_npcs_fk_stats" FOREIGN KEY ("stats")
REFERENCES "stats" ("uuid") DEFERRABLE;
;
ALTER TABLE "player_pjs_flags" ADD CONSTRAINT "player_pjs_flags_fk_owner" FOREIGN KEY ("owner")
REFERENCES "player_pjs" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;
ALTER TABLE "player_pjs_log" ADD CONSTRAINT "player_pjs_log_fk_owner" FOREIGN KEY ("owner")
REFERENCES "player_pjs" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;

View File

@ -0,0 +1,37 @@
-- Convert schema '/home/sergio/LasTres/script/../dbicdh/_source/deploy/4/001-auto.yml' to '/home/sergio/LasTres/script/../dbicdh/_source/deploy/5/001-auto.yml':;
;
BEGIN;
;
CREATE TABLE "player_pjs_flags" (
"name" text NOT NULL,
"owner" uuid NOT NULL,
PRIMARY KEY ("name", "owner")
);
CREATE INDEX "player_pjs_flags_idx_owner" on "player_pjs_flags" ("owner");
CREATE INDEX "index_flag" on "player_pjs_flags" ("owner", "name");
;
CREATE TABLE "player_pjs_log" (
"uuid" uuid NOT NULL,
"content" jsonb NOT NULL,
"owner" uuid NOT NULL,
"date" timestamp DEFAULT NOW() NOT NULL,
PRIMARY KEY ("uuid")
);
CREATE INDEX "player_pjs_log_idx_owner" on "player_pjs_log" ("owner");
CREATE INDEX "index_log" on "player_pjs_log" ("owner", "date");
;
ALTER TABLE "player_pjs_flags" ADD CONSTRAINT "player_pjs_flags_fk_owner" FOREIGN KEY ("owner")
REFERENCES "player_pjs" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;
ALTER TABLE "player_pjs_log" ADD CONSTRAINT "player_pjs_log_fk_owner" FOREIGN KEY ("owner")
REFERENCES "player_pjs" ("uuid") ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE;
;
COMMIT;

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,15 @@
import * as React from 'react'
import { PJ } from '@lastres/pj'
import type { PJ } from '@lastres/pj'
import type { Location } from '@lastres/location'
import type { LogLine } from '@lastres/log-line'
import UpperPanel from '@lastres/components/upper-panel'
import BottomPanel from '@lastres/components/bottom-panel'
import PJSelectionMenu from '@lastres/components/pj-selection-menu'
import OutputPacketInit from '@lastres/output-packet/init'
import OutputPacketPing from '@lastres/output-packet/ping'
import InputPackets from '@lastres/input-packets'
export interface GameProps {
setSelectedPJ: (set: PJ | null) => void
@ -16,7 +21,8 @@ export interface GameProps {
}
export default function Game (props: GameProps): JSX.Element {
if (props.selectedPJ === null) {
const selectedPJ = props.selectedPJ
if (selectedPJ === null) {
return (
<>
<PJSelectionMenu
@ -28,21 +34,47 @@ export default function Game (props: GameProps): JSX.Element {
</>
)
}
window.setTimeout(() => {
const locationProtocol = window.location.protocol
if (locationProtocol == null) {
return
}
const protocol = locationProtocol.match(/https:/) != null ? 'wss' : 'ws'
const webSocket = new WebSocket(`${protocol}://${window.location.host}/ws`)
webSocket.onopen = () => {
webSocket.send(JSON.stringify({hola: "mundo"}))
};
}, 1);
const [websocket, setWebsocket] = React.useState<WebSocket | null>(null)
const [teamPJs, setTeamPJs] = React.useState<PJ[] | null>(null)
const [currentLocation, setCurrentLocation] = React.useState<Location | null>(null)
const [connectedLocations, setConnectedLocations] = React.useState<Location[] | null>(null)
const [logLines, setLogLines] = React.useState<LogLine[] | null>(null)
if (websocket === null) {
window.setTimeout(() => {
const locationProtocol = window.location.protocol
if (locationProtocol == null) {
return
}
const protocol = locationProtocol.match(/https:/) != null ? 'wss' : 'ws'
const webSocket = new WebSocket(`${protocol}://${window.location.host}/ws`)
webSocket.onopen = () => {
new OutputPacketInit(selectedPJ.uuid).send(webSocket)
let interval: number = 0
interval = window.setInterval(() => {
if (webSocket.readyState === WebSocket.OPEN) {
new OutputPacketPing().send(webSocket)
return
}
window.clearInterval(interval)
}, 10000)
}
const inputPackets = new InputPackets(setTeamPJs, setCurrentLocation, setConnectedLocations, setLogLines)
webSocket.onmessage = (event) => {
const packet = JSON.parse(event.data)
inputPackets.handle(packet)
}
webSocket.onerror = (event) => {
console.log(event)
}
setWebsocket(webSocket)
}, 1)
}
return (
<>
<UpperPanel/>
<UpperPanel teamPJs={teamPJs}
currentLocation={currentLocation}
connectedLocations={connectedLocations}
logLines={logLines}/>
<BottomPanel/>
</>
)

View File

@ -0,0 +1,35 @@
import * as React from 'react'
import type { PJ } from '@lastres/pj'
import PJHealthLikeBar from '@lastres/components/pj-health-like-bar'
export interface PJListItemProps {
pj: PJ
}
export default function PJListItem (props: PJListItemProps): JSX.Element {
const pj = props.pj
function avatar (): React.ReactNode {
if (pj.image === undefined) {
return <></>
}
return <><img src={pj.image}/><div className="shadow"/></>
}
return (
<div className="pj-list-item">
<div className="avatar">
{
avatar()
}
</div>
<div className="data">
<p>{pj.nick}</p>
<label className="bar-container">
Salud
<PJHealthLikeBar value={pj.health} max={pj.max_health}/>
</label>
<label className="bar-container">
Mana
<PJHealthLikeBar value={pj.mana} max={pj.max_mana}/>
</label>
</div>
</div>
)
}

View File

@ -1,5 +1,5 @@
import * as React from 'react'
import { PJ } from '@lastres/pj'
import type { PJ } from '@lastres/pj'
import PJHealthLikeBar from '@lastres/components/pj-health-like-bar'
interface PJListSelectionProps {
@ -7,7 +7,7 @@ interface PJListSelectionProps {
setSelectedPJ: (set: PJ | null) => void
}
export default function PJListSelection(props: PJListSelectionProps) {
export default function PJListSelection (props: PJListSelectionProps): JSX.Element {
const pjs = props.pjs
if (pjs === null) {
return (
@ -18,24 +18,21 @@ export default function PJListSelection(props: PJListSelectionProps) {
return (
<>
{
pjs.map( (item, i) =>
<a onClick={() => {
props.setSelectedPJ(item)
}}
href="#"
key={i}>
pjs.map((item, i) =>
<a onClick={() => {
props.setSelectedPJ(item)
}} href="#" key={i}>
<span>{item.full_name}</span>
<span>{item.short_name}</span>
<span>{item.nick}</span>
<label>
Salud
<label className="bar-container">
<PJHealthLikeBar value={item.health} max={item.max_health}/>
</label>
<label>
Mana
<label className="bar-container">
Mana
<PJHealthLikeBar value={item.mana} max={item.max_mana}/>
</label>
</a> )
</a>)
}
</>
)

View File

@ -1,7 +1,85 @@
import * as React from 'react'
export default function UpperPanel (): JSX.Element {
import type { Location } from '@lastres/location'
import type { PJ } from '@lastres/pj'
import type { LogLine } from '@lastres/log-line'
import PJListItem from '@lastres/components/pj-list-item'
interface UpperPanelProps {
connectedLocations: Location[] | null
teamPJs: PJ[] | null
currentLocation: Location | null
logLines: LogLine[] | null
}
interface Style {
color?: string;
background?: string;
}
export default function UpperPanel (props: UpperPanelProps): JSX.Element {
const connectedLocations = props.connectedLocations
const teamPJs = props.teamPJs
const currentLocation = props.currentLocation
const logLines = props.logLines
if (!(teamPJs !== null && currentLocation !== null && connectedLocations !== null)) {
return (
<>
<p>Esperando datos...</p>
</>
)
}
function generateLog (): React.ReactNode {
if (logLines === null || logLines.length < 1) {
return (
<>No log</>
)
}
return logLines.map((item, i) => {
return <>
<b>{item.date}</b> {
item.content.map((item, i) => {
const style: Style = {}
if (item.color !== undefined) {
style.color = item.color
}
if (item.background !== undefined) {
style.background = item.background
}
return <span key={i} style={style}>{item.text}</span>
})
} <br/>
</>
})
}
return (
<>
</>
<div className="presentation">
<div className="presentation-item">
{
teamPJs.map((item, i) => {
return <PJListItem key={i} pj={item}/>
})
}
</div>
<div className="presentation-item">
<code><pre>
{
generateLog()
}
</pre></code>
</div>
<div className="presentation-item">
<p>Estás en {currentLocation.area.name}/{currentLocation.location.name}.</p>
<p>Puedes ir a:</p>
<ul>
{
connectedLocations.map((item, i) => {
return <li key={i}>
<a href="#">{item.area.name}/{item.location.name}</a>
</li>
})
}
</ul>
</div>
</div>
)
}

20
js-src/input-packet.ts Normal file
View File

@ -0,0 +1,20 @@
interface InputPacketJSONDecoded {
command: string
data: any
}
type onReceiveCallback = (data: any) => void
export default abstract class InputPacket {
onreceive: onReceiveCallback | null = null
onReceive (callback: onReceiveCallback): void {
this.onreceive = callback
}
abstract identifier (): string
recv (packet: InputPacketJSONDecoded): void {
if (this.onreceive !== null) {
this.onreceive(packet.data)
}
}
}

View File

@ -0,0 +1,6 @@
import InputPacket from '@lastres/input-packet'
export default class InputPacketInfo extends InputPacket {
identifier (): string {
return 'info'
}
}

View File

@ -0,0 +1,6 @@
import InputPacket from '@lastres/input-packet';
export default class InputPacketPong extends InputPacket {
identifier() {
return 'pong'
}
}

64
js-src/input-packets.ts Normal file
View File

@ -0,0 +1,64 @@
import type { PJ } from '@lastres/pj'
import type { Location } from '@lastres/location'
import type InputPacket from '@lastres/input-packet'
import type { LogLine } from '@lastres/log-line'
import InputPacketInfo from '@lastres/input-packet/info'
import InputPacketPong from '@lastres/input-packet/pong'
type SetTeamPJs = (set: PJ[] | null) => void
type SetCurrentLocation = (set: Location | null) => void
type SetConnectedLocations = (set: Location[] | null) => void
type DispatchHash = Record<string, any>
type SetLogLines = (set: LogLine[] | null) => void
interface Packet {
command: string
data: any
}
export default class InputPackets {
setTeamPJs: SetTeamPJs
setCurrentLocation: SetCurrentLocation
setConnectedLocations: SetConnectedLocations
setLogLines: SetLogLines
cachedHash: DispatchHash | null = null
cachedArray: InputPacket[] | null = null
constructor (setTeamPJs: SetTeamPJs,
setCurrentLocation: SetCurrentLocation,
setConnectedLocations: SetConnectedLocations,
setLogLines: SetLogLines) {
this.setTeamPJs = setTeamPJs
this.setCurrentLocation = setCurrentLocation
this.setConnectedLocations = setConnectedLocations
this.setLogLines = setLogLines
}
handle (packet: Packet): void {
const hash = this.hashAvailablePackets()
const identifier = packet.command
const inputPacket = hash[identifier]
inputPacket.recv(packet)
}
listAvailablePackets (): InputPacket[] {
if (this.cachedArray === null) {
const infoPacket = new InputPacketInfo()
const pongPacket = new InputPacketPong()
infoPacket.onReceive((data) => {
this.setTeamPJs(data.team_pjs)
this.setConnectedLocations(data.location_data.connected_places)
this.setCurrentLocation(data.location_data.current)
this.setLogLines(data.set_log)
})
this.cachedArray = [infoPacket, pongPacket]
}
return this.cachedArray
}
hashAvailablePackets (): DispatchHash {
if (this.cachedHash === null) {
this.cachedHash = {}
for (const inputPacket of this.listAvailablePackets()) {
this.cachedHash[inputPacket.identifier()] = inputPacket
}
}
return this.cachedHash
}
}

10
js-src/location.ts Normal file
View File

@ -0,0 +1,10 @@
export interface NameIdentifierHash {
name: string
identifier: string
}
export interface Location {
location: NameIdentifierHash
area: NameIdentifierHash
super_area: NameIdentifierHash
planet: NameIdentifierHash
}

7
js-src/log-line.ts Normal file
View File

@ -0,0 +1,7 @@
import type { LogSection } from '@lastres/log-section'
export interface LogLine {
content: LogSection[]
date: string
uuid: string
}

5
js-src/log-section.ts Normal file
View File

@ -0,0 +1,5 @@
export interface LogSection {
color?: string
background?: string
text: string
}

11
js-src/output-packet.ts Normal file
View File

@ -0,0 +1,11 @@
export default abstract class OutputPacket {
send(ws: WebSocket): void {
const data = this.data();
ws.send(JSON.stringify({
command: this.command(),
data: this.data(),
}))
}
abstract data(): any
abstract command(): string
}

View File

@ -0,0 +1,22 @@
import OutputPacket from '@lastres/output-packet';
interface OutputPacketInitData {
pj_uuid: string
}
export default class OutputPacketInit extends OutputPacket {
pj_uuid: string
constructor(pj_uuid: string) {
super()
this.pj_uuid = pj_uuid
}
command(): string {
return 'init'
}
data(): OutputPacketInitData {
return {
pj_uuid: this.pj_uuid
}
}
}

View File

@ -0,0 +1,16 @@
import OutputPacket from '@lastres/output-packet';
export default class OutputPacketPing extends OutputPacket {
pj_uuid: string
constructor() {
super()
}
command(): string {
return 'ping'
}
data(): null {
return null
}
}

View File

@ -2,21 +2,22 @@ export interface PJ {
full_name: string
short_name: string
nick: string
health: number
mana: number
health: number
mana: number
max_mana: number
max_health: number
max_health: number
race: string
uuid: string
image?: string
}
export async function fetchMyPjs(setError: (set: string | null) => void): Promise<PJ[]> {
export async function fetchMyPjs (setError: (set: string | null) => void): Promise<PJ[]> {
const response = await fetch('/my/pjs', {
method: 'GET',
mode: 'same-origin',
cache: 'no-cache'
}).catch((error) => {
console.log(error);
console.log(error)
setError('Error recuperando tus pjs')
})
if (response === undefined) {

View File

@ -5,7 +5,22 @@ use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Moo::Role;
requires qw/identifier locations name parent/;
has children => (
is => 'ro',
lazy => 1,
builder => \&_build_children,
);
sub _build_children($self) {
my $locations = $self->locations;
my @locations = map { $locations->{$_} } keys %$locations;
@locations = sort { $a->name cmp $b->name } @locations;
return \@locations;
}
1;

View File

@ -39,17 +39,7 @@ sub list_my_pjs ($self) {
my @pjs = $user->pjs;
my @pjs_hash = (
map {
{
uuid => $_->uuid,
full_name => $_->full_name,
short_name => $_->short_name,
nick => $_->nick,
race => $_->race,
health => $_->health,
max_health => $_->max_health,
mana => $_->mana,
max_mana => $_->max_mana,
}
$_->hash;
} @pjs
);
return $self->render(

View File

@ -12,13 +12,13 @@ use Mojo::Base 'Mojolicious::Controller', -signatures;
use Data::Dumper;
use LasTres::Redis;
use LasTres::DAO::PJs;
use LasTres::Controller::Websocket::InputPackets;
my %sessions;
use LasTres::DAO::PJs;
my $result_set_pjs = LasTres::DAO::PJs->ResultSet;
my $redis = LasTres::Redis->new;
my $redis = LasTres::Redis->new;
my $input_packets = LasTres::Controller::Websocket::InputPackets->new;
sub ws ($self) {
@ -46,6 +46,7 @@ sub ws ($self) {
$self->on(
finish => sub ( $self, $code, $reason ) {
delete $sessions{$session_uuid};
$reason ||= "No reason";
say STDERR
"Websocket for user @{[$user->username]} closed with status $code and reason $reason.";
}
@ -53,6 +54,7 @@ sub ws ($self) {
}
{
sub _handle_packet ( $self, $session, $hash ) {
my $command = $hash->{command};
if ( !defined $command ) {
@ -60,7 +62,7 @@ sub ws ($self) {
$self->send( encode_json( { error => "No command" } ) );
return;
}
my $input_packet = $input_packets->hash->{$command)
my $input_packet = $input_packets->hash->{$command};
if ( !defined $input_packet ) {
say STDERR "Unknown command $command.";

View File

@ -5,21 +5,31 @@ use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Data::Dumper;
use Moo;
use JSON;
use LasTres::Flags;
use LasTres::Redis;
use LasTres::DAO::PJs;
with 'LasTres::Controller::Websocket::InputPacket';
my $redis = LasTres::Redis->new;
my $result_set_pjs = LasTres::DAO::PJs->ResultSet;
sub identifier {
return 'init';
}
my $redis = LasTres::Redis->new;
sub handle ( $self, $ws, $session, $data ) {
if (ref $data ne 'HASH') {
return $ws->send( encode_json( { error => "Data should be a hashref." } ) );
if ( ref $data ne 'HASH' ) {
return $ws->send(
encode_json( { error => "Data should be a hashref." } ) );
}
my $pj_uuid = $data->{pj_uuid};
if ( !defined $pj_uuid ) {
@ -37,6 +47,55 @@ sub handle ( $self, $ws, $session, $data ) {
encode_json( { error => 'You are not the owner of this pj.' } ) );
}
$session->{pj} = $pj;
my $team = $pj->team;
my @team_members = $team->members;
my @friends = grep { $pj->uuid ne $_->uuid } @team_members;
my $team_pjs = [ map { $_->hash } ( $pj, @friends ) ];
my $location = $team->location;
my $connected_places = $self->_get_connected_places($pj);
$pj->append_log_line(
[
{ text => 'Nueva conexion a este pj.', color => 'red' },
]
);
if ( !$pj->get_flag( LasTres::Flags::INTRO_MESSAGE_SENT_FLAG() ) ) {
$pj->set_flag(LasTres::Flags::INTRO_MESSAGE_SENT_FLAG);
$pj->append_log_line(
[
{ text => 'Bienvenido a ' },
{ text => 'LasTres', color => 'green' },
{ text => '. Esperamos que disfrutes del juego.' }
]
);
}
my $info_packet_to_send =
LasTres::Controller::Websocket::OutputPacket::Info->new(
set_log => [$pj->last_50_log],
team_pjs => $team_pjs,
location_data => {
current => $location->hash,
connected_places => $connected_places,
},
clear => $JSON::true,
);
$info_packet_to_send->send($ws);
}
sub _get_connected_places ( $self, $pj ) {
my $team = $pj->team;
my $location = $team->location;
my $connected_places = [];
if ( $location->can('connected_places') ) {
@$connected_places = ( @{ $team->location->connected_places } );
}
@$connected_places =
( @$connected_places, @{ $location->parent->children } );
@$connected_places =
grep { $_->identifier ne $location->identifier } @$connected_places;
@$connected_places = map { $_->hash } @$connected_places;
return $connected_places;
}
1;

View File

@ -0,0 +1,32 @@
package LasTres::Controller::Websocket::InputPacket::Ping;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Data::Dumper;
use Moo;
use JSON;
use LasTres::Redis;
use LasTres::DAO::PJs;
with 'LasTres::Controller::Websocket::InputPacket';
my $redis = LasTres::Redis->new;
my $result_set_pjs = LasTres::DAO::PJs->ResultSet;
sub identifier {
return 'ping';
}
sub handle ( $self, $ws, $session, $data ) {
LasTres::Controller::Websocket::OutputPacket::Pong->new->send($ws);
}
1;

View File

@ -7,16 +7,16 @@ use warnings;
use Moo::Role;
use JSON qw/encode_json/;
requires qw/new identifier data/;
sub send ( $self, $ws ) {
return $ws->send(
encode_json(
{
command => $self->identifier,
data => $self->data
}
)
);
return $ws->send({json =>
{
command => $self->identifier,
data => $self->data
}
});
}
1;

View File

@ -7,6 +7,10 @@ use warnings;
use feature 'signatures';
use Data::Dumper;
use Moo;
with 'LasTres::Controller::Websocket::OutputPacket';
has clear => (
@ -21,6 +25,10 @@ has location_data => (
is => 'rw',
);
has set_log => (
is => 'rw',
);
sub identifier {
return 'info';
}
@ -29,6 +37,7 @@ sub data($self) {
my $clear = $self->clear;
my $team_pjs = $self->team_pjs;
my $location_data = $self->location_data;
my $set_log = $self->set_log;
return {
(
(defined $clear)
@ -44,6 +53,11 @@ sub data($self) {
(defined $location_data)
? (location_data => $location_data)
: ()
),
(
(defined $set_log)
? (set_log => [map { $_->hash } @$set_log])
: ()
)
};
}

View File

@ -0,0 +1,21 @@
package LasTres::Controller::Websocket::OutputPacket::Pong;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Moo;
with 'LasTres::Controller::Websocket::OutputPacket';
sub identifier {
return 'pong';
}
sub data($self) {
return undef;
}
1;

View File

@ -7,17 +7,15 @@ use warnings;
use feature 'signatures';
use Moo;
use Data::Dumper;
use Params::ValidationCompiler qw/validation_for/;
use Types::Standard qw/Str Bool/;
use Moo;
use LasTres::Schema;
my $schema = LasTres::Schema->Schema;
my $result_set = $schema->resultset('PJ');
sub ResultSet {
my $schema = LasTres::Schema->Schema;
my $result_set = $schema->resultset('PJ');
return $result_set;
}
1;

View File

@ -11,10 +11,10 @@ use Moo;
use LasTres::Schema;
my $schema = LasTres::Schema->Schema;
my $result_set = $schema->resultset('Player');
sub ResultSet {
my $schema = LasTres::Schema->Schema;
my $result_set = $schema->resultset('Player');
return $result_set;
}
1;

11
lib/LasTres/Flags.pm Normal file
View File

@ -0,0 +1,11 @@
package LasTres::Flags;
use v5.36.0;
use strict;
use warnings;
sub INTRO_MESSAGE_SENT_FLAG {
return 'INTRO_MESSAGE_FLAG_SENT';
}
1;

View File

@ -5,7 +5,69 @@ use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use LasTres::Planets;
use Moo::Role;
requires qw/identifier name description parent actions npcs/;
my $planets = LasTres::Planets->new;
sub get($planet_id, $super_area_id, $area_id, $location_id) {
my $planet = $planets->hash->{$planet_id};
if (!defined $planet) {
die "No such planet $planet_id.";
}
my $super_area = $planet->super_areas->{$super_area_id};
if (!defined $super_area) {
die "No such super_area $super_area_id in planet $planet_id.";
}
my $area = $super_area->areas->{$area_id};
if (!defined $area) {
die "No such area $area_id in super_area $super_area_id in planet $planet_id.";
}
my $location = $area->locations->{$location_id};
if (!defined $location) {
die "No such location $location_id in area $area_id in super_area $super_area_id in planet $planet_id.";
}
return $location;
}
sub hash($self) {
my $location = $self;
if (!Moo::Role::does_role($location, 'LasTres::Location')) {
die "$location does not implement LasTres::Location.";
}
my $area = $location->parent;
if (!Moo::Role::does_role($area, 'LasTres::Area')) {
die "$area does not implement LasTres::Area.";
}
my $super_area = $area->parent;
if (!Moo::Role::does_role($super_area, 'LasTres::SuperArea')) {
die "$super_area does not implement LasTres::SuperArea.";
}
my $planet = $super_area->parent;
if (!Moo::Role::does_role($planet, 'LasTres::Planet')) {
die "$planet does not implement LasTres::Planet.";
}
return {
planet => {
name => $planet->name,
identifier => $planet->identifier,
},
super_area => {
name => $super_area->name,
identifier => $super_area->identifier,
},
area => {
name => $area->name,
identifier => $area->identifier,
},
location => {
name => $location->name,
identifier => $location->identifier,
},
}
}
1;

View File

@ -1,137 +0,0 @@
package LasTres::PJ;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Scalar::Util qw/blessed/;
use LasTres::DAO::PJs;
my $result_set = LasTres::DAO::PJs->ResultSet;
use Moo;
has uuid => (
is => 'rw',
required => 1,
);
has owner => (
is => 'rw',
required => 1,
);
has full_name => (
is => 'rw',
required => 1,
);
has short_name => (
is => 'rw',
required => 1,
);
has nick => (
is => 'rw',
required => 1,
);
has race => (
is => 'rw',
required => 1,
);
has team => (
is => 'rw',
required => 1,
);
has creation_date => (
is => 'rw',
required => 1,
);
has last_activity => (
is => 'rw',
required => 1,
);
has experience => (
is => 'rw',
required => 1,
);
has equipment => (
is => 'rw',
required => 1,
);
sub _coerce_stats($attr) {
if (blessed($attr) eq 'LasTres::Schema::Result::Stats') {
return $attr->model;
}
}
has born_stats => (
is => 'rw',
required => 1,
coerce => \&_coerce_stats,
);
has training_stats => (
is => 'rw',
required => 1,
coerce => \&_coerce_stats,
);
sub _coerce_skills($attr) {
if (blessed($attr) eq 'LasTres::Schema::Result::SkillLikeList') {
return $attr->model;
}
}
has skills => (
is => 'rw',
required => 1,
coerce => \&_coerce_skills,
);
has spells => (
is => 'rw',
required => 1,
coerce => \&_coerce_skills,
);
has inventory => (
is => 'rw',
required => 1,
);
sub hash ($self) {
return {
uuid => $self->uuid,
owner => $self->owner,
full_name => $self->full_name,
short_name => $self->short_name,
nick => $self->nick,
race => $self->race,
team => $self->team,
creation_date => $self->creation_date,
last_activity => $self->last_activity,
experience => $self->experience,
equipment => $self->equipment,
born_stats => $self->born_stats,
training_stats => $self->training_stats,
skills => $self->skills,
spells => $self->spells,
inventory => $self->inventory,
};
}
sub result_set ($self) {
return $result_set->new( %{ $self->hash } );
}
1;

View File

@ -7,7 +7,13 @@ use warnings;
use Moo;
use Module::Pluggable search_path => ['LasTres::Planet::Bahdder'];
use utf8;
use Module::Pluggable search_path => ['LasTres::Planet::Bahdder'],
instantiate => 'instance',
on_require_error => sub ($plugin, $error) {
die $error;
};
has super_areas => (
is => 'ro',

View File

@ -7,6 +7,8 @@ use warnings;
use feature 'signatures';
use utf8;
use Moo;
use Module::Pluggable search_path => ['LasTres::Planet::Bahdder::BosqueDelHeroe'],

View File

@ -7,6 +7,8 @@ use warnings;
use feature 'signatures';
use utf8;
use Module::Pluggable search_path => ['LasTres::Planet::Bahdder::BosqueDelHeroe::BosqueDelHeroeI'],
instantiate => 'instance',
on_require_error => sub ($plugin, $error) {

View File

@ -4,6 +4,7 @@ use v5.36.0;
use strict;
use warnings;
use utf8;
use Moo;
@ -67,6 +68,12 @@ sub _build_npcs {
return [];
}
sub connected_places {
return [
LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima::Entrada->instance,
];
}
my $singleton;
sub instance {
my $class = shift;

View File

@ -0,0 +1,72 @@
package LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Module::Pluggable search_path => ['LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima'],
instantiate => 'instance',
on_require_error => sub ($plugin, $error) {
die $error;
};
use Moo;
use LasTres::Planet::Bahdder::BosqueDelHeroe;
has locations => (
is => 'ro',
builder => \&_build_locations,
lazy => 1,
);
has identifier => (
is => 'ro',
builder => \&_build_identifier,
);
has name => (
is => 'ro',
builder => \&_build_name,
);
has parent => (
is => 'ro',
builder => \&_build_parent,
);
with 'LasTres::Area';
sub _build_identifier {
return 'tribu_de_la_lima';
}
sub _build_locations {
my $self = shift;
my $hash = {};
my @locations = $self->plugins();
for my $location (@locations) {
$hash->{$location->identifier} = $location;
}
return $hash;
}
sub _build_name {
return 'Tribu de la Lima';
}
sub _build_parent {
return LasTres::Planet::Bahdder::BosqueDelHeroe->instance;
}
my $singleton;
sub instance {
my $class = shift;
if (!defined $singleton) {
$singleton = $class->new(@_);
}
return $singleton;
}
1;

View File

@ -0,0 +1,88 @@
package LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima::Entrada;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use utf8;
use Moo;
use LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima;
has identifier => (
is => 'ro',
builder => \&_build_identifier,
);
has name => (
is => 'ro',
builder => \&_build_name,
);
has description => (
is => 'ro',
builder => \&_build_description,
);
has parent => (
is => 'ro',
builder => \&_build_parent,
);
has actions => (
is => 'ro',
builder => \&_build_actions,
);
has npcs => (
is => 'ro',
builder => \&_build_npcs,
);
with 'LasTres::Location';
sub _build_identifier {
return 'entrada';
}
sub _build_name {
return 'Entrada';
}
sub _build_description {
return 'Un cartel reza. "Tribu de la Lima. Considerate bienvenido si '
. 'no eres un ladrón o un maleante, '
. 'en caso contrario recorre en sentido inverso el sendero de tus pisadas."';
}
sub _build_parent {
return LasTres::Planet::Bahdder::BosqueDelHeroe::TribuDeLaLima->instance;
}
sub _build_actions {
return [];
}
sub _build_npcs {
return [];
}
sub connected_places {
return [
LasTres::Planet::Bahdder::BosqueDelHeroe::BosqueDelHeroeI::TribuDeLaLima->instance,
];
}
my $singleton;
sub instance {
my $class = shift;
if (!defined $singleton) {
$singleton = $class->new(@_);
}
return $singleton;
}
1;

32
lib/LasTres/Planets.pm Normal file
View File

@ -0,0 +1,32 @@
package LasTres::Planets;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Moo;
use Module::Pluggable search_path => ['LasTres::Planet'],
instantiate => 'instance',
on_require_error => sub ($plugin, $error) {
die $error;
};
has hash => (
is => 'rw',
lazy => 1,
builder => \&_build_hash,
);
sub _build_hash($self) {
my @planets = $self->plugins();
my %hash;
for my $planet (@planets) {
$hash{$planet->identifier} = $planet;
}
return \%hash;
}
1;

View File

@ -51,6 +51,10 @@ has is_playable => (
with 'LasTres::Race';
sub image {
return '/img/aldimor.png';
}
sub _build_spawn {
return
LasTres::Planet::Bahdder::BosqueDelHeroe::BosqueDelHeroeI::TribuDeLaLima

View File

@ -1,11 +1,12 @@
package LasTres::Schema;
our $VERSION = 4;
use v5.36.0;
use strict;
use warnings;
our $VERSION = 5;
use feature 'signatures';
use LasTres;
@ -15,29 +16,33 @@ use parent 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces();
my $schema;
sub Schema($class) {
if (!defined $schema) {
my $app = LasTres->new;
my $config = $app->{config};
sub Schema ($class) {
if ( !defined $schema ) {
my $app = LasTres->new;
my $config = $app->{config};
my $database_config = $config->{database};
my $dbname = $database_config->{dbname};
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) {
my $dbname = $database_config->{dbname};
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) {
if ( defined $host ) {
$dsn .= ";host=$host";
}
if (defined $port) {
if ( defined $port ) {
$dsn .= ";port=$port";
}
# Undef is perfectly fine for username and password.
$schema = $class->connect($dsn, $user, $password, {auto_savepoint => 1});
$schema =
$class->connect( $dsn, $user, $password, { auto_savepoint => 1 } );
}
return $schema;
}

View File

@ -9,9 +9,11 @@ use feature 'signatures';
use parent 'DBIx::Class::Core';
use UUID::URandom qw/create_uuid_string/;
use List::AllUtils;
use Data::Dumper;
use LasTres::Schema;
use JSON qw/to_json/;
use Moo;
@ -43,6 +45,7 @@ __PACKAGE__->add_columns(
race => {
data_type => 'text',
is_nullable => 0,
accessor => "_race",
},
team => {
data_type => 'uuid',
@ -94,12 +97,12 @@ __PACKAGE__->add_columns(
},
health => {
data_type => 'integer',
accessor => '_health',
accessor => '_health',
is_nullable => 0,
},
mana => {
data_type => 'integer',
accessor => '_mana',
accessor => '_mana',
is_nullable => 0,
}
);
@ -108,6 +111,8 @@ __PACKAGE__->set_primary_key('uuid');
__PACKAGE__->has_many( 'npcs', 'LasTres::Schema::Result::CompanionNPC',
'owner' );
__PACKAGE__->has_many( 'logs', 'LasTres::Schema::Result::PJLog', 'owner' );
__PACKAGE__->has_many( 'flags', 'LasTres::Schema::Result::PJFlag', 'owner' );
__PACKAGE__->belongs_to( 'born_stats', 'LasTres::Schema::Result::Stats' );
__PACKAGE__->belongs_to( 'training_stats', 'LasTres::Schema::Result::Stats' );
__PACKAGE__->belongs_to( 'inventory', 'LasTres::Schema::Result::Inventory' );
@ -117,14 +122,41 @@ __PACKAGE__->belongs_to( 'equipment', 'LasTres::Schema::Result::Equipment' );
__PACKAGE__->belongs_to( 'team', 'LasTres::Schema::Result::Team' );
__PACKAGE__->belongs_to( 'owner', 'LasTres::Schema::Result::Player' );
sub hash ($self) {
my $image;
my $race = $self->race;
if ( $race->can('image') ) {
$image = $race->image;
}
return {
uuid => $self->uuid,
full_name => $self->full_name,
short_name => $self->short_name,
nick => $self->nick,
race => $self->race,
health => $self->health,
max_health => $self->max_health,
mana => $self->mana,
max_mana => $self->max_mana,
(
( defined $image )
? ( image => $image )
: ()
),
};
}
my $columns = __PACKAGE__->columns_info;
for my $column_name (keys %$columns) {
my $column = $columns->{$column_name};
for my $column_name ( keys %$columns ) {
my $column = $columns->{$column_name};
my $is_nullable = $column->{is_nullable};
$is_nullable //= 0;
my $required = !$is_nullable;
if ( defined $column->{default_value} ) {
$required = 0;
}
has $column_name => (
is => 'rw',
is => 'rw',
required => $required,
accessor => "_moo_$column_name",
);
@ -132,73 +164,151 @@ for my $column_name (keys %$columns) {
sub max_health ($self) {
my $races = LasTres::Races->new;
my $race = $races->hash_playable->{$self->race};
my $race = $self->race;
my $health_base_race = $race->base_stats->health;
my $health_born = $self->born_stats->health;
my $health_training = $self->training_stats->health;
my $health_mix =
2 * $health_base_race + $health_born + ( $health_training / 4 );
my $health_scaled = ( ( $health_mix * $self->level ) / 100 );
return int($health_scaled + $self->level + 10);
return int( $health_scaled + $self->level + 10 );
}
sub max_mana ($self) {
my $races = LasTres::Races->new;
my $race = $races->hash_playable->{$self->race};
my $races = LasTres::Races->new;
my $race = $self->race;
my $mana_base_race = $race->base_stats->mana;
my $mana_born = $self->born_stats->mana;
my $mana_training = $self->training_stats->mana;
my $mana_mix =
2 * $mana_base_race + $mana_born + ( $mana_training / 4 );
my $mana_mix = 2 * $mana_base_race + $mana_born + ( $mana_training / 4 );
my $mana_scaled = ( ( $mana_mix * $self->level ) / 100 );
return int($mana_scaled + $self->level + 10);
return int( $mana_scaled + $self->level + 10 );
}
sub health {
my $self = shift;
my $self = shift;
my $health_to_set = shift;
require LasTres::Schema;
my $schema = LasTres::Schema->Schema;
$schema->txn_do(sub {
if (defined $health_to_set) {
$self->_health($health_to_set);
$self->update;
$schema->txn_do(
sub {
if ( defined $health_to_set ) {
$self->_health($health_to_set);
$self->update;
}
my $health = $self->_health;
if ( $health < 0 ) {
$self->_health(0);
$self->update;
}
if ( $health > $self->max_health ) {
$self->_health( $self->max_health );
$self->update;
}
}
my $health = $self->_health;
if ($health < 0) {
$self->_health(0);
$self->update;
}
if ($health > $self->max_health) {
$self->_health($self->max_health);
$self->update;
}
});
);
return $self->_health;
}
sub mana {
my $self = shift;
my $self = shift;
my $mana_to_set = shift;
require LasTres::Schema;
my $schema = LasTres::Schema->Schema;
$schema->txn_do(sub {
if (defined $mana_to_set) {
$self->_mana($mana_to_set);
$self->update;
$schema->txn_do(
sub {
if ( defined $mana_to_set ) {
$self->_mana($mana_to_set);
$self->update;
}
my $mana = $self->_mana;
if ( $mana < 0 ) {
$self->_mana(0);
$self->update;
}
if ( $mana > $self->max_mana ) {
$self->_mana( $self->max_mana );
$self->update;
}
}
my $mana = $self->_mana;
if ($mana < 0) {
$self->_mana(0);
$self->update;
}
if ($mana > $self->max_mana) {
$self->_mana($self->max_mana);
$self->update;
}
});
);
return $self->_mana;
}
sub race ($self) {
my $hash = LasTres::Races->new->hash_playable;
my $race = $hash->{ $self->_race };
if ( !defined $race ) {
die "Not valid race for pj " . $self->uuid;
}
return $race;
}
sub last_50_log ($self) {
return $self->logs->search( {},
{ limit => 50, order_by => { -desc => 'date' } } );
}
sub append_log_line ( $self, $content ) {
require LasTres::Schema;
if ( ref $content ne 'ARRAY' ) {
die 'Bad log content, not a arrayref.';
}
for my $section (@$content) {
if ( ref $section ne 'HASH' ) {
die 'Invalid section, not a hashref.';
}
my @recognized_log_keys = qw/color background text/;
if (
List::AllUtils::any {
my $key = $_;
(
List::AllUtils::none {
$key eq $_
}
@recognized_log_keys
)
}
keys %$section
)
{
die 'The section '
. ( Data::Dumper::Dumper $section)
. ' has an unrecognized key';
}
if ( !defined $section->{text} ) {
die 'The section has no text.';
}
}
my $uuid = create_uuid_string;
LasTres::Schema->Schema->resultset('PJLog')
->new(
{ uuid => $uuid, owner => $self->uuid, content => to_json($content) } )
->insert;
}
sub level ($self) {
return $self->experience**( 1 / 3 );
}
sub set_flag ( $self, $name ) {
require LasTres::Schema;
my $schema = LasTres::Schema->Schema;
my $result_set_flags = $schema->resultset('PJFlag');
my $flag = $result_set_flags->new({name => $name, owner => $self->uuid})
->update_or_insert;
}
sub get_flag ( $self, $name ) {
my @flags = $self->flags->search({name => $name});
if ( scalar @flags ) {
return 1;
}
return 0;
}
sub clear_flag ( $self, $name ) {
$self->flags->search( name => $name )->delete;
}
1;

View File

@ -0,0 +1,36 @@
package LasTres::Schema::Result::PJFlag;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use parent 'DBIx::Class::Core';
use Data::Dumper;
use Moo;
__PACKAGE__->table('player_pjs_flags');
__PACKAGE__->add_columns(
name => {
data_type => 'text',
is_nullable => 0,
},
owner => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
);
__PACKAGE__->set_primary_key( 'owner', 'name' );
__PACKAGE__->belongs_to( 'owner', 'LasTres::Schema::Result::PJ' );
sub sqlt_deploy_hook ( $self, $sqlt_table ) {
$sqlt_table->add_index( name => 'index_flag', fields => [qw/owner name/] );
}
1;

View File

@ -0,0 +1,60 @@
package LasTres::Schema::Result::PJLog;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use parent 'DBIx::Class::Core';
use Data::Dumper;
use JSON qw/from_json/;
use Moo;
__PACKAGE__->table('player_pjs_log');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
is_nullable => 0,
},
content => {
data_type => 'jsonb',
accessor => '_content',
is_nullable => 0,
},
owner => {
data_type => 'uuid',
is_foreign_key => 1,
is_nullable => 0,
},
date => {
data_type => 'timestamp',
default_value => \'NOW()',
is_nullable => 0,
},
);
sub content($self) {
return from_json($self->_content);
}
__PACKAGE__->set_primary_key('uuid');
__PACKAGE__->belongs_to( 'owner', 'LasTres::Schema::Result::PJ' );
sub hash ($self) {
return {
uuid => $self->uuid,
content => $self->content,
date => $self->date,
};
}
sub sqlt_deploy_hook ( $self, $sqlt_table ) {
$sqlt_table->add_index( name => 'index_log', fields => [qw/owner date/] );
}
1;

View File

@ -7,6 +7,8 @@ use warnings;
use parent 'DBIx::Class::Core';
use LasTres::Location;
__PACKAGE__->table('teams');
__PACKAGE__->add_columns(
@ -45,22 +47,27 @@ __PACKAGE__->add_columns(
},
);
# May throw error, it is needed to handle.
sub location {
my $self = shift;
my $location = shift;
my $planet;
my $super_area;
my $area;
if (defined $location) {
$self->_location($location->identifier);
my $area = $location->parent;
$area = $location->parent;
$self->_area($area->identifier);
my $super_area = $area->parent;
$self->_super_area($super_area->identifier)
my $planet = $super_area->parent;
$super_area = $area->parent;
$self->_super_area($super_area->identifier);
$planet = $super_area->parent;
$self->_planet($planet->identifier);
}
my $location = $self->_location;
my $area = $self->_area;
my $super_area = $self->_super_area;
my $planet = $self->_planet;
$location = $self->_location;
$area = $self->_area;
$super_area = $self->_super_area;
$planet = $self->_planet;
$location = LasTres::Location::get($planet, $super_area, $area, $location);
return $location;
}
__PACKAGE__->set_primary_key('uuid');

View File

@ -1,8 +1,66 @@
@keyframes move-avatar {
0% {
padding-bottom: 0rem; }
50% {
padding-bottom: 0.3rem; }
100% {
padding-bottom: 0rem; } }
body {
margin: 0px;
padding: 0px;
min-height: 100%;
background: ghostwhite; }
body label.bar-container {
width: 90%; }
body label.bar-container div.bar {
width: 100%;
height: 1em;
border: solid 1px black; }
body label.bar-container div.bar div.filled {
background: lightgreen;
height: 100%; }
body div.pj-list-item {
display: flex;
align-items: center; }
body div.pj-list-item div.avatar {
width: 30%;
aspect-ratio: 1/1;
border-radius: 50%;
background: gray;
margin-right: 2%;
display: flex;
align-items: center;
justify-content: center;
position: relative; }
body div.pj-list-item div.avatar img {
animation-name: move-avatar;
animation-duration: 0.5s;
animation-iteration-count: infinite;
width: 80%;
aspect-ratio: 1/1;
z-index: 1; }
body div.pj-list-item div.avatar div.shadow {
top: 78%;
position: absolute;
width: 60%;
aspect-ratio: 7/2;
background: black;
border-radius: 50%; }
body div.pj-list-item div.data {
width: 60%; }
body div.presentation {
display: flex;
flex-direction: row;
width: 95%;
justify-content: center;
height: 50vh; }
body div.presentation div.presentation-item {
margin: 1%;
width: 30%;
overflow-y: scroll; }
body div.presentation div.presentation-item code pre {
white-space: pre-wrap; }
body div.width-max-content {
width: max-content; }
body div#game-container {
@ -74,21 +132,12 @@ body {
body div.pj-selection-menu div.pj-selection-menu-container div.pj-list a:hover {
color: yellow;
background: gray; }
body div.pj-selection-menu div.pj-selection-menu-container div.pj-list a label {
width: 90%; }
body div.pj-selection-menu div.pj-selection-menu-container div.pj-list a label div.bar {
width: 100%;
height: 1em;
border: solid 1px black; }
body div.pj-selection-menu div.pj-selection-menu-container div.pj-list a label div.bar div.filled {
background: lightgreen;
height: 100%; }
body div.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background: url("/img/wallpaper.jpg") no-repeat center black;
background: url() no-repeat center black;
flex-direction: column; }
body div.login-container div.login-contained {
background: #001e8b;

View File

@ -1,4 +1,80 @@
@keyframes move-avatar {
0% {
padding-bottom: 0rem;
}
50% {
padding-bottom: 0.3rem;
}
100% {
padding-bottom: 0rem;
}
}
body {
label.bar-container {
width: 90%;
div.bar {
width: 100%;
height: 1em;
border: solid 1px black;
div.filled {
background: lightgreen;
height: 100%;
}
}
}
div.pj-list-item {
display: flex;
align-items: center;
div.avatar {
width: 30%;
aspect-ratio: 1/1;
border-radius: 50%;
background: gray;
margin-right: 2%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
img {
animation-name: move-avatar;
animation-duration: 0.5s;
animation-iteration-count: infinite;
width: 80%;
aspect-ratio: 1/1;
z-index: 1;
}
div.shadow {
top: 78%;
position: absolute;
width: 60%;
aspect-ratio: 7/2;
background: black;
border-radius: 50%;
}
}
div.data {
width: 60%;
}
}
div.presentation {
display: flex;
flex-direction: row;
width: 95%;
justify-content: center;
div.presentation-item {
code {
pre {
white-space: pre-wrap;
}
}
margin: 1%;
width: 30%;
overflow-y: scroll;
}
height: 50vh;
}
margin: 0px;
padding: 0px;
min-height: 100%;
@ -83,19 +159,7 @@ body {
background: gray;
}
text-decoration: none;
label {
width: 90%;
div.bar {
width: 100%;
height: 1em;
border: solid 1px black;
div.filled {
background: lightgreen;
height: 100%;
}
}
}
}
}
}
}
}
@ -104,7 +168,7 @@ body {
justify-content: center;
align-items: center;
height: 100%;
background: url('/img/wallpaper.jpg') no-repeat center black;
background: url() no-repeat center black;
flex-direction: column;
div.login-contained {
background: rgba(0, 30, 139, 1);

BIN
public/img/aldimor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -96,7 +96,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
\************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Game)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_upper_panel__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/upper-panel */ \"./js-src/components/upper-panel.tsx\");\n/* harmony import */ var _lastres_components_bottom_panel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @lastres/components/bottom-panel */ \"./js-src/components/bottom-panel.tsx\");\n/* harmony import */ var _lastres_components_pj_selection_menu__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @lastres/components/pj-selection-menu */ \"./js-src/components/pj-selection-menu.tsx\");\n\n\n\n\nfunction Game(props) {\n if (props.selectedPJ === null) {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_selection_menu__WEBPACK_IMPORTED_MODULE_3__[\"default\"], { setSelectedPJ: props.setSelectedPJ, userWantsToCreatePJ: props.userWantsToCreatePJ, setUserWantsToCreatePJ: props.setUserWantsToCreatePJ, error: props.error, setError: props.setError })));\n }\n window.setTimeout(() => {\n const locationProtocol = window.location.protocol;\n if (locationProtocol == null) {\n return;\n }\n const protocol = locationProtocol.match(/https:/) != null ? 'wss' : 'ws';\n const webSocket = new WebSocket(`${protocol}://${window.location.host}/ws`);\n webSocket.onopen = () => {\n webSocket.send(JSON.stringify({ hola: \"mundo\" }));\n };\n }, 1);\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_upper_panel__WEBPACK_IMPORTED_MODULE_1__[\"default\"], null),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_bottom_panel__WEBPACK_IMPORTED_MODULE_2__[\"default\"], null)));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/game.tsx?");
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Game)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_upper_panel__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/upper-panel */ \"./js-src/components/upper-panel.tsx\");\n/* harmony import */ var _lastres_components_bottom_panel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @lastres/components/bottom-panel */ \"./js-src/components/bottom-panel.tsx\");\n/* harmony import */ var _lastres_components_pj_selection_menu__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! @lastres/components/pj-selection-menu */ \"./js-src/components/pj-selection-menu.tsx\");\n/* harmony import */ var _lastres_output_packet_init__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! @lastres/output-packet/init */ \"./js-src/output-packet/init.ts\");\n/* harmony import */ var _lastres_output_packet_ping__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! @lastres/output-packet/ping */ \"./js-src/output-packet/ping.ts\");\n/* harmony import */ var _lastres_input_packets__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! @lastres/input-packets */ \"./js-src/input-packets.ts\");\n\n\n\n\n\n\n\nfunction Game(props) {\n const selectedPJ = props.selectedPJ;\n if (selectedPJ === null) {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_selection_menu__WEBPACK_IMPORTED_MODULE_3__[\"default\"], { setSelectedPJ: props.setSelectedPJ, userWantsToCreatePJ: props.userWantsToCreatePJ, setUserWantsToCreatePJ: props.setUserWantsToCreatePJ, error: props.error, setError: props.setError })));\n }\n const [websocket, setWebsocket] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [teamPJs, setTeamPJs] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [currentLocation, setCurrentLocation] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [connectedLocations, setConnectedLocations] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n const [logLines, setLogLines] = react__WEBPACK_IMPORTED_MODULE_0__.useState(null);\n if (websocket === null) {\n window.setTimeout(() => {\n const locationProtocol = window.location.protocol;\n if (locationProtocol == null) {\n return;\n }\n const protocol = locationProtocol.match(/https:/) != null ? 'wss' : 'ws';\n const webSocket = new WebSocket(`${protocol}://${window.location.host}/ws`);\n webSocket.onopen = () => {\n new _lastres_output_packet_init__WEBPACK_IMPORTED_MODULE_4__[\"default\"](selectedPJ.uuid).send(webSocket);\n let interval = 0;\n interval = window.setInterval(() => {\n if (webSocket.readyState === WebSocket.OPEN) {\n new _lastres_output_packet_ping__WEBPACK_IMPORTED_MODULE_5__[\"default\"]().send(webSocket);\n return;\n }\n window.clearInterval(interval);\n }, 10000);\n };\n const inputPackets = new _lastres_input_packets__WEBPACK_IMPORTED_MODULE_6__[\"default\"](setTeamPJs, setCurrentLocation, setConnectedLocations, setLogLines);\n webSocket.onmessage = (event) => {\n const packet = JSON.parse(event.data);\n inputPackets.handle(packet);\n };\n webSocket.onerror = (event) => {\n console.log(event);\n };\n setWebsocket(webSocket);\n }, 1);\n }\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_upper_panel__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { teamPJs: teamPJs, currentLocation: currentLocation, connectedLocations: connectedLocations, logLines: logLines }),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_bottom_panel__WEBPACK_IMPORTED_MODULE_2__[\"default\"], null)));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/game.tsx?");
/***/ }),
@ -140,13 +140,23 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "./js-src/components/pj-list-item.tsx":
/*!********************************************!*\
!*** ./js-src/components/pj-list-item.tsx ***!
\********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ PJListItem)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_pj_health_like_bar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/pj-health-like-bar */ \"./js-src/components/pj-health-like-bar.tsx\");\n\n\nfunction PJListItem(props) {\n const pj = props.pj;\n function avatar() {\n if (pj.image === undefined) {\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null);\n }\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"img\", { src: pj.image }),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { className: \"shadow\" }));\n }\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { className: \"pj-list-item\" },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { className: \"avatar\" }, avatar()),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { className: \"data\" },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"p\", null, pj.nick),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"label\", { className: \"bar-container\" },\n \"Salud\",\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_health_like_bar__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { value: pj.health, max: pj.max_health })),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"label\", { className: \"bar-container\" },\n \"Mana\",\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_health_like_bar__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { value: pj.mana, max: pj.max_mana })))));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/pj-list-item.tsx?");
/***/ }),
/***/ "./js-src/components/pj-list-selection.tsx":
/*!*************************************************!*\
!*** ./js-src/components/pj-list-selection.tsx ***!
\*************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ PJListSelection)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_pj_health_like_bar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/pj-health-like-bar */ \"./js-src/components/pj-health-like-bar.tsx\");\n\n\nfunction PJListSelection(props) {\n const pjs = props.pjs;\n if (pjs === null) {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null));\n }\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, pjs.map((item, i) => react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"a\", { onClick: () => {\n props.setSelectedPJ(item);\n }, href: \"#\", key: i },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, item.full_name),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, item.short_name),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, item.nick),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"label\", null,\n \"Salud\",\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_health_like_bar__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { value: item.health, max: item.max_health })),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"label\", null,\n \"Mana\",\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_health_like_bar__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { value: item.mana, max: item.max_mana }))))));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/pj-list-selection.tsx?");
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ PJListSelection)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_pj_health_like_bar__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/pj-health-like-bar */ \"./js-src/components/pj-health-like-bar.tsx\");\n\n\nfunction PJListSelection(props) {\n const pjs = props.pjs;\n if (pjs === null) {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null));\n }\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, pjs.map((item, i) => react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"a\", { onClick: () => {\n props.setSelectedPJ(item);\n }, href: \"#\", key: i },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, item.full_name),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, item.short_name),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", null, item.nick),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"label\", { className: \"bar-container\" },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_health_like_bar__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { value: item.health, max: item.max_health })),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"label\", { className: \"bar-container\" },\n \"Mana\",\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_health_like_bar__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { value: item.mana, max: item.max_mana }))))));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/pj-list-selection.tsx?");
/***/ }),
@ -176,7 +186,7 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
\*******************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ UpperPanel)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n\nfunction UpperPanel() {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/upper-panel.tsx?");
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ UpperPanel)\n/* harmony export */ });\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");\n/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _lastres_components_pj_list_item__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/components/pj-list-item */ \"./js-src/components/pj-list-item.tsx\");\n\n\nfunction UpperPanel(props) {\n const connectedLocations = props.connectedLocations;\n const teamPJs = props.teamPJs;\n const currentLocation = props.currentLocation;\n const logLines = props.logLines;\n if (!(teamPJs !== null && currentLocation !== null && connectedLocations !== null)) {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"p\", null, \"Esperando datos...\")));\n }\n function generateLog() {\n if (logLines === null || logLines.length < 1) {\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, \"No log\"));\n }\n return logLines.map((item, i) => {\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(react__WEBPACK_IMPORTED_MODULE_0__.Fragment, null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"b\", null, item.date),\n \" \",\n item.content.map((item, i) => {\n const style = {};\n if (item.color !== undefined) {\n style.color = item.color;\n }\n if (item.background !== undefined) {\n style.background = item.background;\n }\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"span\", { key: i, style: style }, item.text);\n }),\n \" \",\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"br\", null));\n });\n }\n return (react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { className: \"presentation\" },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { className: \"presentation-item\" }, teamPJs.map((item, i) => {\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(_lastres_components_pj_list_item__WEBPACK_IMPORTED_MODULE_1__[\"default\"], { key: i, pj: item });\n })),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { className: \"presentation-item\" },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"code\", null,\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"pre\", null, generateLog()))),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"div\", { className: \"presentation-item\" },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"p\", null,\n \"Est\\u00E1s en \",\n currentLocation.area.name,\n \"/\",\n currentLocation.location.name,\n \".\"),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"p\", null, \"Puedes ir a:\"),\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"ul\", null, connectedLocations.map((item, i) => {\n return react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"li\", { key: i },\n react__WEBPACK_IMPORTED_MODULE_0__.createElement(\"a\", { href: \"#\" },\n item.area.name,\n \"/\",\n item.location.name));\n })))));\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/components/upper-panel.tsx?");
/***/ }),
@ -190,6 +200,46 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var reac
/***/ }),
/***/ "./js-src/input-packet.ts":
/*!********************************!*\
!*** ./js-src/input-packet.ts ***!
\********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ InputPacket)\n/* harmony export */ });\nclass InputPacket {\n constructor() {\n this.onreceive = null;\n }\n onReceive(callback) {\n this.onreceive = callback;\n }\n recv(packet) {\n if (this.onreceive !== null) {\n this.onreceive(packet.data);\n }\n }\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/input-packet.ts?");
/***/ }),
/***/ "./js-src/input-packet/info.ts":
/*!*************************************!*\
!*** ./js-src/input-packet/info.ts ***!
\*************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ InputPacketInfo)\n/* harmony export */ });\n/* harmony import */ var _lastres_input_packet__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @lastres/input-packet */ \"./js-src/input-packet.ts\");\n\nclass InputPacketInfo extends _lastres_input_packet__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n identifier() {\n return 'info';\n }\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/input-packet/info.ts?");
/***/ }),
/***/ "./js-src/input-packet/pong.ts":
/*!*************************************!*\
!*** ./js-src/input-packet/pong.ts ***!
\*************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ InputPacketPong)\n/* harmony export */ });\n/* harmony import */ var _lastres_input_packet__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @lastres/input-packet */ \"./js-src/input-packet.ts\");\n\nclass InputPacketPong extends _lastres_input_packet__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n identifier() {\n return 'pong';\n }\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/input-packet/pong.ts?");
/***/ }),
/***/ "./js-src/input-packets.ts":
/*!*********************************!*\
!*** ./js-src/input-packets.ts ***!
\*********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ InputPackets)\n/* harmony export */ });\n/* harmony import */ var _lastres_input_packet_info__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @lastres/input-packet/info */ \"./js-src/input-packet/info.ts\");\n/* harmony import */ var _lastres_input_packet_pong__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! @lastres/input-packet/pong */ \"./js-src/input-packet/pong.ts\");\n\n\nclass InputPackets {\n constructor(setTeamPJs, setCurrentLocation, setConnectedLocations, setLogLines) {\n this.cachedHash = null;\n this.cachedArray = null;\n this.setTeamPJs = setTeamPJs;\n this.setCurrentLocation = setCurrentLocation;\n this.setConnectedLocations = setConnectedLocations;\n this.setLogLines = setLogLines;\n }\n handle(packet) {\n const hash = this.hashAvailablePackets();\n const identifier = packet.command;\n const inputPacket = hash[identifier];\n inputPacket.recv(packet);\n }\n listAvailablePackets() {\n if (this.cachedArray === null) {\n const infoPacket = new _lastres_input_packet_info__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\n const pongPacket = new _lastres_input_packet_pong__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\n infoPacket.onReceive((data) => {\n this.setTeamPJs(data.team_pjs);\n this.setConnectedLocations(data.location_data.connected_places);\n this.setCurrentLocation(data.location_data.current);\n this.setLogLines(data.set_log);\n });\n this.cachedArray = [infoPacket, pongPacket];\n }\n return this.cachedArray;\n }\n hashAvailablePackets() {\n if (this.cachedHash === null) {\n this.cachedHash = {};\n for (const inputPacket of this.listAvailablePackets()) {\n this.cachedHash[inputPacket.identifier()] = inputPacket;\n }\n }\n return this.cachedHash;\n }\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/input-packets.ts?");
/***/ }),
/***/ "./js-src/login.ts":
/*!*************************!*\
!*** ./js-src/login.ts ***!
@ -200,6 +250,36 @@ eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpac
/***/ }),
/***/ "./js-src/output-packet.ts":
/*!*********************************!*\
!*** ./js-src/output-packet.ts ***!
\*********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ OutputPacket)\n/* harmony export */ });\nclass OutputPacket {\n send(ws) {\n const data = this.data();\n ws.send(JSON.stringify({\n command: this.command(),\n data: this.data(),\n }));\n }\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/output-packet.ts?");
/***/ }),
/***/ "./js-src/output-packet/init.ts":
/*!**************************************!*\
!*** ./js-src/output-packet/init.ts ***!
\**************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ OutputPacketInit)\n/* harmony export */ });\n/* harmony import */ var _lastres_output_packet__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @lastres/output-packet */ \"./js-src/output-packet.ts\");\n\nclass OutputPacketInit extends _lastres_output_packet__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n constructor(pj_uuid) {\n super();\n this.pj_uuid = pj_uuid;\n }\n command() {\n return 'init';\n }\n data() {\n return {\n pj_uuid: this.pj_uuid\n };\n }\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/output-packet/init.ts?");
/***/ }),
/***/ "./js-src/output-packet/ping.ts":
/*!**************************************!*\
!*** ./js-src/output-packet/ping.ts ***!
\**************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ OutputPacketPing)\n/* harmony export */ });\n/* harmony import */ var _lastres_output_packet__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @lastres/output-packet */ \"./js-src/output-packet.ts\");\n\nclass OutputPacketPing extends _lastres_output_packet__WEBPACK_IMPORTED_MODULE_0__[\"default\"] {\n constructor() {\n super();\n }\n command() {\n return 'ping';\n }\n data() {\n return null;\n }\n}\n\n\n//# sourceURL=webpack://LasTres/./js-src/output-packet/ping.ts?");
/***/ }),
/***/ "./js-src/pj.ts":
/*!**********************!*\
!*** ./js-src/pj.ts ***!