Adding initial commit.

This commit is contained in:
Sergiotarxz 2023-06-01 08:45:43 +02:00
parent b4fc8175db
commit 1473306fcc
61 changed files with 4034 additions and 0 deletions

24
Build.PL Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env perl
use Module::Build;
my $home = $ENV{HOME};
my $build = Module::Build->new(
module_name => 'LasTres',
license => 'AGPLv3',
dist_author => 'Sergio Iglesias <contact@owlcode.tech>',
dist_abstract => 'Juego de L3TDE.',
requires => {
'Mojolicious' => 0,
'Moo' => 0,
'Params::ValidationCompiler' => 0,
'Types::Standard' => 0,
'Crypt::URandom' => 0,
'Crypt::Bcrypt' => 0,
'DBIx::Class' => 0,
'DBIx::Class::DeploymentHandler' => 0,
'UUID::URandom' => 0,
'Module::Pluggable' => 0,
},
);
$build->create_build_script;

5
babel.config.json Normal file
View File

@ -0,0 +1,5 @@
{
"presets": [
"@babel/preset-react"
]
}

8
build_styles.sh Normal file
View File

@ -0,0 +1,8 @@
#!/bin/bash
if which sassc &> /dev/null; then
sassc public/css/styles.scss > public/css/styles.css
else
echo "No sassc"
exit 1
fi

View File

@ -0,0 +1 @@
CREATE EXTENSION "uuid-ossp";

View File

@ -0,0 +1,18 @@
--
-- Created by SQL::Translator::Producer::PostgreSQL
-- Created on Mon May 29 12:19:47 2023
--
;
--
-- Table: dbix_class_deploymenthandler_versions
--
CREATE TABLE "dbix_class_deploymenthandler_versions" (
"id" serial NOT NULL,
"version" character varying(50) NOT NULL,
"ddl" text,
"upgrade_sql" text,
PRIMARY KEY ("id"),
CONSTRAINT "dbix_class_deploymenthandler_versions_version" UNIQUE ("version")
);
;

View File

