Initial commit
This commit is contained in:
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.venv/
|
||||
*.sqlite3
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
17
docker-compose.yml
Normal file
17
docker-compose.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
backend:
|
||||
build: ./dota-random-builds-back
|
||||
volumes:
|
||||
- backend-data:/app/data
|
||||
expose:
|
||||
- "8000"
|
||||
|
||||
frontend:
|
||||
build: ./dota-random-builds-front
|
||||
ports:
|
||||
- "80:80"
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
volumes:
|
||||
backend-data:
|
||||
8
dota-random-builds-back/.dockerignore
Normal file
8
dota-random-builds-back/.dockerignore
Normal file
@@ -0,0 +1,8 @@
|
||||
.venv
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
.git
|
||||
.gitignore
|
||||
*.sqlite3
|
||||
.env
|
||||
52
dota-random-builds-back/API.md
Normal file
52
dota-random-builds-back/API.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# API Endpoints for Frontend Compatibility
|
||||
|
||||
This document defines the minimal backend surface expected by the Vue frontend. Default base path is `/api`; responses are JSON with `Content-Type: application/json`.
|
||||
|
||||
## POST /api/randomize
|
||||
- **Purpose:** Produce a randomized Dota 2 build and optionally return the full data sets used for rendering.
|
||||
- **Request body:**
|
||||
- `includeSkills` (boolean) — include a skill build suggestion.
|
||||
- `includeAspect` (boolean) — include an aspect suggestion.
|
||||
- `itemsCount` (number) — how many items to return (UI offers 3–6).
|
||||
- **Response 200 body:**
|
||||
- `hero` — object `{ id: string; name: string; primary: string; }`.
|
||||
- `items` — array of `{ id: string; name: string; }` with length `itemsCount` (also used to hydrate cached items if you return the full pool).
|
||||
- `skillBuild` (optional) — `{ id: string; name: string; description?: string; }` when `includeSkills` is true.
|
||||
- `aspect` (optional) — string when `includeAspect` is true.
|
||||
- `heroes` (optional) — full hero list to cache client-side; same shape as `hero`.
|
||||
- `skillBuilds` (optional) — full skill build list; same shape as `skillBuild`.
|
||||
- `aspects` (optional) — array of strings.
|
||||
- **Example request:**
|
||||
```json
|
||||
{
|
||||
"includeSkills": true,
|
||||
"includeAspect": false,
|
||||
"itemsCount": 6
|
||||
}
|
||||
```
|
||||
- **Example response:**
|
||||
```json
|
||||
{
|
||||
"hero": { "id": "pudge", "name": "Pudge", "primary": "strength" },
|
||||
"items": [
|
||||
{ "id": "blink", "name": "Blink Dagger" },
|
||||
{ "id": "bkb", "name": "Black King Bar" }
|
||||
],
|
||||
"skillBuild": { "id": "hookdom", "name": "Hook/Rot Max", "description": "Max hook, then rot; early Flesh Heap." },
|
||||
"heroes": [{ "id": "axe", "name": "Axe", "primary": "strength" }],
|
||||
"skillBuilds": [{ "id": "support", "name": "Support" }],
|
||||
"aspects": ["Heroic Might"]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
- Use standard HTTP status codes (400 invalid input, 500 server error).
|
||||
- Error payload shape:
|
||||
```json
|
||||
{ "message": "Failed to load random build" }
|
||||
```
|
||||
- The UI surfaces `message` directly; keep it user-friendly.
|
||||
|
||||
## Implementation Notes
|
||||
- Ensure deterministic constraints: unique `id` per entity and avoid empty arrays when `includeSkills`/`includeAspect` are true.
|
||||
- If backing data is static, you may return `heroes/items/skillBuilds/aspects` once per request; the frontend caches them after the first successful call.
|
||||
13
dota-random-builds-back/Dockerfile
Normal file
13
dota-random-builds-back/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Run migrations, load data and start server
|
||||
CMD ["sh", "-c", "python manage.py migrate && python manage.py load_static_data && python manage.py runserver 0.0.0.0:8000"]
|
||||
0
dota-random-builds-back/api/__init__.py
Normal file
0
dota-random-builds-back/api/__init__.py
Normal file
3
dota-random-builds-back/api/admin.py
Normal file
3
dota-random-builds-back/api/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
5
dota-random-builds-back/api/apps.py
Normal file
5
dota-random-builds-back/api/apps.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ApiConfig(AppConfig):
|
||||
name = 'api'
|
||||
51
dota-random-builds-back/api/data.py
Normal file
51
dota-random-builds-back/api/data.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import random
|
||||
from typing import Dict
|
||||
|
||||
|
||||
def generate_skill_build() -> Dict[int, str]:
|
||||
"""
|
||||
Generate a random skill build for levels 1-25 following Dota 2 rules:
|
||||
- Ultimate (r) can only be skilled at levels 6, 12, 18
|
||||
- Talents can only be skilled at levels 10, 15, 20, 25
|
||||
- Basic abilities (q, w, e) can be skilled at other levels
|
||||
- Each basic ability maxes at 4 points
|
||||
- Ultimate maxes at 3 points
|
||||
- One talent per tier (left OR right)
|
||||
"""
|
||||
skill_build: Dict[int, str] = {}
|
||||
|
||||
# Track ability points spent
|
||||
ability_points = {"q": 0, "w": 0, "e": 0, "r": 0}
|
||||
max_points = {"q": 4, "w": 4, "e": 4, "r": 3}
|
||||
|
||||
# Talent levels and which side to pick
|
||||
talent_levels = [10, 15, 20, 25]
|
||||
|
||||
# Ultimate levels
|
||||
ult_levels = [6, 12, 18]
|
||||
|
||||
for level in range(1, 26):
|
||||
if level in talent_levels:
|
||||
# Pick random talent side
|
||||
skill_build[level] = random.choice(["left_talent", "right_talent"])
|
||||
elif level in ult_levels:
|
||||
# Must skill ultimate if not maxed
|
||||
if ability_points["r"] < max_points["r"]:
|
||||
skill_build[level] = "r"
|
||||
ability_points["r"] += 1
|
||||
else:
|
||||
# Ultimate maxed, pick a basic ability
|
||||
available = [a for a in ["q", "w", "e"] if ability_points[a] < max_points[a]]
|
||||
if available:
|
||||
choice = random.choice(available)
|
||||
skill_build[level] = choice
|
||||
ability_points[choice] += 1
|
||||
else:
|
||||
# Regular level - pick a basic ability
|
||||
available = [a for a in ["q", "w", "e"] if ability_points[a] < max_points[a]]
|
||||
if available:
|
||||
choice = random.choice(available)
|
||||
skill_build[level] = choice
|
||||
ability_points[choice] += 1
|
||||
|
||||
return skill_build
|
||||
1
dota-random-builds-back/api/management/__init__.py
Normal file
1
dota-random-builds-back/api/management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"" # Package marker
|
||||
@@ -0,0 +1 @@
|
||||
"" # Package marker
|
||||
@@ -0,0 +1,104 @@
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from api.models import Aspect, Hero, Item
|
||||
|
||||
# Add data directory to path for imports
|
||||
sys.path.insert(0, str(Path(settings.BASE_DIR) / "data"))
|
||||
from facets import HERO_FACETS
|
||||
|
||||
|
||||
def _strip_comments(text: str) -> str:
|
||||
"""Remove single-line and block comments from TypeScript data."""
|
||||
text = re.sub(r"/\*.*?\*/", "", text, flags=re.S)
|
||||
lines = []
|
||||
for line in text.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("//"):
|
||||
continue
|
||||
lines.append(line)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _extract_objects(text: str, require_primary: bool = False):
|
||||
cleaned = _strip_comments(text)
|
||||
objects = []
|
||||
|
||||
for match in re.finditer(r"\{[^}]*\}", cleaned):
|
||||
chunk = match.group(0)
|
||||
entry = {}
|
||||
id_match = re.search(r"['\"]?id['\"]?\s*:\s*(['\"])(.*?)\1", chunk)
|
||||
name_match = re.search(r"['\"]?name['\"]?\s*:\s*(['\"])(.*?)\1", chunk)
|
||||
if not (id_match and name_match):
|
||||
continue
|
||||
entry["slug"] = id_match.group(2)
|
||||
entry["name"] = name_match.group(2)
|
||||
|
||||
if require_primary:
|
||||
primary_match = re.search(r"['\"]?primary['\"]?\s*:\s*(['\"])(.*?)\1", chunk)
|
||||
if not primary_match:
|
||||
continue
|
||||
entry["primary"] = primary_match.group(2)
|
||||
|
||||
if len(entry) == (3 if require_primary else 2):
|
||||
objects.append(entry)
|
||||
|
||||
return objects
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Load heroes, items and facets from data files into the database."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
base_dir = Path(settings.BASE_DIR)
|
||||
heroes_path = base_dir / "data" / "heroes.ts"
|
||||
items_path = base_dir / "data" / "items.ts"
|
||||
|
||||
if not heroes_path.exists() or not items_path.exists():
|
||||
raise CommandError("Missing data files in the ./data directory.")
|
||||
|
||||
heroes = _extract_objects(heroes_path.read_text(encoding="utf-8"), require_primary=True)
|
||||
items = _extract_objects(items_path.read_text(encoding="utf-8"), require_primary=False)
|
||||
|
||||
if not heroes:
|
||||
raise CommandError("Failed to parse heroes.ts")
|
||||
if not items:
|
||||
raise CommandError("Failed to parse items.ts")
|
||||
|
||||
hero_created = item_created = aspect_created = 0
|
||||
|
||||
for hero in heroes:
|
||||
hero_obj, created = Hero.objects.update_or_create(
|
||||
name=hero["name"],
|
||||
defaults={
|
||||
"primary": hero["primary"],
|
||||
},
|
||||
)
|
||||
hero_created += int(created)
|
||||
|
||||
# Load facets for this hero
|
||||
facets = HERO_FACETS.get(hero["name"], [])
|
||||
for facet_name in facets:
|
||||
_, facet_created = Aspect.objects.update_or_create(
|
||||
hero=hero_obj,
|
||||
name=facet_name,
|
||||
)
|
||||
aspect_created += int(facet_created)
|
||||
|
||||
for item in items:
|
||||
_, created = Item.objects.update_or_create(
|
||||
name=item["name"],
|
||||
)
|
||||
item_created += int(created)
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
f"Loaded {len(heroes)} heroes ({hero_created} new), "
|
||||
f"{len(items)} items ({item_created} new), "
|
||||
f"and {aspect_created} new facets."
|
||||
)
|
||||
)
|
||||
31
dota-random-builds-back/api/migrations/0001_initial.py
Normal file
31
dota-random-builds-back/api/migrations/0001_initial.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 6.0 on 2025-12-09 03:20
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Hero',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('external_id', models.CharField(max_length=100, unique=True)),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('primary', models.CharField(choices=[('Strength', 'Strength'), ('Agility', 'Agility'), ('Intelligence', 'Intelligence')], max_length=20)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Item',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('external_id', models.CharField(max_length=100, unique=True)),
|
||||
('name', models.CharField(max_length=200)),
|
||||
],
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 6.0 on 2025-12-09 03:33
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='hero',
|
||||
name='external_id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='item',
|
||||
name='external_id',
|
||||
),
|
||||
]
|
||||
25
dota-random-builds-back/api/migrations/0003_aspect.py
Normal file
25
dota-random-builds-back/api/migrations/0003_aspect.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 6.0 on 2025-12-09 18:43
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0002_remove_hero_external_id_remove_item_external_id'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Aspect',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200)),
|
||||
('hero', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='aspects', to='api.hero')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('hero', 'name')},
|
||||
},
|
||||
),
|
||||
]
|
||||
25
dota-random-builds-back/api/migrations/0004_buildofday.py
Normal file
25
dota-random-builds-back/api/migrations/0004_buildofday.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Generated by Django 6.0 on 2025-12-16 18:42
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('api', '0003_aspect'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BuildOfDay',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(unique=True)),
|
||||
('skill_build', models.JSONField(default=dict)),
|
||||
('aspect', models.CharField(blank=True, max_length=200, null=True)),
|
||||
('hero', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.hero')),
|
||||
('items', models.ManyToManyField(to='api.item')),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
dota-random-builds-back/api/migrations/__init__.py
Normal file
0
dota-random-builds-back/api/migrations/__init__.py
Normal file
48
dota-random-builds-back/api/models.py
Normal file
48
dota-random-builds-back/api/models.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Hero(models.Model):
|
||||
PRIMARY_ATTR_STRENGTH = "Strength"
|
||||
PRIMARY_ATTR_AGILITY = "Agility"
|
||||
PRIMARY_ATTR_INTELLIGENCE = "Intelligence"
|
||||
|
||||
PRIMARY_CHOICES = (
|
||||
(PRIMARY_ATTR_STRENGTH, "Strength"),
|
||||
(PRIMARY_ATTR_AGILITY, "Agility"),
|
||||
(PRIMARY_ATTR_INTELLIGENCE, "Intelligence"),
|
||||
)
|
||||
|
||||
name = models.CharField(max_length=200)
|
||||
primary = models.CharField(max_length=20, choices=PRIMARY_CHOICES)
|
||||
|
||||
def __str__(self) -> str: # pragma: no cover - convenience only
|
||||
return f"{self.name} ({self.primary})"
|
||||
|
||||
|
||||
class Item(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
|
||||
def __str__(self) -> str: # pragma: no cover - convenience only
|
||||
return self.name
|
||||
|
||||
|
||||
class Aspect(models.Model):
|
||||
hero = models.ForeignKey(Hero, on_delete=models.CASCADE, related_name="aspects")
|
||||
name = models.CharField(max_length=200)
|
||||
|
||||
class Meta:
|
||||
unique_together = ["hero", "name"]
|
||||
|
||||
def __str__(self) -> str: # pragma: no cover - convenience only
|
||||
return f"{self.hero.name} - {self.name}"
|
||||
|
||||
|
||||
class BuildOfDay(models.Model):
|
||||
date = models.DateField(unique=True)
|
||||
hero = models.ForeignKey(Hero, on_delete=models.CASCADE)
|
||||
items = models.ManyToManyField(Item)
|
||||
skill_build = models.JSONField(default=dict)
|
||||
aspect = models.CharField(max_length=200, blank=True, null=True)
|
||||
|
||||
def __str__(self) -> str: # pragma: no cover - convenience only
|
||||
return f"Build of {self.date} - {self.hero.name}"
|
||||
38
dota-random-builds-back/api/serializers.py
Normal file
38
dota-random-builds-back/api/serializers.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from api.models import Hero, Item
|
||||
|
||||
|
||||
class HeroSerializer(serializers.ModelSerializer):
|
||||
primary = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Hero
|
||||
fields = ["id", "name", "primary"]
|
||||
|
||||
def get_primary(self, obj):
|
||||
return obj.primary.lower()
|
||||
|
||||
|
||||
class ItemSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Item
|
||||
fields = ["id", "name"]
|
||||
|
||||
|
||||
class RandomizeBuildRequestSerializer(serializers.Serializer):
|
||||
includeSkills = serializers.BooleanField()
|
||||
includeAspect = serializers.BooleanField()
|
||||
itemsCount = serializers.IntegerField(min_value=1)
|
||||
heroId = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
||||
|
||||
class RandomizeBuildResponseSerializer(serializers.Serializer):
|
||||
hero = HeroSerializer()
|
||||
items = ItemSerializer(many=True)
|
||||
skillBuild = serializers.DictField(
|
||||
child=serializers.CharField(),
|
||||
required=False,
|
||||
help_text="Map of level (1-25) to skill (q/w/e/r/left_talent/right_talent)"
|
||||
)
|
||||
aspect = serializers.CharField(required=False)
|
||||
69
dota-random-builds-back/api/tests.py
Normal file
69
dota-random-builds-back/api/tests.py
Normal file
@@ -0,0 +1,69 @@
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from api.models import Hero, Item
|
||||
|
||||
|
||||
class RandomizeBuildAPITest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
self.hero = Hero.objects.create(name="Axe", primary="Strength")
|
||||
Item.objects.bulk_create(
|
||||
[
|
||||
Item(name="Blink Dagger"),
|
||||
Item(name="Black King Bar"),
|
||||
Item(name="Boots of Travel"),
|
||||
Item(name="Force Staff"),
|
||||
]
|
||||
)
|
||||
|
||||
def test_randomize_with_skills(self):
|
||||
response = self.client.post(
|
||||
"/api/randomize",
|
||||
{"includeSkills": True, "includeAspect": False, "itemsCount": 3},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
data = response.json()
|
||||
|
||||
self.assertIn("hero", data)
|
||||
self.assertEqual(len(data.get("items", [])), 3)
|
||||
self.assertIn("skillBuild", data)
|
||||
self.assertTrue(data.get("skillBuilds"))
|
||||
self.assertNotIn("aspect", data)
|
||||
|
||||
def test_randomize_with_aspect_only(self):
|
||||
response = self.client.post(
|
||||
"/api/randomize",
|
||||
{"includeSkills": False, "includeAspect": True, "itemsCount": 2},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
data = response.json()
|
||||
|
||||
self.assertIn("aspect", data)
|
||||
self.assertTrue(data.get("aspects"))
|
||||
self.assertNotIn("skillBuild", data)
|
||||
self.assertEqual(len(data.get("items", [])), 2)
|
||||
|
||||
def test_rejects_invalid_payload(self):
|
||||
response = self.client.post(
|
||||
"/api/randomize",
|
||||
{"includeSkills": "yes", "includeAspect": True, "itemsCount": 0},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn("message", response.json())
|
||||
|
||||
|
||||
class LoadStaticDataCommandTest(TestCase):
|
||||
def test_loads_heroes_and_items(self):
|
||||
call_command("load_static_data")
|
||||
|
||||
self.assertGreater(Hero.objects.count(), 0)
|
||||
self.assertGreater(Item.objects.count(), 0)
|
||||
9
dota-random-builds-back/api/urls.py
Normal file
9
dota-random-builds-back/api/urls.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import BuildOfDayView, HeroesListView, RandomizeBuildView
|
||||
|
||||
urlpatterns = [
|
||||
path("randomize", RandomizeBuildView.as_view(), name="randomize-build"),
|
||||
path("heroes", HeroesListView.as_view(), name="heroes-list"),
|
||||
path("build-of-day", BuildOfDayView.as_view(), name="build-of-day"),
|
||||
]
|
||||
164
dota-random-builds-back/api/views.py
Normal file
164
dota-random-builds-back/api/views.py
Normal file
@@ -0,0 +1,164 @@
|
||||
from datetime import date
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from api.models import Aspect, BuildOfDay, Hero, Item
|
||||
from api.serializers import (
|
||||
HeroSerializer,
|
||||
ItemSerializer,
|
||||
RandomizeBuildRequestSerializer,
|
||||
)
|
||||
|
||||
from .data import generate_skill_build
|
||||
|
||||
|
||||
class HeroesListView(APIView):
|
||||
"""
|
||||
GET: Return all available heroes for selection.
|
||||
"""
|
||||
|
||||
def get(self, request):
|
||||
heroes = Hero.objects.all().order_by("name")
|
||||
return Response(HeroSerializer(heroes, many=True).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class RandomizeBuildView(APIView):
|
||||
"""
|
||||
POST: Generate a random Dota 2 build with hero, items, and optionally skills/aspects.
|
||||
"""
|
||||
|
||||
def get_serializer(self, *args, **kwargs):
|
||||
return RandomizeBuildRequestSerializer(*args, **kwargs)
|
||||
|
||||
def post(self, request):
|
||||
serializer = RandomizeBuildRequestSerializer(data=request.data)
|
||||
if not serializer.is_valid():
|
||||
return Response(
|
||||
{"message": self._format_errors(serializer.errors)},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
validated_data = serializer.validated_data
|
||||
include_skills = validated_data["includeSkills"]
|
||||
include_aspect = validated_data["includeAspect"]
|
||||
items_count = validated_data["itemsCount"]
|
||||
hero_id = validated_data.get("heroId")
|
||||
|
||||
hero_count = Hero.objects.count()
|
||||
item_count = Item.objects.count()
|
||||
|
||||
if hero_count == 0:
|
||||
return Response(
|
||||
{"message": "No heroes available. Load data first."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if item_count < items_count:
|
||||
return Response(
|
||||
{"message": f"Not enough items available. Requested {items_count}, found {item_count}."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
if hero_id:
|
||||
hero_obj = Hero.objects.filter(id=hero_id).first()
|
||||
if not hero_obj:
|
||||
return Response(
|
||||
{"message": f"Hero with id {hero_id} not found."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
else:
|
||||
hero_obj = Hero.objects.order_by("?").first()
|
||||
item_objs = Item.objects.order_by("?")[:items_count]
|
||||
|
||||
response_data = {
|
||||
"hero": HeroSerializer(hero_obj).data,
|
||||
"items": ItemSerializer(item_objs, many=True).data,
|
||||
}
|
||||
|
||||
if include_skills:
|
||||
response_data["skillBuild"] = generate_skill_build()
|
||||
|
||||
if include_aspect:
|
||||
hero_aspects = Aspect.objects.filter(hero=hero_obj)
|
||||
if hero_aspects.exists():
|
||||
aspect_obj = hero_aspects.order_by("?").first()
|
||||
response_data["aspect"] = aspect_obj.name
|
||||
|
||||
return Response(response_data, status=status.HTTP_200_OK)
|
||||
|
||||
def _format_errors(self, errors: dict) -> str:
|
||||
"""Format serializer errors into a readable message."""
|
||||
messages = []
|
||||
for field, field_errors in errors.items():
|
||||
for error in field_errors:
|
||||
if field == "itemsCount" and "greater than or equal to" in str(error):
|
||||
messages.append("itemsCount must be at least 1.")
|
||||
elif "valid" in str(error).lower() or "required" in str(error).lower():
|
||||
if field in ("includeSkills", "includeAspect"):
|
||||
messages.append("includeSkills and includeAspect must be boolean.")
|
||||
elif field == "itemsCount":
|
||||
messages.append("itemsCount must be a number.")
|
||||
else:
|
||||
messages.append(f"{field}: {error}")
|
||||
return messages[0] if messages else "Invalid request data."
|
||||
|
||||
|
||||
class BuildOfDayView(APIView):
|
||||
"""
|
||||
GET: Return the build of the day. Generates one if it doesn't exist for today.
|
||||
"""
|
||||
|
||||
ITEMS_COUNT = 6
|
||||
|
||||
def get(self, request):
|
||||
today = date.today()
|
||||
|
||||
build = BuildOfDay.objects.filter(date=today).first()
|
||||
if not build:
|
||||
build = self._generate_build_of_day(today)
|
||||
if build is None:
|
||||
return Response(
|
||||
{"message": "Unable to generate build. Load data first."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
response_data = {
|
||||
"date": build.date.isoformat(),
|
||||
"hero": HeroSerializer(build.hero).data,
|
||||
"items": ItemSerializer(build.items.all(), many=True).data,
|
||||
"skillBuild": build.skill_build,
|
||||
}
|
||||
|
||||
if build.aspect:
|
||||
response_data["aspect"] = build.aspect
|
||||
|
||||
return Response(response_data, status=status.HTTP_200_OK)
|
||||
|
||||
def _generate_build_of_day(self, today: date) -> BuildOfDay | None:
|
||||
"""Generate and save a new build of the day."""
|
||||
hero_count = Hero.objects.count()
|
||||
item_count = Item.objects.count()
|
||||
|
||||
if hero_count == 0 or item_count < self.ITEMS_COUNT:
|
||||
return None
|
||||
|
||||
hero_obj = Hero.objects.order_by("?").first()
|
||||
item_objs = list(Item.objects.order_by("?")[: self.ITEMS_COUNT])
|
||||
|
||||
aspect_name = None
|
||||
hero_aspects = Aspect.objects.filter(hero=hero_obj)
|
||||
if hero_aspects.exists():
|
||||
aspect_obj = hero_aspects.order_by("?").first()
|
||||
aspect_name = aspect_obj.name
|
||||
|
||||
build = BuildOfDay.objects.create(
|
||||
date=today,
|
||||
hero=hero_obj,
|
||||
skill_build=generate_skill_build(),
|
||||
aspect=aspect_name,
|
||||
)
|
||||
build.items.set(item_objs)
|
||||
|
||||
return build
|
||||
0
dota-random-builds-back/config/__init__.py
Normal file
0
dota-random-builds-back/config/__init__.py
Normal file
16
dota-random-builds-back/config/asgi.py
Normal file
16
dota-random-builds-back/config/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for config project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
129
dota-random-builds-back/config/settings.py
Normal file
129
dota-random-builds-back/config/settings.py
Normal file
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Django settings for config project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 6.0.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/6.0/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-a#7v1um&b88$k)2nm7hvxe__o2c(gz=t5%)1)*oaij6u%+i((='
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
"corsheaders",
|
||||
"rest_framework",
|
||||
"api",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'config.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'config.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
||||
|
||||
import os
|
||||
|
||||
DATA_DIR = BASE_DIR / 'data'
|
||||
DATA_DIR.mkdir(exist_ok=True)
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': DATA_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/6.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/6.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
# CORS settings
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
23
dota-random-builds-back/config/urls.py
Normal file
23
dota-random-builds-back/config/urls.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
URL configuration for config project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/6.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path("api/", include("api.urls")),
|
||||
]
|
||||
16
dota-random-builds-back/config/wsgi.py
Normal file
16
dota-random-builds-back/config/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for config project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
7
dota-random-builds-back/data/aspects.ts
Normal file
7
dota-random-builds-back/data/aspects.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const aspects = [
|
||||
'Carry',
|
||||
'Support',
|
||||
'Roamer',
|
||||
'Jungler',
|
||||
'Pusher'
|
||||
]
|
||||
128
dota-random-builds-back/data/facets.py
Normal file
128
dota-random-builds-back/data/facets.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# Hero Facets data from Dota 2 patch 7.36+
|
||||
# Each hero has 2 facets that modify their abilities
|
||||
|
||||
HERO_FACETS = {
|
||||
# Strength Heroes
|
||||
"Abaddon": ["Mephitic Shroud", "The Quickening"],
|
||||
"Alchemist": ["Seed Money", "Mixologist"],
|
||||
"Axe": ["One Man Army", "Call Out"],
|
||||
"Beastmaster": ["Wild Hunt", "Beast Mode"],
|
||||
"Brewmaster": ["Roll Out the Barrel", "Drunken Master"],
|
||||
"Bristleback": ["Seeing Red", "Prickly"],
|
||||
"Centaur Warrunner": ["Horsepower", "Rawhide"],
|
||||
"Chaos Knight": ["Irrationality", "Schism"],
|
||||
"Clockwerk": ["Expanded Armature", "Overclock"],
|
||||
"Dawnbreaker": ["Solar Guardian", "Gleaming Hammer"],
|
||||
"Doom": ["Impending Doom", "Gluttony"],
|
||||
"Dragon Knight": ["Wyrm's Wrath", "Corrosive Dragon"],
|
||||
"Earth Spirit": ["Ready to Roll", "Stepping Stone"],
|
||||
"Earthshaker": ["Tectonic Buildup", "Slugger"],
|
||||
"Elder Titan": ["Deconstruction", "Momentum"],
|
||||
"Huskar": ["Bloodbath", "Incendiary"],
|
||||
"Kunkka": ["Grog Blossom", "Tideskipper"],
|
||||
"Legion Commander": ["Stonehall Plate", "Spoils of War"],
|
||||
"Lifestealer": ["Corpse Eater", "Rage Demon"],
|
||||
"Lycan": ["Spirit Wolves", "Alpha Wolves"],
|
||||
"Magnus": ["Reverse Polarity Field", "Hot-Blooded"],
|
||||
"Mars": ["Shock Troops", "Arena of Blood"],
|
||||
"Night Stalker": ["Blinding Void", "Heart of Darkness"],
|
||||
"Ogre Magi": ["Fat Chances", "Learning Curve"],
|
||||
"Omniknight": ["Degen Aura", "Ardent Revelation"],
|
||||
"Phoenix": ["Dying Light", "Spread the Fire"],
|
||||
"Primal Beast": ["Ferocity", "Irascible"],
|
||||
"Pudge": ["Flayers Hook", "Fresh Meat"],
|
||||
"Slardar": ["Brineguard", "Leg Day"],
|
||||
"Snapfire": ["Ricochet II", "Full Bore"],
|
||||
"Spirit Breaker": ["Imbalanced", "Bulldoze"],
|
||||
"Sven": ["Heavy Plate", "Warcry Aura"],
|
||||
"Tidehunter": ["Blubber", "Kraken Swell"],
|
||||
"Timbersaw": ["Flamethrower", "Shredder"],
|
||||
"Tiny": ["Crash Landing", "Insurmountable"],
|
||||
"Treant Protector": ["Sapling", "Primeval Power"],
|
||||
"Tusk": ["Drinking Buddies", "Overpowering Chill"],
|
||||
"Underlord": ["Abyssal Horde", "Fiend's Gate"],
|
||||
"Undying": ["Rotting Mitts", "Ripped"],
|
||||
"Wraith King": ["Bone Guard", "Death's Hallow"],
|
||||
|
||||
# Agility Heroes
|
||||
"Anti-Mage": ["Magebane", "Mana Thirst"],
|
||||
"Arc Warden": ["Order", "Disorder"],
|
||||
"Bloodseeker": ["Arterial Spray", "Sanguivore"],
|
||||
"Bounty Hunter": ["Through and Through", "Big Game Hunter"],
|
||||
"Broodmother": ["Necrotic Webs", "Feeding Frenzy"],
|
||||
"Clinkz": ["Bone and Arrow", "Engulfing Step"],
|
||||
"Drow Ranger": ["Sidestep", "Vantage Point"],
|
||||
"Ember Spirit": ["Double Impact", "Chain Gang"],
|
||||
"Faceless Void": ["Chronosphere Teamwork", "Distortion Field"],
|
||||
"Gyrocopter": ["Afterburner", "Chop Shop"],
|
||||
"Hoodwink": ["Treebounce Trickshot", "Go Nuts"],
|
||||
"Juggernaut": ["Bladestorm", "Agigain"],
|
||||
"Lone Druid": ["Bear Necessities", "Unbearable"],
|
||||
"Luna": ["Lunar Orbit", "Moonshield"],
|
||||
"Medusa": ["Engorged", "Monstrous"],
|
||||
"Meepo": ["More Meepo", "Pack Rat"],
|
||||
"Mirana": ["Moonlight", "Shooting Star"],
|
||||
"Monkey King": ["Simian Stride", "Wukong's Faithful"],
|
||||
"Muerta": ["Ofrenda", "Dance of the Dead"],
|
||||
"Naga Siren": ["Aquatic Dominance", "Deluge"],
|
||||
"Nyx Assassin": ["Scuttle", "Spelunker"],
|
||||
"Pangolier": ["Double Jump", "Thunderbolt"],
|
||||
"Phantom Assassin": ["Methodical", "Immaterial"],
|
||||
"Phantom Lancer": ["Convergence", "Phantom Edge"],
|
||||
"Razor": ["Dynamo", "Shocking"],
|
||||
"Riki": ["Exterminator", "Infiltrator"],
|
||||
"Shadow Fiend": ["Lasting Presence", "Shadowmire"],
|
||||
"Slark": ["Leeching Leash", "Dark Reef Renegade"],
|
||||
"Sniper": ["Ghillie Suit", "Scattershot"],
|
||||
"Spectre": ["Forsaken", "Twist the Knife"],
|
||||
"Templar Assassin": ["Refractor", "Third Strike"],
|
||||
"Terrorblade": ["Soul Fragment", "Condemned"],
|
||||
"Troll Warlord": ["Bad Influence", "Insensitive"],
|
||||
"Ursa": ["Bear Down", "Grudge Bearer"],
|
||||
"Viper": ["Predator", "Poison Burst"],
|
||||
"Weaver": ["Skitterstep", "Hivemind"],
|
||||
|
||||
# Intelligence Heroes
|
||||
"Ancient Apparition": ["Bone Chill", "Exposure"],
|
||||
"Bane": ["Dream Haunter", "Sleepwalk"],
|
||||
"Batrider": ["Arsonist", "Stoked"],
|
||||
"Chen": ["Centaur Convert", "Totemic"],
|
||||
"Crystal Maiden": ["Cold Comfort", "Frozen Expanse"],
|
||||
"Dark Seer": ["Heart of Battle", "Quick Wit"],
|
||||
"Dark Willow": ["Thorny Thicket", "Wilding"],
|
||||
"Dazzle": ["Nothl Boon", "Poison Bloom"],
|
||||
"Death Prophet": ["Spirit Collector", "Mourning Ritual"],
|
||||
"Disruptor": ["Thunderstorm", "Kinetic Fence"],
|
||||
"Enchantress": ["Overprotective Wisps", "Little Friends"],
|
||||
"Enigma": ["Event Horizon", "Gravity Well"],
|
||||
"Grimstroke": ["Fine Art", "Inkstigate"],
|
||||
"Invoker": ["Elitist", "Magus"],
|
||||
"Jakiro": ["Liquid Fire", "Liquid Frost"],
|
||||
"Keeper of the Light": ["Solar Bind", "Recall"],
|
||||
"Leshrac": ["Diabolic Edict Charges", "Misery"],
|
||||
"Lich": ["Growing Cold", "Ice Spire"],
|
||||
"Lina": ["Slow Burn", "Thermal Runaway"],
|
||||
"Lion": ["Fist of Death", "Gobble Up"],
|
||||
"Nature's Prophet": ["Ironwood Treant", "Curse of the Forest"],
|
||||
"Necrophos": ["Rapid Decay", "Profane Potency"],
|
||||
"Oracle": ["Clairvoyant Curse", "Prognosticate"],
|
||||
"Outworld Destroyer": ["Obsidian Decimator", "Ominous Discernment"],
|
||||
"Puck": ["Jostling Rift", "Curveball"],
|
||||
"Pugna": ["Siphoning Ward", "Rewards of Ruin"],
|
||||
"Queen of Pain": ["Succubus", "Selfish Scream"],
|
||||
"Rubick": ["Frugal Filch", "Arcane Supremacy"],
|
||||
"Shadow Demon": ["Promulgate", "Menace"],
|
||||
"Shadow Shaman": ["Cluster Cluck", "Massive Serpent Wards"],
|
||||
"Silencer": ["Irrepressible", "Reverberating Silence"],
|
||||
"Skywrath Mage": ["Staff of the Scion", "Shield of the Scion"],
|
||||
"Storm Spirit": ["Shock Collar", "Static Slide"],
|
||||
"Techies": ["Squee", "Spleen"],
|
||||
"Tinker": ["Repair Bots", "Translocator"],
|
||||
"Visage": ["Sepulchre", "Death Toll"],
|
||||
"Void Spirit": ["Sanctuary", "Aether Remnant"],
|
||||
"Warlock": ["Black Grimoire", "Champion of Gorroth"],
|
||||
"Windranger": ["Journeyman", "Whirlwind"],
|
||||
"Winter Wyvern": ["Heal Wyvern", "Essence Devourer"],
|
||||
"Witch Doctor": ["Cleft Death", "Voodoo Festeration"],
|
||||
"Zeus": ["Livewire", "Lightning Hands"],
|
||||
}
|
||||
134
dota-random-builds-back/data/heroes.ts
Normal file
134
dota-random-builds-back/data/heroes.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
export interface Hero {
|
||||
id: string
|
||||
name: string
|
||||
primary: 'Strength' | 'Agility' | 'Intelligence'
|
||||
}
|
||||
|
||||
export const heroes: Hero[] = [
|
||||
{ id: 'abaddon', name: 'Abaddon', primary: 'Strength' },
|
||||
{ id: 'alchemist', name: 'Alchemist', primary: 'Strength' },
|
||||
{ id: 'axe', name: 'Axe', primary: 'Strength' },
|
||||
{ id: 'beastmaster', name: 'Beastmaster', primary: 'Strength' },
|
||||
{ id: 'brewmaster', name: 'Brewmaster', primary: 'Strength' },
|
||||
{ id: 'bristleback', name: 'Bristleback', primary: 'Strength' },
|
||||
{ id: 'centaur', name: 'Centaur Warrunner', primary: 'Strength' },
|
||||
{ id: 'chaos_knight', name: 'Chaos Knight', primary: 'Strength' },
|
||||
{ id: 'clockwerk', name: 'Clockwerk', primary: 'Strength' },
|
||||
{ id: 'dawnbreaker', name: 'Dawnbreaker', primary: 'Strength' },
|
||||
{ id: 'doom', name: 'Doom', primary: 'Strength' },
|
||||
{ id: 'dragon_knight', name: 'Dragon Knight', primary: 'Strength' },
|
||||
{ id: 'earth_spirit', name: 'Earth Spirit', primary: 'Strength' },
|
||||
{ id: 'earthshaker', name: 'Earthshaker', primary: 'Strength' },
|
||||
{ id: 'elder_titan', name: 'Elder Titan', primary: 'Strength' },
|
||||
{ id: 'huskar', name: 'Huskar', primary: 'Strength' },
|
||||
{ id: 'kunkka', name: 'Kunkka', primary: 'Strength' },
|
||||
{ id: 'legion_commander', name: 'Legion Commander', primary: 'Strength' },
|
||||
{ id: 'lifestealer', name: 'Lifestealer', primary: 'Strength' },
|
||||
{ id: 'lycan', name: 'Lycan', primary: 'Strength' },
|
||||
{ id: 'magnus', name: 'Magnus', primary: 'Strength' },
|
||||
{ id: 'mars', name: 'Mars', primary: 'Strength' },
|
||||
{ id: 'night_stalker', name: 'Night Stalker', primary: 'Strength' },
|
||||
{ id: 'ogre_magi', name: 'Ogre Magi', primary: 'Strength' },
|
||||
{ id: 'omniknight', name: 'Omniknight', primary: 'Strength' },
|
||||
{ id: 'phoenix', name: 'Phoenix', primary: 'Strength' },
|
||||
{ id: 'primal_beast', name: 'Primal Beast', primary: 'Strength' },
|
||||
{ id: 'pudge', name: 'Pudge', primary: 'Strength' },
|
||||
{ id: 'slardar', name: 'Slardar', primary: 'Strength' },
|
||||
{ id: 'snapfire', name: 'Snapfire', primary: 'Strength' },
|
||||
{ id: 'spirit_breaker', name: 'Spirit Breaker', primary: 'Strength' },
|
||||
{ id: 'sven', name: 'Sven', primary: 'Strength' },
|
||||
{ id: 'tidehunter', name: 'Tidehunter', primary: 'Strength' },
|
||||
{ id: 'timbersaw', name: 'Timbersaw', primary: 'Strength' },
|
||||
{ id: 'tiny', name: 'Tiny', primary: 'Strength' },
|
||||
{ id: 'treant', name: 'Treant Protector', primary: 'Strength' },
|
||||
{ id: 'tusk', name: 'Tusk', primary: 'Strength' },
|
||||
{ id: 'underlord', name: 'Underlord', primary: 'Strength' },
|
||||
{ id: 'undying', name: 'Undying', primary: 'Strength' },
|
||||
{ id: 'wraith_king', name: 'Wraith King', primary: 'Strength' },
|
||||
|
||||
/* -------------------- Agility -------------------- */
|
||||
|
||||
{ id: 'anti_mage', name: 'Anti-Mage', primary: 'Agility' },
|
||||
{ id: 'arc_warden', name: 'Arc Warden', primary: 'Agility' },
|
||||
{ id: 'bloodseeker', name: 'Bloodseeker', primary: 'Agility' },
|
||||
{ id: 'bounty_hunter', name: 'Bounty Hunter', primary: 'Agility' },
|
||||
{ id: 'broodmother', name: 'Broodmother', primary: 'Agility' },
|
||||
{ id: 'clinkz', name: 'Clinkz', primary: 'Agility' },
|
||||
{ id: 'drow_ranger', name: 'Drow Ranger', primary: 'Agility' },
|
||||
{ id: 'ember_spirit', name: 'Ember Spirit', primary: 'Agility' },
|
||||
{ id: 'faceless_void', name: 'Faceless Void', primary: 'Agility' },
|
||||
{ id: 'gyrocopter', name: 'Gyrocopter', primary: 'Agility' },
|
||||
{ id: 'hoodwink', name: 'Hoodwink', primary: 'Agility' },
|
||||
{ id: 'juggernaut', name: 'Juggernaut', primary: 'Agility' },
|
||||
{ id: 'lone_druid', name: 'Lone Druid', primary: 'Agility' },
|
||||
{ id: 'luna', name: 'Luna', primary: 'Agility' },
|
||||
{ id: 'medusa', name: 'Medusa', primary: 'Agility' },
|
||||
{ id: 'meepo', name: 'Meepo', primary: 'Agility' },
|
||||
{ id: 'mirana', name: 'Mirana', primary: 'Agility' },
|
||||
{ id: 'monkey_king', name: 'Monkey King', primary: 'Agility' },
|
||||
{ id: 'muerta', name: 'Muerta', primary: 'Agility' },
|
||||
{ id: 'naga_siren', name: 'Naga Siren', primary: 'Agility' },
|
||||
{ id: 'nyx_assassin', name: 'Nyx Assassin', primary: 'Agility' },
|
||||
{ id: 'pangolier', name: 'Pangolier', primary: 'Agility' },
|
||||
{ id: 'phantom_assassin', name: 'Phantom Assassin', primary: 'Agility' },
|
||||
{ id: 'phantom_lancer', name: 'Phantom Lancer', primary: 'Agility' },
|
||||
{ id: 'razor', name: 'Razor', primary: 'Agility' },
|
||||
{ id: 'riki', name: 'Riki', primary: 'Agility' },
|
||||
{ id: 'shadow_fiend', name: 'Shadow Fiend', primary: 'Agility' },
|
||||
{ id: 'slark', name: 'Slark', primary: 'Agility' },
|
||||
{ id: 'sniper', name: 'Sniper', primary: 'Agility' },
|
||||
{ id: 'spectre', name: 'Spectre', primary: 'Agility' },
|
||||
{ id: 'templar_assassin', name: 'Templar Assassin', primary: 'Agility' },
|
||||
{ id: 'terrorblade', name: 'Terrorblade', primary: 'Agility' },
|
||||
{ id: 'troll_warlord', name: 'Troll Warlord', primary: 'Agility' },
|
||||
{ id: 'ursa', name: 'Ursa', primary: 'Agility' },
|
||||
{ id: 'viper', name: 'Viper', primary: 'Agility' },
|
||||
{ id: 'weaver', name: 'Weaver', primary: 'Agility' },
|
||||
|
||||
/* -------------------- Intelligence -------------------- */
|
||||
|
||||
{ id: 'ancient_apparition', name: 'Ancient Apparition', primary: 'Intelligence' },
|
||||
{ id: 'bane', name: 'Bane', primary: 'Intelligence' },
|
||||
{ id: 'batrider', name: 'Batrider', primary: 'Intelligence' },
|
||||
{ id: 'chen', name: 'Chen', primary: 'Intelligence' },
|
||||
{ id: 'crystal_maiden', name: 'Crystal Maiden', primary: 'Intelligence' },
|
||||
{ id: 'dark_seer', name: 'Dark Seer', primary: 'Intelligence' },
|
||||
{ id: 'dark_willow', name: 'Dark Willow', primary: 'Intelligence' },
|
||||
{ id: 'dazzle', name: 'Dazzle', primary: 'Intelligence' },
|
||||
{ id: 'death_prophet', name: 'Death Prophet', primary: 'Intelligence' },
|
||||
{ id: 'disruptor', name: 'Disruptor', primary: 'Intelligence' },
|
||||
{ id: 'enchantress', name: 'Enchantress', primary: 'Intelligence' },
|
||||
{ id: 'enigma', name: 'Enigma', primary: 'Intelligence' },
|
||||
{ id: 'grimstroke', name: 'Grimstroke', primary: 'Intelligence' },
|
||||
{ id: 'invoker', name: 'Invoker', primary: 'Intelligence' },
|
||||
{ id: 'jakiro', name: 'Jakiro', primary: 'Intelligence' },
|
||||
{ id: 'keeper_of_the_light', name: 'Keeper of the Light', primary: 'Intelligence' },
|
||||
{ id: 'leshrac', name: 'Leshrac', primary: 'Intelligence' },
|
||||
{ id: 'lich', name: 'Lich', primary: 'Intelligence' },
|
||||
{ id: 'lina', name: 'Lina', primary: 'Intelligence' },
|
||||
{ id: 'lion', name: 'Lion', primary: 'Intelligence' },
|
||||
{ id: 'muerta', name: 'Muerta', primary: 'Intelligence' },
|
||||
{ id: 'nature_prophet', name: "Nature's Prophet", primary: 'Intelligence' },
|
||||
{ id: 'necrophos', name: 'Necrophos', primary: 'Intelligence' },
|
||||
{ id: 'ogre_magi_int', name: 'Ogre Magi (Int)', primary: 'Intelligence' },
|
||||
{ id: 'oracle', name: 'Oracle', primary: 'Intelligence' },
|
||||
{ id: 'outworld_destroyer', name: 'Outworld Destroyer', primary: 'Intelligence' },
|
||||
{ id: 'puck', name: 'Puck', primary: 'Intelligence' },
|
||||
{ id: 'pugna', name: 'Pugna', primary: 'Intelligence' },
|
||||
{ id: 'queen_of_pain', name: 'Queen of Pain', primary: 'Intelligence' },
|
||||
{ id: 'rubick', name: 'Rubick', primary: 'Intelligence' },
|
||||
{ id: 'shadow_demon', name: 'Shadow Demon', primary: 'Intelligence' },
|
||||
{ id: 'shadow_shaman', name: 'Shadow Shaman', primary: 'Intelligence' },
|
||||
{ id: 'silencer', name: 'Silencer', primary: 'Intelligence' },
|
||||
{ id: 'skywrath_mage', name: 'Skywrath Mage', primary: 'Intelligence' },
|
||||
{ id: 'storm_spirit', name: 'Storm Spirit', primary: 'Intelligence' },
|
||||
{ id: 'techies', name: 'Techies', primary: 'Intelligence' },
|
||||
{ id: 'tinker', name: 'Tinker', primary: 'Intelligence' },
|
||||
{ id: 'visage', name: 'Visage', primary: 'Intelligence' },
|
||||
{ id: 'void_spirit', name: 'Void Spirit', primary: 'Intelligence' },
|
||||
{ id: 'warlock', name: 'Warlock', primary: 'Intelligence' },
|
||||
{ id: 'windranger', name: 'Windranger', primary: 'Intelligence' },
|
||||
{ id: 'winter_wyvern', name: 'Winter Wyvern', primary: 'Intelligence' },
|
||||
{ id: 'witch_doctor', name: 'Witch Doctor', primary: 'Intelligence' },
|
||||
{ id: 'zeus', name: 'Zeus', primary: 'Intelligence' }
|
||||
];
|
||||
887
dota-random-builds-back/data/items.ts
Normal file
887
dota-random-builds-back/data/items.ts
Normal file
@@ -0,0 +1,887 @@
|
||||
export interface Item {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
|
||||
export const items: Item[] = [
|
||||
{
|
||||
"id": "item_town_portal_scroll",
|
||||
"name": "Town Portal Scroll"
|
||||
},
|
||||
{
|
||||
"id": "item_clarity",
|
||||
"name": "Clarity"
|
||||
},
|
||||
{
|
||||
"id": "item_faerie_fire",
|
||||
"name": "Faerie Fire"
|
||||
},
|
||||
{
|
||||
"id": "item_smoke_of_deceit",
|
||||
"name": "Smoke of Deceit"
|
||||
},
|
||||
{
|
||||
"id": "item_observer_ward",
|
||||
"name": "Observer Ward"
|
||||
},
|
||||
{
|
||||
"id": "item_sentry_ward",
|
||||
"name": "Sentry Ward"
|
||||
},
|
||||
{
|
||||
"id": "item_enchanted_mango",
|
||||
"name": "Enchanted Mango"
|
||||
},
|
||||
{
|
||||
"id": "item_healing_salve",
|
||||
"name": "Healing Salve"
|
||||
},
|
||||
{
|
||||
"id": "item_tango",
|
||||
"name": "Tango"
|
||||
},
|
||||
{
|
||||
"id": "item_blood_grenade",
|
||||
"name": "Blood Grenade"
|
||||
},
|
||||
{
|
||||
"id": "item_dust_of_appearance",
|
||||
"name": "Dust of Appearance"
|
||||
},
|
||||
{
|
||||
"id": "item_bottle",
|
||||
"name": "Bottle"
|
||||
},
|
||||
{
|
||||
"id": "item_aghanim_s_shard",
|
||||
"name": "Aghanim's Shard"
|
||||
},
|
||||
{
|
||||
"id": "item_aghanim_s_blessing",
|
||||
"name": "Aghanim's Blessing"
|
||||
},
|
||||
{
|
||||
"id": "item_observer_and_sentry_wards",
|
||||
"name": "Observer and Sentry Wards"
|
||||
},
|
||||
{
|
||||
"id": "item_iron_branch",
|
||||
"name": "Iron Branch"
|
||||
},
|
||||
{
|
||||
"id": "item_gauntlets_of_strength",
|
||||
"name": "Gauntlets of Strength"
|
||||
},
|
||||
{
|
||||
"id": "item_slippers_of_agility",
|
||||
"name": "Slippers of Agility"
|
||||
},
|
||||
{
|
||||
"id": "item_mantle_of_intelligence",
|
||||
"name": "Mantle of Intelligence"
|
||||
},
|
||||
{
|
||||
"id": "item_circlet",
|
||||
"name": "Circlet"
|
||||
},
|
||||
{
|
||||
"id": "item_belt_of_strength",
|
||||
"name": "Belt of Strength"
|
||||
},
|
||||
{
|
||||
"id": "item_band_of_elvenskin",
|
||||
"name": "Band of Elvenskin"
|
||||
},
|
||||
{
|
||||
"id": "item_robe_of_the_magi",
|
||||
"name": "Robe of the Magi"
|
||||
},
|
||||
{
|
||||
"id": "item_crown",
|
||||
"name": "Crown"
|
||||
},
|
||||
{
|
||||
"id": "item_ogre_axe",
|
||||
"name": "Ogre Axe"
|
||||
},
|
||||
{
|
||||
"id": "item_blade_of_alacrity",
|
||||
"name": "Blade of Alacrity"
|
||||
},
|
||||
{
|
||||
"id": "item_staff_of_wizardry",
|
||||
"name": "Staff of Wizardry"
|
||||
},
|
||||
{
|
||||
"id": "item_diadem",
|
||||
"name": "Diadem"
|
||||
},
|
||||
{
|
||||
"id": "item_quelling_blade",
|
||||
"name": "Quelling Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_protection",
|
||||
"name": "Ring of Protection"
|
||||
},
|
||||
{
|
||||
"id": "item_infused_raindrops",
|
||||
"name": "Infused Raindrops"
|
||||
},
|
||||
{
|
||||
"id": "item_orb_of_venom",
|
||||
"name": "Orb of Venom"
|
||||
},
|
||||
{
|
||||
"id": "item_orb_of_blight",
|
||||
"name": "Orb of Blight"
|
||||
},
|
||||
{
|
||||
"id": "item_blades_of_attack",
|
||||
"name": "Blades of Attack"
|
||||
},
|
||||
{
|
||||
"id": "item_orb_of_frost",
|
||||
"name": "Orb of Frost"
|
||||
},
|
||||
{
|
||||
"id": "item_gloves_of_haste",
|
||||
"name": "Gloves of Haste"
|
||||
},
|
||||
{
|
||||
"id": "item_chainmail",
|
||||
"name": "Chainmail"
|
||||
},
|
||||
{
|
||||
"id": "item_helm_of_iron_will",
|
||||
"name": "Helm of Iron Will"
|
||||
},
|
||||
{
|
||||
"id": "item_broadsword",
|
||||
"name": "Broadsword"
|
||||
},
|
||||
{
|
||||
"id": "item_blitz_knuckles",
|
||||
"name": "Blitz Knuckles"
|
||||
},
|
||||
{
|
||||
"id": "item_javelin",
|
||||
"name": "Javelin"
|
||||
},
|
||||
{
|
||||
"id": "item_claymore",
|
||||
"name": "Claymore"
|
||||
},
|
||||
{
|
||||
"id": "item_mithril_hammer",
|
||||
"name": "Mithril Hammer"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_regen",
|
||||
"name": "Ring of Regen"
|
||||
},
|
||||
{
|
||||
"id": "item_sage_s_mask",
|
||||
"name": "Sage's Mask"
|
||||
},
|
||||
{
|
||||
"id": "item_magic_stick",
|
||||
"name": "Magic Stick"
|
||||
},
|
||||
{
|
||||
"id": "item_fluffy_hat",
|
||||
"name": "Fluffy Hat"
|
||||
},
|
||||
{
|
||||
"id": "item_wind_lace",
|
||||
"name": "Wind Lace"
|
||||
},
|
||||
{
|
||||
"id": "item_cloak",
|
||||
"name": "Cloak"
|
||||
},
|
||||
{
|
||||
"id": "item_boots_of_speed",
|
||||
"name": "Boots of Speed"
|
||||
},
|
||||
{
|
||||
"id": "item_gem_of_true_sight",
|
||||
"name": "Gem of True Sight"
|
||||
},
|
||||
{
|
||||
"id": "item_morbid_mask",
|
||||
"name": "Morbid Mask"
|
||||
},
|
||||
{
|
||||
"id": "item_voodoo_mask",
|
||||
"name": "Voodoo Mask"
|
||||
},
|
||||
{
|
||||
"id": "item_shadow_amulet",
|
||||
"name": "Shadow Amulet"
|
||||
},
|
||||
{
|
||||
"id": "item_ghost_scepter",
|
||||
"name": "Ghost Scepter"
|
||||
},
|
||||
{
|
||||
"id": "item_blink_dagger",
|
||||
"name": "Blink Dagger"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_health",
|
||||
"name": "Ring of Health"
|
||||
},
|
||||
{
|
||||
"id": "item_void_stone",
|
||||
"name": "Void Stone"
|
||||
},
|
||||
{
|
||||
"id": "item_magic_wand",
|
||||
"name": "Magic Wand"
|
||||
},
|
||||
{
|
||||
"id": "item_null_talisman",
|
||||
"name": "Null Talisman"
|
||||
},
|
||||
{
|
||||
"id": "item_wraith_band",
|
||||
"name": "Wraith Band"
|
||||
},
|
||||
{
|
||||
"id": "item_bracer",
|
||||
"name": "Bracer"
|
||||
},
|
||||
{
|
||||
"id": "item_soul_ring",
|
||||
"name": "Soul Ring"
|
||||
},
|
||||
{
|
||||
"id": "item_orb_of_corrosion",
|
||||
"name": "Orb of Corrosion"
|
||||
},
|
||||
{
|
||||
"id": "item_falcon_blade",
|
||||
"name": "Falcon Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_power_treads",
|
||||
"name": "Power Treads"
|
||||
},
|
||||
{
|
||||
"id": "item_phase_boots",
|
||||
"name": "Phase Boots"
|
||||
},
|
||||
{
|
||||
"id": "item_oblivion_staff",
|
||||
"name": "Oblivion Staff"
|
||||
},
|
||||
{
|
||||
"id": "item_perseverance",
|
||||
"name": "Perseverance"
|
||||
},
|
||||
{
|
||||
"id": "item_mask_of_madness",
|
||||
"name": "Mask of Madness"
|
||||
},
|
||||
{
|
||||
"id": "item_hand_of_midas",
|
||||
"name": "Hand of Midas"
|
||||
},
|
||||
{
|
||||
"id": "item_helm_of_the_dominator",
|
||||
"name": "Helm of the Dominator"
|
||||
},
|
||||
{
|
||||
"id": "item_boots_of_travel",
|
||||
"name": "Boots of Travel"
|
||||
},
|
||||
{
|
||||
"id": "item_moon_shard",
|
||||
"name": "Moon Shard"
|
||||
},
|
||||
{
|
||||
"id": "item_boots_of_travel_2",
|
||||
"name": "Boots of Travel 2"
|
||||
},
|
||||
{
|
||||
"id": "item_buckler",
|
||||
"name": "Buckler"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_basilius",
|
||||
"name": "Ring of Basilius"
|
||||
},
|
||||
{
|
||||
"id": "item_headdress",
|
||||
"name": "Headdress"
|
||||
},
|
||||
{
|
||||
"id": "item_urn_of_shadows",
|
||||
"name": "Urn of Shadows"
|
||||
},
|
||||
{
|
||||
"id": "item_tranquil_boots",
|
||||
"name": "Tranquil Boots"
|
||||
},
|
||||
{
|
||||
"id": "item_pavise",
|
||||
"name": "Pavise"
|
||||
},
|
||||
{
|
||||
"id": "item_arcane_boots",
|
||||
"name": "Arcane Boots"
|
||||
},
|
||||
{
|
||||
"id": "item_drum_of_endurance",
|
||||
"name": "Drum of Endurance"
|
||||
},
|
||||
{
|
||||
"id": "item_mekansm",
|
||||
"name": "Mekansm"
|
||||
},
|
||||
{
|
||||
"id": "item_holy_locket",
|
||||
"name": "Holy Locket"
|
||||
},
|
||||
{
|
||||
"id": "item_vladmir_s_offering",
|
||||
"name": "Vladmir's Offering"
|
||||
},
|
||||
{
|
||||
"id": "item_spirit_vessel",
|
||||
"name": "Spirit Vessel"
|
||||
},
|
||||
{
|
||||
"id": "item_pipe_of_insight",
|
||||
"name": "Pipe of Insight"
|
||||
},
|
||||
{
|
||||
"id": "item_guardian_greaves",
|
||||
"name": "Guardian Greaves"
|
||||
},
|
||||
{
|
||||
"id": "item_boots_of_bearing",
|
||||
"name": "Boots of Bearing"
|
||||
},
|
||||
{
|
||||
"id": "item_parasma",
|
||||
"name": "Parasma"
|
||||
},
|
||||
{
|
||||
"id": "item_veil_of_discord",
|
||||
"name": "Veil of Discord"
|
||||
},
|
||||
{
|
||||
"id": "item_glimmer_cape",
|
||||
"name": "Glimmer Cape"
|
||||
},
|
||||
{
|
||||
"id": "item_force_staff",
|
||||
"name": "Force Staff"
|
||||
},
|
||||
{
|
||||
"id": "item_aether_lens",
|
||||
"name": "Aether Lens"
|
||||
},
|
||||
{
|
||||
"id": "item_witch_blade",
|
||||
"name": "Witch Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_eul_s_scepter_of_divinity",
|
||||
"name": "Eul's Scepter of Divinity"
|
||||
},
|
||||
{
|
||||
"id": "item_rod_of_atos",
|
||||
"name": "Rod of Atos"
|
||||
},
|
||||
{
|
||||
"id": "item_dagon",
|
||||
"name": "Dagon"
|
||||
},
|
||||
{
|
||||
"id": "item_orchid_malevolence",
|
||||
"name": "Orchid Malevolence"
|
||||
},
|
||||
{
|
||||
"id": "item_solar_crest",
|
||||
"name": "Solar Crest"
|
||||
},
|
||||
{
|
||||
"id": "item_aghanim_s_scepter",
|
||||
"name": "Aghanim's Scepter"
|
||||
},
|
||||
{
|
||||
"id": "item_refresher_orb",
|
||||
"name": "Refresher Orb"
|
||||
},
|
||||
{
|
||||
"id": "item_octarine_core",
|
||||
"name": "Octarine Core"
|
||||
},
|
||||
{
|
||||
"id": "item_scythe_of_vyse",
|
||||
"name": "Scythe of Vyse"
|
||||
},
|
||||
{
|
||||
"id": "item_gleipnir",
|
||||
"name": "Gleipnir"
|
||||
},
|
||||
{
|
||||
"id": "item_wind_waker",
|
||||
"name": "Wind Waker"
|
||||
},
|
||||
{
|
||||
"id": "item_crystalys",
|
||||
"name": "Crystalys"
|
||||
},
|
||||
{
|
||||
"id": "item_meteor_hammer",
|
||||
"name": "Meteor Hammer"
|
||||
},
|
||||
{
|
||||
"id": "item_armlet_of_mordiggian",
|
||||
"name": "Armlet of Mordiggian"
|
||||
},
|
||||
{
|
||||
"id": "item_skull_basher",
|
||||
"name": "Skull Basher"
|
||||
},
|
||||
{
|
||||
"id": "item_shadow_blade",
|
||||
"name": "Shadow Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_desolator",
|
||||
"name": "Desolator"
|
||||
},
|
||||
{
|
||||
"id": "item_battle_fury",
|
||||
"name": "Battle Fury"
|
||||
},
|
||||
{
|
||||
"id": "item_ethereal_blade",
|
||||
"name": "Ethereal Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_nullifier",
|
||||
"name": "Nullifier"
|
||||
},
|
||||
{
|
||||
"id": "item_monkey_king_bar",
|
||||
"name": "Monkey King Bar"
|
||||
},
|
||||
{
|
||||
"id": "item_butterfly",
|
||||
"name": "Butterfly"
|
||||
},
|
||||
{
|
||||
"id": "item_radiance",
|
||||
"name": "Radiance"
|
||||
},
|
||||
{
|
||||
"id": "item_daedalus",
|
||||
"name": "Daedalus"
|
||||
},
|
||||
{
|
||||
"id": "item_silver_edge",
|
||||
"name": "Silver Edge"
|
||||
},
|
||||
{
|
||||
"id": "item_divine_rapier",
|
||||
"name": "Divine Rapier"
|
||||
},
|
||||
{
|
||||
"id": "item_bloodthorn",
|
||||
"name": "Bloodthorn"
|
||||
},
|
||||
{
|
||||
"id": "item_abyssal_blade",
|
||||
"name": "Abyssal Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_revenant_s_brooch",
|
||||
"name": "Revenant's Brooch"
|
||||
},
|
||||
{
|
||||
"id": "item_disperser",
|
||||
"name": "Disperser"
|
||||
},
|
||||
{
|
||||
"id": "item_khanda",
|
||||
"name": "Khanda"
|
||||
},
|
||||
{
|
||||
"id": "item_vanguard",
|
||||
"name": "Vanguard"
|
||||
},
|
||||
{
|
||||
"id": "item_blade_mail",
|
||||
"name": "Blade Mail"
|
||||
},
|
||||
{
|
||||
"id": "item_aeon_disk",
|
||||
"name": "Aeon Disk"
|
||||
},
|
||||
{
|
||||
"id": "item_soul_booster",
|
||||
"name": "Soul Booster"
|
||||
},
|
||||
{
|
||||
"id": "item_crimson_guard",
|
||||
"name": "Crimson Guard"
|
||||
},
|
||||
{
|
||||
"id": "item_lotus_orb",
|
||||
"name": "Lotus Orb"
|
||||
},
|
||||
{
|
||||
"id": "item_black_king_bar",
|
||||
"name": "Black King Bar"
|
||||
},
|
||||
{
|
||||
"id": "item_hurricane_pike",
|
||||
"name": "Hurricane Pike"
|
||||
},
|
||||
{
|
||||
"id": "item_manta_style",
|
||||
"name": "Manta Style"
|
||||
},
|
||||
{
|
||||
"id": "item_linken_s_sphere",
|
||||
"name": "Linken's Sphere"
|
||||
},
|
||||
{
|
||||
"id": "item_shiva_s_guard",
|
||||
"name": "Shiva's Guard"
|
||||
},
|
||||
{
|
||||
"id": "item_heart_of_tarrasque",
|
||||
"name": "Heart of Tarrasque"
|
||||
},
|
||||
{
|
||||
"id": "item_assault_cuirass",
|
||||
"name": "Assault Cuirass"
|
||||
},
|
||||
{
|
||||
"id": "item_bloodstone",
|
||||
"name": "Bloodstone"
|
||||
},
|
||||
{
|
||||
"id": "item_helm_of_the_overlord",
|
||||
"name": "Helm of the Overlord"
|
||||
},
|
||||
{
|
||||
"id": "item_eternal_shroud",
|
||||
"name": "Eternal Shroud"
|
||||
},
|
||||
{
|
||||
"id": "item_dragon_lance",
|
||||
"name": "Dragon Lance"
|
||||
},
|
||||
{
|
||||
"id": "item_sange",
|
||||
"name": "Sange"
|
||||
},
|
||||
{
|
||||
"id": "item_yasha",
|
||||
"name": "Yasha"
|
||||
},
|
||||
{
|
||||
"id": "item_kaya",
|
||||
"name": "Kaya"
|
||||
},
|
||||
{
|
||||
"id": "item_echo_sabre",
|
||||
"name": "Echo Sabre"
|
||||
},
|
||||
{
|
||||
"id": "item_maelstrom",
|
||||
"name": "Maelstrom"
|
||||
},
|
||||
{
|
||||
"id": "item_diffusal_blade",
|
||||
"name": "Diffusal Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_mage_slayer",
|
||||
"name": "Mage Slayer"
|
||||
},
|
||||
{
|
||||
"id": "item_phylactery",
|
||||
"name": "Phylactery"
|
||||
},
|
||||
{
|
||||
"id": "item_heaven_s_halberd",
|
||||
"name": "Heaven's Halberd"
|
||||
},
|
||||
{
|
||||
"id": "item_kaya_and_sange",
|
||||
"name": "Kaya and Sange"
|
||||
},
|
||||
{
|
||||
"id": "item_sange_and_yasha",
|
||||
"name": "Sange and Yasha"
|
||||
},
|
||||
{
|
||||
"id": "item_yasha_and_kaya",
|
||||
"name": "Yasha and Kaya"
|
||||
},
|
||||
{
|
||||
"id": "item_satanic",
|
||||
"name": "Satanic"
|
||||
},
|
||||
{
|
||||
"id": "item_eye_of_skadi",
|
||||
"name": "Eye of Skadi"
|
||||
},
|
||||
{
|
||||
"id": "item_mjollnir",
|
||||
"name": "Mjollnir"
|
||||
},
|
||||
{
|
||||
"id": "item_overwhelming_blink",
|
||||
"name": "Overwhelming Blink"
|
||||
},
|
||||
{
|
||||
"id": "item_swift_blink",
|
||||
"name": "Swift Blink"
|
||||
},
|
||||
{
|
||||
"id": "item_arcane_blink",
|
||||
"name": "Arcane Blink"
|
||||
},
|
||||
{
|
||||
"id": "item_harpoon",
|
||||
"name": "Harpoon"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_tarrasque",
|
||||
"name": "Ring of Tarrasque"
|
||||
},
|
||||
{
|
||||
"id": "item_tiara_of_selemene",
|
||||
"name": "Tiara of Selemene"
|
||||
},
|
||||
{
|
||||
"id": "item_cornucopia",
|
||||
"name": "Cornucopia"
|
||||
},
|
||||
{
|
||||
"id": "item_energy_booster",
|
||||
"name": "Energy Booster"
|
||||
},
|
||||
{
|
||||
"id": "item_vitality_booster",
|
||||
"name": "Vitality Booster"
|
||||
},
|
||||
{
|
||||
"id": "item_point_booster",
|
||||
"name": "Point Booster"
|
||||
},
|
||||
{
|
||||
"id": "item_talisman_of_evasion",
|
||||
"name": "Talisman of Evasion"
|
||||
},
|
||||
{
|
||||
"id": "item_platemail",
|
||||
"name": "Platemail"
|
||||
},
|
||||
{
|
||||
"id": "item_hyperstone",
|
||||
"name": "Hyperstone"
|
||||
},
|
||||
{
|
||||
"id": "item_ultimate_orb",
|
||||
"name": "Ultimate Orb"
|
||||
},
|
||||
{
|
||||
"id": "item_demon_edge",
|
||||
"name": "Demon Edge"
|
||||
},
|
||||
{
|
||||
"id": "item_mystic_staff",
|
||||
"name": "Mystic Staff"
|
||||
},
|
||||
{
|
||||
"id": "item_reaver",
|
||||
"name": "Reaver"
|
||||
},
|
||||
{
|
||||
"id": "item_eaglesong",
|
||||
"name": "Eaglesong"
|
||||
},
|
||||
{
|
||||
"id": "item_sacred_relic",
|
||||
"name": "Sacred Relic"
|
||||
}
|
||||
]
|
||||
|
||||
// export const items: Item[] = [
|
||||
// { id: 'item_magic_wand', name: 'Magic Wand' },
|
||||
// { id: 'item_bracer', name: 'Bracer' },
|
||||
// { id: 'item_wraith_band', name: 'Wraith Band' },
|
||||
// { id: 'item_null_talisman', name: 'Null Talisman' },
|
||||
// { id: 'item_soul_ring', name: 'Soul Ring' },
|
||||
// { id: 'item_falcon_blade', name: 'Falcon Blade' },
|
||||
|
||||
// // Boots
|
||||
// { id: 'item_phase_boots', name: 'Phase Boots' },
|
||||
// { id: 'item_power_treads', name: 'Power Treads' },
|
||||
// { id: 'item_arcane_boots', name: 'Arcane Boots' },
|
||||
// { id: 'item_tranquil_boots', name: 'Tranquil Boots' },
|
||||
// { id: 'item_boots_of_bearing', name: 'Boots of Bearing' },
|
||||
// { id: 'item_guardian_greaves', name: 'Guardian Greaves' },
|
||||
|
||||
// // Mobility
|
||||
// { id: 'item_blink', name: 'Blink Dagger' },
|
||||
// { id: 'item_force_staff', name: 'Force Staff' },
|
||||
// { id: 'item_hurricane_pike', name: 'Hurricane Pike' },
|
||||
// { id: 'item_overwhelming_blink', name: 'Overwhelming Blink' },
|
||||
// { id: 'item_swift_blink', name: 'Swift Blink' },
|
||||
// { id: 'item_arcane_blink', name: 'Arcane Blink' },
|
||||
|
||||
// // Armor / regen aura
|
||||
// { id: 'item_mekansm', name: 'Mekansm' },
|
||||
// { id: 'item_pipe', name: 'Pipe of Insight' },
|
||||
// { id: 'item_vladmir', name: 'Vladmir’s Offering' },
|
||||
// { id: 'item_solar_crest', name: 'Solar Crest' },
|
||||
// { id: 'item_spirit_vessel', name: 'Spirit Vessel' },
|
||||
|
||||
// // Simple DPS items
|
||||
// { id: 'item_mask_of_madness', name: 'Mask of Madness' },
|
||||
// { id: 'item_armlet', name: 'Armlet of Mordiggian' },
|
||||
// { id: 'item_echo_sabre', name: 'Echo Sabre' },
|
||||
// { id: 'item_skull_basher', name: 'Skull Basher' },
|
||||
// { id: 'item_desolator', name: 'Desolator' },
|
||||
// { id: 'item_silver_edge', name: 'Silver Edge' },
|
||||
|
||||
// // Magic items
|
||||
// { id: 'item_veil_of_discord', name: 'Veil of Discord' },
|
||||
// { id: 'item_aether_lens', name: 'Aether Lens' },
|
||||
// { id: 'item_kaya', name: 'Kaya' },
|
||||
// { id: 'item_rod_of_atos', name: 'Rod of Atos' },
|
||||
// { id: 'item_dagon', name: 'Dagon' },
|
||||
|
||||
// // HP items
|
||||
// { id: 'item_hood_of_defiance', name: 'Hood of Defiance' },
|
||||
// { id: 'item_heart', name: 'Heart of Tarrasque' },
|
||||
// // Strong DPS items
|
||||
// { id: 'item_monkey_king_bar', name: 'Monkey King Bar' },
|
||||
// { id: 'item_butterfly', name: 'Butterfly' },
|
||||
// { id: 'item_daedalus', name: 'Daedalus' },
|
||||
// { id: 'item_greater_crit', name: 'Greater Crit' },
|
||||
// { id: 'item_bfury', name: 'Battle Fury' },
|
||||
// { id: 'item_satanic', name: 'Satanic' },
|
||||
// { id: 'item_mjollnir', name: 'Mjollnir' },
|
||||
// { id: 'item_radiance', name: 'Radiance' },
|
||||
// { id: 'item_diffusal_blade', name: 'Diffusal Blade' },
|
||||
// { id: 'item_abissal_blade', name: 'Abyssal Blade' },
|
||||
// { id: 'item_maelstrom', name: 'Maelstrom' },
|
||||
// { id: 'item_mage_slayer', name: 'Mage Slayer' },
|
||||
|
||||
// // Tank / defensive items
|
||||
// { id: 'item_black_king_bar', name: 'Black King Bar' },
|
||||
// { id: 'item_shivas_guard', name: 'Shiva’s Guard' },
|
||||
// { id: 'item_blade_mail', name: 'Blade Mail' },
|
||||
// { id: 'item_lotus_orb', name: 'Lotus Orb' },
|
||||
// { id: 'item_vanguard', name: 'Vanguard' },
|
||||
// { id: 'item_pipe', name: 'Pipe of Insight' },
|
||||
// { id: 'item_eternal_shroud', name: 'Eternal Shroud' },
|
||||
// { id: 'item_crimson_guard', name: 'Crimson Guard' },
|
||||
// { id: 'item_heavens_halberd', name: 'Heaven’s Halberd' },
|
||||
|
||||
// // Mobility / utility
|
||||
// { id: 'item_cyclone', name: 'Eul’s Scepter of Divinity' },
|
||||
// { id: 'item_glimmer_cape', name: 'Glimmer Cape' },
|
||||
// { id: 'item_ghost', name: 'Ghost Scepter' },
|
||||
// { id: 'item_ethereal_blade', name: 'Ethereal Blade' },
|
||||
// { id: 'item_aeon_disk', name: 'Aeon Disk' },
|
||||
// { id: 'item_boots_of_bearing', name: 'Boots of Bearing' },
|
||||
// { id: 'item_rod_of_atos', name: 'Rod of Atos' },
|
||||
// { id: 'item_solar_crest', name: 'Solar Crest' },
|
||||
|
||||
// // Healing / support items
|
||||
// { id: 'item_guardian_greaves', name: 'Guardian Greaves' },
|
||||
// { id: 'item_holy_locket', name: 'Holy Locket' },
|
||||
// { id: 'item_arcane_boots', name: 'Arcane Boots' },
|
||||
// { id: 'item_spirit_vessel', name: 'Spirit Vessel' },
|
||||
// { id: 'item_medallion_of_courage', name: 'Medallion of Courage' },
|
||||
|
||||
// // Magical burst / caster items
|
||||
// { id: 'item_kaya', name: 'Kaya' },
|
||||
// { id: 'item_kaya_and_sange', name: 'Kaya and Sange' },
|
||||
// { id: 'item_yasha_and_kaya', name: 'Yasha and Kaya' },
|
||||
// { id: 'item_sange_and_yasha', name: 'Sange and Yasha' },
|
||||
// { id: 'item_octarine_core', name: 'Octarine Core' },
|
||||
// { id: 'item_scythe_of_vyse', name: 'Scythe of Vyse' },
|
||||
// { id: 'item_dagon', name: 'Dagon' },
|
||||
|
||||
// { id: 'item_rapier', name: 'Divine Rapier' },
|
||||
// { id: 'item_moon_shard', name: 'Moon Shard' },
|
||||
// { id: 'item_silver_edge', name: 'Silver Edge' },
|
||||
// { id: 'item_bloodthorn', name: 'Bloodthorn' },
|
||||
// { id: 'item_nullifier', name: 'Nullifier' },
|
||||
|
||||
// // Intelligence & spellpower
|
||||
// { id: 'item_refresher', name: 'Refresher Orb' },
|
||||
// { id: 'item_aghanims_scepter', name: 'Aghanim’s Scepter' },
|
||||
// { id: 'item_aghanims_shard', name: 'Aghanim’s Shard' },
|
||||
// { id: 'item_witch_blade', name: 'Witch Blade' },
|
||||
// { id: 'item_gungir', name: 'Gleipnir' },
|
||||
|
||||
// // Tank / sustain late game
|
||||
// { id: 'item_heart', name: 'Heart of Tarrasque' },
|
||||
// { id: 'item_assault', name: 'Assault Cuirass' },
|
||||
// { id: 'item_satanic', name: 'Satanic' },
|
||||
// { id: 'item_harpoon', name: 'Harpoon' },
|
||||
|
||||
// // Universal top-tier
|
||||
// { id: 'item_sphere', name: 'Linken’s Sphere' },
|
||||
// { id: 'item_skadi', name: 'Eye of Skadi' },
|
||||
// { id: 'item_manta', name: 'Manta Style' },
|
||||
// { id: 'item_overwhelming_blink', name: 'Overwhelming Blink' },
|
||||
// { id: 'item_swift_blink', name: 'Swift Blink' },
|
||||
|
||||
// // Summon & utility
|
||||
// { id: 'item_necronomicon', name: 'Necronomicon' },
|
||||
// { id: 'item_drum', name: 'Drum of Endurance' },
|
||||
// { id: 'item_helm_of_the_overlord', name: 'Helm of the Overlord' },
|
||||
|
||||
// // Roshan rewards
|
||||
// { id: 'item_aegis', name: 'Aegis of the Immortal' },
|
||||
// { id: 'item_cheese', name: 'Cheese' },
|
||||
// { id: 'item_refresher_shard', name: 'Refresher Shard' },
|
||||
// { id: 'item_aghanims_blessing', name: 'Aghanim’s Blessing' },
|
||||
|
||||
// // Neutral items — Tier 1
|
||||
// { id: 'item_arcane_ring', name: 'Arcane Ring' },
|
||||
// { id: 'item_faded_broach', name: 'Faded Broach' },
|
||||
// { id: 'item_keen_optic', name: 'Keen Optic' },
|
||||
|
||||
// // Tier 2
|
||||
// { id: 'item_grove_bow', name: 'Grove Bow' },
|
||||
// { id: 'item_pupils_gift', name: 'Pupil’s Gift' },
|
||||
// { id: 'item_philosophers_stone', name: 'Philosopher’s Stone' },
|
||||
|
||||
// // Tier 3
|
||||
// { id: 'item_paladin_sword', name: 'Paladin Sword' },
|
||||
// { id: 'item_quickening_charm', name: 'Quickening Charm' },
|
||||
// { id: 'item_spider_legs', name: 'Spider Legs' },
|
||||
|
||||
// // Tier 4
|
||||
// { id: 'item_spell_prism', name: 'Spell Prism' },
|
||||
// { id: 'item_timeless_relic', name: 'Timeless Relic' },
|
||||
// { id: 'item_mind_breaker', name: 'Mind Breaker' },
|
||||
|
||||
// // Tier 5
|
||||
// { id: 'item_apex', name: 'Apex' },
|
||||
// { id: 'item_ex_machina', name: 'Ex Machina' },
|
||||
// { id: 'item_mirror_shield', name: 'Mirror Shield' },
|
||||
// { id: 'item_book_of_shadows', name: 'Book of Shadows' },
|
||||
// { id: 'item_seer_stone', name: 'Seer Stone' }
|
||||
// ];
|
||||
|
||||
|
||||
12
dota-random-builds-back/data/skills.ts
Normal file
12
dota-random-builds-back/data/skills.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface SkillBuildOption {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const skillBuilds: SkillBuildOption[] = [
|
||||
{ id: 'q_first', name: 'Max Q first', description: 'Prioritize first ability' },
|
||||
{ id: 'w_first', name: 'Max W first', description: 'Prioritize second ability' },
|
||||
{ id: 'e_first', name: 'Max E first', description: 'Prioritize third ability' },
|
||||
{ id: 'balanced', name: 'Balanced', description: 'Evenly distribute points' }
|
||||
]
|
||||
22
dota-random-builds-back/manage.py
Executable file
22
dota-random-builds-back/manage.py
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1
dota-random-builds-back/prompt.txt
Normal file
1
dota-random-builds-back/prompt.txt
Normal file
@@ -0,0 +1 @@
|
||||
создай django + drf приложение щас в среде используется python 3.12.3
|
||||
3
dota-random-builds-back/requirements.txt
Normal file
3
dota-random-builds-back/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
django>=6.0,<7.0
|
||||
djangorestframework>=3.16,<4.0
|
||||
django-cors-headers>=4.9,<5.0
|
||||
6
dota-random-builds-front/.dockerignore
Normal file
6
dota-random-builds-front/.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
dist
|
||||
.git
|
||||
.gitignore
|
||||
*.log
|
||||
.env*
|
||||
36
dota-random-builds-front/.gitignore
vendored
Normal file
36
dota-random-builds-front/.gitignore
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Cypress
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Vitest
|
||||
__screenshots__/
|
||||
37
dota-random-builds-front/AGENTS.md
Normal file
37
dota-random-builds-front/AGENTS.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `src/main.ts` bootstraps the Vue app, mounts `App.vue`, and registers Pinia + router.
|
||||
- `src/App.vue` hosts the layout shell; global theme styles live in `src/assets/main.css`.
|
||||
- `src/pages` holds route-level views (e.g., `HomePage.vue`), wired in `src/router/index.ts`.
|
||||
- `src/components` contains shareable UI (`Randomizer.vue`); aim to keep these presentational.
|
||||
- `src/stores` stores state and side effects (e.g., `randomData.ts` for `/api/randomize` and localStorage prefs); static datasets sit in `src/data`.
|
||||
- `public` serves static assets as-is; `vite.config.ts` configures aliases (`@` → `src`) and plugins.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- `npm run dev` — start Vite dev server with hot reload.
|
||||
- `npm run build` — type-check via `vue-tsc`, then produce the production bundle.
|
||||
- `npm run build-only` — production bundle without type-check (useful for quick iteration).
|
||||
- `npm run preview` — serve the built assets locally to verify production output.
|
||||
- `npm run type-check` — standalone TS/SFC type validation.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Vue 3 + TypeScript with `<script setup>`; keep logic typed and colocated with templates/styles.
|
||||
- Use 2-space indentation, single quotes for strings, and omit semicolons to match existing files.
|
||||
- Components/pages use PascalCase filenames; route-level views use a `Page` suffix.
|
||||
- Prefer `@/` alias imports over deep relatives; group imports by library/local.
|
||||
- Keep styles inside SFC blocks or `src/assets/main.css`; reuse existing CSS variables where possible.
|
||||
|
||||
## Testing Guidelines
|
||||
- No automated tests exist yet; before pushing, run `npm run type-check` and `npm run build` to catch regressions.
|
||||
- Manual QA: exercise the randomizer flow (toggle skills/aspects, adjust item count, observe loading/error states).
|
||||
- When adding tests, favor Vitest + Vue Test Utils and mirror component filenames (e.g., `Randomizer.spec.ts`).
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Git history is minimal; use concise, imperative messages (prefer Conventional Commits such as `feat: add randomizer store`) that describe intent.
|
||||
- PRs should include: summary of changes, impacted areas (UI/store/API), steps to reproduce/test, and screenshots for UI-facing updates.
|
||||
- Link to relevant issues, keep PRs small and focused, and call out any API or config assumptions (backend `/api/randomize` endpoint, localStorage usage).
|
||||
|
||||
## Environment & API Notes
|
||||
- The frontend expects an `/api/randomize` POST endpoint; if running locally without the backend, configure a mock or Vite proxy.
|
||||
- Avoid storing secrets in the repo; prefer runtime env vars and document any required values in the PR description.
|
||||
52
dota-random-builds-front/API.md
Normal file
52
dota-random-builds-front/API.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# API Endpoints for Frontend Compatibility
|
||||
|
||||
This document defines the minimal backend surface expected by the Vue frontend. Default base path is `/api`; responses are JSON with `Content-Type: application/json`.
|
||||
|
||||
## POST /api/randomize
|
||||
- **Purpose:** Produce a randomized Dota 2 build and optionally return the full data sets used for rendering.
|
||||
- **Request body:**
|
||||
- `includeSkills` (boolean) — include a skill build suggestion.
|
||||
- `includeAspect` (boolean) — include an aspect suggestion.
|
||||
- `itemsCount` (number) — how many items to return (UI offers 3–6).
|
||||
- **Response 200 body:**
|
||||
- `hero` — object `{ id: string; name: string; primary: string; }`.
|
||||
- `items` — array of `{ id: string; name: string; }` with length `itemsCount` (also used to hydrate cached items if you return the full pool).
|
||||
- `skillBuild` (optional) — `{ id: string; name: string; description?: string; }` when `includeSkills` is true.
|
||||
- `aspect` (optional) — string when `includeAspect` is true.
|
||||
- `heroes` (optional) — full hero list to cache client-side; same shape as `hero`.
|
||||
- `skillBuilds` (optional) — full skill build list; same shape as `skillBuild`.
|
||||
- `aspects` (optional) — array of strings.
|
||||
- **Example request:**
|
||||
```json
|
||||
{
|
||||
"includeSkills": true,
|
||||
"includeAspect": false,
|
||||
"itemsCount": 6
|
||||
}
|
||||
```
|
||||
- **Example response:**
|
||||
```json
|
||||
{
|
||||
"hero": { "id": "pudge", "name": "Pudge", "primary": "strength" },
|
||||
"items": [
|
||||
{ "id": "blink", "name": "Blink Dagger" },
|
||||
{ "id": "bkb", "name": "Black King Bar" }
|
||||
],
|
||||
"skillBuild": { "id": "hookdom", "name": "Hook/Rot Max", "description": "Max hook, then rot; early Flesh Heap." },
|
||||
"heroes": [{ "id": "axe", "name": "Axe", "primary": "strength" }],
|
||||
"skillBuilds": [{ "id": "support", "name": "Support" }],
|
||||
"aspects": ["Heroic Might"]
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
- Use standard HTTP status codes (400 invalid input, 500 server error).
|
||||
- Error payload shape:
|
||||
```json
|
||||
{ "message": "Failed to load random build" }
|
||||
```
|
||||
- The UI surfaces `message` directly; keep it user-friendly.
|
||||
|
||||
## Implementation Notes
|
||||
- Ensure deterministic constraints: unique `id` per entity and avoid empty arrays when `includeSkills`/`includeAspect` are true.
|
||||
- If backing data is static, you may return `heroes/items/skillBuilds/aspects` once per request; the frontend caches them after the first successful call.
|
||||
21
dota-random-builds-front/Dockerfile
Normal file
21
dota-random-builds-front/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
||||
FROM node:22-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Copy source and build
|
||||
COPY . .
|
||||
RUN npm run build-only
|
||||
|
||||
# Production stage with nginx
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
42
dota-random-builds-front/README.md
Normal file
42
dota-random-builds-front/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# dota-random-builds-front
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Recommended Browser Setup
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
1
dota-random-builds-front/env.d.ts
vendored
Normal file
1
dota-random-builds-front/env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
13
dota-random-builds-front/index.html
Normal file
13
dota-random-builds-front/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
23
dota-random-builds-front/nginx.conf
Normal file
23
dota-random-builds-front/nginx.conf
Normal file
@@ -0,0 +1,23 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8000/api/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
3088
dota-random-builds-front/package-lock.json
generated
Normal file
3088
dota-random-builds-front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
dota-random-builds-front/package.json
Normal file
33
dota-random-builds-front/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "dota-random-builds-front",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || >=22.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.2",
|
||||
"pinia": "^3.0.4",
|
||||
"vue": "^3.5.25",
|
||||
"vue-router": "^4.6.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node24": "^24.0.3",
|
||||
"@types/node": "^24.10.1",
|
||||
"@vitejs/plugin-vue": "^6.0.2",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"typescript": "~5.9.0",
|
||||
"vite": "^7.2.4",
|
||||
"vite-plugin-vue-devtools": "^8.0.5",
|
||||
"vue-tsc": "^3.1.5"
|
||||
}
|
||||
}
|
||||
BIN
dota-random-builds-front/public/favicon.ico
Normal file
BIN
dota-random-builds-front/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
127
dota-random-builds-front/src/App.vue
Normal file
127
dota-random-builds-front/src/App.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterView, RouterLink } from 'vue-router';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="container">
|
||||
<nav class="nav">
|
||||
<RouterLink to="/" class="nav-link">Randomizer</RouterLink>
|
||||
<RouterLink to="/build-of-day" class="nav-link">Build of the Day</RouterLink>
|
||||
</nav>
|
||||
<RouterView />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
|
||||
|
||||
#app{
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
/* --- Многослойное Dota/Arcane освещение --- */
|
||||
background:
|
||||
/* Бирюзовый левый свет */
|
||||
radial-gradient(900px 700px at 16% 22%, var(--glow-1), transparent 70%),
|
||||
|
||||
/* Синий правый объемный свет */
|
||||
radial-gradient(800px 600px at 86% 78%, var(--glow-2), transparent 72%),
|
||||
|
||||
/* Верхнее золото (очень тонкое, “блеск”) */
|
||||
radial-gradient(1000px 800px at 50% -20%, var(--gold-glow), transparent 85%),
|
||||
|
||||
/* Нижнее мягкое свечение */
|
||||
radial-gradient(900px 900px at 50% 120%, var(--blue-glow), transparent 75%),
|
||||
|
||||
/* Вертикальный общий градиент */
|
||||
linear-gradient(180deg, var(--bg-1) 0%, var(--bg-2) 100%);
|
||||
|
||||
color: #e6f0fa;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial;
|
||||
}
|
||||
|
||||
/* --- Лёгкий аркановый блик сверху --- */
|
||||
#app::before{
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255,255,255,0.06), transparent 45%);
|
||||
mix-blend-mode: soft-light;
|
||||
}
|
||||
|
||||
/* --- Premium noise — чуть заметный, как в Dota UI --- */
|
||||
#app::after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
opacity: var(--noise-opacity);
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* Контейнер контента */
|
||||
.container{
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
padding: 32px;
|
||||
box-sizing: border-box;
|
||||
|
||||
/* Мягкое появление */
|
||||
animation: fadeIn 0.6s ease forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(14px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* mobile */
|
||||
@media (max-width: 720px){
|
||||
.container{ padding: 20px; }
|
||||
}
|
||||
|
||||
/* Navigation */
|
||||
.nav {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
padding: 10px 20px;
|
||||
border-radius: 10px;
|
||||
text-decoration: none;
|
||||
color: #e6f0fa;
|
||||
font-weight: 600;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
box-shadow: 0 0 12px rgba(79, 195, 247, 0.2);
|
||||
}
|
||||
|
||||
.nav-link.router-link-exact-active {
|
||||
background: linear-gradient(90deg, #b63a2b, #8b241b);
|
||||
color: #f8e7c6;
|
||||
border-color: rgba(182, 58, 43, 0.5);
|
||||
box-shadow: 0 0 12px rgba(182, 58, 43, 0.45);
|
||||
}
|
||||
</style>
|
||||
31
dota-random-builds-front/src/assets/main.css
Normal file
31
dota-random-builds-front/src/assets/main.css
Normal file
@@ -0,0 +1,31 @@
|
||||
:root{
|
||||
--bg-1: #050a19;
|
||||
--bg-2: #0b1f3a;
|
||||
|
||||
/* Бирюзово-синие glows */
|
||||
--glow-1: rgba(79, 209, 197, 0.16);
|
||||
--glow-2: rgba(56, 189, 248, 0.12);
|
||||
--glow-3: rgba(96, 165, 250, 0.10);
|
||||
|
||||
/* Акценты и текст */
|
||||
--accent: #4fd1c5;
|
||||
--muted: #9fb3cc;
|
||||
|
||||
--noise-opacity: 0.04;
|
||||
|
||||
/* Dota-стильные подсветки */
|
||||
--gold-glow: rgba(200, 168, 106, 0.12);
|
||||
--blue-glow: rgba(79,195,247,0.12);
|
||||
}
|
||||
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
/* width: 100%;
|
||||
height: 100%; */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
410
dota-random-builds-front/src/components/Randomizer.vue
Normal file
410
dota-random-builds-front/src/components/Randomizer.vue
Normal file
@@ -0,0 +1,410 @@
|
||||
<template>
|
||||
<div class="randomizer">
|
||||
<h2>Random Dota 2 Build</h2>
|
||||
|
||||
<div class="controls">
|
||||
<label>
|
||||
Hero:
|
||||
<select v-model="selectedHeroId">
|
||||
<option :value="null">Random</option>
|
||||
<option v-for="hero in heroes" :key="hero.id" :value="hero.id">
|
||||
{{ hero.name }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label><input type="checkbox" v-model="includeSkills" /> Include skill build</label>
|
||||
<label><input type="checkbox" v-model="includeAspect" /> Include aspect</label>
|
||||
<label>
|
||||
Items to pick:
|
||||
<select v-model.number="itemsCount">
|
||||
<option :value="3">3</option>
|
||||
<option :value="4">4</option>
|
||||
<option :value="5">5</option>
|
||||
<option :value="6">6</option>
|
||||
</select>
|
||||
</label>
|
||||
<button @click="requestRandomize" :disabled="!canRandomize">
|
||||
{{ loading ? 'Loading...' : 'Randomize' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="error" class="error">Не удалось загрузить данные: {{ error }}</p>
|
||||
|
||||
<div v-if="result" class="result">
|
||||
<div>
|
||||
<h3>Hero</h3>
|
||||
<div class="hero">{{ result.hero.name }}</div>
|
||||
<div class="hero-badge">{{ result.hero.primary }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Items</h3>
|
||||
<ul>
|
||||
<li v-for="it in result.items" :key="it.id">{{ it.name }}</li>
|
||||
</ul>
|
||||
|
||||
<div v-if="includeSkills">
|
||||
<h3>Skill Build</h3>
|
||||
<div v-if="result?.skillBuild" class="skill-build">
|
||||
<div v-for="level in skillLevels" :key="level" class="skill-level">
|
||||
<span class="level-num">{{ level }}</span>
|
||||
<span class="skill-key" :class="getSkillClass(result.skillBuild[String(level)])">
|
||||
{{ formatSkill(result.skillBuild[String(level)]) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="warning">Скилл билд не был выбран, сгенерируйте челлендж заново.</div>
|
||||
</div>
|
||||
|
||||
<div v-if="includeAspect">
|
||||
<h3>Aspect</h3>
|
||||
<div v-if="result.aspect">
|
||||
{{ result.aspect }}
|
||||
</div>
|
||||
<div v-else class="warning">Аспект не был выбран, сгенерируйте челлендж заново.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRandomDataStore } from '@/stores/randomData'
|
||||
|
||||
const randomDataStore = useRandomDataStore()
|
||||
const { prefs, result, loading, error, heroes } = storeToRefs(randomDataStore)
|
||||
|
||||
const skillLevels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 25]
|
||||
|
||||
function formatSkill(skill: string | undefined): string {
|
||||
if (!skill) return '-'
|
||||
const map: Record<string, string> = {
|
||||
q: 'Q',
|
||||
w: 'W',
|
||||
e: 'E',
|
||||
r: 'R',
|
||||
left_talent: 'L',
|
||||
right_talent: 'R'
|
||||
}
|
||||
return map[skill] ?? skill
|
||||
}
|
||||
|
||||
function getSkillClass(skill: string | undefined): string {
|
||||
if (!skill) return ''
|
||||
if (skill === 'r') return 'skill-ult'
|
||||
if (skill === 'left_talent' || skill === 'right_talent') return 'skill-talent'
|
||||
return 'skill-basic'
|
||||
}
|
||||
|
||||
const includeSkills = computed({
|
||||
get: () => prefs.value.includeSkills,
|
||||
set: (val: boolean) => randomDataStore.setPrefs({ includeSkills: val })
|
||||
})
|
||||
|
||||
const includeAspect = computed({
|
||||
get: () => prefs.value.includeAspect,
|
||||
set: (val: boolean) => randomDataStore.setPrefs({ includeAspect: val })
|
||||
})
|
||||
|
||||
const itemsCount = computed({
|
||||
get: () => prefs.value.itemsCount,
|
||||
set: (val: number) => randomDataStore.setPrefs({ itemsCount: val })
|
||||
})
|
||||
|
||||
const selectedHeroId = computed({
|
||||
get: () => prefs.value.heroId ?? null,
|
||||
set: (val: number | null) => randomDataStore.setPrefs({ heroId: val })
|
||||
})
|
||||
|
||||
const canRandomize = computed(() => !loading.value)
|
||||
|
||||
function requestRandomize() {
|
||||
randomDataStore.randomize({
|
||||
includeSkills: includeSkills.value,
|
||||
includeAspect: includeAspect.value,
|
||||
itemsCount: itemsCount.value,
|
||||
heroId: selectedHeroId.value
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
randomDataStore.loadPrefs()
|
||||
randomDataStore.loadHeroes()
|
||||
randomDataStore.randomize()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:root {
|
||||
--dota-red: #b63a2b;
|
||||
--dota-red-glow: rgba(182, 58, 43, 0.45);
|
||||
--dota-gold: #c8a86a;
|
||||
--dota-gold-glow: rgba(200, 168, 106, 0.3);
|
||||
--dota-blue: #4fc3f7;
|
||||
--dota-blue-glow: rgba(79, 195, 247, 0.35);
|
||||
--panel: rgba(10, 14, 20, 0.6);
|
||||
}
|
||||
|
||||
/* MAIN BLOCK -------------------------------------------------- */
|
||||
.randomizer {
|
||||
padding: 26px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(180deg, rgba(20, 25, 40, 0.9), rgba(8, 10, 16, 0.92)),
|
||||
radial-gradient(120% 120% at 0% 0%, rgba(255, 255, 255, 0.05), transparent);
|
||||
color: #e6f0fa;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
box-shadow:
|
||||
0 0 18px rgba(0, 0, 0, 0.6),
|
||||
0 0 32px rgba(0, 0, 0, 0.4),
|
||||
inset 0 0 20px rgba(255, 255, 255, 0.02);
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Decorative glow line */
|
||||
.randomizer::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at top left,
|
||||
rgba(255, 255, 255, 0.10),
|
||||
transparent 50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* TITLE -------------------------------------------------- */
|
||||
.randomizer>h2 {
|
||||
text-align: center;
|
||||
font-weight: 800;
|
||||
font-size: 1.7rem;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 18px;
|
||||
text-shadow: 0 0 6px var(--dota-red-glow);
|
||||
color: var(--dota-gold);
|
||||
}
|
||||
|
||||
/* CONTROLS -------------------------------------------------- */
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.controls label {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 0.95rem;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
transition: 0.2s ease;
|
||||
}
|
||||
|
||||
.controls label:hover {
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
box-shadow: 0 0 14px rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.controls select {
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
color: inherit;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
padding: 6px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* BUTTON -------------------------------------------------- */
|
||||
.controls button {
|
||||
background: linear-gradient(90deg, var(--dota-red), #8b241b);
|
||||
color: #f8e7c6;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
|
||||
padding: 8px 16px;
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
|
||||
box-shadow:
|
||||
0 0 12px var(--dota-red-glow),
|
||||
inset 0 0 6px rgba(0, 0, 0, 0.4);
|
||||
|
||||
transition: transform .12s ease, box-shadow .12s ease, opacity .12s ease;
|
||||
}
|
||||
|
||||
.controls button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
0 0 18px var(--dota-red-glow),
|
||||
inset 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.controls button:active {
|
||||
transform: translateY(1px);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.controls button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f87171;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #fbbf24;
|
||||
font-weight: 600;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* RESULT PANEL -------------------------------------------------- */
|
||||
.result {
|
||||
margin-top: 12px;
|
||||
display: grid;
|
||||
grid-template-columns: 220px 1fr;
|
||||
gap: 22px;
|
||||
|
||||
background: linear-gradient(180deg, rgba(15, 17, 23, 0.9), rgba(6, 8, 13, 0.9));
|
||||
padding: 18px;
|
||||
border-radius: 14px;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
box-shadow: inset 0 0 18px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
/* HERO -------------------------------------------------- */
|
||||
.hero {
|
||||
font-weight: 900;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 8px;
|
||||
color: var(--dota-blue);
|
||||
text-shadow: 0 0 6px var(--dota-blue-glow);
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
display: inline-block;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
|
||||
background: linear-gradient(90deg,
|
||||
rgba(255, 255, 255, 0.07),
|
||||
rgba(255, 255, 255, 0.02));
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
color: var(--dota-gold);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ITEMS -------------------------------------------------- */
|
||||
.result ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.result li {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
color: #f2f7ff;
|
||||
|
||||
font-weight: 600;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
|
||||
box-shadow:
|
||||
inset 0 -2px 0 rgba(0, 0, 0, 0.3),
|
||||
0 0 10px rgba(40, 150, 255, 0.15);
|
||||
|
||||
transition: 0.15s ease;
|
||||
}
|
||||
|
||||
.result li:hover {
|
||||
background: rgba(255, 255, 255, 0.09);
|
||||
box-shadow:
|
||||
inset 0 -3px 0 rgba(0, 0, 0, 0.4),
|
||||
0 0 14px rgba(40, 150, 255, 0.22);
|
||||
}
|
||||
|
||||
/* SKILL + ASPECT -------------------------------------------------- */
|
||||
h3 {
|
||||
margin: 8px 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: var(--dota-gold);
|
||||
text-shadow: 0 0 4px var(--dota-gold-glow);
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: var(--muted);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* SKILL BUILD -------------------------------------------------- */
|
||||
.skill-build {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.skill-level {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.level-num {
|
||||
font-size: 0.7rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.skill-key {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.skill-basic {
|
||||
color: #8bc34a;
|
||||
}
|
||||
|
||||
.skill-ult {
|
||||
color: #ffd54f;
|
||||
background: rgba(255, 213, 79, 0.15);
|
||||
border-color: rgba(255, 213, 79, 0.3);
|
||||
}
|
||||
|
||||
.skill-talent {
|
||||
color: #4fc3f7;
|
||||
background: rgba(79, 195, 247, 0.15);
|
||||
border-color: rgba(79, 195, 247, 0.3);
|
||||
}
|
||||
|
||||
/* MOBILE -------------------------------------------------- */
|
||||
@media (max-width: 720px) {
|
||||
.result {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
7
dota-random-builds-front/src/data/aspects.ts
Normal file
7
dota-random-builds-front/src/data/aspects.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const aspects = [
|
||||
'Carry',
|
||||
'Support',
|
||||
'Roamer',
|
||||
'Jungler',
|
||||
'Pusher'
|
||||
]
|
||||
134
dota-random-builds-front/src/data/heroes.ts
Normal file
134
dota-random-builds-front/src/data/heroes.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
export interface Hero {
|
||||
id: string
|
||||
name: string
|
||||
primary: 'Strength' | 'Agility' | 'Intelligence'
|
||||
}
|
||||
|
||||
export const heroes: Hero[] = [
|
||||
{ id: 'abaddon', name: 'Abaddon', primary: 'Strength' },
|
||||
{ id: 'alchemist', name: 'Alchemist', primary: 'Strength' },
|
||||
{ id: 'axe', name: 'Axe', primary: 'Strength' },
|
||||
{ id: 'beastmaster', name: 'Beastmaster', primary: 'Strength' },
|
||||
{ id: 'brewmaster', name: 'Brewmaster', primary: 'Strength' },
|
||||
{ id: 'bristleback', name: 'Bristleback', primary: 'Strength' },
|
||||
{ id: 'centaur', name: 'Centaur Warrunner', primary: 'Strength' },
|
||||
{ id: 'chaos_knight', name: 'Chaos Knight', primary: 'Strength' },
|
||||
{ id: 'clockwerk', name: 'Clockwerk', primary: 'Strength' },
|
||||
{ id: 'dawnbreaker', name: 'Dawnbreaker', primary: 'Strength' },
|
||||
{ id: 'doom', name: 'Doom', primary: 'Strength' },
|
||||
{ id: 'dragon_knight', name: 'Dragon Knight', primary: 'Strength' },
|
||||
{ id: 'earth_spirit', name: 'Earth Spirit', primary: 'Strength' },
|
||||
{ id: 'earthshaker', name: 'Earthshaker', primary: 'Strength' },
|
||||
{ id: 'elder_titan', name: 'Elder Titan', primary: 'Strength' },
|
||||
{ id: 'huskar', name: 'Huskar', primary: 'Strength' },
|
||||
{ id: 'kunkka', name: 'Kunkka', primary: 'Strength' },
|
||||
{ id: 'legion_commander', name: 'Legion Commander', primary: 'Strength' },
|
||||
{ id: 'lifestealer', name: 'Lifestealer', primary: 'Strength' },
|
||||
{ id: 'lycan', name: 'Lycan', primary: 'Strength' },
|
||||
{ id: 'magnus', name: 'Magnus', primary: 'Strength' },
|
||||
{ id: 'mars', name: 'Mars', primary: 'Strength' },
|
||||
{ id: 'night_stalker', name: 'Night Stalker', primary: 'Strength' },
|
||||
{ id: 'ogre_magi', name: 'Ogre Magi', primary: 'Strength' },
|
||||
{ id: 'omniknight', name: 'Omniknight', primary: 'Strength' },
|
||||
{ id: 'phoenix', name: 'Phoenix', primary: 'Strength' },
|
||||
{ id: 'primal_beast', name: 'Primal Beast', primary: 'Strength' },
|
||||
{ id: 'pudge', name: 'Pudge', primary: 'Strength' },
|
||||
{ id: 'slardar', name: 'Slardar', primary: 'Strength' },
|
||||
{ id: 'snapfire', name: 'Snapfire', primary: 'Strength' },
|
||||
{ id: 'spirit_breaker', name: 'Spirit Breaker', primary: 'Strength' },
|
||||
{ id: 'sven', name: 'Sven', primary: 'Strength' },
|
||||
{ id: 'tidehunter', name: 'Tidehunter', primary: 'Strength' },
|
||||
{ id: 'timbersaw', name: 'Timbersaw', primary: 'Strength' },
|
||||
{ id: 'tiny', name: 'Tiny', primary: 'Strength' },
|
||||
{ id: 'treant', name: 'Treant Protector', primary: 'Strength' },
|
||||
{ id: 'tusk', name: 'Tusk', primary: 'Strength' },
|
||||
{ id: 'underlord', name: 'Underlord', primary: 'Strength' },
|
||||
{ id: 'undying', name: 'Undying', primary: 'Strength' },
|
||||
{ id: 'wraith_king', name: 'Wraith King', primary: 'Strength' },
|
||||
|
||||
/* -------------------- Agility -------------------- */
|
||||
|
||||
{ id: 'anti_mage', name: 'Anti-Mage', primary: 'Agility' },
|
||||
{ id: 'arc_warden', name: 'Arc Warden', primary: 'Agility' },
|
||||
{ id: 'bloodseeker', name: 'Bloodseeker', primary: 'Agility' },
|
||||
{ id: 'bounty_hunter', name: 'Bounty Hunter', primary: 'Agility' },
|
||||
{ id: 'broodmother', name: 'Broodmother', primary: 'Agility' },
|
||||
{ id: 'clinkz', name: 'Clinkz', primary: 'Agility' },
|
||||
{ id: 'drow_ranger', name: 'Drow Ranger', primary: 'Agility' },
|
||||
{ id: 'ember_spirit', name: 'Ember Spirit', primary: 'Agility' },
|
||||
{ id: 'faceless_void', name: 'Faceless Void', primary: 'Agility' },
|
||||
{ id: 'gyrocopter', name: 'Gyrocopter', primary: 'Agility' },
|
||||
{ id: 'hoodwink', name: 'Hoodwink', primary: 'Agility' },
|
||||
{ id: 'juggernaut', name: 'Juggernaut', primary: 'Agility' },
|
||||
{ id: 'lone_druid', name: 'Lone Druid', primary: 'Agility' },
|
||||
{ id: 'luna', name: 'Luna', primary: 'Agility' },
|
||||
{ id: 'medusa', name: 'Medusa', primary: 'Agility' },
|
||||
{ id: 'meepo', name: 'Meepo', primary: 'Agility' },
|
||||
{ id: 'mirana', name: 'Mirana', primary: 'Agility' },
|
||||
{ id: 'monkey_king', name: 'Monkey King', primary: 'Agility' },
|
||||
{ id: 'muerta', name: 'Muerta', primary: 'Agility' },
|
||||
{ id: 'naga_siren', name: 'Naga Siren', primary: 'Agility' },
|
||||
{ id: 'nyx_assassin', name: 'Nyx Assassin', primary: 'Agility' },
|
||||
{ id: 'pangolier', name: 'Pangolier', primary: 'Agility' },
|
||||
{ id: 'phantom_assassin', name: 'Phantom Assassin', primary: 'Agility' },
|
||||
{ id: 'phantom_lancer', name: 'Phantom Lancer', primary: 'Agility' },
|
||||
{ id: 'razor', name: 'Razor', primary: 'Agility' },
|
||||
{ id: 'riki', name: 'Riki', primary: 'Agility' },
|
||||
{ id: 'shadow_fiend', name: 'Shadow Fiend', primary: 'Agility' },
|
||||
{ id: 'slark', name: 'Slark', primary: 'Agility' },
|
||||
{ id: 'sniper', name: 'Sniper', primary: 'Agility' },
|
||||
{ id: 'spectre', name: 'Spectre', primary: 'Agility' },
|
||||
{ id: 'templar_assassin', name: 'Templar Assassin', primary: 'Agility' },
|
||||
{ id: 'terrorblade', name: 'Terrorblade', primary: 'Agility' },
|
||||
{ id: 'troll_warlord', name: 'Troll Warlord', primary: 'Agility' },
|
||||
{ id: 'ursa', name: 'Ursa', primary: 'Agility' },
|
||||
{ id: 'viper', name: 'Viper', primary: 'Agility' },
|
||||
{ id: 'weaver', name: 'Weaver', primary: 'Agility' },
|
||||
|
||||
/* -------------------- Intelligence -------------------- */
|
||||
|
||||
{ id: 'ancient_apparition', name: 'Ancient Apparition', primary: 'Intelligence' },
|
||||
{ id: 'bane', name: 'Bane', primary: 'Intelligence' },
|
||||
{ id: 'batrider', name: 'Batrider', primary: 'Intelligence' },
|
||||
{ id: 'chen', name: 'Chen', primary: 'Intelligence' },
|
||||
{ id: 'crystal_maiden', name: 'Crystal Maiden', primary: 'Intelligence' },
|
||||
{ id: 'dark_seer', name: 'Dark Seer', primary: 'Intelligence' },
|
||||
{ id: 'dark_willow', name: 'Dark Willow', primary: 'Intelligence' },
|
||||
{ id: 'dazzle', name: 'Dazzle', primary: 'Intelligence' },
|
||||
{ id: 'death_prophet', name: 'Death Prophet', primary: 'Intelligence' },
|
||||
{ id: 'disruptor', name: 'Disruptor', primary: 'Intelligence' },
|
||||
{ id: 'enchantress', name: 'Enchantress', primary: 'Intelligence' },
|
||||
{ id: 'enigma', name: 'Enigma', primary: 'Intelligence' },
|
||||
{ id: 'grimstroke', name: 'Grimstroke', primary: 'Intelligence' },
|
||||
{ id: 'invoker', name: 'Invoker', primary: 'Intelligence' },
|
||||
{ id: 'jakiro', name: 'Jakiro', primary: 'Intelligence' },
|
||||
{ id: 'keeper_of_the_light', name: 'Keeper of the Light', primary: 'Intelligence' },
|
||||
{ id: 'leshrac', name: 'Leshrac', primary: 'Intelligence' },
|
||||
{ id: 'lich', name: 'Lich', primary: 'Intelligence' },
|
||||
{ id: 'lina', name: 'Lina', primary: 'Intelligence' },
|
||||
{ id: 'lion', name: 'Lion', primary: 'Intelligence' },
|
||||
{ id: 'muerta', name: 'Muerta', primary: 'Intelligence' },
|
||||
{ id: 'nature_prophet', name: "Nature's Prophet", primary: 'Intelligence' },
|
||||
{ id: 'necrophos', name: 'Necrophos', primary: 'Intelligence' },
|
||||
{ id: 'ogre_magi_int', name: 'Ogre Magi (Int)', primary: 'Intelligence' },
|
||||
{ id: 'oracle', name: 'Oracle', primary: 'Intelligence' },
|
||||
{ id: 'outworld_destroyer', name: 'Outworld Destroyer', primary: 'Intelligence' },
|
||||
{ id: 'puck', name: 'Puck', primary: 'Intelligence' },
|
||||
{ id: 'pugna', name: 'Pugna', primary: 'Intelligence' },
|
||||
{ id: 'queen_of_pain', name: 'Queen of Pain', primary: 'Intelligence' },
|
||||
{ id: 'rubick', name: 'Rubick', primary: 'Intelligence' },
|
||||
{ id: 'shadow_demon', name: 'Shadow Demon', primary: 'Intelligence' },
|
||||
{ id: 'shadow_shaman', name: 'Shadow Shaman', primary: 'Intelligence' },
|
||||
{ id: 'silencer', name: 'Silencer', primary: 'Intelligence' },
|
||||
{ id: 'skywrath_mage', name: 'Skywrath Mage', primary: 'Intelligence' },
|
||||
{ id: 'storm_spirit', name: 'Storm Spirit', primary: 'Intelligence' },
|
||||
{ id: 'techies', name: 'Techies', primary: 'Intelligence' },
|
||||
{ id: 'tinker', name: 'Tinker', primary: 'Intelligence' },
|
||||
{ id: 'visage', name: 'Visage', primary: 'Intelligence' },
|
||||
{ id: 'void_spirit', name: 'Void Spirit', primary: 'Intelligence' },
|
||||
{ id: 'warlock', name: 'Warlock', primary: 'Intelligence' },
|
||||
{ id: 'windranger', name: 'Windranger', primary: 'Intelligence' },
|
||||
{ id: 'winter_wyvern', name: 'Winter Wyvern', primary: 'Intelligence' },
|
||||
{ id: 'witch_doctor', name: 'Witch Doctor', primary: 'Intelligence' },
|
||||
{ id: 'zeus', name: 'Zeus', primary: 'Intelligence' }
|
||||
];
|
||||
887
dota-random-builds-front/src/data/items.ts
Normal file
887
dota-random-builds-front/src/data/items.ts
Normal file
@@ -0,0 +1,887 @@
|
||||
export interface Item {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
|
||||
|
||||
export const items: Item[] = [
|
||||
{
|
||||
"id": "item_town_portal_scroll",
|
||||
"name": "Town Portal Scroll"
|
||||
},
|
||||
{
|
||||
"id": "item_clarity",
|
||||
"name": "Clarity"
|
||||
},
|
||||
{
|
||||
"id": "item_faerie_fire",
|
||||
"name": "Faerie Fire"
|
||||
},
|
||||
{
|
||||
"id": "item_smoke_of_deceit",
|
||||
"name": "Smoke of Deceit"
|
||||
},
|
||||
{
|
||||
"id": "item_observer_ward",
|
||||
"name": "Observer Ward"
|
||||
},
|
||||
{
|
||||
"id": "item_sentry_ward",
|
||||
"name": "Sentry Ward"
|
||||
},
|
||||
{
|
||||
"id": "item_enchanted_mango",
|
||||
"name": "Enchanted Mango"
|
||||
},
|
||||
{
|
||||
"id": "item_healing_salve",
|
||||
"name": "Healing Salve"
|
||||
},
|
||||
{
|
||||
"id": "item_tango",
|
||||
"name": "Tango"
|
||||
},
|
||||
{
|
||||
"id": "item_blood_grenade",
|
||||
"name": "Blood Grenade"
|
||||
},
|
||||
{
|
||||
"id": "item_dust_of_appearance",
|
||||
"name": "Dust of Appearance"
|
||||
},
|
||||
{
|
||||
"id": "item_bottle",
|
||||
"name": "Bottle"
|
||||
},
|
||||
{
|
||||
"id": "item_aghanim_s_shard",
|
||||
"name": "Aghanim's Shard"
|
||||
},
|
||||
{
|
||||
"id": "item_aghanim_s_blessing",
|
||||
"name": "Aghanim's Blessing"
|
||||
},
|
||||
{
|
||||
"id": "item_observer_and_sentry_wards",
|
||||
"name": "Observer and Sentry Wards"
|
||||
},
|
||||
{
|
||||
"id": "item_iron_branch",
|
||||
"name": "Iron Branch"
|
||||
},
|
||||
{
|
||||
"id": "item_gauntlets_of_strength",
|
||||
"name": "Gauntlets of Strength"
|
||||
},
|
||||
{
|
||||
"id": "item_slippers_of_agility",
|
||||
"name": "Slippers of Agility"
|
||||
},
|
||||
{
|
||||
"id": "item_mantle_of_intelligence",
|
||||
"name": "Mantle of Intelligence"
|
||||
},
|
||||
{
|
||||
"id": "item_circlet",
|
||||
"name": "Circlet"
|
||||
},
|
||||
{
|
||||
"id": "item_belt_of_strength",
|
||||
"name": "Belt of Strength"
|
||||
},
|
||||
{
|
||||
"id": "item_band_of_elvenskin",
|
||||
"name": "Band of Elvenskin"
|
||||
},
|
||||
{
|
||||
"id": "item_robe_of_the_magi",
|
||||
"name": "Robe of the Magi"
|
||||
},
|
||||
{
|
||||
"id": "item_crown",
|
||||
"name": "Crown"
|
||||
},
|
||||
{
|
||||
"id": "item_ogre_axe",
|
||||
"name": "Ogre Axe"
|
||||
},
|
||||
{
|
||||
"id": "item_blade_of_alacrity",
|
||||
"name": "Blade of Alacrity"
|
||||
},
|
||||
{
|
||||
"id": "item_staff_of_wizardry",
|
||||
"name": "Staff of Wizardry"
|
||||
},
|
||||
{
|
||||
"id": "item_diadem",
|
||||
"name": "Diadem"
|
||||
},
|
||||
{
|
||||
"id": "item_quelling_blade",
|
||||
"name": "Quelling Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_protection",
|
||||
"name": "Ring of Protection"
|
||||
},
|
||||
{
|
||||
"id": "item_infused_raindrops",
|
||||
"name": "Infused Raindrops"
|
||||
},
|
||||
{
|
||||
"id": "item_orb_of_venom",
|
||||
"name": "Orb of Venom"
|
||||
},
|
||||
{
|
||||
"id": "item_orb_of_blight",
|
||||
"name": "Orb of Blight"
|
||||
},
|
||||
{
|
||||
"id": "item_blades_of_attack",
|
||||
"name": "Blades of Attack"
|
||||
},
|
||||
{
|
||||
"id": "item_orb_of_frost",
|
||||
"name": "Orb of Frost"
|
||||
},
|
||||
{
|
||||
"id": "item_gloves_of_haste",
|
||||
"name": "Gloves of Haste"
|
||||
},
|
||||
{
|
||||
"id": "item_chainmail",
|
||||
"name": "Chainmail"
|
||||
},
|
||||
{
|
||||
"id": "item_helm_of_iron_will",
|
||||
"name": "Helm of Iron Will"
|
||||
},
|
||||
{
|
||||
"id": "item_broadsword",
|
||||
"name": "Broadsword"
|
||||
},
|
||||
{
|
||||
"id": "item_blitz_knuckles",
|
||||
"name": "Blitz Knuckles"
|
||||
},
|
||||
{
|
||||
"id": "item_javelin",
|
||||
"name": "Javelin"
|
||||
},
|
||||
{
|
||||
"id": "item_claymore",
|
||||
"name": "Claymore"
|
||||
},
|
||||
{
|
||||
"id": "item_mithril_hammer",
|
||||
"name": "Mithril Hammer"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_regen",
|
||||
"name": "Ring of Regen"
|
||||
},
|
||||
{
|
||||
"id": "item_sage_s_mask",
|
||||
"name": "Sage's Mask"
|
||||
},
|
||||
{
|
||||
"id": "item_magic_stick",
|
||||
"name": "Magic Stick"
|
||||
},
|
||||
{
|
||||
"id": "item_fluffy_hat",
|
||||
"name": "Fluffy Hat"
|
||||
},
|
||||
{
|
||||
"id": "item_wind_lace",
|
||||
"name": "Wind Lace"
|
||||
},
|
||||
{
|
||||
"id": "item_cloak",
|
||||
"name": "Cloak"
|
||||
},
|
||||
{
|
||||
"id": "item_boots_of_speed",
|
||||
"name": "Boots of Speed"
|
||||
},
|
||||
{
|
||||
"id": "item_gem_of_true_sight",
|
||||
"name": "Gem of True Sight"
|
||||
},
|
||||
{
|
||||
"id": "item_morbid_mask",
|
||||
"name": "Morbid Mask"
|
||||
},
|
||||
{
|
||||
"id": "item_voodoo_mask",
|
||||
"name": "Voodoo Mask"
|
||||
},
|
||||
{
|
||||
"id": "item_shadow_amulet",
|
||||
"name": "Shadow Amulet"
|
||||
},
|
||||
{
|
||||
"id": "item_ghost_scepter",
|
||||
"name": "Ghost Scepter"
|
||||
},
|
||||
{
|
||||
"id": "item_blink_dagger",
|
||||
"name": "Blink Dagger"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_health",
|
||||
"name": "Ring of Health"
|
||||
},
|
||||
{
|
||||
"id": "item_void_stone",
|
||||
"name": "Void Stone"
|
||||
},
|
||||
{
|
||||
"id": "item_magic_wand",
|
||||
"name": "Magic Wand"
|
||||
},
|
||||
{
|
||||
"id": "item_null_talisman",
|
||||
"name": "Null Talisman"
|
||||
},
|
||||
{
|
||||
"id": "item_wraith_band",
|
||||
"name": "Wraith Band"
|
||||
},
|
||||
{
|
||||
"id": "item_bracer",
|
||||
"name": "Bracer"
|
||||
},
|
||||
{
|
||||
"id": "item_soul_ring",
|
||||
"name": "Soul Ring"
|
||||
},
|
||||
{
|
||||
"id": "item_orb_of_corrosion",
|
||||
"name": "Orb of Corrosion"
|
||||
},
|
||||
{
|
||||
"id": "item_falcon_blade",
|
||||
"name": "Falcon Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_power_treads",
|
||||
"name": "Power Treads"
|
||||
},
|
||||
{
|
||||
"id": "item_phase_boots",
|
||||
"name": "Phase Boots"
|
||||
},
|
||||
{
|
||||
"id": "item_oblivion_staff",
|
||||
"name": "Oblivion Staff"
|
||||
},
|
||||
{
|
||||
"id": "item_perseverance",
|
||||
"name": "Perseverance"
|
||||
},
|
||||
{
|
||||
"id": "item_mask_of_madness",
|
||||
"name": "Mask of Madness"
|
||||
},
|
||||
{
|
||||
"id": "item_hand_of_midas",
|
||||
"name": "Hand of Midas"
|
||||
},
|
||||
{
|
||||
"id": "item_helm_of_the_dominator",
|
||||
"name": "Helm of the Dominator"
|
||||
},
|
||||
{
|
||||
"id": "item_boots_of_travel",
|
||||
"name": "Boots of Travel"
|
||||
},
|
||||
{
|
||||
"id": "item_moon_shard",
|
||||
"name": "Moon Shard"
|
||||
},
|
||||
{
|
||||
"id": "item_boots_of_travel_2",
|
||||
"name": "Boots of Travel 2"
|
||||
},
|
||||
{
|
||||
"id": "item_buckler",
|
||||
"name": "Buckler"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_basilius",
|
||||
"name": "Ring of Basilius"
|
||||
},
|
||||
{
|
||||
"id": "item_headdress",
|
||||
"name": "Headdress"
|
||||
},
|
||||
{
|
||||
"id": "item_urn_of_shadows",
|
||||
"name": "Urn of Shadows"
|
||||
},
|
||||
{
|
||||
"id": "item_tranquil_boots",
|
||||
"name": "Tranquil Boots"
|
||||
},
|
||||
{
|
||||
"id": "item_pavise",
|
||||
"name": "Pavise"
|
||||
},
|
||||
{
|
||||
"id": "item_arcane_boots",
|
||||
"name": "Arcane Boots"
|
||||
},
|
||||
{
|
||||
"id": "item_drum_of_endurance",
|
||||
"name": "Drum of Endurance"
|
||||
},
|
||||
{
|
||||
"id": "item_mekansm",
|
||||
"name": "Mekansm"
|
||||
},
|
||||
{
|
||||
"id": "item_holy_locket",
|
||||
"name": "Holy Locket"
|
||||
},
|
||||
{
|
||||
"id": "item_vladmir_s_offering",
|
||||
"name": "Vladmir's Offering"
|
||||
},
|
||||
{
|
||||
"id": "item_spirit_vessel",
|
||||
"name": "Spirit Vessel"
|
||||
},
|
||||
{
|
||||
"id": "item_pipe_of_insight",
|
||||
"name": "Pipe of Insight"
|
||||
},
|
||||
{
|
||||
"id": "item_guardian_greaves",
|
||||
"name": "Guardian Greaves"
|
||||
},
|
||||
{
|
||||
"id": "item_boots_of_bearing",
|
||||
"name": "Boots of Bearing"
|
||||
},
|
||||
{
|
||||
"id": "item_parasma",
|
||||
"name": "Parasma"
|
||||
},
|
||||
{
|
||||
"id": "item_veil_of_discord",
|
||||
"name": "Veil of Discord"
|
||||
},
|
||||
{
|
||||
"id": "item_glimmer_cape",
|
||||
"name": "Glimmer Cape"
|
||||
},
|
||||
{
|
||||
"id": "item_force_staff",
|
||||
"name": "Force Staff"
|
||||
},
|
||||
{
|
||||
"id": "item_aether_lens",
|
||||
"name": "Aether Lens"
|
||||
},
|
||||
{
|
||||
"id": "item_witch_blade",
|
||||
"name": "Witch Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_eul_s_scepter_of_divinity",
|
||||
"name": "Eul's Scepter of Divinity"
|
||||
},
|
||||
{
|
||||
"id": "item_rod_of_atos",
|
||||
"name": "Rod of Atos"
|
||||
},
|
||||
{
|
||||
"id": "item_dagon",
|
||||
"name": "Dagon"
|
||||
},
|
||||
{
|
||||
"id": "item_orchid_malevolence",
|
||||
"name": "Orchid Malevolence"
|
||||
},
|
||||
{
|
||||
"id": "item_solar_crest",
|
||||
"name": "Solar Crest"
|
||||
},
|
||||
{
|
||||
"id": "item_aghanim_s_scepter",
|
||||
"name": "Aghanim's Scepter"
|
||||
},
|
||||
{
|
||||
"id": "item_refresher_orb",
|
||||
"name": "Refresher Orb"
|
||||
},
|
||||
{
|
||||
"id": "item_octarine_core",
|
||||
"name": "Octarine Core"
|
||||
},
|
||||
{
|
||||
"id": "item_scythe_of_vyse",
|
||||
"name": "Scythe of Vyse"
|
||||
},
|
||||
{
|
||||
"id": "item_gleipnir",
|
||||
"name": "Gleipnir"
|
||||
},
|
||||
{
|
||||
"id": "item_wind_waker",
|
||||
"name": "Wind Waker"
|
||||
},
|
||||
{
|
||||
"id": "item_crystalys",
|
||||
"name": "Crystalys"
|
||||
},
|
||||
{
|
||||
"id": "item_meteor_hammer",
|
||||
"name": "Meteor Hammer"
|
||||
},
|
||||
{
|
||||
"id": "item_armlet_of_mordiggian",
|
||||
"name": "Armlet of Mordiggian"
|
||||
},
|
||||
{
|
||||
"id": "item_skull_basher",
|
||||
"name": "Skull Basher"
|
||||
},
|
||||
{
|
||||
"id": "item_shadow_blade",
|
||||
"name": "Shadow Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_desolator",
|
||||
"name": "Desolator"
|
||||
},
|
||||
{
|
||||
"id": "item_battle_fury",
|
||||
"name": "Battle Fury"
|
||||
},
|
||||
{
|
||||
"id": "item_ethereal_blade",
|
||||
"name": "Ethereal Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_nullifier",
|
||||
"name": "Nullifier"
|
||||
},
|
||||
{
|
||||
"id": "item_monkey_king_bar",
|
||||
"name": "Monkey King Bar"
|
||||
},
|
||||
{
|
||||
"id": "item_butterfly",
|
||||
"name": "Butterfly"
|
||||
},
|
||||
{
|
||||
"id": "item_radiance",
|
||||
"name": "Radiance"
|
||||
},
|
||||
{
|
||||
"id": "item_daedalus",
|
||||
"name": "Daedalus"
|
||||
},
|
||||
{
|
||||
"id": "item_silver_edge",
|
||||
"name": "Silver Edge"
|
||||
},
|
||||
{
|
||||
"id": "item_divine_rapier",
|
||||
"name": "Divine Rapier"
|
||||
},
|
||||
{
|
||||
"id": "item_bloodthorn",
|
||||
"name": "Bloodthorn"
|
||||
},
|
||||
{
|
||||
"id": "item_abyssal_blade",
|
||||
"name": "Abyssal Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_revenant_s_brooch",
|
||||
"name": "Revenant's Brooch"
|
||||
},
|
||||
{
|
||||
"id": "item_disperser",
|
||||
"name": "Disperser"
|
||||
},
|
||||
{
|
||||
"id": "item_khanda",
|
||||
"name": "Khanda"
|
||||
},
|
||||
{
|
||||
"id": "item_vanguard",
|
||||
"name": "Vanguard"
|
||||
},
|
||||
{
|
||||
"id": "item_blade_mail",
|
||||
"name": "Blade Mail"
|
||||
},
|
||||
{
|
||||
"id": "item_aeon_disk",
|
||||
"name": "Aeon Disk"
|
||||
},
|
||||
{
|
||||
"id": "item_soul_booster",
|
||||
"name": "Soul Booster"
|
||||
},
|
||||
{
|
||||
"id": "item_crimson_guard",
|
||||
"name": "Crimson Guard"
|
||||
},
|
||||
{
|
||||
"id": "item_lotus_orb",
|
||||
"name": "Lotus Orb"
|
||||
},
|
||||
{
|
||||
"id": "item_black_king_bar",
|
||||
"name": "Black King Bar"
|
||||
},
|
||||
{
|
||||
"id": "item_hurricane_pike",
|
||||
"name": "Hurricane Pike"
|
||||
},
|
||||
{
|
||||
"id": "item_manta_style",
|
||||
"name": "Manta Style"
|
||||
},
|
||||
{
|
||||
"id": "item_linken_s_sphere",
|
||||
"name": "Linken's Sphere"
|
||||
},
|
||||
{
|
||||
"id": "item_shiva_s_guard",
|
||||
"name": "Shiva's Guard"
|
||||
},
|
||||
{
|
||||
"id": "item_heart_of_tarrasque",
|
||||
"name": "Heart of Tarrasque"
|
||||
},
|
||||
{
|
||||
"id": "item_assault_cuirass",
|
||||
"name": "Assault Cuirass"
|
||||
},
|
||||
{
|
||||
"id": "item_bloodstone",
|
||||
"name": "Bloodstone"
|
||||
},
|
||||
{
|
||||
"id": "item_helm_of_the_overlord",
|
||||
"name": "Helm of the Overlord"
|
||||
},
|
||||
{
|
||||
"id": "item_eternal_shroud",
|
||||
"name": "Eternal Shroud"
|
||||
},
|
||||
{
|
||||
"id": "item_dragon_lance",
|
||||
"name": "Dragon Lance"
|
||||
},
|
||||
{
|
||||
"id": "item_sange",
|
||||
"name": "Sange"
|
||||
},
|
||||
{
|
||||
"id": "item_yasha",
|
||||
"name": "Yasha"
|
||||
},
|
||||
{
|
||||
"id": "item_kaya",
|
||||
"name": "Kaya"
|
||||
},
|
||||
{
|
||||
"id": "item_echo_sabre",
|
||||
"name": "Echo Sabre"
|
||||
},
|
||||
{
|
||||
"id": "item_maelstrom",
|
||||
"name": "Maelstrom"
|
||||
},
|
||||
{
|
||||
"id": "item_diffusal_blade",
|
||||
"name": "Diffusal Blade"
|
||||
},
|
||||
{
|
||||
"id": "item_mage_slayer",
|
||||
"name": "Mage Slayer"
|
||||
},
|
||||
{
|
||||
"id": "item_phylactery",
|
||||
"name": "Phylactery"
|
||||
},
|
||||
{
|
||||
"id": "item_heaven_s_halberd",
|
||||
"name": "Heaven's Halberd"
|
||||
},
|
||||
{
|
||||
"id": "item_kaya_and_sange",
|
||||
"name": "Kaya and Sange"
|
||||
},
|
||||
{
|
||||
"id": "item_sange_and_yasha",
|
||||
"name": "Sange and Yasha"
|
||||
},
|
||||
{
|
||||
"id": "item_yasha_and_kaya",
|
||||
"name": "Yasha and Kaya"
|
||||
},
|
||||
{
|
||||
"id": "item_satanic",
|
||||
"name": "Satanic"
|
||||
},
|
||||
{
|
||||
"id": "item_eye_of_skadi",
|
||||
"name": "Eye of Skadi"
|
||||
},
|
||||
{
|
||||
"id": "item_mjollnir",
|
||||
"name": "Mjollnir"
|
||||
},
|
||||
{
|
||||
"id": "item_overwhelming_blink",
|
||||
"name": "Overwhelming Blink"
|
||||
},
|
||||
{
|
||||
"id": "item_swift_blink",
|
||||
"name": "Swift Blink"
|
||||
},
|
||||
{
|
||||
"id": "item_arcane_blink",
|
||||
"name": "Arcane Blink"
|
||||
},
|
||||
{
|
||||
"id": "item_harpoon",
|
||||
"name": "Harpoon"
|
||||
},
|
||||
{
|
||||
"id": "item_ring_of_tarrasque",
|
||||
"name": "Ring of Tarrasque"
|
||||
},
|
||||
{
|
||||
"id": "item_tiara_of_selemene",
|
||||
"name": "Tiara of Selemene"
|
||||
},
|
||||
{
|
||||
"id": "item_cornucopia",
|
||||
"name": "Cornucopia"
|
||||
},
|
||||
{
|
||||
"id": "item_energy_booster",
|
||||
"name": "Energy Booster"
|
||||
},
|
||||
{
|
||||
"id": "item_vitality_booster",
|
||||
"name": "Vitality Booster"
|
||||
},
|
||||
{
|
||||
"id": "item_point_booster",
|
||||
"name": "Point Booster"
|
||||
},
|
||||
{
|
||||
"id": "item_talisman_of_evasion",
|
||||
"name": "Talisman of Evasion"
|
||||
},
|
||||
{
|
||||
"id": "item_platemail",
|
||||
"name": "Platemail"
|
||||
},
|
||||
{
|
||||
"id": "item_hyperstone",
|
||||
"name": "Hyperstone"
|
||||
},
|
||||
{
|
||||
"id": "item_ultimate_orb",
|
||||
"name": "Ultimate Orb"
|
||||
},
|
||||
{
|
||||
"id": "item_demon_edge",
|
||||
"name": "Demon Edge"
|
||||
},
|
||||
{
|
||||
"id": "item_mystic_staff",
|
||||
"name": "Mystic Staff"
|
||||
},
|
||||
{
|
||||
"id": "item_reaver",
|
||||
"name": "Reaver"
|
||||
},
|
||||
{
|
||||
"id": "item_eaglesong",
|
||||
"name": "Eaglesong"
|
||||
},
|
||||
{
|
||||
"id": "item_sacred_relic",
|
||||
"name": "Sacred Relic"
|
||||
}
|
||||
]
|
||||
|
||||
// export const items: Item[] = [
|
||||
// { id: 'item_magic_wand', name: 'Magic Wand' },
|
||||
// { id: 'item_bracer', name: 'Bracer' },
|
||||
// { id: 'item_wraith_band', name: 'Wraith Band' },
|
||||
// { id: 'item_null_talisman', name: 'Null Talisman' },
|
||||
// { id: 'item_soul_ring', name: 'Soul Ring' },
|
||||
// { id: 'item_falcon_blade', name: 'Falcon Blade' },
|
||||
|
||||
// // Boots
|
||||
// { id: 'item_phase_boots', name: 'Phase Boots' },
|
||||
// { id: 'item_power_treads', name: 'Power Treads' },
|
||||
// { id: 'item_arcane_boots', name: 'Arcane Boots' },
|
||||
// { id: 'item_tranquil_boots', name: 'Tranquil Boots' },
|
||||
// { id: 'item_boots_of_bearing', name: 'Boots of Bearing' },
|
||||
// { id: 'item_guardian_greaves', name: 'Guardian Greaves' },
|
||||
|
||||
// // Mobility
|
||||
// { id: 'item_blink', name: 'Blink Dagger' },
|
||||
// { id: 'item_force_staff', name: 'Force Staff' },
|
||||
// { id: 'item_hurricane_pike', name: 'Hurricane Pike' },
|
||||
// { id: 'item_overwhelming_blink', name: 'Overwhelming Blink' },
|
||||
// { id: 'item_swift_blink', name: 'Swift Blink' },
|
||||
// { id: 'item_arcane_blink', name: 'Arcane Blink' },
|
||||
|
||||
// // Armor / regen aura
|
||||
// { id: 'item_mekansm', name: 'Mekansm' },
|
||||
// { id: 'item_pipe', name: 'Pipe of Insight' },
|
||||
// { id: 'item_vladmir', name: 'Vladmir’s Offering' },
|
||||
// { id: 'item_solar_crest', name: 'Solar Crest' },
|
||||
// { id: 'item_spirit_vessel', name: 'Spirit Vessel' },
|
||||
|
||||
// // Simple DPS items
|
||||
// { id: 'item_mask_of_madness', name: 'Mask of Madness' },
|
||||
// { id: 'item_armlet', name: 'Armlet of Mordiggian' },
|
||||
// { id: 'item_echo_sabre', name: 'Echo Sabre' },
|
||||
// { id: 'item_skull_basher', name: 'Skull Basher' },
|
||||
// { id: 'item_desolator', name: 'Desolator' },
|
||||
// { id: 'item_silver_edge', name: 'Silver Edge' },
|
||||
|
||||
// // Magic items
|
||||
// { id: 'item_veil_of_discord', name: 'Veil of Discord' },
|
||||
// { id: 'item_aether_lens', name: 'Aether Lens' },
|
||||
// { id: 'item_kaya', name: 'Kaya' },
|
||||
// { id: 'item_rod_of_atos', name: 'Rod of Atos' },
|
||||
// { id: 'item_dagon', name: 'Dagon' },
|
||||
|
||||
// // HP items
|
||||
// { id: 'item_hood_of_defiance', name: 'Hood of Defiance' },
|
||||
// { id: 'item_heart', name: 'Heart of Tarrasque' },
|
||||
// // Strong DPS items
|
||||
// { id: 'item_monkey_king_bar', name: 'Monkey King Bar' },
|
||||
// { id: 'item_butterfly', name: 'Butterfly' },
|
||||
// { id: 'item_daedalus', name: 'Daedalus' },
|
||||
// { id: 'item_greater_crit', name: 'Greater Crit' },
|
||||
// { id: 'item_bfury', name: 'Battle Fury' },
|
||||
// { id: 'item_satanic', name: 'Satanic' },
|
||||
// { id: 'item_mjollnir', name: 'Mjollnir' },
|
||||
// { id: 'item_radiance', name: 'Radiance' },
|
||||
// { id: 'item_diffusal_blade', name: 'Diffusal Blade' },
|
||||
// { id: 'item_abissal_blade', name: 'Abyssal Blade' },
|
||||
// { id: 'item_maelstrom', name: 'Maelstrom' },
|
||||
// { id: 'item_mage_slayer', name: 'Mage Slayer' },
|
||||
|
||||
// // Tank / defensive items
|
||||
// { id: 'item_black_king_bar', name: 'Black King Bar' },
|
||||
// { id: 'item_shivas_guard', name: 'Shiva’s Guard' },
|
||||
// { id: 'item_blade_mail', name: 'Blade Mail' },
|
||||
// { id: 'item_lotus_orb', name: 'Lotus Orb' },
|
||||
// { id: 'item_vanguard', name: 'Vanguard' },
|
||||
// { id: 'item_pipe', name: 'Pipe of Insight' },
|
||||
// { id: 'item_eternal_shroud', name: 'Eternal Shroud' },
|
||||
// { id: 'item_crimson_guard', name: 'Crimson Guard' },
|
||||
// { id: 'item_heavens_halberd', name: 'Heaven’s Halberd' },
|
||||
|
||||
// // Mobility / utility
|
||||
// { id: 'item_cyclone', name: 'Eul’s Scepter of Divinity' },
|
||||
// { id: 'item_glimmer_cape', name: 'Glimmer Cape' },
|
||||
// { id: 'item_ghost', name: 'Ghost Scepter' },
|
||||
// { id: 'item_ethereal_blade', name: 'Ethereal Blade' },
|
||||
// { id: 'item_aeon_disk', name: 'Aeon Disk' },
|
||||
// { id: 'item_boots_of_bearing', name: 'Boots of Bearing' },
|
||||
// { id: 'item_rod_of_atos', name: 'Rod of Atos' },
|
||||
// { id: 'item_solar_crest', name: 'Solar Crest' },
|
||||
|
||||
// // Healing / support items
|
||||
// { id: 'item_guardian_greaves', name: 'Guardian Greaves' },
|
||||
// { id: 'item_holy_locket', name: 'Holy Locket' },
|
||||
// { id: 'item_arcane_boots', name: 'Arcane Boots' },
|
||||
// { id: 'item_spirit_vessel', name: 'Spirit Vessel' },
|
||||
// { id: 'item_medallion_of_courage', name: 'Medallion of Courage' },
|
||||
|
||||
// // Magical burst / caster items
|
||||
// { id: 'item_kaya', name: 'Kaya' },
|
||||
// { id: 'item_kaya_and_sange', name: 'Kaya and Sange' },
|
||||
// { id: 'item_yasha_and_kaya', name: 'Yasha and Kaya' },
|
||||
// { id: 'item_sange_and_yasha', name: 'Sange and Yasha' },
|
||||
// { id: 'item_octarine_core', name: 'Octarine Core' },
|
||||
// { id: 'item_scythe_of_vyse', name: 'Scythe of Vyse' },
|
||||
// { id: 'item_dagon', name: 'Dagon' },
|
||||
|
||||
// { id: 'item_rapier', name: 'Divine Rapier' },
|
||||
// { id: 'item_moon_shard', name: 'Moon Shard' },
|
||||
// { id: 'item_silver_edge', name: 'Silver Edge' },
|
||||
// { id: 'item_bloodthorn', name: 'Bloodthorn' },
|
||||
// { id: 'item_nullifier', name: 'Nullifier' },
|
||||
|
||||
// // Intelligence & spellpower
|
||||
// { id: 'item_refresher', name: 'Refresher Orb' },
|
||||
// { id: 'item_aghanims_scepter', name: 'Aghanim’s Scepter' },
|
||||
// { id: 'item_aghanims_shard', name: 'Aghanim’s Shard' },
|
||||
// { id: 'item_witch_blade', name: 'Witch Blade' },
|
||||
// { id: 'item_gungir', name: 'Gleipnir' },
|
||||
|
||||
// // Tank / sustain late game
|
||||
// { id: 'item_heart', name: 'Heart of Tarrasque' },
|
||||
// { id: 'item_assault', name: 'Assault Cuirass' },
|
||||
// { id: 'item_satanic', name: 'Satanic' },
|
||||
// { id: 'item_harpoon', name: 'Harpoon' },
|
||||
|
||||
// // Universal top-tier
|
||||
// { id: 'item_sphere', name: 'Linken’s Sphere' },
|
||||
// { id: 'item_skadi', name: 'Eye of Skadi' },
|
||||
// { id: 'item_manta', name: 'Manta Style' },
|
||||
// { id: 'item_overwhelming_blink', name: 'Overwhelming Blink' },
|
||||
// { id: 'item_swift_blink', name: 'Swift Blink' },
|
||||
|
||||
// // Summon & utility
|
||||
// { id: 'item_necronomicon', name: 'Necronomicon' },
|
||||
// { id: 'item_drum', name: 'Drum of Endurance' },
|
||||
// { id: 'item_helm_of_the_overlord', name: 'Helm of the Overlord' },
|
||||
|
||||
// // Roshan rewards
|
||||
// { id: 'item_aegis', name: 'Aegis of the Immortal' },
|
||||
// { id: 'item_cheese', name: 'Cheese' },
|
||||
// { id: 'item_refresher_shard', name: 'Refresher Shard' },
|
||||
// { id: 'item_aghanims_blessing', name: 'Aghanim’s Blessing' },
|
||||
|
||||
// // Neutral items — Tier 1
|
||||
// { id: 'item_arcane_ring', name: 'Arcane Ring' },
|
||||
// { id: 'item_faded_broach', name: 'Faded Broach' },
|
||||
// { id: 'item_keen_optic', name: 'Keen Optic' },
|
||||
|
||||
// // Tier 2
|
||||
// { id: 'item_grove_bow', name: 'Grove Bow' },
|
||||
// { id: 'item_pupils_gift', name: 'Pupil’s Gift' },
|
||||
// { id: 'item_philosophers_stone', name: 'Philosopher’s Stone' },
|
||||
|
||||
// // Tier 3
|
||||
// { id: 'item_paladin_sword', name: 'Paladin Sword' },
|
||||
// { id: 'item_quickening_charm', name: 'Quickening Charm' },
|
||||
// { id: 'item_spider_legs', name: 'Spider Legs' },
|
||||
|
||||
// // Tier 4
|
||||
// { id: 'item_spell_prism', name: 'Spell Prism' },
|
||||
// { id: 'item_timeless_relic', name: 'Timeless Relic' },
|
||||
// { id: 'item_mind_breaker', name: 'Mind Breaker' },
|
||||
|
||||
// // Tier 5
|
||||
// { id: 'item_apex', name: 'Apex' },
|
||||
// { id: 'item_ex_machina', name: 'Ex Machina' },
|
||||
// { id: 'item_mirror_shield', name: 'Mirror Shield' },
|
||||
// { id: 'item_book_of_shadows', name: 'Book of Shadows' },
|
||||
// { id: 'item_seer_stone', name: 'Seer Stone' }
|
||||
// ];
|
||||
|
||||
|
||||
12
dota-random-builds-front/src/data/skills.ts
Normal file
12
dota-random-builds-front/src/data/skills.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface SkillBuildOption {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export const skillBuilds: SkillBuildOption[] = [
|
||||
{ id: 'q_first', name: 'Max Q first', description: 'Prioritize first ability' },
|
||||
{ id: 'w_first', name: 'Max W first', description: 'Prioritize second ability' },
|
||||
{ id: 'e_first', name: 'Max E first', description: 'Prioritize third ability' },
|
||||
{ id: 'balanced', name: 'Balanced', description: 'Evenly distribute points' }
|
||||
]
|
||||
13
dota-random-builds-front/src/main.ts
Normal file
13
dota-random-builds-front/src/main.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import "@/assets/main.css"
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
306
dota-random-builds-front/src/pages/BuildOfTheDayPage.vue
Normal file
306
dota-random-builds-front/src/pages/BuildOfTheDayPage.vue
Normal file
@@ -0,0 +1,306 @@
|
||||
<template>
|
||||
<div class="build-of-day">
|
||||
<h2>Build of the Day</h2>
|
||||
|
||||
<p v-if="loading" class="loading">Loading...</p>
|
||||
<p v-if="error" class="error">Failed to load: {{ error }}</p>
|
||||
|
||||
<div v-if="build" class="result">
|
||||
<div class="date-badge">{{ formattedDate }}</div>
|
||||
|
||||
<div class="hero-section">
|
||||
<h3>Hero</h3>
|
||||
<div class="hero">{{ build.hero.name }}</div>
|
||||
<div class="hero-badge">{{ build.hero.primary }}</div>
|
||||
</div>
|
||||
|
||||
<div class="build-section">
|
||||
<h3>Items</h3>
|
||||
<ul>
|
||||
<li v-for="it in build.items" :key="it.id">{{ it.name }}</li>
|
||||
</ul>
|
||||
|
||||
<h3>Skill Build</h3>
|
||||
<div class="skill-build">
|
||||
<div v-for="level in skillLevels" :key="level" class="skill-level">
|
||||
<span class="level-num">{{ level }}</span>
|
||||
<span class="skill-key" :class="getSkillClass(build.skillBuild[String(level)])">
|
||||
{{ formatSkill(build.skillBuild[String(level)]) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="build.aspect">
|
||||
<h3>Aspect</h3>
|
||||
<div class="aspect">{{ build.aspect }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRandomDataStore } from '@/stores/randomData'
|
||||
|
||||
const randomDataStore = useRandomDataStore()
|
||||
const { buildOfDay: build, buildOfDayLoading: loading, buildOfDayError: error } = storeToRefs(randomDataStore)
|
||||
|
||||
const skillLevels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 25]
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
if (!build.value?.date) return ''
|
||||
const d = new Date(build.value.date)
|
||||
return d.toLocaleDateString('ru-RU', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
})
|
||||
})
|
||||
|
||||
function formatSkill(skill: string | undefined): string {
|
||||
if (!skill) return '-'
|
||||
const map: Record<string, string> = {
|
||||
q: 'Q',
|
||||
w: 'W',
|
||||
e: 'E',
|
||||
r: 'R',
|
||||
left_talent: 'L',
|
||||
right_talent: 'R'
|
||||
}
|
||||
return map[skill] ?? skill
|
||||
}
|
||||
|
||||
function getSkillClass(skill: string | undefined): string {
|
||||
if (!skill) return ''
|
||||
if (skill === 'r') return 'skill-ult'
|
||||
if (skill === 'left_talent' || skill === 'right_talent') return 'skill-talent'
|
||||
return 'skill-basic'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
randomDataStore.fetchBuildOfDay()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:root {
|
||||
--dota-red: #b63a2b;
|
||||
--dota-red-glow: rgba(182, 58, 43, 0.45);
|
||||
--dota-gold: #c8a86a;
|
||||
--dota-gold-glow: rgba(200, 168, 106, 0.3);
|
||||
--dota-blue: #4fc3f7;
|
||||
--dota-blue-glow: rgba(79, 195, 247, 0.35);
|
||||
--panel: rgba(10, 14, 20, 0.6);
|
||||
}
|
||||
|
||||
/* MAIN BLOCK -------------------------------------------------- */
|
||||
.build-of-day {
|
||||
padding: 26px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(180deg, rgba(20, 25, 40, 0.9), rgba(8, 10, 16, 0.92)),
|
||||
radial-gradient(120% 120% at 0% 0%, rgba(255, 255, 255, 0.05), transparent);
|
||||
color: #e6f0fa;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
box-shadow:
|
||||
0 0 18px rgba(0, 0, 0, 0.6),
|
||||
0 0 32px rgba(0, 0, 0, 0.4),
|
||||
inset 0 0 20px rgba(255, 255, 255, 0.02);
|
||||
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Decorative glow line */
|
||||
.build-of-day::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(circle at top left,
|
||||
rgba(255, 255, 255, 0.10),
|
||||
transparent 50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* TITLE -------------------------------------------------- */
|
||||
.build-of-day > h2 {
|
||||
text-align: center;
|
||||
font-weight: 800;
|
||||
font-size: 1.7rem;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 18px;
|
||||
text-shadow: 0 0 6px var(--dota-red-glow);
|
||||
color: var(--dota-gold);
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: var(--dota-blue);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f87171;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* DATE BADGE -------------------------------------------------- */
|
||||
.date-badge {
|
||||
text-align: center;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: var(--dota-gold);
|
||||
margin-bottom: 18px;
|
||||
padding: 10px 20px;
|
||||
background: rgba(200, 168, 106, 0.1);
|
||||
border: 1px solid rgba(200, 168, 106, 0.3);
|
||||
border-radius: 999px;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* RESULT PANEL -------------------------------------------------- */
|
||||
.result {
|
||||
margin-top: 12px;
|
||||
background: linear-gradient(180deg, rgba(15, 17, 23, 0.9), rgba(6, 8, 13, 0.9));
|
||||
padding: 18px;
|
||||
border-radius: 14px;
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
box-shadow: inset 0 0 18px rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.hero-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* HERO -------------------------------------------------- */
|
||||
.hero {
|
||||
font-weight: 900;
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 8px;
|
||||
color: var(--dota-blue);
|
||||
text-shadow: 0 0 6px var(--dota-blue-glow);
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
display: inline-block;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
|
||||
background: linear-gradient(90deg,
|
||||
rgba(255, 255, 255, 0.07),
|
||||
rgba(255, 255, 255, 0.02));
|
||||
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
color: var(--dota-gold);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ITEMS -------------------------------------------------- */
|
||||
.result ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 10px 0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.result li {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
color: #f2f7ff;
|
||||
|
||||
font-weight: 600;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
|
||||
box-shadow:
|
||||
inset 0 -2px 0 rgba(0, 0, 0, 0.3),
|
||||
0 0 10px rgba(40, 150, 255, 0.15);
|
||||
|
||||
transition: 0.15s ease;
|
||||
}
|
||||
|
||||
.result li:hover {
|
||||
background: rgba(255, 255, 255, 0.09);
|
||||
box-shadow:
|
||||
inset 0 -3px 0 rgba(0, 0, 0, 0.4),
|
||||
0 0 14px rgba(40, 150, 255, 0.22);
|
||||
}
|
||||
|
||||
/* SKILL + ASPECT -------------------------------------------------- */
|
||||
h3 {
|
||||
margin: 8px 0;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 700;
|
||||
color: var(--dota-gold);
|
||||
text-shadow: 0 0 4px var(--dota-gold-glow);
|
||||
}
|
||||
|
||||
.aspect {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
padding: 8px 14px;
|
||||
border-radius: 10px;
|
||||
color: #f2f7ff;
|
||||
font-weight: 600;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* SKILL BUILD -------------------------------------------------- */
|
||||
.skill-build {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.skill-level {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 32px;
|
||||
}
|
||||
|
||||
.level-num {
|
||||
font-size: 0.7rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.skill-key {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.skill-basic {
|
||||
color: #8bc34a;
|
||||
}
|
||||
|
||||
.skill-ult {
|
||||
color: #ffd54f;
|
||||
background: rgba(255, 213, 79, 0.15);
|
||||
border-color: rgba(255, 213, 79, 0.3);
|
||||
}
|
||||
|
||||
.skill-talent {
|
||||
color: #4fc3f7;
|
||||
background: rgba(79, 195, 247, 0.15);
|
||||
border-color: rgba(79, 195, 247, 0.3);
|
||||
}
|
||||
|
||||
/* MOBILE -------------------------------------------------- */
|
||||
@media (max-width: 720px) {
|
||||
.build-of-day {
|
||||
padding: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
24
dota-random-builds-front/src/pages/HomePage.vue
Normal file
24
dota-random-builds-front/src/pages/HomePage.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<h1>Dota Random Builds</h1>
|
||||
<Randomizer />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Randomizer from '@/components/Randomizer.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-page {
|
||||
padding: 20px;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
min-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 12px
|
||||
}
|
||||
</style>
|
||||
21
dota-random-builds-front/src/router/index.ts
Normal file
21
dota-random-builds-front/src/router/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import HomePage from '@/pages/HomePage.vue'
|
||||
import BuildOfTheDayPage from '@/pages/BuildOfTheDayPage.vue'
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'HomePage',
|
||||
component: HomePage
|
||||
},
|
||||
{
|
||||
path: '/build-of-day',
|
||||
name: 'BuildOfTheDayPage',
|
||||
component: BuildOfTheDayPage
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default router
|
||||
12
dota-random-builds-front/src/stores/counter.ts
Normal file
12
dota-random-builds-front/src/stores/counter.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
||||
139
dota-random-builds-front/src/stores/randomData.ts
Normal file
139
dota-random-builds-front/src/stores/randomData.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import axios from 'axios'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export interface Hero {
|
||||
id: number
|
||||
name: string
|
||||
primary: string
|
||||
}
|
||||
|
||||
export interface Item {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type SkillBuild = Record<string, string>
|
||||
|
||||
export interface RandomizeResult {
|
||||
hero: Hero
|
||||
items: Item[]
|
||||
skillBuild?: SkillBuild
|
||||
aspect?: string
|
||||
}
|
||||
|
||||
export interface BuildOfDayResult {
|
||||
date: string
|
||||
hero: Hero
|
||||
items: Item[]
|
||||
skillBuild: SkillBuild
|
||||
aspect?: string
|
||||
}
|
||||
|
||||
type RandomizePayload = {
|
||||
includeSkills: boolean
|
||||
includeAspect: boolean
|
||||
itemsCount: number
|
||||
heroId?: number | null
|
||||
}
|
||||
|
||||
const PREFS_KEY = 'randomizer:prefs'
|
||||
const BASE_URL = import.meta.env.VITE_API_URL || ''
|
||||
|
||||
export const useRandomDataStore = defineStore('randomData', {
|
||||
state: () => ({
|
||||
heroes: [] as Hero[],
|
||||
result: null as RandomizeResult | null,
|
||||
loading: false,
|
||||
error: null as string | null,
|
||||
prefs: {
|
||||
includeSkills: false,
|
||||
includeAspect: false,
|
||||
itemsCount: 6,
|
||||
heroId: null as number | null
|
||||
} as RandomizePayload,
|
||||
buildOfDay: null as BuildOfDayResult | null,
|
||||
buildOfDayLoading: false,
|
||||
buildOfDayError: null as string | null
|
||||
}),
|
||||
|
||||
actions: {
|
||||
loadPrefs() {
|
||||
try {
|
||||
const raw = localStorage.getItem(PREFS_KEY)
|
||||
if (!raw) return
|
||||
const parsed = JSON.parse(raw)
|
||||
this.prefs = {
|
||||
includeSkills: Boolean(parsed.includeSkills),
|
||||
includeAspect: Boolean(parsed.includeAspect),
|
||||
itemsCount: Number(parsed.itemsCount) || 6,
|
||||
heroId: parsed.heroId ?? null
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Failed to load prefs', err)
|
||||
}
|
||||
},
|
||||
|
||||
async loadHeroes() {
|
||||
try {
|
||||
const { data } = await axios.get<Hero[]>(`${BASE_URL}/api/heroes`)
|
||||
this.heroes = data
|
||||
} catch (err) {
|
||||
console.warn('Failed to load heroes', err)
|
||||
}
|
||||
},
|
||||
|
||||
savePrefs() {
|
||||
try {
|
||||
localStorage.setItem(PREFS_KEY, JSON.stringify(this.prefs))
|
||||
} catch (err) {
|
||||
console.warn('Failed to save prefs', err)
|
||||
}
|
||||
},
|
||||
|
||||
setPrefs(patch: Partial<RandomizePayload>) {
|
||||
this.prefs = { ...this.prefs, ...patch }
|
||||
this.savePrefs()
|
||||
},
|
||||
|
||||
async randomize(overrides?: Partial<RandomizePayload>) {
|
||||
if (this.loading) return
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
const payload: RandomizePayload = {
|
||||
...this.prefs,
|
||||
...overrides
|
||||
}
|
||||
|
||||
// sync prefs with overrides so UI reflects latest choice
|
||||
this.prefs = payload
|
||||
this.savePrefs()
|
||||
|
||||
try {
|
||||
const { data } = await axios.post<RandomizeResult>(`${BASE_URL}/api/randomize`, { ...payload })
|
||||
this.result = data
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to load random build'
|
||||
this.error = message
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
async fetchBuildOfDay() {
|
||||
if (this.buildOfDayLoading) return
|
||||
this.buildOfDayLoading = true
|
||||
this.buildOfDayError = null
|
||||
|
||||
try {
|
||||
const { data } = await axios.get<BuildOfDayResult>(`${BASE_URL}/api/build-of-day`)
|
||||
this.buildOfDay = data
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : 'Failed to load build of the day'
|
||||
this.buildOfDayError = message
|
||||
} finally {
|
||||
this.buildOfDayLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
12
dota-random-builds-front/tsconfig.app.json
Normal file
12
dota-random-builds-front/tsconfig.app.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
11
dota-random-builds-front/tsconfig.json
Normal file
11
dota-random-builds-front/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
dota-random-builds-front/tsconfig.node.json
Normal file
19
dota-random-builds-front/tsconfig.node.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node24/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*",
|
||||
"eslint.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
18
dota-random-builds-front/vite.config.ts
Normal file
18
dota-random-builds-front/vite.config.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user