adding missing files
16
app/blueprints/ajax.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from flask import Blueprint, request, url_for, render_template, session,flash
|
||||
from app.services.appwrite_client import AppWriteClient
|
||||
|
||||
|
||||
ajax_bp = Blueprint("ajax", __name__, url_prefix="/ajax")
|
||||
|
||||
|
||||
@ajax_bp.route("/races", methods=["GET", "POST"])
|
||||
def races():
|
||||
race = request.args.get("race")
|
||||
return render_template(f"ajax/race_{race}.html")
|
||||
|
||||
@ajax_bp.route("/prof", methods=["GET", "POST"])
|
||||
def professions():
|
||||
prof = request.args.get("prof")
|
||||
return render_template(f"ajax/prof_{prof}.html")
|
||||
112
app/blueprints/auth.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# app/auth/routes.py
|
||||
from flask import Blueprint, render_template, request, redirect, url_for, flash, session,make_response
|
||||
|
||||
from app.services.appwrite_client import AppWriteClient
|
||||
|
||||
|
||||
auth_bp = Blueprint("auth", __name__, url_prefix="/auth")
|
||||
|
||||
|
||||
@auth_bp.route("/register", methods=["GET", "POST"])
|
||||
def register():
|
||||
if request.method == "POST":
|
||||
email = request.form.get("email", "").strip()
|
||||
password = request.form.get("password", "")
|
||||
name = (request.form.get("name") or "").strip() or None
|
||||
|
||||
if not email or not password:
|
||||
flash("Email and password are required.", "error")
|
||||
return redirect(url_for("auth.register"))
|
||||
|
||||
try:
|
||||
aw = AppWriteClient()
|
||||
aw.create_new_user(email,password,name)
|
||||
|
||||
login_valid, error = aw.log_user_in(email,password)
|
||||
if login_valid:
|
||||
flash("Account created and you are now logged in.", "success")
|
||||
return redirect(url_for("main.dashboard"))
|
||||
else:
|
||||
flash(str(error), "error")
|
||||
except Exception as e:
|
||||
flash(str(e), "error")
|
||||
return redirect(url_for("auth.register"))
|
||||
|
||||
return render_template("auth/register.html")
|
||||
|
||||
@auth_bp.route("/login", methods=["GET", "POST"])
|
||||
def login():
|
||||
if request.method == "POST":
|
||||
email = request.form.get("email", "").strip()
|
||||
password = request.form.get("password", "")
|
||||
if not email or not password:
|
||||
flash("Email and password are required.", "error")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
aw = AppWriteClient()
|
||||
login_valid, error = aw.log_user_in(email,password)
|
||||
try:
|
||||
if login_valid:
|
||||
username = session.get("user",{}).get("name","User")
|
||||
flash(f"Welcome Back {username}", "success")
|
||||
return redirect(url_for("main.dashboard"))
|
||||
else:
|
||||
flash(str(error), "error")
|
||||
except Exception as e:
|
||||
flash(str(e), "error")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
# Get method
|
||||
|
||||
return render_template("auth/login.html")
|
||||
|
||||
@auth_bp.route("/logout", methods=["GET", "POST"])
|
||||
def logout():
|
||||
aw = AppWriteClient()
|
||||
aw.log_user_out()
|
||||
session.clear()
|
||||
flash("Signed out.", "success")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
@auth_bp.route("/send", methods=["POST"])
|
||||
def send():
|
||||
"""
|
||||
Sends a verification email to the currently logged-in user.
|
||||
Appwrite will redirect the user back to /verify/callback with userId & secret.
|
||||
"""
|
||||
try:
|
||||
aw = AppWriteClient()
|
||||
aw.send_email_verification()
|
||||
flash("Verification email sent. Please check your inbox.", "info")
|
||||
except Exception as e:
|
||||
flash(f"Could not send verification email: {e}", "error")
|
||||
# Go back to where the user came from, or your dashboard
|
||||
return redirect(request.referrer or url_for("main.dashboard"))
|
||||
|
||||
@auth_bp.route("/callback", methods=["GET"])
|
||||
def callback():
|
||||
"""
|
||||
Completes verification after user clicks the email link.
|
||||
Requires the user to be logged in (Appwrite Account endpoints need a session).
|
||||
If not logged in, we stash the link params and send them to log in first.
|
||||
"""
|
||||
aw = AppWriteClient()
|
||||
user_id = request.args.get("userId")
|
||||
secret = request.args.get("secret")
|
||||
if not user_id or not secret:
|
||||
flash("Invalid verification link.", "error")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
try:
|
||||
# If we don't currently have an Appwrite session, ask them to log in, then resume.
|
||||
if not aw.verify_email(user_id,secret):
|
||||
session["pending_verification"] = {"userId": user_id, "secret": secret}
|
||||
flash("Please log in to complete email verification.", "warning")
|
||||
return redirect(url_for("auth.login"))
|
||||
|
||||
# We have a session; complete verification
|
||||
flash("Email verified! You're all set.", "success")
|
||||
return redirect(url_for("main.dashboard"))
|
||||
except Exception as e:
|
||||
flash(f"Verification failed: {e}", "error")
|
||||
return redirect(url_for("auth.login"))
|
||||
46
app/blueprints/char.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import os
|
||||
import json
|
||||
import random
|
||||
from flask import Blueprint, redirect, url_for, render_template, g, request, flash
|
||||
from typing import cast
|
||||
|
||||
from app.utils.session_user import SessionUser
|
||||
from app.services.appwrite_client import AppWriteClient
|
||||
from app.services.appwrite_db import AppwriteTables, Env
|
||||
from app.services.coc_api import CoCApi
|
||||
|
||||
char_bp = Blueprint("char", __name__, url_prefix="/char")
|
||||
|
||||
cocapi = CoCApi()
|
||||
|
||||
|
||||
def get_current_user() -> SessionUser:
|
||||
return cast(SessionUser, g.current_user)
|
||||
|
||||
|
||||
@char_bp.route("/create", methods=["GET", "POST"])
|
||||
def create_char():
|
||||
if request.method == "POST":
|
||||
name = request.form.get("character_name")
|
||||
race_id = request.form.get("race_dropdown")
|
||||
profession_id = request.form.get("profession_dropdown")
|
||||
origin_story = request.form.get("origin_story")
|
||||
uuid = cocapi.create_char(name=name,origin_story=origin_story,race_id=race_id,profession_id=profession_id)
|
||||
redirect(url_for("main.dashboard"))
|
||||
|
||||
ai_dumps_path = os.path.join("app","game_data","ai_dumps.json")
|
||||
with open(ai_dumps_path,"r") as f:
|
||||
data = json.load(f)
|
||||
|
||||
origin_stories = data.get("origin_stories",[])
|
||||
if len(origin_stories) > 0:
|
||||
starter_text = random.choice(origin_stories)
|
||||
else:
|
||||
starter_text = "I was born in a small, secluded village on the edge of a vast and mysterious forest, where whispers of ancient magic still lingered in the air."
|
||||
|
||||
template_data = {
|
||||
"starter_text":starter_text
|
||||
}
|
||||
|
||||
return render_template("char/create_char.html", data=template_data)
|
||||
|
||||
27
app/blueprints/main.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import os
|
||||
from flask import Blueprint, redirect, url_for, render_template, g, session,flash
|
||||
from typing import cast
|
||||
|
||||
from app.utils.session_user import SessionUser
|
||||
from app.services.appwrite_client import AppWriteClient
|
||||
from app.services.appwrite_db import AppwriteTables
|
||||
from app.services.coc_api import CoCApi
|
||||
|
||||
main_bp = Blueprint("main", __name__, url_prefix="/main")
|
||||
cocapi = CoCApi()
|
||||
|
||||
|
||||
def get_current_user() -> SessionUser:
|
||||
return cast(SessionUser, g.current_user)
|
||||
|
||||
|
||||
@main_bp.route("/dashboard")
|
||||
def dashboard():
|
||||
db_tables = AppwriteTables()
|
||||
user = get_current_user()
|
||||
results = db_tables.get_characters_for_user_id(user.id)
|
||||
if len(results) == 0:
|
||||
return redirect(url_for("char.create_char"))
|
||||
else:
|
||||
char=results
|
||||
return render_template("main/dashboard.html", profile=g.current_user, jwt_info=char)
|
||||
10
app/blueprints/public.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from flask import Blueprint, redirect, url_for, render_template, session,flash
|
||||
from app.services.appwrite_client import AppWriteClient
|
||||
|
||||
|
||||
public_bp = Blueprint("public", __name__, url_prefix="/")
|
||||
|
||||
|
||||
@public_bp.route("/")
|
||||
def home():
|
||||
return render_template("public/home.html")
|
||||
19
app/game_data/ai_dumps.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"origin_stories":[
|
||||
"I was born in a small village on the edge of a vast and mysterious forest.",
|
||||
"The day I was born, the stars aligned in a peculiar pattern above our town.",
|
||||
"My earliest memories are of wandering through the ruins of an ancient city.",
|
||||
"In the depths of my childhood, I stumbled upon a hidden text that changed everything.",
|
||||
"I was raised by a family of nomads who taught me the ways of the wind and the sun.",
|
||||
"The village elder predicted my arrival on the day I was born, as if I were already known.",
|
||||
"I've always felt like there's something missing in my memories, like pieces are waiting to be filled in.",
|
||||
"As a child, I would often sneak into the local library at night, devouring forbidden knowledge from ancient tomes.",
|
||||
"My family's past is shrouded in mystery, but one thing is certain: we've always been on the move.",
|
||||
"I was found as a baby in the heart of a dense jungle, with no memory of who I am or where I came from.",
|
||||
"The village priestess foretold my destiny on the day of my birth, speaking words that only I could understand.",
|
||||
"Growing up, I would often experience strange and vivid dreams that felt more real than reality itself.",
|
||||
"I was born with a rare gift: the ability to communicate with animals in ways others cannot.",
|
||||
"The village elder's prophecies spoke of me as a bringer of change, but I'm still unsure what that means."
|
||||
]
|
||||
|
||||
}
|
||||
87
app/services/appwrite_db.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from enum import StrEnum
|
||||
from typing import Final, Optional
|
||||
from flask import current_app
|
||||
|
||||
from appwrite.client import Client
|
||||
from appwrite.services.tables_db import TablesDB
|
||||
from appwrite.query import Query
|
||||
from appwrite.id import ID
|
||||
|
||||
from app.utils.logging import get_logger
|
||||
from app.utils.settings import get_settings, Environment
|
||||
|
||||
settings = get_settings()
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class Env(StrEnum):
|
||||
PROD = "prod"
|
||||
DEV = "dev"
|
||||
|
||||
# --- Database schemas (strongly-typed namespaces) ----------------------------
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Database:
|
||||
"""Schema for a single database: each attribute is a table name (or ID)."""
|
||||
id: str
|
||||
characters: str
|
||||
inventory: str
|
||||
# add more tables here as you grow your schema
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Databases:
|
||||
"""Top-level namespace exposing prod/dev as attributes."""
|
||||
prod: Database
|
||||
dev: Database
|
||||
|
||||
DB: Final[Databases] = Databases(
|
||||
prod=Database(
|
||||
id="SETME", # actual DB / ID
|
||||
characters="SETME", # actual table / ID
|
||||
inventory="inventory",
|
||||
),
|
||||
dev=Database(
|
||||
id="69041f9600177b675485",
|
||||
characters="69050f830024afb0d253",
|
||||
inventory="inventory",
|
||||
),
|
||||
)
|
||||
|
||||
class AppwriteTables:
|
||||
|
||||
def __init__(self):
|
||||
print()
|
||||
self.client = (Client()
|
||||
.set_endpoint(settings.appwrite_endpoint)
|
||||
.set_project(settings.appwrite_project_id)
|
||||
.set_key(settings.appwrite_api_key)
|
||||
)
|
||||
self.tables_db = TablesDB(self.client)
|
||||
self.env = Env.DEV
|
||||
if settings.env == Environment.PROD:
|
||||
self.env = Env.PROD
|
||||
|
||||
@property
|
||||
def db(self) -> Database:
|
||||
# Gives autocompletion for .character, .users, etc.
|
||||
return DB.prod if self.env is Env.PROD else DB.dev
|
||||
|
||||
def get_characters_for_user_id(self, user_id: str) -> Optional[dict]:
|
||||
|
||||
try:
|
||||
result = self.tables_db.list_rows(
|
||||
self.db.id,
|
||||
self.db.characters,
|
||||
[
|
||||
Query.equal('player_id', [str(user_id)]),
|
||||
]
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Unable to list rows for char. User id: {user_id}")
|
||||
return {}
|
||||
|
||||
return result.get("rows",{})
|
||||
|
||||
232
app/services/coc_api.py
Normal file
@@ -0,0 +1,232 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import requests
|
||||
from requests import Response, Session
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util.retry import Retry
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from app.services.appwrite_client import AppWriteClient
|
||||
from app.utils.logging import get_logger
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
|
||||
class CoCApi:
|
||||
"""
|
||||
Centralized API client for Code of Conquest.
|
||||
All HTTP interactions go through _request() for consistent behavior and logging.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str = "http://localhost:8000",
|
||||
default_timeout: tuple[float, float] = (5.0, 10.0),
|
||||
max_retries: int = 3,
|
||||
) -> None:
|
||||
"""
|
||||
:param base_url: Base URL for the API (no trailing slash needed).
|
||||
:param default_timeout: (connect_timeout, read_timeout)
|
||||
:param max_retries: Number of retries for transient network/server errors.
|
||||
"""
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.default_timeout = default_timeout
|
||||
self._aw = AppWriteClient()
|
||||
|
||||
# Base headers for JSON APIs.
|
||||
self._base_headers: Dict[str, str] = {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "CoC-Client/1.0",
|
||||
}
|
||||
|
||||
# Pre-configured Session with retries.
|
||||
self._session = self._build_session(max_retries=max_retries)
|
||||
|
||||
# ---------- Public convenience methods ----------
|
||||
|
||||
def create_char(self, name:str, origin_story:str, race_id:str, profession_id:str) -> Dict[str, Any]:
|
||||
payload = {
|
||||
"name":name,
|
||||
"origin_story":origin_story,
|
||||
"race_id":race_id,
|
||||
"profession_id":profession_id
|
||||
}
|
||||
result = self.post("/char/new",payload=payload)
|
||||
player_uuid = result.get("result")
|
||||
return player_uuid
|
||||
|
||||
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
return self._request("GET", path, params=params)
|
||||
|
||||
def post(self, path: str, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
return self._request("POST", path, json_body=payload)
|
||||
|
||||
def patch(self, path: str, json_body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
return self._request("PATCH", path, json_body=json_body)
|
||||
|
||||
# ---------- Internal helpers ----------
|
||||
|
||||
def _build_session(self, max_retries: int) -> Session:
|
||||
"""
|
||||
Create a Session with sane retries for transient network/server failures.
|
||||
We retry idempotent methods and some 5xx responses.
|
||||
"""
|
||||
session = requests.Session()
|
||||
|
||||
retries = Retry(
|
||||
total=max_retries,
|
||||
connect=max_retries,
|
||||
read=max_retries,
|
||||
backoff_factor=0.5,
|
||||
status_forcelist=(502, 503, 504),
|
||||
allowed_methods=frozenset(["GET", "HEAD", "OPTIONS", "TRACE"]),
|
||||
raise_on_status=False,
|
||||
)
|
||||
|
||||
adapter = HTTPAdapter(max_retries=retries)
|
||||
session.mount("http://", adapter)
|
||||
session.mount("https://", adapter)
|
||||
return session
|
||||
|
||||
def _mint_jwt(self) -> str:
|
||||
"""Mint a JWT from AppWrite; empty string if unavailable (don’t block requests)."""
|
||||
try:
|
||||
token = self._aw.mint_jwt() # expected to return dict-like
|
||||
return token.get("jwt", "") if token else ""
|
||||
except Exception as e:
|
||||
logger.warning("Failed to mint JWT", extra={"error": str(e)})
|
||||
return ""
|
||||
|
||||
def _auth_headers(self) -> Dict[str, str]:
|
||||
"""
|
||||
Build per-call headers with Authorization if a JWT is available.
|
||||
Avoid mutating shared headers.
|
||||
"""
|
||||
headers = dict(self._base_headers)
|
||||
jwt = self._mint_jwt()
|
||||
if jwt:
|
||||
headers["Authorization"] = f"Bearer {jwt}"
|
||||
return headers
|
||||
|
||||
def _resolve_url(self, path_or_url: str) -> str:
|
||||
"""
|
||||
Accept either a full URL or a path (e.g., '/char/'). Join with base_url when needed.
|
||||
"""
|
||||
if path_or_url.lower().startswith(("http://", "https://")):
|
||||
return path_or_url
|
||||
return urljoin(self.base_url + "/", path_or_url.lstrip("/"))
|
||||
|
||||
def _safe_json(self, resp: Response) -> Dict[str, Any]:
|
||||
"""
|
||||
Attempt to parse JSON. If body is empty or not JSON, return {}.
|
||||
"""
|
||||
# 204 No Content or truly empty payloads
|
||||
if resp.status_code == 204 or not resp.content:
|
||||
return {}
|
||||
|
||||
try:
|
||||
return resp.json()
|
||||
except ValueError:
|
||||
# Non-JSON payload; log preview and return empty.
|
||||
preview = ""
|
||||
try:
|
||||
preview = resp.text[:400]
|
||||
except Exception:
|
||||
pass
|
||||
logger.warning(
|
||||
"Non-JSON response body",
|
||||
extra={
|
||||
"url": resp.request.url if resp.request else None,
|
||||
"status": resp.status_code,
|
||||
"body_preview": preview,
|
||||
},
|
||||
)
|
||||
return {}
|
||||
|
||||
def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
*,
|
||||
params: Optional[Dict[str, Any]] = None,
|
||||
json_body: Optional[Dict[str, Any]] = None,
|
||||
timeout: Optional[tuple[float, float]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Central request executor. Never raises to the caller.
|
||||
Returns parsed JSON on success or {} on any error.
|
||||
"""
|
||||
url = self._resolve_url(path)
|
||||
headers = self._auth_headers()
|
||||
to = timeout or self.default_timeout
|
||||
|
||||
try:
|
||||
resp = self._session.request(
|
||||
method=method.upper(),
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=params,
|
||||
json=json_body,
|
||||
timeout=to,
|
||||
)
|
||||
|
||||
# Log and return {} on non-2xx
|
||||
if not (200 <= resp.status_code < 300):
|
||||
# Truncate body in logs to avoid huge entries
|
||||
preview = ""
|
||||
try:
|
||||
preview = resp.text[:400]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.warning(
|
||||
"HTTP request failed",
|
||||
extra={
|
||||
"method": method.upper(),
|
||||
"url": resp.request.url if resp.request else url,
|
||||
"status": resp.status_code,
|
||||
"params": params,
|
||||
"json_body": json_body,
|
||||
"body_preview": preview,
|
||||
},
|
||||
)
|
||||
return {}
|
||||
|
||||
# Success path: parse JSON safely
|
||||
return self._safe_json(resp)
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
# Network/timeout/connection errors
|
||||
logger.warning(
|
||||
"Network error during HTTP request",
|
||||
extra={
|
||||
"method": method.upper(),
|
||||
"url": url,
|
||||
"params": params,
|
||||
"json_body": json_body,
|
||||
"error_type": type(e).__name__,
|
||||
"error": str(e),
|
||||
},
|
||||
exc_info=True,
|
||||
)
|
||||
return {}
|
||||
except Exception as e:
|
||||
# Absolute last-resort guardrail
|
||||
logger.error(
|
||||
"Unexpected error during HTTP request",
|
||||
extra={
|
||||
"method": method.upper(),
|
||||
"url": url,
|
||||
"params": params,
|
||||
"json_body": json_body,
|
||||
"error_type": type(e).__name__,
|
||||
"error": str(e),
|
||||
},
|
||||
exc_info=True,
|
||||
)
|
||||
return {}
|
||||
BIN
app/static/images/races/avaline.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
app/static/images/races/beastfolk.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
app/static/images/races/draconian.jpg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
app/static/images/races/dwarf.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
app/static/images/races/elf.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
app/static/images/races/hellion.jpg
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
app/static/images/races/terran.jpg
Normal file
|
After Width: | Height: | Size: 66 KiB |
BIN
app/static/images/races/vorgath.jpg
Normal file
|
After Width: | Height: | Size: 85 KiB |
18
app/templates/ajax/prof_archanist.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<h3>Archanist</h3>
|
||||
<p>
|
||||
The Archanists are masters of the arcane arts, wielding the fundamental forces of reality to achieve their goals.
|
||||
They possess a deep understanding of the underlying fabric of existence, allowing them to manipulate the threads
|
||||
of fate, bend time and space to their will, and summon the raw power of the cosmos. Through tireless study,
|
||||
experimentation, and mystical communion with the celestial forces, Archanists have developed a unique blend of
|
||||
magical prowess, strategic insight, and philosophical clarity. They are often sought as counselors, diplomats, and
|
||||
problem-solvers by those in need of guidance or solution to complex challenges.
|
||||
</p>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes:</h4>
|
||||
<ul>
|
||||
<li>Health: 8</li>
|
||||
<li>Magic Power: 14</li>
|
||||
<li>Primary Stat: INT</li>
|
||||
</ul>
|
||||
</div>
|
||||
19
app/templates/ajax/prof_assassin.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<h3>Assassins</h3>
|
||||
<p>
|
||||
The Assassins are stealthy and deadly agents, trained to stalk the shadows and strike without warning. They
|
||||
possess a unique blend of physical agility, mental focus, and cunning strategy, allowing them to move unseen,
|
||||
gather information, and eliminate targets with precision and silence. Through years of rigorous training in the
|
||||
art of espionage, deception, and murder, Assassins have honed their skills to the point where they can blend into
|
||||
the background, become one with the darkness, and disappear into the night like specters. They are often hired as
|
||||
mercenaries, bodyguards, or spies, but those who employ them do so at their own peril.
|
||||
|
||||
</p>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes:</h4>
|
||||
<ul>
|
||||
<li>Health: 10</li>
|
||||
<li>Magic Power: 10</li>
|
||||
<li>Primary Stat: DEX</li>
|
||||
</ul>
|
||||
</div>
|
||||
19
app/templates/ajax/prof_bloodborn.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<h3>Bloodborn</h3>
|
||||
<p>
|
||||
The Bloodborn are unbridled warriors, born of the darkest depths of human nature and forged in the fire of
|
||||
unforgiving violence. They possess a primal connection to their own rage and fury, allowing them to tap into a
|
||||
deep wellspring of strength, speed, and ferocity when faced with adversity. Through generations of brutal training
|
||||
and unrelenting combat, Bloodborn have learned to channel their inner turmoil into a maelstrom of aggression,
|
||||
unleashing devastating attacks that leave foes reeling in terror. They are often seen as outcasts, driven by their
|
||||
own unyielding passions and instincts, but those who dare oppose them do so at the risk of facing an unrelenting
|
||||
storm of bloodlust and fury.
|
||||
</p>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes:</h4>
|
||||
<ul>
|
||||
<li>Health: 14</li>
|
||||
<li>Magic Power: 5</li>
|
||||
<li>Primary Stat: STR</li>
|
||||
</ul>
|
||||
</div>
|
||||
17
app/templates/ajax/prof_cleric.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<h3>Clerics</h3>
|
||||
<p>
|
||||
The Clerics are devoted servants of a higher power, channeling the divine energies to heal, protect, and uplift
|
||||
those around them. They possess a deep understanding of the mysteries of life and death, allowing them to tend to
|
||||
wounds, calm fears, and bring solace to the afflicted. Through their sacred vows and mystical communion with the
|
||||
divine, Clerics have developed a unique blend of spiritual insight, empathetic compassion, and healing artistry.
|
||||
As agents of hope and redemption, they often walk among the sick, the dying, and the grieving, offering comfort,
|
||||
guidance, and the promise of a brighter tomorrow.
|
||||
</p>
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes:</h4>
|
||||
<ul>
|
||||
<li>Health: 8</li>
|
||||
<li>Magic Power: 14</li>
|
||||
<li>Primary Stat: WIS</li>
|
||||
</ul>
|
||||
</div>
|
||||
17
app/templates/ajax/prof_guardian.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<h3>Guardians</h3>
|
||||
<p>
|
||||
The Guardians are holy warriors, sworn to defend the innocent and uphold the principles of justice, honor, and
|
||||
righteousness. They possess a strong sense of duty, unshakeable conviction, and unwavering compassion, guiding
|
||||
them as they stride into battle with valor in their hearts. Through their sacred oath and spiritual connection to
|
||||
a higher power, Guardians have developed a unique blend of martial prowess, moral authority, and divine
|
||||
intervention. As champions of the faithful, they are often called upon to vanquish evil, protect the vulnerable,
|
||||
and defend the sacred realms against darkness and despair.
|
||||
</p>
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes:</h4>
|
||||
<ul>
|
||||
<li>Health: 14</li>
|
||||
<li>Magic Power: 5</li>
|
||||
<li>Primary Stat: STR</li>
|
||||
</ul>
|
||||
</div>
|
||||
17
app/templates/ajax/prof_hexist.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<h3>Hexists</h3>
|
||||
<p>
|
||||
The Hexists are masters of dark and malevolent magic, delighting in the suffering and terror they inspire in
|
||||
others. They possess a twisted understanding of the arcane forces, allowing them to manipulate reality itself to
|
||||
inflict cruel and capricious punishment upon their enemies. Through their mastery of dark arts and infernal pacts,
|
||||
Hexists have developed a unique blend of magical cunning, sadistic glee, and corrupted ambition. As agents of
|
||||
chaos and despair, they often seek to undermine the fabric of society, sow discord, and revel in the misery of
|
||||
others.
|
||||
</p>
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes:</h4>
|
||||
<ul>
|
||||
<li>Health: 8</li>
|
||||
<li>Magic Power: 14</li>
|
||||
<li>Primary Stat: INT</li>
|
||||
</ul>
|
||||
</div>
|
||||
16
app/templates/ajax/prof_ranger.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<h3>Rangers</h3>
|
||||
<p>
|
||||
The Rangers are skilled trackers, hunters, and guardians of the natural world. They possess a deep understanding
|
||||
of the land, its rhythms, and its creatures, allowing them to move unseen, stalk their prey with precision, and
|
||||
track down even the most elusive foes. Through their connection to the wild, Rangers have developed a unique blend
|
||||
of survival skills, stealth, and primal intuition. As champions of the wilderness, they often walk a fine line
|
||||
between hunter and protector, defending the innocent and vanquishing those who would desecrate the land.
|
||||
</p>
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes:</h4>
|
||||
<ul>
|
||||
<li>Health: 10</li>
|
||||
<li>Magic Power: 10</li>
|
||||
<li>Primary Stat: DEX</li>
|
||||
</ul>
|
||||
</div>
|
||||
17
app/templates/ajax/prof_warlock.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<h3>Warlock</h3>
|
||||
<p>
|
||||
The Warlocks are adepts of dark and forbidden magic, forging pacts with malevolent forces to wield power beyond
|
||||
mortal comprehension. They possess a mastery of arcane energies, allowing them to summon eldritch powers,
|
||||
manipulate reality, and bend the fabric of existence to their will. Through their pact with an otherworldly
|
||||
patron, Warlocks have developed a unique blend of magical prowess, psychological manipulation, and shadowy
|
||||
intrigue. As agents of chaos and darkness, they often walk a delicate balance between power and corruption, using
|
||||
their mastery of the arcane to further their own sinister agendas.
|
||||
</p>
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes:</h4>
|
||||
<ul>
|
||||
<li>Health: 8</li>
|
||||
<li>Magic Power: 14</li>
|
||||
<li>Primary Stat: WIS</li>
|
||||
</ul>
|
||||
</div>
|
||||
30
app/templates/ajax/race_avaline.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<h3>Avaline</h3>
|
||||
<p>
|
||||
The Avaline are a divine and majestic people, born from the celestial realm to serve as radiant warriors of the
|
||||
sky. Their physical form is one of elegance and power, with slender bodies, delicate wings, and eyes that shine
|
||||
like stars. With their connection to the divine, they possess exceptional martial prowess, wielding blades that
|
||||
seem to cut through the very fabric of reality. The Avaline are drawn to conflict, not for the sake of bloodshed,
|
||||
but to vanquish darkness and restore balance to a world in need. Their presence on the battlefield is a beacon of
|
||||
hope, inspiring allies and striking fear into the hearts of their enemies.
|
||||
|
||||
</p>
|
||||
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<img src="{{ url_for('static', filename='images/races/avaline.jpg') }}" style="width: 300px;" alt="Race Image"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes: + / - from 10</h4>
|
||||
<ul>
|
||||
<li>Strength (Str): +5</li>
|
||||
<li>Dexterity (Dex): 0</li>
|
||||
<li>Intelligence (Int): +3</li>
|
||||
<li>Wisdom (Wis): 1</li>
|
||||
<li>Luck (Luk): -2</li>
|
||||
<li>Charisma (Cha): -1</li>
|
||||
<li>Constitution (Con): 0</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
29
app/templates/ajax/race_beastfolk.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<h3>Beastfolk</h3>
|
||||
<p>
|
||||
Beastfolk are feral and resilient tribes that have adapted to life in the wilderness, developing a deep connection
|
||||
with the natural world and its creatures. They possess a unique physical form, blending human and animal
|
||||
characteristics, allowing them to move unseen, communicate through scent and sound, and track their prey with
|
||||
ease. Beastfolk are fiercely protective of their territory and those they care about, making them formidable
|
||||
allies or fierce opponents. Despite their primal appearance, they are not mindless beasts, but rather complex
|
||||
individuals with a rich culture and history.
|
||||
</p>
|
||||
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<img src="{{ url_for('static', filename='images/races/beastfolk.jpg') }}" style="width: 300px;" alt="Race Image"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes: + / - from 10</h4>
|
||||
<ul>
|
||||
<li>Strength (Str): +3</li>
|
||||
<li>Dexterity (Dex): +5</li>
|
||||
<li>Intelligence (Int): +1</li>
|
||||
<li>Wisdom (Wis): -2</li>
|
||||
<li>Luck (Luk): -1</li>
|
||||
<li>Charisma (Cha): 0</li>
|
||||
<li>Constitution (Con): +1</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
30
app/templates/ajax/race_draconian.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<h3>Draconian</h3>
|
||||
<p>
|
||||
The Draconians are a proud and ancient people, born from the union of dragons and terrans. Their physical form is a
|
||||
testament to their draconic heritage, with scales that shimmer like polished gemstones, wings that soar through
|
||||
the skies, and eyes that burn with inner fire. As half-dragon, they possess a unique blend of ferocity,
|
||||
intelligence, and charisma, making them formidable diplomats, warriors, and leaders. The Draconians walk the line
|
||||
between two worlds, bridging the gap between humans and dragons, and often serving as mediators and ambassadors
|
||||
between their respective cultures. Their presence is often accompanied by a hint of smoke and flame, signifying
|
||||
their connection to the primal forces of nature.
|
||||
|
||||
</p>
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<img src="{{ url_for('static', filename='images/races/draconian.jpg') }}" style="width: 300px;" alt="Race Image"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes: + / - from 10</h4>
|
||||
<ul>
|
||||
<li>Strength (Str): +1</li>
|
||||
<li>Dexterity (Dex): +3</li>
|
||||
<li>Intelligence (Int): 0</li>
|
||||
<li>Wisdom (Wis): -2</li>
|
||||
<li>Luck (Luk): 5</li>
|
||||
<li>Charisma (Cha): +1</li>
|
||||
<li>Constitution (Con): -1</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
30
app/templates/ajax/race_dwarf.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<h3>Dwarf</h3>
|
||||
<p>
|
||||
The Dwarves are a sturdy and proud people, born from the depths of the earth to forge a legacy of resilience and
|
||||
determination. Their physical form is a testament to their craft and industry, with stout bodies, strong limbs,
|
||||
and eyes that shine like polished iron. As skilled artisans and master craftsmen, they possess an unyielding
|
||||
dedication to their work, honing their skills in the depths of mountains and caverns. Dwarves are a people of
|
||||
tradition and heritage, standing guard over ancient secrets, hidden treasures, and forgotten knowledge. Their
|
||||
connection to the earth is deep and abiding, granting them a profound understanding of the natural world and its
|
||||
rhythms.
|
||||
|
||||
</p>
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<img src="{{ url_for('static', filename='images/races/dwarf.jpg') }}" style="width: 300px;" alt="Race Image"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes: + / - from 10</h4>
|
||||
<ul>
|
||||
<li>Strength (Str): +5</li>
|
||||
<li>Dexterity (Dex): -2</li>
|
||||
<li>Intelligence (Int): 0</li>
|
||||
<li>Wisdom (Wis): -1</li>
|
||||
<li>Luck (Luk): +1</li>
|
||||
<li>Charisma (Cha): +1</li>
|
||||
<li>Constitution (Con): +3</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
29
app/templates/ajax/race_elf.html
Normal file
@@ -0,0 +1,29 @@
|
||||
<h3>Elves</h3>
|
||||
<p>
|
||||
Elves are an ancient and enigmatic people, known for their striking physical beauty, exceptional magical
|
||||
abilities, and unparalleled connection with nature. They possess a deep understanding of the natural world,
|
||||
allowing them to communicate with animals, manipulate plants, and wield the elements with precision. Elves are
|
||||
highly attuned to their surroundings, making them formidable hunters, skilled archers, and gifted warriors.
|
||||
Despite their refined features, elves are not immune to the harsh realities of life, and they have developed a
|
||||
rich culture that reflects their struggles against the forces of darkness.
|
||||
</p>
|
||||
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<img src="{{ url_for('static', filename='images/races/elf.jpg') }}" style="width: 300px;" alt="Race Image"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes: + / - from 10</h4>
|
||||
<ul>
|
||||
<li>Strength (Str): -2</li>
|
||||
<li>Dexterity (Dex): +5</li>
|
||||
<li>Intelligence (Int): +3</li>
|
||||
<li>Wisdom (Wis): 0</li>
|
||||
<li>Luck (Luk): 1</li>
|
||||
<li>Charisma (Cha): 1</li>
|
||||
<li>Constitution (Con): -1</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
32
app/templates/ajax/race_hellion.html
Normal file
@@ -0,0 +1,32 @@
|
||||
<h3>Hellions</h3>
|
||||
<p>
|
||||
The Hellions are a malevolent and enigmatic people, born from the dark recesses of the Shadow Realm to bring
|
||||
terror and chaos into the world. Their physical form is a twisted mockery of humanity, with bodies that seem to
|
||||
shift and writhe like living shadows, eyes that burn with an otherworldly green fire, and skin that seems to
|
||||
absorb the light around them. As half-demon, they possess a unique blend of dark magic, cunning, and charisma,
|
||||
making them formidable manipulators, spies, and assassins. Hellions are drawn to the darker aspects of life,
|
||||
reveling in the fear and suffering of others, and often serving as agents of chaos and destruction for their
|
||||
masters. Their presence is accompanied by an aura of malevolent energy, casting a pall of dread over those around
|
||||
them.
|
||||
|
||||
</p>
|
||||
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<img src="{{ url_for('static', filename='images/races/hellion.jpg') }}" style="width: 300px;" alt="Race Image"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes: + / - from 10</h4>
|
||||
<ul>
|
||||
<li>Strength (Str): +3</li>
|
||||
<li>Dexterity (Dex): +1</li>
|
||||
<li>Intelligence (Int): +1</li>
|
||||
<li>Wisdom (Wis): 0</li>
|
||||
<li>Luck (Luk): -2</li>
|
||||
<li>Charisma (Cha): +5</li>
|
||||
<li>Constitution (Con): -1</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
31
app/templates/ajax/race_terran.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<h3>Terrans</h3>
|
||||
<p>
|
||||
The Terrans are a sturdy and resilient people, born from the rich soil of the land to thrive in the everyday
|
||||
world. Their physical form is unremarkable, yet serviceable, with bodies that adapt to their surroundings, eyes
|
||||
that see clearly through the mundane, and hearts that beat with a deep connection to the earth. As ordinary
|
||||
individuals, they possess an extraordinary capacity for empathy, compassion, and determination, making them
|
||||
formidable mediators, leaders, and guardians of the common good. Terrans are not remarkable in any one way, yet
|
||||
their collective strength lies in their ability to work together, support each other, and build a better world
|
||||
through incremental progress and everyday heroism.
|
||||
|
||||
</p>
|
||||
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<img src="{{ url_for('static', filename='images/races/terran.jpg') }}" style="width: 300px;" alt="Race Image"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes: + / - from 10</h4>
|
||||
<ul>
|
||||
<li>Strength (Str): +1</li>
|
||||
<li>Dexterity (Dex): +3</li>
|
||||
<li>Intelligence (Int): 0</li>
|
||||
<li>Wisdom (Wis): -2</li>
|
||||
<li>Luck (Luk): +5</li>
|
||||
<li>Charisma (Cha): +1</li>
|
||||
<li>Constitution (Con): -1</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
33
app/templates/ajax/race_vorgath.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<h3>Vorgath</h3>
|
||||
<p>
|
||||
The Vorgath are a twisted and malevolent people, born from the darkest recesses of existence to bring unrelenting
|
||||
destruction into the world. Their physical form is an affront to nature, with bodies that seem to be perpetually
|
||||
corrupted, eyes that blaze with an otherworldly intensity, and skin that appears to writhe like living darkness.
|
||||
As abominations of the cosmos, they possess a unique blend of dark energy, unnatural resilience, and malevolent
|
||||
willpower, making them formidable enemies, unyielding in their pursuit of chaos and despair. Vorgath are driven by
|
||||
an insatiable hunger for destruction, reveling in the suffering of others, and often serving as agents of darkness
|
||||
and terror for their masters. Their presence is accompanied by an aura of unrelenting malevolence, striking fear
|
||||
into the hearts of all who behold them.
|
||||
|
||||
</p>
|
||||
|
||||
<div class="row align-items-center justify-content-center">
|
||||
<div class="col-12 col-md-6">
|
||||
<img src="{{ url_for('static', filename='images/races/vorgath.jpg') }}" style="width: 300px;" alt="Race Image"/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6" id="attributes">
|
||||
<h4>Attributes: + / - from 10</h4>
|
||||
<ul>
|
||||
<li>Strength (Str): +5</li>
|
||||
<li>Dexterity (Dex): +3</li>
|
||||
<li>Intelligence (Int): 1</li>
|
||||
<li>Wisdom (Wis): 0</li>
|
||||
<li>Luck (Luk): -1</li>
|
||||
<li>Charisma (Cha): -2</li>
|
||||
<li>Constitution (Con): +3</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
136
app/templates/char/create_char.html
Normal file
@@ -0,0 +1,136 @@
|
||||
{% extends "bases/main_base.html" %}
|
||||
|
||||
{% block title %}Admin Panel — Dashboard{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="container-fluid vh-100">
|
||||
<div class="row vh-100 align-items-center justify-content-center">
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="card w-400 mw-full mx-auto" style="padding: 20px;">
|
||||
|
||||
<h5 class="card-title text-center">Create Character</h5>
|
||||
|
||||
<form action="" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="text-input" class="required">Name</label>
|
||||
<input type="text" class="form-control mb-4" id="character_name" name="character_name" placeholder="Name your Character" required >
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="race_dropdown">Choose Race</label>
|
||||
<select class="form-control mb-4" id="race_dropdown" name="race_dropdown" required >
|
||||
<option value="" disabled selected>Select a Race...</option>
|
||||
<option value="avaline">Avaline - Divine, Strong, Intelligent</option>
|
||||
<option value="beastfolk">Beastfolk - Half Beast, Half Terran</option>
|
||||
<option value="draconian">Draconian - Half Dragon, Half Terran</option>
|
||||
<option value="dwarf">Dwarf - Stout, Strong, Short</option>
|
||||
<option value="elf">Elf - Tall, Slender, Agile</option>
|
||||
<option value="hellion">Hellion - Dark, Shadowy Humanoid</option>
|
||||
<option value="terran">Terran - Some call them Human.</option>
|
||||
<option value="vorgath">Vorgath - Monstrous Evil Humanoid</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="profession_dropdown">Choose Profession</label>
|
||||
<select class="form-control mb-4" id="profession_dropdown" name="profession_dropdown" required >
|
||||
<option value="" disabled selected>Select a Profession...</option>
|
||||
<option value="archanist">Archanist - Magic</option>
|
||||
<option value="assassin">Assassin - Physical</option>
|
||||
<option value="bloodborn">Bloodborn - Physical</option>
|
||||
<option value="cleric">Cleric - Magic</option>
|
||||
<option value="guardian">Guardian - Physical</option>
|
||||
<option value="hexist">Hexist - Magic</option>
|
||||
<option value="ranger">Ranger - Physical</option>
|
||||
<option value="warlock">Warlock - Magic</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="origin_story">Origin Story</label>
|
||||
<textarea class="form-control mb-4" id="origin_story" name="origin_story" rows="10" cols="40" required >{{data.starter_text}}</textarea>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary btn-block">
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="text_holder" class="col-12 col-md-6">
|
||||
<p>
|
||||
To start playing, we need to help you create your unique character.
|
||||
</p>
|
||||
|
||||
<p>The following fields are required:</p>
|
||||
<ul>
|
||||
<li>Name: This is your character's identity in the game.</li>
|
||||
<li>Race: Choose from our selection of options.</li>
|
||||
<li>Profession: Select from our list of profession (commonly called classes).</li>
|
||||
<li>Origin Story: This section is optional, but can provide additional context for your character.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Reference to the dropdown and image
|
||||
const racedropdown = document.getElementById('race_dropdown');
|
||||
const profdropdown = document.getElementById('profession_dropdown');
|
||||
const contentArea = document.getElementById('text_holder');
|
||||
|
||||
racedropdown.addEventListener('change', async function () {
|
||||
const value = this.value;
|
||||
|
||||
const url = `/ajax/races?race=${value}`;
|
||||
|
||||
try {
|
||||
// Fetch HTML fragment from the server
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const html = await response.text();
|
||||
|
||||
// Replace existing content
|
||||
contentArea.innerHTML = html;
|
||||
} catch (err) {
|
||||
console.error("Error loading content:", err);
|
||||
contentArea.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
Failed to load content. Please try again.
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
profdropdown.addEventListener('change', async function () {
|
||||
const value = this.value;
|
||||
|
||||
const url = `/ajax/prof?prof=${value}`;
|
||||
|
||||
try {
|
||||
// Fetch HTML fragment from the server
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const html = await response.text();
|
||||
|
||||
// Replace existing content
|
||||
contentArea.innerHTML = html;
|
||||
} catch (err) {
|
||||
console.error("Error loading content:", err);
|
||||
contentArea.innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
Failed to load content. Please try again.
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||