@ -0,0 +1,252 @@
--
-- Created by SQL::Translator::Producer::PostgreSQL
-- Created on Mon May 29 12:19:46 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,
"charisma" 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 NOT NULL,
"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,
"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_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_stats" on "player_pjs" ("stats");
CREATE INDEX "player_pjs_idx_team" on "player_pjs" ("team");
;
--
-- 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");
;
--
-- 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_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") 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_stats" FOREIGN KEY ("stats")
REFERENCES "stats" ("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_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;
;

View File

@ -0,0 +1,91 @@
---
schema:
procedures: {}
tables:
dbix_class_deploymenthandler_versions:
constraints:
- deferrable: 1
expression: ''
fields:
- id
match_type: ''
name: ''
on_delete: ''
on_update: ''
options: []
reference_fields: []
reference_table: ''
type: PRIMARY KEY
- deferrable: 1
expression: ''
fields:
- version
match_type: ''
name: dbix_class_deploymenthandler_versions_version
on_delete: ''
on_update: ''
options: []
reference_fields: []
reference_table: ''
type: UNIQUE
fields:
ddl:
data_type: text
default_value: ~
is_nullable: 1
is_primary_key: 0
is_unique: 0
name: ddl
order: 3
size:
- 0
id:
data_type: int
default_value: ~
is_auto_increment: 1
is_nullable: 0
is_primary_key: 1
is_unique: 0
name: id
order: 1
size:
- 0
upgrade_sql:
data_type: text
default_value: ~
is_nullable: 1
is_primary_key: 0
is_unique: 0
name: upgrade_sql
order: 4
size:
- 0
version:
data_type: varchar
default_value: ~
is_nullable: 0
is_primary_key: 0
is_unique: 1
name: version
order: 2
size:
- 50
indices: []
name: dbix_class_deploymenthandler_versions
options: []
order: 1
triggers: {}
views: {}
translator:
add_drop_table: 0
filename: ~
no_comments: 0
parser_args:
sources:
- __VERSION
parser_type: SQL::Translator::Parser::DBIx::Class
producer_args: {}
producer_type: SQL::Translator::Producer::YAML
show_warnings: 0
trace: 0
version: 1.63

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
import * as React from 'react'
export default function BottomPanel (): JSX.Element {
return (
<>
</>
)
}

View File

@ -0,0 +1,35 @@
import * as React from 'react'
import UpperPanel from '@lastres/components/upper-panel'
import BottomPanel from '@lastres/components/bottom-panel'
import PJSelectionMenu from '@lastres/components/pj-selection-menu'
export interface GameProps {
setSelectedPJ: (set: string | null) => void
selectedPJ: string | null
userWantsToCreatePJ: boolean
setUserWantsToCreatePJ: (set: boolean) => void
error: string | null
setError: (set: string | null) => void
}
export default function Game (props: GameProps): JSX.Element {
if (props.selectedPJ === null) {
return (
<>
<PJSelectionMenu
setSelectedPJ={props.setSelectedPJ}
userWantsToCreatePJ={props.userWantsToCreatePJ}
setUserWantsToCreatePJ={props.setUserWantsToCreatePJ}
error={props.error}
setError={props.setError}/>
</>
)
}
return (
<>
<UpperPanel/>
<BottomPanel/>
</>
)
}

View File

@ -0,0 +1,65 @@
import * as React from 'react'
export interface LoginPageProps {
setIsLoggedIn: (set: boolean) => void
setIsAskingForRegistration: (set: boolean) => void
setError: (set: string | null) => void
error: string | null
}
export default function LoginPage (props: LoginPageProps): JSX.Element {
const userInputRef = React.useRef<HTMLInputElement>(null)
const passwordInputRef = React.useRef<HTMLInputElement>(null)
const onUserAsksForRegistration = (): void => {
props.setIsAskingForRegistration(true)
}
const login = (): void => {
if (userInputRef.current === null) {
return
}
if (passwordInputRef.current == null) {
return
}
fetch('/player/login', {
method: 'POST',
mode: 'same-origin',
cache: 'no-cache',
body: JSON.stringify({
username: userInputRef.current.value,
password: passwordInputRef.current.value
})
}).then(async (response) => {
const statusCode = response.status
const data = await response.json()
if (statusCode !== 200) {
props.setError(data.error)
return
}
props.setError(null)
props.setIsLoggedIn(true)
}).catch((error) => {
console.log(error)
})
}
return (
<>
<div className="login-container">
<div className="login-contained">
<h1>Inicia sesión en L3TDE online</h1>
{(props.error !== null
? (<p style={{ background: 'red' }}>{props.error}</p>)
: (<></>)
)}
<div className="login-form">
<input ref={userInputRef} type="text" placeholder="Nombre de usuario"/>
<input ref={passwordInputRef} type="password" placeholder="Password"/>
<div className="width-max-content align-self-end">
<button onClick={login}>Login</button>
</div>
</div>
<p>¿Todavía no tienes cuenta? <a href="#" onClick={onUserAsksForRegistration}>Registrate</a></p>
</div>
</div>
</>
)
}

View File

@ -0,0 +1,42 @@
'use strict'
import * as React from 'react'
export interface MenuBarProps {
isLoggedIn: boolean
};
export interface MenuItem {
onClick: () => void
text: string
};
export default function MenuBar (props: MenuBarProps): JSX.Element {
const isLoggedIn = props.isLoggedIn
const menuItems: MenuItem[] = []
fillMenu(menuItems, isLoggedIn)
return (
<nav className="menu-bar">
{
menuItems.map((item, i) => <a key={i} href="#" onClick={item.onClick}>{item.text}</a>)
}
</nav>
)
}
function fillMenu (menuItems: MenuItem[], isLoggedIn: boolean): void {
if (isLoggedIn) {
menuItems.push({
text: 'Cerrar Sesión',
onClick: () => { alert('logout') }
})
return
}
menuItems.push({
text: 'Regístrate',
onClick: () => { alert('register') }
})
menuItems.push({
text: 'Inicia Sesión',
onClick: () => { alert('login') }
})
}

View File

@ -0,0 +1,52 @@
import * as React from 'react'
import LoginPage from '@lastres/components/login-page'
import Game from '@lastres/components/game'
import { checkLogin } from '@lastres/login'
import RegistrationPage from '@lastres/components/registration-page'
// import Game from '@lastres/components/game'
export default function Page (): JSX.Element {
const [isLoggedIn, setIsLoggedIn] = React.useState<boolean>(false)
const [userWantsToCreatePJ, setUserWantsToCreatePJ] = React.useState<boolean>(false)
const [isAskingForRegistration, setIsAskingForRegistration] = React.useState<boolean>(false)
const [error, setError] = React.useState<string | null>(null)
const [selectedPJ, setSelectedPJ] = React.useState<string | null>(null)
checkLogin(setError, setIsLoggedIn)
if (!isLoggedIn) {
return notLoggedRender(setIsLoggedIn, isAskingForRegistration, setIsAskingForRegistration, error, setError)
}
return (
<Game selectedPJ={selectedPJ}
setSelectedPJ={setSelectedPJ}
userWantsToCreatePJ={userWantsToCreatePJ}
setUserWantsToCreatePJ={setUserWantsToCreatePJ}
error={error}
setError={setError}/>
)
}
function notLoggedRender (setIsLoggedIn: (a: boolean) => void,
isAskingForRegistration: boolean,
setIsAskingForRegistration: (a: boolean) => void,
error: string | null,
setError: (a: string | null) => void): JSX.Element {
if (isAskingForRegistration) {
return (
<>
<RegistrationPage setIsLoggedIn={setIsLoggedIn}
setIsAskingForRegistration={setIsAskingForRegistration}
error={error}
setError={setError}/>
</>
)
}
return (
<>
<LoginPage setIsLoggedIn={setIsLoggedIn}
error={error}
setError={setError}
setIsAskingForRegistration={setIsAskingForRegistration}/>
</>
)
}

View File

@ -0,0 +1,75 @@
import * as React from 'react'
export interface PJCreationMenuProps {
error: string | null
setSelectedPJ: (set: string | null) => void
setUserWantsToCreatePJ: (set: boolean) => void
setError: (set: string | null) => void
}
export interface Race {
identifier: string;
name_selection: string;
description: string;
}
export interface Races {
[id: string]: Race
}
export default function PJCreationMenu (props: PJCreationMenuProps): JSX.Element {
const longNameInputRef = React.useRef<HTMLInputElement>(null)
const shortNameInputRef = React.useRef<HTMLInputElement>(null)
const nickInputRef = React.useRef<HTMLInputElement>(null)
const raceSelectRef = React.useRef<HTMLSelectElement>(null)
const [playableRaces, setPlayableRaces] = React.useState<Races>({})
React.useEffect(() => {
fetch('/races/playable', {
method: 'GET',
mode: 'same-origin',
cache: 'no-cache'
}).then(async (response) => {
const data = await response.json()
setPlayableRaces(data)
}).catch((error) => {
console.log(error)
props.setError('Imposible conectar al servidor para recibir las razas.')
})
})
return (
<>
<div className="login-container">
<div className="login-contained">
<h1>Crea tu personaje.</h1>
{(props.error !== null
? (<p style={{ background: 'red' }}>{props.error}</p>)
: (<></>)
)}
<div className="login-form">
<label>Nombre largo. (Se usará en la historia en situaciones formales)</label>
<input ref={longNameInputRef} type="text"/>
<label>Nombre corto. (Se usará de forma coloquial)</label>
<input ref={shortNameInputRef} type="text"/>
<label>Apodo. (Se usará en las conversaciones más distendidas)</label>
<input ref={nickInputRef} type="text"/>
<label>Raza. (Determina tu localización inicial y tus estadísticas)</label>
<select ref={raceSelectRef}>
{
Object.keys(playableRaces)
.map(
(item, i) => {
return <option key={i} value={playableRaces[item].identifier}>
{`${playableRaces[item].name_selection} (${playableRaces[item].description})`}
</option>
}
)
}
</select>
<div className="width-max-content align-self-end">
<button>Crear Personaje</button>
</div>
</div>
</div>
</div>
</>
)
}

View File

@ -0,0 +1,36 @@
import * as React from 'react'
import PJCreationMenu from '@lastres/components/pj-creation-menu'
export interface PJSelectionMenuProps {
setSelectedPJ: (set: string | null) => void
setUserWantsToCreatePJ: (set: boolean) => void
userWantsToCreatePJ: boolean
error: string | null
setError: (set: string | null) => void
}
export default function PJSelectionMenu (props: PJSelectionMenuProps): JSX.Element {
const createPJ = (): void => {
props.setUserWantsToCreatePJ(true)
}
if (props.userWantsToCreatePJ) {
return (
<PJCreationMenu
setSelectedPJ={props.setSelectedPJ}
setUserWantsToCreatePJ={props.setUserWantsToCreatePJ}
error={props.error}
setError={props.setError}/>
)
}
return (
<div className="pj-selection-menu">
<div className="pj-selection-menu-container">
<h1>L3TDE</h1>
<h2>Selecciona un Personaje</h2>
<div className="pj-list">
<a onClick={createPJ} href="#">Crear un nuevo personaje</a>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,76 @@
import * as React from 'react'
export interface RegistrationPageProps {
setIsLoggedIn: (set: boolean) => void
setIsAskingForRegistration: (set: boolean) => void
error: string | null
setError: (a: string|null) => void
}
export default function RegistrationPage (props: RegistrationPageProps): JSX.Element {
const userInputRef = React.useRef<HTMLInputElement>(null)
const passwordInputRef = React.useRef<HTMLInputElement>(null)
const repeatPasswordInputRef = React.useRef<HTMLInputElement>(null)
const emailInputRef = React.useRef<HTMLInputElement>(null)
const onUserAsksForLogin = (): void => {
props.setIsAskingForRegistration(false)
}
function registerUser (): void {
if (userInputRef.current === null) {
return
}
if (passwordInputRef.current === null) {
return
}
if (repeatPasswordInputRef.current === null) {
return
}
if (emailInputRef.current === null) {
return
}
fetch('/player/register', {
method: 'POST',
mode: 'same-origin',
cache: 'no-cache',
body: JSON.stringify({
username: userInputRef.current.value,
password: passwordInputRef.current.value,
repeat_password: repeatPasswordInputRef.current.value,
email: emailInputRef.current.value
})
}).then(async (response) => {
const statusCode = response.status
const data = await response.json()
if (statusCode !== 200) {
props.setError(data.error)
return
}
props.setError(null)
}).catch((error) => {
console.log(error)
})
}
return (
<>
<div className="login-container">
<div className="login-contained">
<h1>Inicia sesión en L3TDE online</h1>
{(props.error !== null
? (<p style={{ background: 'red' }}>{props.error}</p>)
: (<></>)
)}
<div className="login-form">
<input ref={userInputRef} type="text" placeholder="Nombre de usuario."/>
<input ref={passwordInputRef} type="password" placeholder="Password."/>
<input ref={repeatPasswordInputRef} type="password" placeholder="Repeat password."/>
<input ref={emailInputRef} type="text" placeholder="Email."/>
<div className="width-max-content align-self-end">
<button onClick={registerUser}>Registrate</button>
</div>
</div>
<p>¿Ya tienes cuenta? <a href="#" onClick={onUserAsksForLogin}>Inicia sesión</a>.</p>
</div>
</div>
</>
)
}

View File

@ -0,0 +1,7 @@
import * as React from 'react'
export default function UpperPanel (): JSX.Element {
return (
<>
</>
)
}

11
js-src/index.tsx Normal file
View File

@ -0,0 +1,11 @@
'use strict'
import * as React from 'react'
import * as ReactDOMClient from 'react-dom/client'
import Page from '@lastres/components/page'
const gameContainer = document.querySelector('#game-container')
if (gameContainer !== null) {
const root = ReactDOMClient.createRoot(gameContainer)
root.render(<Page/>)
}

15
js-src/login.ts Normal file
View File

@ -0,0 +1,15 @@
export function checkLogin (setError: (set: string | null) => void,
setIsLoggedIn: (set: boolean) => void): void {
fetch('/player/check_login', {
method: 'POST',
mode: 'same-origin',
cache: 'no-cache'
}).then(async (response) => {
const data = await response.json()
if (data.is_login === 1) {
setIsLoggedIn(true)
}
}).catch((error) => {
console.log(error)
})
}

48
lib/LasTres.pm Normal file
View File

@ -0,0 +1,48 @@
package LasTres;
use Mojo::Base 'Mojolicious', -signatures;
# This method will run once at server start
sub startup ($self) {
# Load configuration from config file
my $config = $self->plugin('NotYAMLConfig');
# Configure the application
$self->secrets( $config->{secrets} );
# Router
my $r = $self->routes;
$self->helper(user => sub($self) {
require LasTres::DAO::Players;
my $result_set_players = LasTres::DAO::Players->ResultSet;
my $uuid = $self->session->{uuid};
if (!defined $uuid) {
return;
}
my @players = $result_set_players->search({
uuid => $uuid,
});
my $player = $players[0];
if (!defined $player) {
delete $self->session->{uuid};
return;
}
return $player;
});
my $sessions = Mojolicious::Sessions->new;
$sessions->cookie_name('LasTres');
$sessions->default_expiration(86400);
# Normal route to controller
$r->get('/')->to('Root#index');
$r->post('/player/register')->to('Player#register');
$r->post('/player/login')->to('Player#login');
$r->post('/player/check_login')->to('Player#check_login');
$r->post('/pj/create')->to('PJ#create');;
$r->websocket('/ws')->to('Websocket#ws');
}
1;

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

@ -0,0 +1,11 @@
package LasTres::Area;
use v5.36.0;
use strict;
use warnings;
use Moo::Role;
requires qw/identifier locations name description parent/;
1;

View File

@ -0,0 +1,11 @@
package LasTres::Controller::Example;
use Mojo::Base 'Mojolicious::Controller', -signatures;
# This action will render a template
sub welcome ($self) {
# Render template "example/welcome.html.ep" with message
$self->render(msg => 'Welcome to the Mojolicious real-time web framework!');
}
1;

View File

@ -0,0 +1,57 @@
package LasTres::Controller::PJ;
use v5.36.0;
use strict;
use warnings;
use UUID::URandom qw/create_uuid_string/;
use LasTres::DAO::Players;
use Mojo::Base 'Mojolicious::Controller', -signatures;
my $result_set_pjs = LasTres::DAO::PJ->ResultSet;
sub create($self) {
my %params = %{ $self->req->json };
my $user = $self->user;
if (!defined $user) {
return $self->render(
status => 401,
json => { error => 'You must login first.' }
);
}
my @pjs = $user->pjs;
if (scalar @pjs >= 0) {
return $self->render(
status => 401,
json => { error => 'You reached the limit of free pjs, delete one or update to premium.', }
);
}
my $uuid = create_uuid_string();
my $owner = $user;
my $full_name = $param{full_name};
if (!defined $full_name || !length $full_name > 3) {
return $self->render(
status => 400,
json => { error => 'The full_name is too short.', }
);
}
my $short_name = $param{full_name};
if (!defined $short_name || !length $full_name > 3) {
return $self->render(
status => 400,
json => { error => 'The short_name is too short.', }
);
}
my $nick = $param{'nick'};
if (!defined $nick || !length $nick > 0) {
return $self->render(
status => 400,
json => { error => 'You must set a nick.', }
);
}
my $race = $param{'race'};
}
1;

View File

@ -0,0 +1,177 @@
package LasTres::Controller::Player;
use v5.36.0;
use strict;
use warnings;
use Crypt::URandom qw/urandom/;
use Crypt::Bcrypt qw/bcrypt bcrypt_check/;
use UUID::URandom qw/create_uuid_string/;
use LasTres::DAO::Players;
use Mojo::Base 'Mojolicious::Controller', -signatures;
my $result_set_players = LasTres::DAO::Players->ResultSet;
sub check_login($self) {
my $user = $self->user;
my $return_hash = {
is_login => 0,
is_verified => 0,
};
if (!defined $user) {
return $self->render(status => 200, json => $return_hash);
}
$return_hash->{is_login} = 1;
if ($user->verified) {
$return_hash->{is_verified} = 1;
}
return $self->render(status => 200, json => $return_hash);
}
sub login ($self) {
my %params = %{ $self->req->json };
my $username = $params{username};
my $password = $params{password};
my @players = $result_set_players->search({
-or => [
username => $username,
email => $username,
]
});
my $player = $players[0];
if (!defined $player) {
return $self->return_incorrect_username_or_password();
}
if (!bcrypt_check($password, $player->encrypted_password)) {
return $self->return_incorrect_username_or_password();
}
$self->session->{uuid} = $player->uuid;
return $self->render(
status => 200,
json => {
info => 'Login sucessful',
}
);
}
sub return_incorrect_username_or_password($self) {
return $self->render(
status => 400,
json => {
error => 'Incorrect username or password.',
}
);
}
sub register ($self) {
my %params = %{ $self->req->json };
my $username = $params{username};
my $password = $params{password};
my $repeat_password = $params{repeat_password};
my $email = $params{email};
if ( !defined $username || length $username < 5 ) {
return $self->render(
status => 400,
json => { error => 'Username too short, minimum 5 characters.', },
);
}
if ($username =~ /@/) {
return $self->render(
status => 400,
json => { error => 'The character @ is forbidden in usernames.', },
);
}
if ( !defined $email || $email !~ /@/ ) {
return $self->render(
status => 400,
json => { error => 'This is not a valid mail.', },
);
}
if ( $password ne $repeat_password ) {
return $self->render(
status => 400,
json => { error => 'Passwords do not match.' }
);
}
if ( $password =~ /^\d+$/ ) {
return $self->render(
status => 400,
json => {
error => 'Numeric only password are too unsafe to be allowed.'
}
);
}
if ( length $password < 12 ) {
return $self->render(
status => 400,
json => {
error => 'The password should be at least 12 characters long.'
}
);
}
my $new_salt = urandom(16);
my $encrypted_password = bcrypt $password, '2b', 12, $new_salt;
my $uuid = create_uuid_string();
eval {
$result_set_players->create(
{
uuid => $uuid,
username => $username,
encrypted_password => $encrypted_password,
email => $email,
verified => 0,
}
);
};
if ($@) {
if ( $@ =~ /Key \((.*?)\)=\((.*?)\) already exists\./ ) {
return $self->render(
status => 400,
json => {
error => "The key $1 ($2) already exists in the database.",
}
);
}
say STDERR $@;
return $self->render(
status => 500,
json => {
error => 'Unhandled database error.',
}
);
}
my @players = $result_set_players->search({
uuid => $uuid,
});
my $player = $players[0];
if (!defined $player) {
return $self->render(
status => 500,
json => {
error => 'Unknown error creating entity player.',
}
);
}
undef($@);
$self->session->{uuid} = $player->uuid;
return $self->render(
status => 200,
json => {
user => {
username => $username,
email => $email,
verified => 0,
},
info => 'User sucessfully created',
}
);
}
1;

View File

@ -0,0 +1,13 @@
package LasTres::Controller::Root;
use v5.36.0;
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Controller', -signatures;
sub index($self) {
return $self->render;
}
1;

View File

@ -0,0 +1,32 @@
package LasTres::Controller::Websocket;
use v5.36.0;
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Controller', -signatures;
sub ws($self) {
my $user = $self->user;
if (!defined $user) {
return $self->render(
status => 401,
json => {
error => 'You are not logged in.',
}
);
}
if (!$user->verified) {
return $self->render(
status => 401,
json => {
error => 'Your user is not verified.',
}
);
}
$self->on(json => sub($self, $hash) {
$self->_handle_packet($user, $hash);
});
}
1;

View File

@ -0,0 +1,23 @@
package LasTres::DAO::Players;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Moo;
use Params::ValidationCompiler qw/validation_for/;
use Types::Standard qw/Str Bool/;
use LasTres::Schema;
my $schema = LasTres::Schema->Schema;
my $result_set = $schema->resultset('Player');
sub ResultSet {
return $result_set;
}
1;

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

@ -0,0 +1,11 @@
package LasTres::Location;
use v5.36.0;
use strict;
use warnings;
use Moo::Role;
requires qw/identifier name description parent actions npcs/;
1;

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

@ -0,0 +1,11 @@
package LasTres::Planet;
use v5.36.0;
use strict;
use warnings;
use Moo::Role;
requires qw/identifier super_areas name description/;
1;

View File

@ -0,0 +1,41 @@
package LasTres::Planet::Bahdder;
use v5.36.0;
use strict;
use warnings;
use Moo;
with 'LasTres::Planet';
use Module::Pluggable search_path => ['LasTres::Planet::Bahdder'];
has super_areas => (
is => 'lazy'
);
sub identifier {
return 'bahdder',
}
sub _build_super_areas {
my $self = shift;
my $hash = {};
my @super_areas = $self->plugins();
for $super_area (@super_areas) {
$hash->{$super_area->identifier} = $super_area;
}
return $hash;
}
sub name {
return 'Bahdder';
}
sub description {
return 'Archivo de la Patrulla Galáctica: Bahdder es uno de los planetas con mayor población de la galaxia con una población estimada '
. 'de tres mil millones de individuos pertenecientes a especies consideradas inteligentes. '
. 'Es conocido como el planeta origen de los Yaren; no obstante la presencia de los mismos en este planeta es actualmente simbólica. '
. 'Su población actual esta mayormente compuesta por razas locales como los Áldimor. ';
}
1;

View File

@ -0,0 +1,47 @@
package LasTres::Planet::Bahdder::BosqueDelHeroe;
use v5.36.0;
use strict;
use warnings;
use Moo;
with 'LasTres::SuperArea';
use Module::Pluggable search_path => ['LasTres::Planet::Bahdder::BosqueDelHeroe'];
has areas => (
is => 'lazy'
);
sub identifier {
return 'bosque_del_heroe';
}
sub _build_areas {
my $self = shift;
my $hash = {};
my @areas = $self->plugins();
for $area (@areas) {
$hash->{$area->identifier} = $area;
}
return $hash;
}
sub name {
return 'Bosque del Héroe';
}
sub description {
return 'El Bosque del Héroe es el pulmón del planeta Bahdder. '
. 'Se cree que solo una pequeña parte de las especies que viven en el mismo han sido catalogadas. '
. 'Los áldimor viven en este bosque en armonía con la naturaleza. '
. 'En este bosque se encuentra la Torre de hechicería Áldimor, lugar donde los áldimor más talentosos aprenden '
. 'a usar la magia para la guerra. ';
}
sub parent {
return LasTres::Planet::Bahdder->new;
}
1;

View File

@ -0,0 +1,37 @@
package LasTres::Planet::Bahdder::BosqueDelHeroe::BosqueDelHeroeI;
use v5.36.0;
use strict;
use warnings;
with 'LasTres::Area';
use Module::Pluggable search_path => ['LasTres::Planet::Bahdder::BosqueDelHeroe::BosqueDelHeroeI'];
has locations => (
is => 'lazy'
);
sub identifier {
return 'bosque_del_heroe_i';
}
sub _build_locations {
my $self = shift;
my $hash = {};
my @locations = $self->plugins();
for $location (@locations) {
$hash->{$location->identifier} = $location;
}
return $hash;
}
sub name {
return 'Bosque del Héroe (I)';
}
sub parent {
return LasTres::Planet::Bahdder::BosqueDelHeroe->new;
}
1;

View File

@ -0,0 +1,35 @@
package LasTres::Planet::Bahdder::BosqueDelHeroe::BosqueDelHeroeI::TribuDeLaLima;
use v5.36.0;
use strict;
use warnings;
with 'LasTres::Location';
sub identifier {
return 'tribu_de_la_lima';
}
sub name {
return 'Tribu de la Lima (Exterior)';
}
sub description {
return 'La Tribu de la Lima se siente como un hogar seas o no de aquí. '
. 'Las casitas están improvisadas con paja que los aldeanos intercambian con otras tribus. '
. 'Los cultivos de Lima están siempre buscando trabajadores, el sueldo es una parte de lo cosechado. ';
}
sub parent {
return LasTres::Planet::Bahdder::BosqueDelHeroe::BosqueDelHeroeI->new;
}
sub actions {
return [];
}
sub npcs {
return [];
}
1;

23
lib/LasTres/Race.pm Normal file
View File

@ -0,0 +1,23 @@
package LasTres::Race;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Moo::Role;
requires qw/spawn identifier name name_selection description is_playable/;
sub hash($self) {
return {
identifier => $self->{identifier},
name => $self->{name},
name_selection => $self->{name_selection},
description => $self->{description},
is_playable => $self->{is_playable},
}
}
1;

View File

@ -0,0 +1,35 @@
package LasTres::Race::Aldimor;
use v5.36.0;
use strict;
use warnings;
use Moo;
with 'LasTres::Race';
sub spawn {
return LasTres::Planet::Bahdder::BosqueDelHeroe::BosqueDelHeroeI::TribuDeLaLima->new;
}
sub identifier {
return 'aldimor';
}
sub name {
return 'Aldimor';
}
sub name_selection {
return 'Aldimor del Bosque del Héroe.';
}
sub description {
return 'La raza de la naturaleza y la magia.';
}
sub is_playable {
return 1;
}
1;

77
lib/LasTres/Races.pm Normal file
View File

@ -0,0 +1,77 @@
package LasTres::Races;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use Moo;
use Module::Pluggable search_path => ['LasTres::Race'];
has hash => (
is => 'lazy',
);
has hash_playable => (
is => 'lazy',
)
has hash_all => (
is => 'lazy',
)
sub _build_hash_all($self) {
return { map { $_ => $self->hash->{$_}->hash } (keys %{$self->hash}) };
}
sub _build_hash_all_playable($self) {
my %hash = %{$self->hash};
for my $race_key (keys %hash) {
my $race = $hash{$race_key};
if (!$race->is_playable) {
delete $hash{$race_key};
}
$hash{$race_key} = $race->hash;
}
return \%hash;
}
sub _build_hash {
my $self = shift;
my $hash = {};
my @races = $self->plugins();
for my $race (@races) {
$hash{$race->identifier} = $race;
}
return $hash;
}
sub _build_hash_playable {
my $self = shift;
my $hash = {@{$self->hash}};
for my $identifier_race (keys %$hash) {
my $race = $hash->{$identifier_race};
if (!$race->is_playable) {
delete $hash->{$identifier_race};
}
}
return $hash;
}
sub get($self, $race_identifier) {
return $self->hash->{$race_identifier};
}
sub get_playable($self, $race_identifier) {
my $race = $self->hash->{$race_identifier};
if (!defined $race) {
return undef;
}
if (!$race->is_playable) {
return undef;
}
return $race;
}
1;

40
lib/LasTres/Schema.pm Normal file
View File

@ -0,0 +1,40 @@
package LasTres::Schema;
our $VERSION = 1;
use v5.36.0;
use strict;
use warnings;
use feature 'signatures';
use LasTres;
use parent 'DBIx::Class::Schema';
__PACKAGE__->load_namespaces();
sub Schema($class) {
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) {
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.
return $class->connect($dsn, $user, $password);
}
1;

View File

@ -0,0 +1,79 @@
package LasTres::Schema::Result::CompanionNPC;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('player_companion_npcs');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
default_value => \'uuid_generate_v4()',
is_nullable => 0,
},
owner => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
identifier => {
data_type => 'text',
is_nullable => 0,
},
nick => {
data_type => 'text',
is_nullable => 1,
},
race => {
data_type => 'text',
is_nullable => 0,
},
level => {
data_type => 'integer',
default_value => \'1',
is_nullable => 0,
},
exp => {
data_type => 'integer',
default_value => \'1',
is_nullable => 0,
},
equipment => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
stats => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
skills => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
spells => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
inventory => {
data_type => 'uuid',
is_nullable => 0,
}
);
__PACKAGE__->set_primary_key('uuid');
__PACKAGE__->belongs_to('stats', 'LasTres::Schema::Result::Stats');
__PACKAGE__->belongs_to('inventory', 'LasTres::Schema::Result::Inventory');
__PACKAGE__->belongs_to('equipment', 'LasTres::Schema::Result::Equipment');
__PACKAGE__->belongs_to('skills', 'LasTres::Schema::Result::SkillLikeList');
__PACKAGE__->belongs_to('spells', 'LasTres::Schema::Result::SkillLikeList');
__PACKAGE__->belongs_to('owner', 'LasTres::Schema::Result::PJ');
1;

View File

@ -0,0 +1,21 @@
package LasTres::Schema::Result::Equipment;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('equipment');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
is_nullable => 0,
},
);
__PACKAGE__->set_primary_key('uuid');
__PACKAGE__->has_many('items', 'LasTres::Schema::Result::EquipmentItem', 'equipment');
1;

View File

@ -0,0 +1,34 @@
package LasTres::Schema::Result::EquipmentItem;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('equipment_items');
__PACKAGE__->add_columns(
kind => {
data_type => 'text',
is_nullable => 0,
},
equipment => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
identifier => {
data_type => 'text',
is_nullable => 0,
},
quantity => {
data_type => 'Integer',
is_nullable => 0,
},
);
__PACKAGE__->set_primary_key('kind', 'equipment');
__PACKAGE__->belongs_to('equipment', 'LasTres::Schema::Result::Equipment');
1;

View File

@ -0,0 +1,20 @@
package LasTres::Schema::Result::Inventory;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('inventories');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
is_nullable => 0,
},
);
__PACKAGE__->set_primary_key('uuid');
__PACKAGE__->has_many('items', 'LasTres::Schema::Result::InventoryItem', 'inventory');
1;

View File

@ -0,0 +1,35 @@
package LasTres::Schema::Result::InventoryItem;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('inventory_items');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
is_nullable => 0,
default_value => \'uuid_generate_v4()',
},
inventory => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
identifier => {
data_type => 'text',
is_nullable => 0,
},
quantity => {
data_type => 'Integer',
is_nullable => 0,
},
);
__PACKAGE__->set_primary_key('uuid');
__PACKAGE__->belongs_to('inventory', 'LasTres::Schema::Result::Inventory');
1;

View File

@ -0,0 +1,99 @@
package LasTres::Schema::Result::PJ;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('player_pjs');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
default_value => \'uuid_generate_v4()',
is_nullable => 0,
},
owner => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
full_name => {
data_type => 'text',
is_nullable => 0,
},
short_name => {
data_type => 'text',
is_nullable => 0,
},
nick => {
data_type => 'text',
is_nullable => 0,
},
race => {
data_type => 'text',
is_nullable => 0,
},
team => {
data_type => 'uuid',
is_nullable => 0,
},
creation_date => {
data_type => 'timestamp',
default_value => \'NOW()',
is_nullable => 0,
},
last_activity => {
data_type => 'timestamp',
default_value => \'NOW()',
is_nullable => 0,
},
level => {
data_type => 'integer',
default_value => \'1',
is_nullable => 0,
},
exp => {
data_type => 'integer',
default_value => \'1',
is_nullable => 0,
},
equipment => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
stats => {
data_type => 'uuid',
is_foreign_key => 1,
is_nullable => 0,
},
skills => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
spells => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
inventory => {
data_type => 'uuid',
is_nullable => 0,
}
);
__PACKAGE__->set_primary_key('uuid');
__PACKAGE__->has_many('npcs', 'LasTres::Schema::Result::CompanionNPC', 'owner');
__PACKAGE__->belongs_to('stats', 'LasTres::Schema::Result::Stats');
__PACKAGE__->belongs_to('inventory', 'LasTres::Schema::Result::Inventory');
__PACKAGE__->belongs_to('skills', 'LasTres::Schema::Result::SkillLikeList');
__PACKAGE__->belongs_to('spells', 'LasTres::Schema::Result::SkillLikeList');
__PACKAGE__->belongs_to('equipment', 'LasTres::Schema::Result::Equipment');
__PACKAGE__->belongs_to('team', 'LasTres::Schema::Result::Team');
__PACKAGE__->belongs_to('owner', 'LasTres::Schema::Result::Player');
1;

View File

@ -0,0 +1,52 @@
package LasTres::Schema::Result::Player;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('players');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
is_nullable => 0,
},
username => {
data_type => 'text',
is_nullable => 0,
},
encrypted_password => {
data_type => 'text',
is_nullable => 0,
},
email => {
data_type => 'text',
is_nullable => 0,
},
verified => {
data_type => 'boolean',
is_nullable => 0,
},
verification_token => {
data_type => 'text',
is_nullable => 1,
},
register_date => {
data_type => 'timestamp',
is_nullable => 0,
default_value => \'NOW()',
},
last_activity => {
data_type => 'timestamp',
is_nullable => 0,
default_value => \'NOW()',
},
);
__PACKAGE__->set_primary_key('uuid');
__PACKAGE__->has_many('pjs', 'LasTres::Schema::Result::PJ', 'owner');
__PACKAGE__->add_unique_constraint("unique_constraint_username", ['username']);
__PACKAGE__->add_unique_constraint("unique_constraint_email", ["email"]);
1;

View File

@ -0,0 +1,29 @@
package LasTres::Schema::Result::SkillLikeItem;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('skill_like_items');
__PACKAGE__->add_columns(
identifier => {
data_type => 'text',
is_nullable => 0,
},
owner_list => {
data_type => 'uuid',
is_nullable => 0,
},
level => {
data_type => 'integer',
is_nullable => 0,
default_value => \'1',
}
);
__PACKAGE__->set_primary_key('identifier', 'owner_list');
__PACKAGE__->belongs_to('owner_list', 'LasTres::Schema::Result::SkillLikeList');
1;

View File

@ -0,0 +1,21 @@
package LasTres::Schema::Result::SkillLikeList;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('skill_like_lists');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
default_value => \'uuid_generate_v4()',
is_nullable => 0,
},
);
__PACKAGE__->set_primary_key('uuid');
__PACKAGE__->has_many('items', 'LasTres::Schema::Result::SkillLikeItem', 'owner_list');
1;

View File

@ -0,0 +1,51 @@
package LasTres::Schema::Result::Stats;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('stats');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
is_nullable => 0,
},
health => {
data_type => 'integer',
is_nullable => 0,
},
mana => {
data_type => 'integer',
is_nullable => 0,
},
strength => {
data_type => 'integer',
is_nullable => 0,
},
resistance => {
data_type => 'integer',
is_nullable => 0,
},
magic => {
data_type => 'integer',
is_nullable => 0,
},
speed => {
data_type => 'integer',
is_nullable => 0,
},
intelligence => {
data_type => 'integer',
is_nullable => 0,
},
charisma => {
data_type => 'integer',
is_nullable => 0,
},
);
__PACKAGE__->set_primary_key('uuid');
1;

View File

@ -0,0 +1,49 @@
package LasTres::Schema::Result::Team;
use v5.36.0;
use strict;
use warnings;
use parent 'DBIx::Class::Core';
__PACKAGE__->table('teams');
__PACKAGE__->add_columns(
uuid => {
data_type => 'uuid',
is_nullable => 0,
},
leader => {
data_type => 'uuid',
is_nullable => 0,
is_foreign_key => 1,
},
name => {
data_type => 'text',
is_nullable => 0,
},
planet => {
data_type => 'text',
is_nullable => 0,
},
super_area => {
data_type => 'text',
is_nullable => 0,
},
area => {
data_type => 'text',
is_nullable => 0,
},
location => {
data_type => 'text',
is_nullable => 0,
},
);
__PACKAGE__->set_primary_key('uuid');
__PACKAGE__->add_unique_constraint(u_name => ['name']);
__PACKAGE__->has_many('members', 'LasTres::Schema::Result::PJ', 'team');
__PACKAGE__->belongs_to('leader', 'LasTres::Schema::Result::PJ');
1;

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

@ -0,0 +1,11 @@
package LasTres::SuperArea;
use v5.36.0;
use strict;
use warnings;
use Moo::Role;
requires qw/identifier areas name description parent/;
1;

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"name": "LasTres",
"version": "0.1.1",
"description": "",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@babel/preset-react": "^7.18.6",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"babel-loader": "^9.1.2",
"eslint": "^8.36.0",
"eslint-config-standard-with-typescript": "^34.0.1",
"eslint-import-resolver-typescript": "^3.5.3",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-n": "^15.6.1",
"eslint-plugin-no-relative-import-paths": "^1.5.2",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.32.2",
"file-loader": "^6.2.0",
"ts-loader": "^9.4.2",
"typescript": "^5.0.2",
"typescript-transform-paths": "^3.4.6",
"url-loader": "^4.1.1",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.2"
},
"dependencies": {
"babel-preset-react": "^6.24.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

103
public/css/styles.css Normal file
View File

@ -0,0 +1,103 @@
body {
margin: 0px;
padding: 0px;
min-height: 100%; }
body div.width-max-content {
width: max-content; }
body div#game-container {
min-height: 100%; }
body div#game-container nav.menu-bar {
width: 100%;
background: grey;
height: 3rem;
display: flex;
justify-content: end;
align-items: center; }
body div#game-container nav.menu-bar a {
font-size: 1.7rem;
display: inline-block;
background: chocolate;
border-radius: 0.3rem;
color: ghostwhite;
text-decoration: none;
margin-right: 0.7rem;
margin-left: 0.7rem;
height: 100%; }
body div#game-container nav.menu-bar a:last-child {
margin-right: 0px; }
body div#game-container nav.menu-bar a:hover {
background: ghostwhite;
color: black; }
body div.pj-selection-menu {
display: flex;
justify-content: center;
align-items: center;
min-height: 100%;
background: ghostwhite;
flex-direction: column; }
body div.pj-selection-menu div.pj-selection-menu-container {
background: ghostwhite;
color: black;
padding: 30px;
display: flex;
align-items: center;
min-width: 80%;
min-height: 100%;
flex-direction: column;
overflow: scroll; }
body div.pj-selection-menu div.pj-selection-menu-container div.pj-list {
background: ghostwhite;
width: 90%;
padding: 10px;
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-content: center;
justify-content: center;
align-items: center;
flex-grow: 1px;
gap: 8px; }
body div.pj-selection-menu div.pj-selection-menu-container div.pj-list a {
width: 10em;
height: 10em;
background: white;
border: solid 1px black;
box-shadow: 3px 1px 3px 3px #333;
color: blue;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
text-decoration: none; }
body div.pj-selection-menu div.pj-selection-menu-container div.pj-list a:hover {
color: yellow;
background: gray; }
body div.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background: url("/img/wallpaper.jpg") no-repeat center black;
flex-direction: column; }
body div.login-container div.login-contained {
background: #001e8b;
color: azure;
padding: 30px;
border: solid 1px black;
display: flex;
align-items: center;
flex-direction: column; }
body div.login-container div.login-contained input {
display: block; }
body div.login-container div.login-contained button {
display: block; }
body div.login-container div.login-contained div.login-form {
display: flex;
flex-direction: column;
width: max-content; }
body div.login-container div.login-contained div.login-form div.align-self-end {
align-self: end; }
body div.login-container div.login-contained a {
color: aquamarine; }
body div.login-container div.login-contained a:hover {
color: yellow; }

125
public/css/styles.scss Normal file
View File

@ -0,0 +1,125 @@
body {
margin: 0px;
padding: 0px;
min-height: 100%;
div.width-max-content {
width: max-content;
}
div#game-container {
min-height: 100%;
nav.menu-bar {
width: 100%;
background: grey;
height: 3rem;
display: flex;
justify-content: end;
align-items: center;
a {
font-size: 1.7rem;
display: inline-block;
background: chocolate;
border-radius: 0.3rem;
color: ghostwhite;
text-decoration: none;
margin-right: 0.7rem;
margin-left: 0.7rem;
height: 100%;
&:last-child {
margin-right: 0px;
}
&:hover {
background: ghostwhite;
color: black;
}
}
}
}
div.pj-selection-menu {
display: flex;
justify-content: center;
align-items: center;
min-height: 100%;
background: ghostwhite;
flex-direction: column;
div.pj-selection-menu-container {
background: ghostwhite;
color: black;
padding: 30px;
display: flex;
align-items: center;
min-width: 80%;
min-height: 100%;
flex-direction: column;
overflow: scroll;
div.pj-list {
background: ghostwhite;
width: 90%;
padding: 10px;
display: flex;
flex-wrap: wrap;
flex-direction: row;
align-content: center;
justify-content: center;
align-items: center;
flex-grow: 1px;
gap: 8px;
a {
width: 10em;
height: 10em;
background: white;
border: solid 1px black;
box-shadow: 3px 1px 3px 3px #333;
color: blue;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
&:hover {
color: yellow;
background: gray;
}
text-decoration: none;
}
}
}
}
div.login-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background: url('/img/wallpaper.jpg') no-repeat center black;
flex-direction: column;
div.login-contained {
background: rgba(0, 30, 139, 1);
color: azure;
padding: 30px;
border: solid 1px black;
display: flex;
align-items: center;
flex-direction: column;
input {
display: block;
}
button {
display: block;
}
div.login-form {
display: flex;
flex-direction: column;
width: max-content;
div.align-self-end {
align-self: end;
}
}
a {
color: aquamarine;
&:hover {
color: yellow;
}
}
}
}
}

BIN
public/img/wallpaper.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

270
public/js/bundle.js Normal file

File diff suppressed because one or more lines are too long

29
script/install.pl Normal file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env perl
use strict;
use warnings;
use aliased 'DBIx::Class::DeploymentHandler' => 'DH';
use Getopt::Long;
use FindBin;
use lib "$FindBin::Bin/../lib";
use LasTres::Schema;
my $force_overwrite = 0;
unless ( GetOptions( 'force_overwrite!' => \$force_overwrite ) ) {
die "Invalid options";
}
my $schema = LasTres::Schema->Schema;
my $dh = DH->new(
{
schema => $schema,
script_directory => "$FindBin::Bin/../dbicdh",
databases => 'PostgreSQL',
sql_translator_args => { add_drop_table => 0 },
force_overwrite => $force_overwrite,
}
);
$dh->install;

11
script/las_tres Executable file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env perl
use strict;
use warnings;
use Mojo::File qw(curfile);
use lib curfile->dirname->sibling('lib')->to_string;
use Mojolicious::Commands;
# Start command line interface for application
Mojolicious::Commands->start_app('LasTres');

35
script/prepare.pl Normal file
View File

@ -0,0 +1,35 @@
#!/usr/bin/env perl
use strict;
use warnings;
use aliased 'DBIx::Class::DeploymentHandler' => 'DH';
use Getopt::Long;
use FindBin;
use lib "$FindBin::Bin/../lib";
use LasTres::Schema;
my $force_overwrite = 1;
unless ( GetOptions( 'force_overwrite!' => \$force_overwrite ) ) {
die "Invalid options";
}
my $schema = LasTres::Schema->Schema;
my $dh = DH->new(
{
schema => $schema,
script_directory => "$FindBin::Bin/../dbicdh",
databases => 'PostgreSQL',
sql_translator_args => { add_drop_table => 0 },
force_overwrite => $force_overwrite,
}
);
$dh->prepare_deploy;
eval {
$dh->prepare_upgrade;
};
if ($@) {
$dh->prepare_install;
}

29
script/upgrade.pl Normal file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env perl
use strict;
use warnings;
use aliased 'DBIx::Class::DeploymentHandler' => 'DH';
use Getopt::Long;
use FindBin;
use lib "$FindBin::Bin/../lib";
use LasTres::Schema;
my $force_overwrite = 1;
unless ( GetOptions( 'force_overwrite!' => \$force_overwrite ) ) {
die "Invalid options";
}
my $schema = LasTres::Schema->Schema;
my $dh = DH->new(
{
schema => $schema,
script_directory => "$FindBin::Bin/../dbicdh",
databases => 'PostgreSQL',
sql_translator_args => { add_drop_table => 0 },
force_overwrite => $force_overwrite,
}
);
$dh->upgrade;

View File

@ -0,0 +1,20 @@
<html>
<head>
<link rel="stylesheet" href="/css/styles.css"/>
<meta name="viewport" content="height = device-height,
width = device-width,
initial-scale = 1.0,
minimum-scale = 1.0,
maximum-scale = 1.0,
user-scalable = no,
target-densitydpi = device-dpi"/>
</head>
<body>
<noscript style="font-size: 40px">
<p>LasTres cannot work without javascript, consider using the API to make you own client if this is a concern to you.</p>
</noscript>
<div id="game-container">
</div>
<script src="/js/bundle.js"></script>
</body>
</html>

30
tsconfig.json Normal file
View File

@ -0,0 +1,30 @@
{
"compilerOptions": {
"outDir": "./public/js/",
"noImplicitAny": true,
"module": "es2020",
"target": "es2020",
"jsx": "react",
"allowJs": true,
"moduleResolution": "node",
"strictNullChecks": true,
"baseUrl": ".",
"paths": {
"@lastres/*": ["js-src/*"],
"/*": ["js-src/*"]
},
"plugins": [
{
"transform": "typescript-transform-paths"
},
{
"transform": "typescript-transform-paths",
"afterDeclarations": true
}
]
},
"include": [
"js-src/*.ts", "js-src/*/*.ts",
"js-src/*.tsx", "js-src/*/*.tsx"
]
}

38
webpack.config.js Normal file
View File

@ -0,0 +1,38 @@
const path = require('path')
module.exports = {
entry: './js-src/index.tsx',
mode: 'development',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public/js/')
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
roots: [
path.resolve(__dirname, 'js-src/')
],
alias: {
'@lastres': path.resolve(__dirname, 'js-src')
}
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.jpe?g|png$/,
exclude: /node_modules/,
use: ['url-loader', 'file-loader']
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
}
}