Initial release of scripts and knowledge base
This commit is contained in:
parent
3c5b7c63ab
commit
027de4192c
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,7 @@
|
|||||||
|
# Custom ignores:
|
||||||
|
fleetyard_login.ini
|
||||||
|
uex_api_key
|
||||||
|
|
||||||
# ---> Python
|
# ---> Python
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
70
README.md
70
README.md
@ -1,3 +1,71 @@
|
|||||||
# SC-Discord-Bot
|
# SC-Discord-Bot
|
||||||
|
|
||||||
Using open-webui features and ollama's inference engine to host a LLM Discord bot related to Star Citizen
|
This project provides a sophisticated Discord bot focused on the game *Star Citizen*. It leverages a Large Language Model (LLM) hosted via an [Open-WebUI](https://github.com/open-webui/open-webui) backend, enhanced with Retrieval-Augmented Generation (RAG) and custom tools to provide accurate, up-to-date information.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Discord Integration**: A simple and effective Discord bot that responds to user queries in whitelisted channels.
|
||||||
|
- **LLM-Powered**: Connects to any OpenAI-compatible API, allowing you to use a variety of powerful language models.
|
||||||
|
- **Retrieval-Augmented Generation (RAG)**: The bot's knowledge is supplemented by a collection of JSON files in [`llm_rag_knowledge/`](llm_rag_knowledge/) containing detailed information about in-game events, crafting, and vehicle data.
|
||||||
|
- **Custom Tools**: The LLM can invoke a suite of Python tools located in [`llm_tools/`](llm_tools/) to fetch dynamic data:
|
||||||
|
- **Ship Information**: Get detailed ship specifications and compare vessels using data from `starcitizen.tools`.
|
||||||
|
- **Price Lookups**: Query real-time prices for commodities and items from a local database synced with `uexcorp.space`.
|
||||||
|
- **Fleet Ownership**: Check who in your organization owns which ship using data from `fleetyards.net`.
|
||||||
|
- **Data Persistence**: Utilizes SQLite databases in [`databases/`](databases/) to store and quickly access commodity, item, and fleet information.
|
||||||
|
- **Containerized**: Comes with a [`Dockerfile`](discord_connector/Dockerfile) and [`docker-compose.yaml`](discord_connector/docker-compose.yaml) for easy and consistent deployment.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The system is composed of several key parts:
|
||||||
|
|
||||||
|
1. **Discord Connector**: The [`open-webui_to_discord.py`](discord_connector/open-webui_to_discord.py) script is the frontend, listening for user messages on Discord.
|
||||||
|
2. **Open-WebUI & LLM**: The bot forwards queries to an Open-WebUI instance, which in turn uses an LLM (e.g., Llama3, Mixtral) for inference.
|
||||||
|
3. **Knowledge Base & Tools**: The LLM's responses are enriched by:
|
||||||
|
- The static JSON files in [`llm_rag_knowledge/`](llm_rag_knowledge/).
|
||||||
|
- The dynamic Python scripts in [`llm_tools/`](llm_tools/).
|
||||||
|
4. **Data Sync Scripts**: The scripts [`get_commodities.py`](llm_tools/get_commodities.py), [`get_items.py`](llm_tools/get_items.py), and [`fleetyard.py`](llm_tools/fleetyard.py) run independently to keep the local SQLite databases current.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Docker and Docker Compose
|
||||||
|
- A running Open-WebUI instance with a loaded model.
|
||||||
|
- A Discord Bot Token.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. **Clone the repository:**
|
||||||
|
```sh
|
||||||
|
git clone https://gitea.zephyre.one/Pakobbix/SC-Discord-Bot.git
|
||||||
|
cd SC-Discord-Bot
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Configure the bot:**
|
||||||
|
- Navigate to the `discord_connector` directory.
|
||||||
|
- Copy [`example.config.yml`](discord_connector/example.config.yml) to `config.yml`.
|
||||||
|
- Edit `config.yml` with your details:
|
||||||
|
- `discord_token`
|
||||||
|
- `whitelist_channels`
|
||||||
|
- `open_webui_url` and `open-webui_api_key`
|
||||||
|
- `model_name` and any `tools` you have configured in Open-WebUI.
|
||||||
|
|
||||||
|
3. **Populate Databases:**
|
||||||
|
Before running the bot, you may need to run the data sync scripts to populate the databases. These scripts are designed to be run within the bot's environment or a similar one with the required dependencies.
|
||||||
|
```sh
|
||||||
|
# Example for one script
|
||||||
|
python llm_tools/get_commodities.py
|
||||||
|
python llm_tools/get_items.py
|
||||||
|
python llm_tools/fleetyard.py
|
||||||
|
```
|
||||||
|
*Note: You may need to adjust paths or run these within the Docker container for them to access the correct database location.*
|
||||||
|
|
||||||
|
4. **Run with Docker Compose:**
|
||||||
|
From the `discord_connector` directory, run:
|
||||||
|
```sh
|
||||||
|
docker-compose up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for
|
||||||
|
|||||||
BIN
databases/commodities.db
Normal file
BIN
databases/commodities.db
Normal file
Binary file not shown.
BIN
databases/fleet.db
Normal file
BIN
databases/fleet.db
Normal file
Binary file not shown.
BIN
databases/items.db
Normal file
BIN
databases/items.db
Normal file
Binary file not shown.
17
discord_connector/Dockerfile
Normal file
17
discord_connector/Dockerfile
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# We use an official Python runtime as a parent image
|
||||||
|
FROM python:3.12-alpine
|
||||||
|
|
||||||
|
# Set the working directory in the container
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the dependencies file to the working directory
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
# Install any needed packages specified in requirements.txt
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy the rest of the application's code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Run the bot when the container launches
|
||||||
|
CMD ["python", "open-webui_to_discord.py"]
|
||||||
7
discord_connector/docker-compose.yaml
Normal file
7
discord_connector/docker-compose.yaml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
services:
|
||||||
|
discord-bot:
|
||||||
|
build: .
|
||||||
|
container_name: discord-sc-answer-bot
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- ./config.yml:/app/config.yml
|
||||||
22
discord_connector/example.config.yml
Normal file
22
discord_connector/example.config.yml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# ─────────── Discord Settings ───────────
|
||||||
|
discord_token: "YOUR_DISCORD_BOT_TOKEN_HERE"
|
||||||
|
|
||||||
|
whitelist_channels:
|
||||||
|
- 123456789123456789 # <-- channel id
|
||||||
|
- 987654321987654321 # <-- another channel id
|
||||||
|
|
||||||
|
allow_dms: false # set to true if you want the bot to answer private DMs (Currently not working)
|
||||||
|
|
||||||
|
# ─────────── Open‑WebUI Settings ───────────
|
||||||
|
open_webui_url: "http://your_open-webui_ip_or_domain:port"
|
||||||
|
open-webui_api_key: "user_api_key_from_open_webui"
|
||||||
|
model_name: "model_id_from_open-webui"
|
||||||
|
knowledge_base: "knowledge_base_id_from_open-webui"
|
||||||
|
|
||||||
|
# tools map – label → tool‑id that your Open‑WebUI instance recognises
|
||||||
|
tools:
|
||||||
|
- Tool_ID_1
|
||||||
|
- Tool_ID_2
|
||||||
|
|
||||||
|
# optional system prompt (you can leave it empty to use the default one or the systemprompt given in open-webui for the specific model)
|
||||||
|
system_prompt: ""
|
||||||
144
discord_connector/open-webui_to_discord.py
Normal file
144
discord_connector/open-webui_to_discord.py
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import yaml
|
||||||
|
import asyncio
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────
|
||||||
|
def load_config(path: str):
|
||||||
|
"""Return a dict loaded from a YAML file."""
|
||||||
|
with open(path, "r", encoding="utf‑8") as fh:
|
||||||
|
return yaml.safe_load(fh)
|
||||||
|
|
||||||
|
config = load_config("config.yml") # <- loads the config file
|
||||||
|
|
||||||
|
DISCORD_TOKEN = config["discord_token"] # Discord bot token
|
||||||
|
WHITELIST_CHANNELS = set(map(int, config.get("whitelist_channels", []))) # Set of whitelisted channel IDs (int)
|
||||||
|
|
||||||
|
OPENWEBUI_URL = config["open_webui_url"].rstrip('/') # Ensure no trailing slash
|
||||||
|
OPENWEBUI_API_KEY = config["open-webui_api_key"] # API key for Open-WebUI
|
||||||
|
MODEL_NAME = config["model_name"] # Model name to use, e.g., "gpt-3.5-turbo"
|
||||||
|
KNOW_BASE = config["knowledge_base"] # Knowledge base to use, e.g., "knowledge_base_v1"
|
||||||
|
|
||||||
|
TOOLS = config.get("tools", []) # list of tool-ids
|
||||||
|
|
||||||
|
SYSTEM_PROMPT = config.get("system_prompt", None) # Optional system prompt to prepend to user messages
|
||||||
|
ALLOW_DMS = config.get("allow_dms", False) # Allow DMs to the bot (default: False)
|
||||||
|
|
||||||
|
async def _query_openwebui(user_text: str, channel_id: int, tools_list: list):
|
||||||
|
"""
|
||||||
|
Payload structure for the OpenAI-compatible endpoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_text (str): The user's message to send to the Open-WebUI.
|
||||||
|
channel_id (int): The Discord channel ID where the message was sent.
|
||||||
|
tools_list (list): List of tool IDs to use, if any.
|
||||||
|
"""
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
# This payload structure is for the OpenAI-compatible endpoint from open-webui
|
||||||
|
payload = {
|
||||||
|
"model": MODEL_NAME,
|
||||||
|
"stream": False,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": user_text
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
# Attach tools if provided in the config file
|
||||||
|
if tools_list:
|
||||||
|
payload["tool_ids"] = tools_list
|
||||||
|
print(f"🔧 Using tools: {payload['tool_ids']}")
|
||||||
|
|
||||||
|
# The endpoint path for your instance appears to be /api/chat/completions
|
||||||
|
async with session.post(f"{OPENWEBUI_URL}/api/chat/completions",
|
||||||
|
json=payload,
|
||||||
|
headers={"Authorization": f"Bearer {OPENWEBUI_API_KEY}"}) as resp:
|
||||||
|
|
||||||
|
if resp.status != 200:
|
||||||
|
# If the response is not 200, raise an error with the response text
|
||||||
|
data = await resp.text()
|
||||||
|
raise RuntimeError(f"Open‑WebUI responded {resp.status}: {data}")
|
||||||
|
|
||||||
|
# If the response is OK, parse the JSON and return the content
|
||||||
|
response_data = await resp.json()
|
||||||
|
return response_data['choices'][0]['message']['content']
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Discord bot logic – discord.py
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
intents = discord.Intents.default() # Default intents
|
||||||
|
intents.message_content = True # Required to read message content
|
||||||
|
intents.members = True # Required to read member info (if needed)
|
||||||
|
|
||||||
|
bot = commands.Bot(command_prefix='!', intents=intents) # Command prefix is '!' by default to allow commands like !ping
|
||||||
|
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_ready():
|
||||||
|
print(f"✅ Logged in as {bot.user} (id={bot.user.id})")
|
||||||
|
|
||||||
|
# Only a test for commands, I add later
|
||||||
|
@bot.command(name="ping")
|
||||||
|
async def ping(ctx):
|
||||||
|
await ctx.send("🏓 Pong!")
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Main logic – only respond to allowed channels / DM flag
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_message(message):
|
||||||
|
# Ignore messages from bots (incl. the bot itself)
|
||||||
|
if message.author.bot: return
|
||||||
|
|
||||||
|
# Allow commands to be processed
|
||||||
|
await bot.process_commands(message)
|
||||||
|
|
||||||
|
# Skip if we are in a DM and that is disabled
|
||||||
|
if not ALLOW_DMS and isinstance(message.channel, discord.DMChannel): return
|
||||||
|
|
||||||
|
# --- debugging ---
|
||||||
|
print(f"ℹ️ Message received in channel: {message.channel.id}")
|
||||||
|
print(f"📢 Whitelisted channels are: {WHITELIST_CHANNELS}")
|
||||||
|
# -----------------------------
|
||||||
|
|
||||||
|
# Allow only the whitelist channels – empty list means “all channels”
|
||||||
|
if WHITELIST_CHANNELS and message.channel.id not in WHITELIST_CHANNELS:
|
||||||
|
return
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------- #
|
||||||
|
# A. Prepare payload
|
||||||
|
# ----------------------------------------------------------------------- #
|
||||||
|
# The OpenAI endpoint works better without the extra context in the prompt
|
||||||
|
prompt = message.content
|
||||||
|
if SYSTEM_PROMPT:
|
||||||
|
# The system prompt is handled differently in the OpenAI-compatible API
|
||||||
|
# For simplicity, we'll prepend it here. A more robust solution
|
||||||
|
# would add it as a separate message with the 'system' role.
|
||||||
|
prompt = f"{SYSTEM_PROMPT}\n\nUser Question: {message.content}"
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------- #
|
||||||
|
# B. Query Open‑WebUI and show typing indicator
|
||||||
|
# ----------------------------------------------------------------------- #
|
||||||
|
try:
|
||||||
|
async with message.channel.typing():
|
||||||
|
# Query the Open-WebUI API while showing "Bot is typing..."
|
||||||
|
reply = await _query_openwebui(prompt, message.channel.id, TOOLS)
|
||||||
|
# Send the reply
|
||||||
|
await message.reply(reply)
|
||||||
|
except Exception as e:
|
||||||
|
await message.reply(f"⚠ Error contacting the Open‑WebUI API: {e}")
|
||||||
|
# No need to return here as the function ends after this block.
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Start bot
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
asyncio.run(bot.run(DISCORD_TOKEN))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("🤖 Shutting down…")
|
||||||
3
discord_connector/requirements.txt
Normal file
3
discord_connector/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
PyYAML
|
||||||
|
discord.py
|
||||||
|
aiohttp
|
||||||
267
llm_rag_knowledge/align_and_mine_event.json
Normal file
267
llm_rag_knowledge/align_and_mine_event.json
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
{
|
||||||
|
"event": "Align & Mine",
|
||||||
|
"game": "Star Citizen",
|
||||||
|
"introduction": {
|
||||||
|
"description": "Das Align & Mine Event ist ein hochriskantes Bergbau-Event, bei dem Spieler zusammenarbeiten müssen, um einen Orbital-Mining-Laser zu aktivieren, der Zugang zu wertvollen unterirdischen Ressourcen freischaltet.",
|
||||||
|
"key_requirements": [
|
||||||
|
"Drei Satellitenschüsseln ausrichten",
|
||||||
|
"Verschiedene Keycards sammeln",
|
||||||
|
"Batteriepacks sichern",
|
||||||
|
"Gegner abwehren (NPCs und andere Spieler)"
|
||||||
|
],
|
||||||
|
"objective": "Aktiviere den Orbital-Mining-Laser und sichere wertvolle Ressourcen."
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"name": "Daymar",
|
||||||
|
"description": "Ein felsiger, wüstenähnlicher Mond mit Stickstoff-Methan-Atmosphäre. Temperaturen zwischen -40°C und 78°C.",
|
||||||
|
"required_equipment": {
|
||||||
|
"suit": "Standard-Raumanzug",
|
||||||
|
"additional": "Sauerstoffreserven"
|
||||||
|
},
|
||||||
|
"points_of_interest": [
|
||||||
|
{
|
||||||
|
"name": "Lamina",
|
||||||
|
"type": "POI",
|
||||||
|
"description": "Ein POI auf Daymar mit drei PAF-Sites und einem OLP.",
|
||||||
|
"sublocations": [
|
||||||
|
{
|
||||||
|
"name": "Lamina-PAF I",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Lamina."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lamina-PAF II",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Lamina."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lamina-PAF III",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Lamina."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lamina-OLP",
|
||||||
|
"type": "Orbital Laser Platform",
|
||||||
|
"description": "Der Orbitallaser befindet sich hier."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attritus",
|
||||||
|
"type": "POI",
|
||||||
|
"description": "Ein POI auf Daymar mit drei PAF-Sites und einem OLP.",
|
||||||
|
"sublocations": [
|
||||||
|
{
|
||||||
|
"name": "Attritus-PAF I",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Attritus."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attritus-PAF II",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Attritus."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attritus-PAF III",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Attritus."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attritus-OLP",
|
||||||
|
"type": "Orbital Laser Platform",
|
||||||
|
"description": "Der Orbitallaser befindet sich hier."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Aberdeen",
|
||||||
|
"description": "Ein tödlich heißer Mond mit Schwefelatmosphäre. Temperaturen zwischen 170°C und 237°C.",
|
||||||
|
"required_equipment": {
|
||||||
|
"suit": "Pembroke Exploration Suit",
|
||||||
|
"additional": "Hitzebeständige Ausrüstung"
|
||||||
|
},
|
||||||
|
"points_of_interest": [
|
||||||
|
{
|
||||||
|
"name": "Ruptura",
|
||||||
|
"type": "POI",
|
||||||
|
"description": "Ein POI auf Aberdeen mit drei PAF-Sites und einem OLP.",
|
||||||
|
"sublocations": [
|
||||||
|
{
|
||||||
|
"name": "Ruptura-PAF I",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Ruptura."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ruptura-PAF II",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Ruptura."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ruptura-PAF III",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Ruptura."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ruptura-OLP",
|
||||||
|
"type": "Orbital Laser Platform",
|
||||||
|
"description": "Der Orbitallaser befindet sich hier."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vivere",
|
||||||
|
"type": "POI",
|
||||||
|
"description": "Ein POI auf Aberdeen mit drei PAF-Sites und einem OLP.",
|
||||||
|
"sublocations": [
|
||||||
|
{
|
||||||
|
"name": "Vivere-PAF I",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Vivere."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vivere-PAF II",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Vivere."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vivere-PAF III",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Vivere."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vivere-OLP",
|
||||||
|
"type": "Orbital Laser Platform",
|
||||||
|
"description": "Der Orbitallaser befindet sich hier."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"description": "Nach dem Bergbau können Ressourcen bei Wikelo's gegen spezielle Gegenstände wie Rüstungen und eine Polaris eingetauscht werden.",
|
||||||
|
"example_items": [
|
||||||
|
"Polaris",
|
||||||
|
"Spezielle Rüstungen",
|
||||||
|
"Seltenes Bergbau-Material"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"preparation": {
|
||||||
|
"equipment": {
|
||||||
|
"racing_suits": {
|
||||||
|
"Daymar": "Standard-Raumanzug mit Sauerstoffreserven",
|
||||||
|
"Aberdeen": "Pembroke Exploration Suit"
|
||||||
|
},
|
||||||
|
"weapons": {
|
||||||
|
"primary": "Beispiel: FS-9 LMG für Sperrfeuer",
|
||||||
|
"secondary": "Beispiel: Arclight Pistole für Nahkämpfe",
|
||||||
|
"ammunition": "Mindestens ein Dutzend Magazine"
|
||||||
|
},
|
||||||
|
"tools": [
|
||||||
|
"MaxLift Tractor Beam",
|
||||||
|
"MedGun mit Nachfüllpatronen",
|
||||||
|
"MedPens"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ships": {
|
||||||
|
"solo": "Avenger Titan",
|
||||||
|
"small_team": "Cutlass Black",
|
||||||
|
"multi_crew": [
|
||||||
|
"Valkyrie",
|
||||||
|
"Carrack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"title": "Aktivierung der Satellitenschüsseln",
|
||||||
|
"description": "Finde und aktiviere drei Satellitenschüsseln, die um den Orbital-Mining-Laser positioniert sind.",
|
||||||
|
"substeps": [
|
||||||
|
{
|
||||||
|
"title": "Lokalisierung der Schüsseln",
|
||||||
|
"description": "Suche nach drei Schüsseln, wobei einige bereits aktiviert sein können."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Keycards finden",
|
||||||
|
"description": "Finde zwei Keycards: PAF Maintenance Keycard und PAF Security Keycard.",
|
||||||
|
"spawn_locations": [
|
||||||
|
"Tote NPCs/Wachen",
|
||||||
|
"Schließfächer",
|
||||||
|
"Kontrollräume"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Terminal-Aktivierung",
|
||||||
|
"description": "Gehe in den Kontrollraum, füge Alignment Blades in den Terminal ein, warte auf Aktivierung.",
|
||||||
|
"warnings": [
|
||||||
|
"Valakkar-Angriffe werden ausgelöst!",
|
||||||
|
"Deckung suchen, Spotter einsetzen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Batteriepacks sichern",
|
||||||
|
"description": "Nach Aktivierung öffnet sich eine Garagentür und gibt ein Batteriepack frei. Sammle drei Batterien.",
|
||||||
|
"tools": [
|
||||||
|
"MaxLift Tractor Beam",
|
||||||
|
"Sichere Lagerung"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"title": "Stromversorgung des Orbital-Mining-Lasers",
|
||||||
|
"description": "Bringe die drei Batteriepacks zum Mining-Laser und verbinde sie mit den Power Banks.",
|
||||||
|
"substeps": [
|
||||||
|
{
|
||||||
|
"title": "Sicherung des Gebiets",
|
||||||
|
"description": "Bereite dich auf NPC- und Spielergegner vor. Nutze Teamkoordination (Verteidiger, Operateure)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Batterieeinsetzen",
|
||||||
|
"description": "Setze die Batterien in die drei Power Banks ein. Der Laser wird hochgefahren."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Keycard holen",
|
||||||
|
"description": "Entnehme die Laser Activation Keycard vom Terminal.",
|
||||||
|
"tips": [
|
||||||
|
"Wenn Power LEDs nicht grün leuchten, Batterie erneut einsetzen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Optionale Loots",
|
||||||
|
"description": "Finde OLP Storage Card und Supervisor Access Card für zusätzliche Beute.",
|
||||||
|
"supervisor_room": {
|
||||||
|
"access": "Mit Supervisor Access Card",
|
||||||
|
"contents": "Hochwertige Gegenstände"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"title": "Abfeuern des Orbital-Lasers",
|
||||||
|
"description": "Nutze die Laser Activation Keycard, um den Laser vom Bunker aus abzufeuern.",
|
||||||
|
"action": "Drücke den großen roten Knopf, um den Höhleneingang zu öffnen."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"title": "Betreten der Mine und Sammeln von Ressourcen",
|
||||||
|
"description": "Gehe in die neu geöffnete Höhle und sammle wertvolle Ressourcen.",
|
||||||
|
"warnings": [
|
||||||
|
"Diese Phase ist oft umkämpft",
|
||||||
|
"Bleibe wachsam, bewege dich schnell"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tips": [
|
||||||
|
"Nutze einen Spotter für Valakkar-Bedrohungen",
|
||||||
|
"Plane Rollenverteilung für Teamkoordination",
|
||||||
|
"Sichere Batterien und Keycards vor Einbrüchen",
|
||||||
|
"Suche nach mehreren Supervisor Access Cards für schnelleren Zugriff"
|
||||||
|
]
|
||||||
|
}
|
||||||
193
llm_rag_knowledge/map.json
Normal file
193
llm_rag_knowledge/map.json
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
{
|
||||||
|
"systems": [
|
||||||
|
{
|
||||||
|
"name": "Stanton",
|
||||||
|
"type": "System",
|
||||||
|
"description": "Besteht aus mehreren Planeten und Monden",
|
||||||
|
"Planeten:": 4,
|
||||||
|
"Zugehörigkeit:": "UEE",
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"name": "ArcCorp",
|
||||||
|
"type": "Planet",
|
||||||
|
"description": "Planet: Stanton III - ArcCorp",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"landingZone": "Area18 (Start Location, Ignis Borealis Heimatplanet)",
|
||||||
|
"lagrangeStation": "Baijini Point",
|
||||||
|
"moons": ["Wala", "Lyria"],
|
||||||
|
"lagrangianPoints": [
|
||||||
|
"ARC-L1 Wide Forest Station",
|
||||||
|
"ARC-L2 Lively Pathway Station",
|
||||||
|
"ARC-L3 Modern Express Station",
|
||||||
|
"ARC-L4 Faint Glen Station",
|
||||||
|
"ARC-L5 Yellow Core Station"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MicroTech",
|
||||||
|
"type": "Planet",
|
||||||
|
"description": "Planet: Stanton IV - MicroTech",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"landingZone": "New Babbage (Start Location)",
|
||||||
|
"lagrangeStation": "Port Tressler",
|
||||||
|
"moons": ["Calliope", "Clio", "Euterpe"],
|
||||||
|
"lagrangianPoints": [
|
||||||
|
"MIC-L1 Shallow Frontier Station",
|
||||||
|
"MIC-L2 Long Forest Station",
|
||||||
|
"MIC-L3 Endless Odyssey Station",
|
||||||
|
"MIC-L4 Red Crossroads Station",
|
||||||
|
"MIC-L5 Modern Icarus Station"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Crusader",
|
||||||
|
"type": "Planet",
|
||||||
|
"description": "Planet: Stanton II - Crusader",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"landingZone": "Orison (Start Location)",
|
||||||
|
"lagrangeStation": "Seraphim Station",
|
||||||
|
"moons": ["Cellin", "Daymar", "Yela"],
|
||||||
|
"lagrangianPoints": [
|
||||||
|
"CRU-L1 Ambitious Dream Station",
|
||||||
|
"CRU-L4 Shallow Fields Station",
|
||||||
|
"CRU-L5 Beautiful Glen Station"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hurston",
|
||||||
|
"type": "Planet",
|
||||||
|
"description": "Planet: Stanton I - Hurston",
|
||||||
|
"environment": "Sicher (Bisher)",
|
||||||
|
"landingZone": "Lorville (Start Location)",
|
||||||
|
"lagrangeStation": "Everus Harbor",
|
||||||
|
"moons": ["Arial", "Aberdeen", "Magda", "Ita"],
|
||||||
|
"lagrangianPoints": [
|
||||||
|
"HUR-L1 Green Glade Station",
|
||||||
|
"HUR-L2 Faithful Dream Station",
|
||||||
|
"HUR-L3 Thundering Express Station",
|
||||||
|
"HUR-L4 Melodic Fields Station",
|
||||||
|
"HUR-L5 High Course Station"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Security Post Kareah",
|
||||||
|
"type": "Station",
|
||||||
|
"description": "Typ: Station",
|
||||||
|
"environment": "Sicher, hostile NPC's",
|
||||||
|
"location": "Umrundet Cellin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Grim Hex",
|
||||||
|
"type": "Station",
|
||||||
|
"description": "Typ: Station",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"location": "In der Nähe von Yela im Asteroiden Gürtel",
|
||||||
|
"faction": "Unabhängig",
|
||||||
|
"relations": [
|
||||||
|
{
|
||||||
|
"to": "Stanton",
|
||||||
|
"relationType": "is located in"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"to": "Yela",
|
||||||
|
"relationType": "is near"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pyro Gateway",
|
||||||
|
"type": "Station",
|
||||||
|
"description": "Typ: Station",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"purpose": "Gateway um nach Pyro zu gelangen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Magnus Gateway",
|
||||||
|
"type": "Station",
|
||||||
|
"description": "Typ: Station",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"purpose": "Gateway um nach Magnus zu gelangen (Noch nicht im Spiel)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Terra Gateway",
|
||||||
|
"type": "Station",
|
||||||
|
"description": "Typ: Station",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"purpose": "Gateway um nach Terra zu gelangen (Noch nicht im Spiel)"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pyro",
|
||||||
|
"type": "System",
|
||||||
|
"description": "Besteht aus mehreren Planeten und Monden",
|
||||||
|
"Planeten": 6,
|
||||||
|
"Zugehörigkeit": "Keine",
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"name": "Pyro I",
|
||||||
|
"type": "Planet",
|
||||||
|
"description": "Planet: Pyro I",
|
||||||
|
"environment": "Tödlich",
|
||||||
|
"moons": "Keine",
|
||||||
|
"lagrangianPoints": [
|
||||||
|
"PYAM-FARSTAT-1-2",
|
||||||
|
"PYAM-FARSTAT-1-3",
|
||||||
|
"PYAM-FARSTAT-1-5"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pyro II - Monox",
|
||||||
|
"type": "Planet",
|
||||||
|
"description": "Planet: Pyro II - Monox",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"moons": "Keine",
|
||||||
|
"lagrangianPoints": [
|
||||||
|
"Checkmate Station (Start Location)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pyro III - Bloom",
|
||||||
|
"type": "Planet",
|
||||||
|
"description": "Planet: Pyro III - Bloom",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"moons": "Keine",
|
||||||
|
"lagrangianPoints": [
|
||||||
|
"Starlight Service Station",
|
||||||
|
"Patch City",
|
||||||
|
"PYAM-FARSTAT-3-5",
|
||||||
|
"Orbituary (Starting Location)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pyro V",
|
||||||
|
"type": "Planet",
|
||||||
|
"description": "Planet: Pyro V",
|
||||||
|
"environment": "Tödlich",
|
||||||
|
"moons": ["Ignis", "Vatra", "Adir", "Fairo", "Fuego", "Vuur", "Pyro IV"],
|
||||||
|
"lagrangianPoints": [
|
||||||
|
"PYAM-FARSTAT-5-1",
|
||||||
|
"Gaslight",
|
||||||
|
"PYAM-FARSTAT-5-3",
|
||||||
|
"Rod's Fuel 'N Supplies",
|
||||||
|
"Rat's Nest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Pyro VI - Terminus",
|
||||||
|
"type": "Planet",
|
||||||
|
"description": "Planet: Pyro VI - Terminus",
|
||||||
|
"environment": "Sicher",
|
||||||
|
"moons": "Keine",
|
||||||
|
"lagrangianPoints": [
|
||||||
|
"PYAM-FARSTAT-5-2",
|
||||||
|
"Endgame",
|
||||||
|
"Dudley & Daughters",
|
||||||
|
"Megumi Refueling",
|
||||||
|
"Ruin Station (Starting Location)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
267
llm_rag_knowledge/resource_drive_second_life_event.json
Normal file
267
llm_rag_knowledge/resource_drive_second_life_event.json
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
{
|
||||||
|
"event": "Align & Mine",
|
||||||
|
"game": "Star Citizen",
|
||||||
|
"introduction": {
|
||||||
|
"description": "Das Align & Mine Event ist ein hochriskantes Bergbau-Event, bei dem Spieler zusammenarbeiten müssen, um einen Orbital-Mining-Laser zu aktivieren, der Zugang zu wertvollen unterirdischen Ressourcen freischaltet.",
|
||||||
|
"key_requirements": [
|
||||||
|
"Drei Satellitenschüsseln ausrichten",
|
||||||
|
"Verschiedene Keycards sammeln",
|
||||||
|
"Batteriepacks sichern",
|
||||||
|
"Gegner abwehren (NPCs und andere Spieler)"
|
||||||
|
],
|
||||||
|
"objective": "Aktiviere den Orbital-Mining-Laser und sichere wertvolle Ressourcen."
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"name": "Daymar",
|
||||||
|
"description": "Ein felsiger, wüstenähnlicher Mond mit Stickstoff-Methan-Atmosphäre. Temperaturen zwischen -40°C und 78°C.",
|
||||||
|
"required_equipment": {
|
||||||
|
"suit": "Standard-Raumanzug",
|
||||||
|
"additional": "Sauerstoffreserven"
|
||||||
|
},
|
||||||
|
"points_of_interest": [
|
||||||
|
{
|
||||||
|
"name": "Lamina",
|
||||||
|
"type": "POI",
|
||||||
|
"description": "Ein POI auf Daymar mit drei PAF-Sites und einem OLP.",
|
||||||
|
"sublocations": [
|
||||||
|
{
|
||||||
|
"name": "Lamina-PAF I",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Lamina."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lamina-PAF II",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Lamina."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lamina-PAF III",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Lamina."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lamina-OLP",
|
||||||
|
"type": "Orbital Laser Platform",
|
||||||
|
"description": "Der Orbitallaser befindet sich hier."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attritus",
|
||||||
|
"type": "POI",
|
||||||
|
"description": "Ein POI auf Daymar mit drei PAF-Sites und einem OLP.",
|
||||||
|
"sublocations": [
|
||||||
|
{
|
||||||
|
"name": "Attritus-PAF I",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Attritus."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attritus-PAF II",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Attritus."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attritus-PAF III",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Attritus."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attritus-OLP",
|
||||||
|
"type": "Orbital Laser Platform",
|
||||||
|
"description": "Der Orbitallaser befindet sich hier."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Aberdeen",
|
||||||
|
"description": "Ein tödlich heißer Mond mit Schwefelatmosphäre. Temperaturen zwischen 170°C und 237°C.",
|
||||||
|
"required_equipment": {
|
||||||
|
"suit": "Pembroke Exploration Suit",
|
||||||
|
"additional": "Hitzebeständige Ausrüstung"
|
||||||
|
},
|
||||||
|
"points_of_interest": [
|
||||||
|
{
|
||||||
|
"name": "Ruptura",
|
||||||
|
"type": "POI",
|
||||||
|
"description": "Ein POI auf Aberdeen mit drei PAF-Sites und einem OLP.",
|
||||||
|
"sublocations": [
|
||||||
|
{
|
||||||
|
"name": "Ruptura-PAF I",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Ruptura."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ruptura-PAF II",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Ruptura."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ruptura-PAF III",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Ruptura."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ruptura-OLP",
|
||||||
|
"type": "Orbital Laser Platform",
|
||||||
|
"description": "Der Orbitallaser befindet sich hier."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vivere",
|
||||||
|
"type": "POI",
|
||||||
|
"description": "Ein POI auf Aberdeen mit drei PAF-Sites und einem OLP.",
|
||||||
|
"sublocations": [
|
||||||
|
{
|
||||||
|
"name": "Vivere-PAF I",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Vivere."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vivere-PAF II",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Vivere."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vivere-PAF III",
|
||||||
|
"type": "Satellite Dish",
|
||||||
|
"description": "Eine der drei Satellitenschüsseln auf Vivere."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vivere-OLP",
|
||||||
|
"type": "Orbital Laser Platform",
|
||||||
|
"description": "Der Orbitallaser befindet sich hier."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rewards": {
|
||||||
|
"description": "Nach dem Bergbau können Ressourcen bei Wikelo's gegen spezielle Gegenstände wie Rüstungen und eine Polaris eingetauscht werden.",
|
||||||
|
"example_items": [
|
||||||
|
"Polaris",
|
||||||
|
"Spezielle Rüstungen",
|
||||||
|
"Seltenes Bergbau-Material"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"preparation": {
|
||||||
|
"equipment": {
|
||||||
|
"racing_suits": {
|
||||||
|
"Daymar": "Standard-Raumanzug mit Sauerstoffreserven",
|
||||||
|
"Aberdeen": "Pembroke Exploration Suit"
|
||||||
|
},
|
||||||
|
"weapons": {
|
||||||
|
"primary": "Beispiel: FS-9 LMG für Sperrfeuer",
|
||||||
|
"secondary": "Beispiel: Arclight Pistole für Nahkämpfe",
|
||||||
|
"ammunition": "Mindestens ein Dutzend Magazine"
|
||||||
|
},
|
||||||
|
"tools": [
|
||||||
|
"MaxLift Tractor Beam",
|
||||||
|
"MedGun mit Nachfüllpatronen",
|
||||||
|
"MedPens"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ships": {
|
||||||
|
"solo": "Avenger Titan",
|
||||||
|
"small_team": "Cutlass Black",
|
||||||
|
"multi_crew": [
|
||||||
|
"Valkyrie",
|
||||||
|
"Carrack"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"steps": [
|
||||||
|
{
|
||||||
|
"step": 1,
|
||||||
|
"title": "Aktivierung der Satellitenschüsseln",
|
||||||
|
"description": "Finde und aktiviere drei Satellitenschüsseln, die um den Orbital-Mining-Laser positioniert sind.",
|
||||||
|
"substeps": [
|
||||||
|
{
|
||||||
|
"title": "Lokalisierung der Schüsseln",
|
||||||
|
"description": "Suche nach drei Schüsseln, wobei einige bereits aktiviert sein können."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Keycards finden",
|
||||||
|
"description": "Finde zwei Keycards: PAF Maintenance Keycard und PAF Security Keycard.",
|
||||||
|
"spawn_locations": [
|
||||||
|
"Tote NPCs/Wachen",
|
||||||
|
"Schließfächer",
|
||||||
|
"Kontrollräume"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Terminal-Aktivierung",
|
||||||
|
"description": "Gehe in den Kontrollraum, füge Alignment Blades in den Terminal ein, warte auf Aktivierung.",
|
||||||
|
"warnings": [
|
||||||
|
"Valakkar-Angriffe werden ausgelöst!",
|
||||||
|
"Deckung suchen, Spotter einsetzen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Batteriepacks sichern",
|
||||||
|
"description": "Nach Aktivierung öffnet sich eine Garagentür und gibt ein Batteriepack frei. Sammle drei Batterien.",
|
||||||
|
"tools": [
|
||||||
|
"MaxLift Tractor Beam",
|
||||||
|
"Sichere Lagerung"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 2,
|
||||||
|
"title": "Stromversorgung des Orbital-Mining-Lasers",
|
||||||
|
"description": "Bringe die drei Batteriepacks zum Mining-Laser und verbinde sie mit den Power Banks.",
|
||||||
|
"substeps": [
|
||||||
|
{
|
||||||
|
"title": "Sicherung des Gebiets",
|
||||||
|
"description": "Bereite dich auf NPC- und Spielergegner vor. Nutze Teamkoordination (Verteidiger, Operateure)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Batterieeinsetzen",
|
||||||
|
"description": "Setze die Batterien in die drei Power Banks ein. Der Laser wird hochgefahren."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Keycard holen",
|
||||||
|
"description": "Entnehme die Laser Activation Keycard vom Terminal.",
|
||||||
|
"tips": [
|
||||||
|
"Wenn Power LEDs nicht grün leuchten, Batterie erneut einsetzen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Optionale Loots",
|
||||||
|
"description": "Finde OLP Storage Card und Supervisor Access Card für zusätzliche Beute.",
|
||||||
|
"supervisor_room": {
|
||||||
|
"access": "Mit Supervisor Access Card",
|
||||||
|
"contents": "Hochwertige Gegenstände"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 3,
|
||||||
|
"title": "Abfeuern des Orbital-Lasers",
|
||||||
|
"description": "Nutze die Laser Activation Keycard, um den Laser vom Bunker aus abzufeuern.",
|
||||||
|
"action": "Drücke den großen roten Knopf, um den Höhleneingang zu öffnen."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"step": 4,
|
||||||
|
"title": "Betreten der Mine und Sammeln von Ressourcen",
|
||||||
|
"description": "Gehe in die neu geöffnete Höhle und sammle wertvolle Ressourcen.",
|
||||||
|
"warnings": [
|
||||||
|
"Diese Phase ist oft umkämpft",
|
||||||
|
"Bleibe wachsam, bewege dich schnell"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tips": [
|
||||||
|
"Nutze einen Spotter für Valakkar-Bedrohungen",
|
||||||
|
"Plane Rollenverteilung für Teamkoordination",
|
||||||
|
"Sichere Batterien und Keycards vor Einbrüchen",
|
||||||
|
"Suche nach mehreren Supervisor Access Cards für schnelleren Zugriff"
|
||||||
|
]
|
||||||
|
}
|
||||||
69
llm_rag_knowledge/stormbreaker_part_1.json
Normal file
69
llm_rag_knowledge/stormbreaker_part_1.json
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"event": "Stormbreaker Event Part 1",
|
||||||
|
"location": "Pyro IV",
|
||||||
|
"farro_data_centers": 10,
|
||||||
|
"goal": "Externe Forschungseinrichtungen für den Lazarus-Komplex",
|
||||||
|
"escort": {
|
||||||
|
"security_forces": true,
|
||||||
|
"scientists": true,
|
||||||
|
"engineers": true
|
||||||
|
},
|
||||||
|
"optional_units": {
|
||||||
|
"heavy_units": "Von Fackel & BitZeros"
|
||||||
|
},
|
||||||
|
"optional_quest": {
|
||||||
|
"offered_by": "Eckhart & BitZeros"
|
||||||
|
},
|
||||||
|
"required_keycard": {
|
||||||
|
"name": "Probenbehälter-Schlüsselkarte",
|
||||||
|
"source": "Farro Datenzentren (FDZs)",
|
||||||
|
"purpose": "Lazarus Komplex (LKs) abschließen"
|
||||||
|
},
|
||||||
|
"security_level_02": {
|
||||||
|
"access": "Über einen lebenden Körper mit Laborkittel",
|
||||||
|
"scientist_locations": [
|
||||||
|
"Nebengebäude der Klinik",
|
||||||
|
"Messe",
|
||||||
|
"Grünbereich",
|
||||||
|
"Edain-Labor"
|
||||||
|
],
|
||||||
|
"action": "Wissenschaftler bewusstlos zur Sicherheits-Tür bringen",
|
||||||
|
"room_contents": {
|
||||||
|
"safe": "In der Wand",
|
||||||
|
"laptop": {
|
||||||
|
"code": "4-stellig"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required_item": "Laborkittel"
|
||||||
|
},
|
||||||
|
"security_level_03": {
|
||||||
|
"required_keycards": [
|
||||||
|
{
|
||||||
|
"name": "Datenverarbeitungs-Schlüsselkarte",
|
||||||
|
"source": "Safes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Wartungs-Schlüsselkarte",
|
||||||
|
"source": "Notunterkunft",
|
||||||
|
"optional": true,
|
||||||
|
"purpose": "Kein Laborkittel benötigt oder ATLS-Rad umgehen"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"data_processing_room": {
|
||||||
|
"laptop": {
|
||||||
|
"code": "5-stellig"
|
||||||
|
},
|
||||||
|
"action": "Probenbehälter-Schlüsselkarte im Turm eingeben (Raummitte)"
|
||||||
|
},
|
||||||
|
"press_action": {
|
||||||
|
"spawns": "Schwere Einheiten",
|
||||||
|
"doors": "Datenverarbeitung offen",
|
||||||
|
"duration": "5 Minuten",
|
||||||
|
"interruption": "Mindestens eine Unterbrechung muss bestätigt werden"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abbreviations": {
|
||||||
|
"FDZ": "Farro Datenzentren",
|
||||||
|
"LK": "Lazarus Komplex"
|
||||||
|
}
|
||||||
|
}
|
||||||
120
llm_rag_knowledge/stormbreaker_part_2.json
Normal file
120
llm_rag_knowledge/stormbreaker_part_2.json
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
{
|
||||||
|
"event": "Stormbreaker Event Part 2",
|
||||||
|
"complexes": [
|
||||||
|
{
|
||||||
|
"name": "Phoenix",
|
||||||
|
"location": "Pyro I",
|
||||||
|
"storms": "Immerwährende Stürme",
|
||||||
|
"verzerrungsfeld": true,
|
||||||
|
"labs": 3,
|
||||||
|
"transit": {
|
||||||
|
"method": "Transitknoten am Feldrand",
|
||||||
|
"distance": "22-30 km",
|
||||||
|
"shuttle": {
|
||||||
|
"frequency": "Alle 2 Minuten"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radiation_zone": {
|
||||||
|
"danger_level": "Tödlich",
|
||||||
|
"rate": "175 REM pro Sekunde",
|
||||||
|
"ungeschützt": {
|
||||||
|
"death_time": "Innerhalb von 5 Sekunden"
|
||||||
|
},
|
||||||
|
"heavy_armor": {
|
||||||
|
"protection_time": "Ca. 15 Minuten"
|
||||||
|
},
|
||||||
|
"stirling_suit": {
|
||||||
|
"protection": "Immunität"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe_approach": {
|
||||||
|
"method": "Via Orbital Marker",
|
||||||
|
"markers": [
|
||||||
|
"OM-1",
|
||||||
|
"OM-3",
|
||||||
|
"OM-6"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tithonus",
|
||||||
|
"location": "Pyro I",
|
||||||
|
"storms": "Immerwährende Stürme",
|
||||||
|
"verzerrungsfeld": true,
|
||||||
|
"labs": 3,
|
||||||
|
"transit": {
|
||||||
|
"method": "Transitknoten am Feldrand",
|
||||||
|
"distance": "22-30 km",
|
||||||
|
"shuttle": {
|
||||||
|
"frequency": "Alle 2 Minuten"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radiation_zone": {
|
||||||
|
"danger_level": "Tödlich",
|
||||||
|
"rate": "175 REM pro Sekunde",
|
||||||
|
"ungeschützt": {
|
||||||
|
"death_time": "Innerhalb von 5 Sekunden"
|
||||||
|
},
|
||||||
|
"heavy_armor": {
|
||||||
|
"protection_time": "Ca. 15 Minuten"
|
||||||
|
},
|
||||||
|
"stirling_suit": {
|
||||||
|
"protection": "Immunität"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"safe_approach": {
|
||||||
|
"method": "Via Orbital Marker",
|
||||||
|
"markers": [
|
||||||
|
"OM-6",
|
||||||
|
"OM-5",
|
||||||
|
"OM-1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transit_station": {
|
||||||
|
"guards": {
|
||||||
|
"count": "10 - 15",
|
||||||
|
"armor": "Mittel- bis schwer gepanzert"
|
||||||
|
},
|
||||||
|
"shuttles": {
|
||||||
|
"frequency": "Alle 2 Minuten"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lab_location": {
|
||||||
|
"access": {
|
||||||
|
"from_shuttle": "Zu Eingang 01 zur Krankenstation gehen"
|
||||||
|
},
|
||||||
|
"door_code": {
|
||||||
|
"source": "Fenster zu Dr. Jorrits Büro",
|
||||||
|
"method": "Zahlen im Periodensystem hervorgehoben"
|
||||||
|
},
|
||||||
|
"interior": {
|
||||||
|
"items": [
|
||||||
|
"Volt-Schrotflinte",
|
||||||
|
"Stirling-Anzug ASD-Edition"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"egg_extraction": {
|
||||||
|
"duration": "3 Minuten plus Pausen",
|
||||||
|
"interaction": "Eine Interaktion erforderlich",
|
||||||
|
"security_forces": "In Wellen nach Pausen & Strahlungs-Entlüftung"
|
||||||
|
},
|
||||||
|
"egg_preparation": {
|
||||||
|
"location": "Rufturm",
|
||||||
|
"actions": [
|
||||||
|
"Ei einlegen & bereiten",
|
||||||
|
"Apex spawns mit ständig auftauchenden Ausgewachsenen und Jungen",
|
||||||
|
"Auf Schwachpunkte zielen (im Mund & Grüne Pustel)",
|
||||||
|
"Schwachpunkt-Treffer haben grünen splatter VFX",
|
||||||
|
"Zittert alle 20% verlorene Gesundheit",
|
||||||
|
"Bei 20% Gesundheit schlägt ein Blitz ein",
|
||||||
|
"Wenn tot sind 6 Zähne & 15 Perlen Extrahierbar"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abbreviations": {
|
||||||
|
"REM": "roentgen equivalent man",
|
||||||
|
"ASD": "Advanced Sience and Deployment"
|
||||||
|
}
|
||||||
|
}
|
||||||
319
llm_rag_knowledge/verhicle_fitment_index.json
Normal file
319
llm_rag_knowledge/verhicle_fitment_index.json
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
{
|
||||||
|
"title": "Ground Vehicle Fitment Index",
|
||||||
|
"author": "ChrisGBG",
|
||||||
|
"vehicles": [
|
||||||
|
{
|
||||||
|
"name": "Reliant Kore",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Technically fits, not recommended" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"HoverQuad", "Nox", "Dragonfly", "PTV", "ROC", "Cyclone", "ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Caterpillar",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"PTV", "STV", "Mule", "ROC", "Cyclone", "ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
],
|
||||||
|
"notes": [
|
||||||
|
{ "vehicle": "Mule", "note": "Fits, but can't enter/exit comfortably" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Avenger Titan",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Technically fits, not recommended" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"PTV", "STV", "Mule", "ROC", "Cyclone", "ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cutter",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Technically fits, not recommended" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"ROC", "Cyclone", "ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Freelancer",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Mule", "ROC", "Cyclone", "ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Nomad",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"ROC", "Cyclone", "ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "400i",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Technically fits, not recommended" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Cyclone", "ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Cutlass Black",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Cyclone", "ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Freelancer MAX",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Starfarer",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Technically fits, not recommended" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"ROC-DS", "Ursa", "Ballista", "Nova"
|
||||||
|
],
|
||||||
|
"notes": [
|
||||||
|
{ "vehicle": "Cyclone", "note": "Not confirmed" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hammerhead",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC-DS", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Ursa", "Ballista", "Nova"
|
||||||
|
],
|
||||||
|
"notes": [
|
||||||
|
{ "vehicle": "Cyclone", "note": "Not confirmed" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "600i",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC-DS", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ursa", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "MSR",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC-DS", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ursa", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Corsair",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC-DS", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ursa", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Valkyrie",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC-DS", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ursa", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Carrack",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC-DS", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ursa", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Constellation Series",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC-DS", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ursa", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Ballista", "Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "890j",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC-DS", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ursa", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ballista", "fitment": "Comfortable fit" }
|
||||||
|
],
|
||||||
|
"doesNotFit": [
|
||||||
|
"Nova"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Starlifter Series",
|
||||||
|
"fits": [
|
||||||
|
{ "vehicle": "HoverQuad", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nox", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Dragonfly", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "PTV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "STV", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Mule", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Cyclone", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "ROC-DS", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ursa", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Ballista", "fitment": "Comfortable fit" },
|
||||||
|
{ "vehicle": "Nova", "fitment": "Comfortable fit" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"legend": {
|
||||||
|
"Comfortable fit": "Fits without blocking access to entry/exit, doesn't require force or tricks.",
|
||||||
|
"Uncomfortable Fit": "Fits, but blocks access to entry/exit, doesn't require force or tricks.",
|
||||||
|
"Technically fits, not recommended": "Fits but requires force or tricks.",
|
||||||
|
"Doesn't fit": "Including unable to close ramp without coin toss of ship exploding.",
|
||||||
|
"Fits, but can't enter/exit comfortably": "Caterpillar's elevator doors aren't implemented yet.",
|
||||||
|
"Not confirmed": "Inferred from vehicles of similar dimensions fitting/not fitting."
|
||||||
|
}
|
||||||
|
}
|
||||||
435
llm_rag_knowledge/wikelo_crafting_information_part_1.json
Normal file
435
llm_rag_knowledge/wikelo_crafting_information_part_1.json
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
{
|
||||||
|
"Wikelo Crafting": [
|
||||||
|
{
|
||||||
|
"Mission": "Noxy Mod",
|
||||||
|
"Fahrzeugname": "Nox",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "IonWave", "Klasse": "Civilian B" },
|
||||||
|
{ "Name": "Tepilo", "Klasse": "Civilian A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Pulse Plus",
|
||||||
|
"Fahrzeugname": "Pulse",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "Radix", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Kelvid", "Klasse": "Civilian B" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Make a Ursa Mod",
|
||||||
|
"Fahrzeugname": "Ursa Medivac",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "MagnaBloom", "Klasse": "Civilian B" },
|
||||||
|
{ "Name": "Castra", "Klasse": "Industrial C" },
|
||||||
|
{ "Name": "Kelvid", "Klasse": "Civilian B" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 5,
|
||||||
|
"Saldynium (Erz)": 40,
|
||||||
|
"Jaclium (Erz)": 40
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Upgrade Intrepid",
|
||||||
|
"Fahrzeugname": "Intrepid",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "WhiteRose", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Palisade", "Klasse": "Industrial A" },
|
||||||
|
{ "Name": "Atlas", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Ultra-Flow", "Klasse": "Industrial A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 5,
|
||||||
|
"Government Cartography Agency Medal (Makellos)": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Fortune ship for you",
|
||||||
|
"Fahrzeugname": "Fortune",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "Lotus", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "75A Concord", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Atlas", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Aufeis", "Klasse": "Civilian A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 6,
|
||||||
|
"Carinite (Rein)": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Spirit Cargo mod",
|
||||||
|
"Fahrzeugname": "C1 Spirit",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "Lotus", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "7MA Lorica", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Hemera", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Aufeis", "Klasse": "Civilian A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 8,
|
||||||
|
"Tevarin War Service Marker (Makellos)": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Zeus Special",
|
||||||
|
"Fahrzeugname": "Zeus MK II ES",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "Genoa", "Klasse": "Industrial A" },
|
||||||
|
{ "Name": "Rampart", "Klasse": "Industrial A" },
|
||||||
|
{ "Name": "Hemera", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Snowpack", "Klasse": "Industrial A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 10,
|
||||||
|
"UEE 6th Platoon Medal (Makellos)": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Peregine Wikelo Mod",
|
||||||
|
"Fahrzeugname": "Sabre Peregrine",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "LumaCore", "Klasse": "Competition A" },
|
||||||
|
{ "Name": "Jaghte", "Klasse": "Competition B" },
|
||||||
|
{ "Name": "FoxFire", "Klasse": "Competition B" },
|
||||||
|
{ "Name": "ZeroRush", "Klasse": "Competition B" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 8,
|
||||||
|
"DCHS-05 Comp-Board": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Guardian",
|
||||||
|
"Fahrzeugname": "Guardian",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "QuadraCell", "Klasse": "Military A" },
|
||||||
|
{ "Name": "FR-76", "Klasse": "Military A" },
|
||||||
|
{ "Name": "VK-00", "Klasse": "Military A" },
|
||||||
|
{ "Name": "Glacier", "Klasse": "Military A" }
|
||||||
|
],
|
||||||
|
"Kosten": null,
|
||||||
|
"Notiz": "Kosten fehlen in 4.2.1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Firebird Mod",
|
||||||
|
"Fahrzeugname": "Sabre Firebird",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "QuadraCell", "Klasse": "Military A" },
|
||||||
|
{ "Name": "FR-66", "Klasse": "Military A" },
|
||||||
|
{ "Name": "VK-00", "Klasse": "Military A" },
|
||||||
|
{ "Name": "Glacier", "Klasse": "Military A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 20,
|
||||||
|
"Polaris Bit": 1,
|
||||||
|
"DCHS-05 Comp-Board": 1,
|
||||||
|
"Ace Interceptor Helmet": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Build a Mod Scorpius",
|
||||||
|
"Fahrzeugname": "Scorpius",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "Slipstream", "Klasse": "Stealth A" },
|
||||||
|
{ "Name": "Umbra", "Klasse": "Stealth A" },
|
||||||
|
{ "Name": "Spectre", "Klasse": "Stealth A" },
|
||||||
|
{ "Name": "SnowBlind", "Klasse": "Stealth A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 20,
|
||||||
|
"Polaris Bit": 1,
|
||||||
|
"DCHS-05 Comp-Board": 12,
|
||||||
|
"Carinite": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Wikelo Navy F7",
|
||||||
|
"Fahrzeugname": "F7C Super Hornet Mk II",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "JS-400", "Klasse": "Military A" },
|
||||||
|
{ "Name": "FR-66", "Klasse": "Military A" },
|
||||||
|
{ "Name": "VK-00", "Klasse": "Military A" },
|
||||||
|
{ "Name": "Glacier", "Klasse": "Military A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 20,
|
||||||
|
"DCHS-05 Comp-Board": 6,
|
||||||
|
"Ace Interceptor Helmet": 5,
|
||||||
|
"Government Medal (Makellos)": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Guardian take down ship",
|
||||||
|
"Fahrzeugname": "Guardian Q1",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "LumaCore", "Klasse": "Competition A" },
|
||||||
|
{ "Name": "Haltur", "Klasse": "Competition B" },
|
||||||
|
{ "Name": "SunFire", "Klasse": "Competition B" },
|
||||||
|
{ "Name": "AbsoluteZero", "Klasse": "Competition B" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 30,
|
||||||
|
"DCHS-05 Comp-Board": 15,
|
||||||
|
"Irradiated Valakkar Pearl (Grad AA)": 15,
|
||||||
|
"UEE 6th Platoon Medal (Makellos)": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Zeus Cargo Special",
|
||||||
|
"Fahrzeugname": "Zeus MK II CL",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "Genoa", "Klasse": "Industrial C" },
|
||||||
|
{ "Name": "Rampart", "Klasse": "Industrial A" },
|
||||||
|
{ "Name": "Hemera", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Snowpack", "Klasse": "Industrial A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 30,
|
||||||
|
"Carinite": 24,
|
||||||
|
"Ace Interceptor Helmet": 15,
|
||||||
|
"Carinite (Rein)": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "More than a Max",
|
||||||
|
"Fahrzeugname": "Starlancer MAX",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "Lotus", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Parapet", "Klasse": "Industrial A" },
|
||||||
|
{ "Name": "Hemera", "Klasse": "Civilian A" },
|
||||||
|
{ "Name": "Aufeis", "Klasse": "Civilian A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 30,
|
||||||
|
"Ace Interceptor Helmet": 15,
|
||||||
|
"Carinite (Rein)": 5,
|
||||||
|
"Irradiated Valakkar Pearl (Grad AAA)": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Want Taurus ship",
|
||||||
|
"Fahrzeugname": "Constellation Taurus",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "QuadraCell MT", "Klasse": "Military A" },
|
||||||
|
{ "Name": "FR-86", "Klasse": "Military A" },
|
||||||
|
{ "Name": "XL-1", "Klasse": "Military A" },
|
||||||
|
{ "Name": "Avalanche", "Klasse": "Military A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 30,
|
||||||
|
"Carinite (Rein)": 5,
|
||||||
|
"Irradiated Valakkar Pearl (Grad AAA)": 5,
|
||||||
|
"Government Cartography Agency Medal (Makellos)": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "F8 War Mod",
|
||||||
|
"Fahrzeugname": "F8C Lightning Mil",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "LuxCore", "Klasse": "Competition A" },
|
||||||
|
{ "Name": "FR-76", "Klasse": "Military A" },
|
||||||
|
{ "Name": "Colossus", "Klasse": "Industrial B" },
|
||||||
|
{ "Name": "Glacier", "Klasse": "Military A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 40,
|
||||||
|
"Carinite (Rein)": 6,
|
||||||
|
"Irradiated Valakkar Pearl (Grad AAA)": 6,
|
||||||
|
"Tevarin War Service Marker (Makellos)": 6,
|
||||||
|
"Argo ATLS RX11": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Sneaky Slabber",
|
||||||
|
"Fahrzeugname": "F8C Lightning Stealth",
|
||||||
|
"Komponenten": [
|
||||||
|
{ "Name": "Eclipse", "Klasse": "Stealth A" },
|
||||||
|
{ "Name": "Umbra", "Klasse": "Stealth A" },
|
||||||
|
{ "Name": "Colossus", "Klasse": "Industrial B" },
|
||||||
|
{ "Name": "SnowBlind", "Klasse": "Stealth A" }
|
||||||
|
],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 40,
|
||||||
|
"DCHS-05 Comp-Board": 20,
|
||||||
|
"Carinite (Rein)": 5,
|
||||||
|
"Irradiated Valakkar Pearl (Grad AAA)": 5,
|
||||||
|
"Xanithule Ascension Helmet": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Now make Polaris. Short Time Deal.",
|
||||||
|
"Fahrzeugname": "Polaris",
|
||||||
|
"Komponenten": [],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 50,
|
||||||
|
"Polaris Bits": 25,
|
||||||
|
"DCHS-05 Comp-Board": 20,
|
||||||
|
"Carinite": 20,
|
||||||
|
"Irradiated Valakkar Fang (Apex)": 20,
|
||||||
|
"MG Scrip": 20,
|
||||||
|
"Ace Interceptor Helmet": 15,
|
||||||
|
"Irradiated Valakkar Pearl (Grade AAA)": 20,
|
||||||
|
"UEE 6th Platoon Medal (Pristine)": 20,
|
||||||
|
"Carinite (Pure)": 20,
|
||||||
|
"Finley The Stormwall Large Plushie": 1,
|
||||||
|
"Picotball": 1,
|
||||||
|
"Janalite": 5,
|
||||||
|
"Wowblast Desperado Toy Pistol Red": 1,
|
||||||
|
"Atatium": {
|
||||||
|
"Menge": 6,
|
||||||
|
"Notiz": "6 Einheiten mit je 8 SCU (48 SCU total)"
|
||||||
|
},
|
||||||
|
"Scourge Railgun": 10
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "Golem Rocks",
|
||||||
|
"Fahrzeugname": "Golem",
|
||||||
|
"Komponenten": [],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 5,
|
||||||
|
"ASD Secure Drive": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "New Move Big Starlancer Ship",
|
||||||
|
"Fahrzeugname": "Starlancer TAG",
|
||||||
|
"Komponenten": [],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 50,
|
||||||
|
"Ace Interceptor Helmet": 15,
|
||||||
|
"ASD Secure Drive": 5,
|
||||||
|
"Irradiated Valakkar Pearl (Grad AAA)": 5,
|
||||||
|
"Tevarin War Service Marker (Makellos)": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Mission": "What is Terrapin?",
|
||||||
|
"Fahrzeugname": "Terrapin Medivac",
|
||||||
|
"Komponenten": [],
|
||||||
|
"Kosten": {
|
||||||
|
"Wikelo Favor": 15,
|
||||||
|
"ASD Secure Drive": 5,
|
||||||
|
"Tevarin War Service Marker (Makellos)": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Materialien Quellen": [
|
||||||
|
{
|
||||||
|
"Material": "Ace Interceptor Helmet",
|
||||||
|
"Quelle": "Beute von gefallenen Ace-Piloten in den neuen Foxwell Patrol Missionen / Headhunters Patrol Missionen. Darf nur Soft Death, sonst verschwindet der Ace Pilot. Kann auch in Bräunungsboxen bei Align & Storm gefunden werden."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Advocacy Badge (Replica)",
|
||||||
|
"Quelle": "Beute von Security Post Kareah rund um Cellin, Contested Zones, Larzarus locations etc."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Argo ATLS Ikti",
|
||||||
|
"Quelle": "Wikelo Mission."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Atlasium",
|
||||||
|
"Quelle": "Handelsware, Daten darüber, wo sie verkauft wird: https://sc-trade.tools/commodities/Atlasium."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Carinite",
|
||||||
|
"Quelle": "Align & Mine Erz."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Carinite (Rein)",
|
||||||
|
"Quelle": "Seltenes Align & Mine Erz. Nur 1% Drop chance."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "DCHS-05 Comp-Board",
|
||||||
|
"Quelle": "DCHS-05 Orbital Positioning Comp-Board als Beute von Ghost Arena in Ruin Station."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Finley The Stormwall Large Plushie",
|
||||||
|
"Quelle": "Orison August Dunlow Spaceport - Gift Shop & Cloudview Center - Stratus - Kel-To ConStore."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Government Cartography (Makellos)",
|
||||||
|
"Quelle": "Beute von Ace Piloten (gute Beute), lootbar von blauen Boxen bei Align & Mine und Storm Breaker Locations und kleinen Boxen bei Derelict Outpost Locations (schreckliche Beute)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Irradiated Kopion Horn",
|
||||||
|
"Quelle": "Irradiated Kopion."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Irradiated Valakkar Fang (Erwachsen)",
|
||||||
|
"Quelle": "Erwachsener Irradiated Valakkar - Storm Breaker Lazarus Locations."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Irradiated Valakkar Fang (Juvenile)",
|
||||||
|
"Quelle": "Juveniler Irradiated Valakkar - Storm Breaker Lazarus Locations."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Irradiated Valakkar Pearl (Grad AAA)",
|
||||||
|
"Quelle": "Apex-Irradiated Valakkar - Storm Breaker Lazarus Locations (Ich bin mir nicht sicher, wie das Grad-System funktioniert)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Jaclium (Erz)",
|
||||||
|
"Quelle": "Align & Mine Erz."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Janalite",
|
||||||
|
"Quelle": "Seltenes FPS-Minable-Erz."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "MG Scrip",
|
||||||
|
"Quelle": "Foxwell Patrol / Ambush Missions, Gilly's Combat Gauntlet missionen."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Picoball",
|
||||||
|
"Quelle": "Beute von Aid Shelters, Outposts, Farro Datacenter, Larzarus, Align & Mine Paf sites. Spawnt nicht neu."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Polaris Bits",
|
||||||
|
"Quelle": "24x Quantanium, abgebaut mit Schiff (Konvertierung mit 'Want Polaris? Need something special' Mission)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Saldynium (Erz)",
|
||||||
|
"Quelle": "Align & Mine Erz."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Scourge Railgun",
|
||||||
|
"Quelle": "Beute von roten Boxen in Bunkern, großen grünen Boxen bei Align & Mine und Storm Breaker Locations."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Tevarin War Service Marker (Makellos)",
|
||||||
|
"Quelle": "Beute von Ace-Piloten (gute Beute), lootbar von blauen Boxen bei Align & Mine und Storm Breaker Locations und kleinen Boxen bei Derelict Outpost Locations (schreckliche Beute)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "UEE 6th Platoon Medal (Makellos)",
|
||||||
|
"Quelle": "Beute von Ace-Piloten (gute Beute), lootbar von blauen Boxen bei Align & Mine und Storm Breaker Locations und kleinen Boxen bei Derelict Outpost Locations (schreckliche Beute)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Wikelo Favor",
|
||||||
|
"Quelle": "50x MG Scrip / 50x Council Scrip / 50x Carinite / 15x Irradiated Valakkar Pearl."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Wowblast Desperado Toy Pistol Red",
|
||||||
|
"Quelle": "Allgemeine Beute. Kann überall gefunden werden."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Xanthule Ascension Helmet",
|
||||||
|
"Quelle": "Wikelo hat eine Mission, um die Basisversion in die Aufstiegsversion zu konvertieren, auch verkauft im pledge store."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "Xanthule Ascension Suit",
|
||||||
|
"Quelle": "Wikelo hat eine Mission, um die Basisversion in die Aufstiegsversion zu konvertieren, auch verkauft im pledge store."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Material": "ASD Secure Drive",
|
||||||
|
"Quelle": "Kann in den Onyx Facilities in Stanton gelootet werden."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
136
llm_rag_knowledge/wikelo_crafting_information_part_2.json
Normal file
136
llm_rag_knowledge/wikelo_crafting_information_part_2.json
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"contracts": [
|
||||||
|
{
|
||||||
|
"name": "Armor with Horn and String",
|
||||||
|
"items_needed": [
|
||||||
|
{ "quantity": 30, "item": "Saldynium (Ore)" },
|
||||||
|
{ "quantity": 15, "item": "Carinite" },
|
||||||
|
{ "quantity": 45, "item": "Jaclium (Ore)" },
|
||||||
|
{ "quantity": 1, "item": "Carinite (Pure)" }
|
||||||
|
],
|
||||||
|
"reward_items": [
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Helmet Endro" },
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Core Endro" },
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Arms Endro" },
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Legs Endro" },
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Core Endro" },
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Arms Endro" },
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Legs Endro" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Look at desert but don't see you",
|
||||||
|
"items_needed": [
|
||||||
|
{ "quantity": 3, "item": "Wikelo Favor" },
|
||||||
|
{ "quantity": 5, "item": "Ace Interceptor Helmet" },
|
||||||
|
{ "quantity": 20, "item": "Advocacy Badge (Replica)" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Core Woodland" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Arms Woodland" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Legs Woodland" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Helmet Woodland" }
|
||||||
|
],
|
||||||
|
"reward_items": [
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Helmet Hunter Camo" },
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Arms Hunter Camo" },
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Core Hunter Camo" },
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Legs Hunter Camo" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Want armor look like tree?",
|
||||||
|
"items_needed": [
|
||||||
|
{ "quantity": 3, "item": "Wikelo Favor" },
|
||||||
|
{ "quantity": 5, "item": "Ace Interceptor Helmet" },
|
||||||
|
{ "quantity": 50, "item": "Valakkar Fang (Juvenile)" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Core Woodland" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Arms Woodland" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Legs Woodland" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Helmet Woodland" }
|
||||||
|
],
|
||||||
|
"reward_items": [
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Helmet Jungle Camo" },
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Arms Jungle Camo" },
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Core Jungle Camo" },
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Legs Jungle Camo" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Make space navy armor",
|
||||||
|
"items_needed": [
|
||||||
|
{ "quantity": 3, "item": "Wikelo Favor" },
|
||||||
|
{ "quantity": 5, "item": "Ace Interceptor Helmet" },
|
||||||
|
{ "quantity": 50, "item": "Grassland Quasi Grazer Egg" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Core Woodland" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Arms Woodland" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Legs Woodland" },
|
||||||
|
{ "quantity": 1, "item": "ADP-mk4 Helmet Woodland" }
|
||||||
|
],
|
||||||
|
"reward_items": [
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Helmet Cobalt Camo" },
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Arms Cobalt Camo" },
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Core Cobalt Camo" },
|
||||||
|
{ "quantity": 1, "item": "DCP Armor Legs Cobalt Camo" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Make glowy armor",
|
||||||
|
"items_needed": [
|
||||||
|
{ "quantity": 1, "item": "Irradiated Valakkar Pearl (Grade AAA)" },
|
||||||
|
{ "quantity": 2, "item": "Irradiated Valakkar Fang (Apex)" },
|
||||||
|
{ "quantity": 15, "item": "Irradiated Valakkar Fang (Adult)" },
|
||||||
|
{ "quantity": 20, "item": "Irradiated Valakkar Fang (Juvenile)" }
|
||||||
|
],
|
||||||
|
"reward_items": [
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Helmet Endro" },
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Core Endro" },
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Arms Endro" },
|
||||||
|
{ "quantity": 1, "item": "Ana Armor Legs Endro" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Walk in danger. Look good.",
|
||||||
|
"items_needed": [
|
||||||
|
{ "quantity": 30, "item": "MG Scrip" },
|
||||||
|
{ "quantity": 1, "item": "Novikov Exploration Suit" },
|
||||||
|
{ "quantity": 1, "item": "Novikov Helmet" },
|
||||||
|
{ "quantity": 10, "item": "Irradiated Valakkar Fang (Adult)" },
|
||||||
|
{ "quantity": 20, "item": "Irradiated Valakkar Fang (Juvenile)" }
|
||||||
|
],
|
||||||
|
"reward_items": [
|
||||||
|
{ "quantity": 1, "item": "Irradiated Valakkar Pearl (Grade AAA)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Xi'an Xanthule Suit made better",
|
||||||
|
"items_needed": [
|
||||||
|
{ "quantity": 20, "item": "MG Scrip" },
|
||||||
|
{ "quantity": 1, "item": "Xanthule Suit" },
|
||||||
|
{ "quantity": 1, "item": "Xanthule Helmet" },
|
||||||
|
{ "quantity": 15, "item": "Ace Interceptor Helmet" },
|
||||||
|
{ "quantity": 1, "item": "Tevarian War Service Marker (Pristine)" }
|
||||||
|
],
|
||||||
|
"reward_items": []
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Adventure a A-Venture",
|
||||||
|
"items_needed": [
|
||||||
|
{ "quantity": 30, "item": "MG Scrip" },
|
||||||
|
{ "quantity": 1, "item": "Venture Arms" },
|
||||||
|
{ "quantity": 1, "item": "Venture Core" },
|
||||||
|
{ "quantity": 1, "item": "Venture Helmet White" },
|
||||||
|
{ "quantity": 1, "item": "Venture Legs" },
|
||||||
|
{ "quantity": 10, "item": "Saldynium (Ore)" },
|
||||||
|
{ "quantity": 10, "item": "Jaclium (Ore)" },
|
||||||
|
{ "quantity": 1, "item": "Carinite (Pure)" }
|
||||||
|
],
|
||||||
|
"reward_items": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
108
llm_tools/fleetyard.py
Normal file
108
llm_tools/fleetyard.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import sqlite3
|
||||||
|
import configparser
|
||||||
|
from collections import defaultdict
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Testing out the fleetyard API
|
||||||
|
|
||||||
|
class FleetyardAPI:
|
||||||
|
def __init__(self, base_url):
|
||||||
|
self.base_url = base_url
|
||||||
|
self.session = requests.Session()
|
||||||
|
|
||||||
|
def create_session(self, ini_path="fleetyard_login.ini"):
|
||||||
|
"""Create a new session using credentials from an INI file."""
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(ini_path)
|
||||||
|
login = config.get('login', 'username', fallback=None)
|
||||||
|
password = config.get('login', 'password', fallback=None)
|
||||||
|
remember_me = config.getboolean('login', 'rememberMe', fallback=True)
|
||||||
|
if not login or not password:
|
||||||
|
raise ValueError("Missing login or password in fleetyard_login.ini")
|
||||||
|
payload = {
|
||||||
|
"login": login,
|
||||||
|
"password": password,
|
||||||
|
"rememberMe": remember_me,
|
||||||
|
}
|
||||||
|
response = self.session.post(f"{self.base_url}/sessions", json=payload)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def get_igns_fleet(self):
|
||||||
|
"""Get the IGNs fleet."""
|
||||||
|
fleet_url = f"{self.base_url}/fleets/igns/vehicles/export"
|
||||||
|
response = self.session.get(fleet_url)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def process_fleet_data(fleet_data):
|
||||||
|
"""Groups fleet data by ship and aggregates owners."""
|
||||||
|
ship_owners = defaultdict(list)
|
||||||
|
for ship in fleet_data:
|
||||||
|
if "username" in ship:
|
||||||
|
key = (ship["manufacturerName"], ship["name"])
|
||||||
|
ship_owners[key].append(ship["username"])
|
||||||
|
return ship_owners
|
||||||
|
|
||||||
|
def store_in_database(ship_owners):
|
||||||
|
"""Stores the processed fleet data in a SQLite database."""
|
||||||
|
db_file = "fleet.db"
|
||||||
|
conn = sqlite3.connect(db_file)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Create table
|
||||||
|
cursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS ship_owner_list (
|
||||||
|
manufacturerName TEXT,
|
||||||
|
name TEXT,
|
||||||
|
usernames TEXT,
|
||||||
|
PRIMARY KEY (manufacturerName, name)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# Clear existing data to prevent duplicates on re-runs
|
||||||
|
cursor.execute('DELETE FROM ship_owner_list')
|
||||||
|
|
||||||
|
# Insert new data
|
||||||
|
for (manufacturer, ship_name), owners in ship_owners.items():
|
||||||
|
usernames_str = ", ".join(sorted(owners))
|
||||||
|
cursor.execute(
|
||||||
|
"INSERT INTO ship_owner_list (manufacturerName, name, usernames) VALUES (?, ?, ?)",
|
||||||
|
(manufacturer, ship_name, usernames_str)
|
||||||
|
)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print(f"Data successfully stored in {db_file}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
base_url = "https://api.fleetyards.net/v1"
|
||||||
|
api = FleetyardAPI(base_url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Create a session
|
||||||
|
session_response = api.create_session()
|
||||||
|
print("Session created.")
|
||||||
|
|
||||||
|
# Get the IGNs fleet
|
||||||
|
if session_response.get("code") == "success":
|
||||||
|
fleet_json = api.get_igns_fleet()
|
||||||
|
print("IGNs fleet data retrieved.")
|
||||||
|
|
||||||
|
# Process the data
|
||||||
|
processed_data = process_fleet_data(fleet_json)
|
||||||
|
|
||||||
|
# Store in database
|
||||||
|
store_in_database(processed_data)
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Failed to create session, cannot retrieve fleet.")
|
||||||
|
|
||||||
|
except requests.exceptions.HTTPError as e:
|
||||||
|
print(f"An HTTP error occurred: {e}")
|
||||||
|
print(f"Response body: {e.response.text}")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"A request error occurred: {e}")
|
||||||
168
llm_tools/get_commodities.py
Normal file
168
llm_tools/get_commodities.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import requests
|
||||||
|
import sqlite3
|
||||||
|
import time
|
||||||
|
import schedule
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
API_URL = "https://api.uexcorp.space/2.0/commodities_prices_all"
|
||||||
|
with open("uex_api_key", "r") as f:
|
||||||
|
BEARER_TOKEN = f.read().strip()
|
||||||
|
|
||||||
|
DB_NAME = "commodities.db"
|
||||||
|
TABLE_NAME = "commodity_prices"
|
||||||
|
|
||||||
|
def setup_database():
|
||||||
|
"""
|
||||||
|
Sets up the SQLite database and creates the table if it doesn't exist.
|
||||||
|
The table uses a composite primary key (id_commodity, id_terminal)
|
||||||
|
to ensure each commodity at each terminal has only one latest entry.
|
||||||
|
"""
|
||||||
|
conn = sqlite3.connect(DB_NAME)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Using "IF NOT EXISTS" prevents errors on subsequent runs.
|
||||||
|
# The schema is derived from your provided image.
|
||||||
|
# We use INSERT OR REPLACE later, so a primary key is important.
|
||||||
|
# (id_commodity, id_terminal) is a good candidate for a unique key.
|
||||||
|
cursor.execute(f'''
|
||||||
|
CREATE TABLE IF NOT EXISTS {TABLE_NAME} (
|
||||||
|
id INTEGER,
|
||||||
|
id_commodity INTEGER,
|
||||||
|
id_terminal INTEGER,
|
||||||
|
price_buy REAL,
|
||||||
|
price_buy_avg REAL,
|
||||||
|
price_sell REAL,
|
||||||
|
price_sell_avg REAL,
|
||||||
|
scu_buy REAL,
|
||||||
|
scu_buy_avg REAL,
|
||||||
|
scu_sell_stock REAL,
|
||||||
|
scu_sell_stock_avg REAL,
|
||||||
|
scu_sell REAL,
|
||||||
|
scu_sell_avg REAL,
|
||||||
|
status_buy INTEGER,
|
||||||
|
status_sell INTEGER,
|
||||||
|
date_added INTEGER,
|
||||||
|
date_modified INTEGER,
|
||||||
|
commodity_name TEXT,
|
||||||
|
commodity_code TEXT,
|
||||||
|
commodity_slug TEXT,
|
||||||
|
terminal_name TEXT,
|
||||||
|
terminal_code TEXT,
|
||||||
|
terminal_slug TEXT,
|
||||||
|
PRIMARY KEY (id_commodity, id_terminal)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print("Database setup complete. Table 'commodity_prices' is ready.")
|
||||||
|
|
||||||
|
def fetch_data_from_api():
|
||||||
|
"""
|
||||||
|
Fetches the latest commodity data from the UAX Corp API.
|
||||||
|
Returns the data as a list of dictionaries or None if an error occurs.
|
||||||
|
"""
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {BEARER_TOKEN}"
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = requests.get(API_URL, headers=headers)
|
||||||
|
# Raise an exception for bad status codes (4xx or 5xx)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
if 'data' in data:
|
||||||
|
return data['data']
|
||||||
|
else:
|
||||||
|
# Handle cases where the structure might be flat
|
||||||
|
return data
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error fetching data from API: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save_data_to_db(data):
|
||||||
|
"""
|
||||||
|
Saves the fetched data into the SQLite database.
|
||||||
|
Uses 'INSERT OR REPLACE' to update existing records for a
|
||||||
|
commodity/terminal pair or insert new ones.
|
||||||
|
"""
|
||||||
|
if not data:
|
||||||
|
print("No data to save.")
|
||||||
|
return
|
||||||
|
|
||||||
|
conn = sqlite3.connect(DB_NAME)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Prepare data for insertion
|
||||||
|
records_to_insert = []
|
||||||
|
for item in data:
|
||||||
|
# The order of values must match the table schema
|
||||||
|
records_to_insert.append((
|
||||||
|
item.get('id'),
|
||||||
|
item.get('id_commodity'),
|
||||||
|
item.get('id_terminal'),
|
||||||
|
item.get('price_buy'),
|
||||||
|
item.get('price_buy_avg'),
|
||||||
|
item.get('price_sell'),
|
||||||
|
item.get('price_sell_avg'),
|
||||||
|
item.get('scu_buy'),
|
||||||
|
item.get('scu_buy_avg'),
|
||||||
|
item.get('scu_sell_stock'),
|
||||||
|
item.get('scu_sell_stock_avg'),
|
||||||
|
item.get('scu_sell'),
|
||||||
|
item.get('scu_sell_avg'),
|
||||||
|
item.get('status_buy'),
|
||||||
|
item.get('status_sell'),
|
||||||
|
item.get('date_added'),
|
||||||
|
item.get('date_modified'),
|
||||||
|
item.get('commodity_name'),
|
||||||
|
item.get('commodity_code'),
|
||||||
|
item.get('commodity_slug'),
|
||||||
|
item.get('terminal_name'),
|
||||||
|
item.get('terminal_code'),
|
||||||
|
item.get('terminal_slug')
|
||||||
|
))
|
||||||
|
|
||||||
|
# Using executemany is much more efficient than one by one
|
||||||
|
sql_statement = f'''
|
||||||
|
INSERT OR REPLACE INTO {TABLE_NAME} (
|
||||||
|
id, id_commodity, id_terminal, price_buy, price_buy_avg, price_sell,
|
||||||
|
price_sell_avg, scu_buy, scu_buy_avg, scu_sell_stock, scu_sell_stock_avg,
|
||||||
|
scu_sell, scu_sell_avg, status_buy, status_sell, date_added, date_modified,
|
||||||
|
commodity_name, commodity_code, commodity_slug, terminal_name, terminal_code,
|
||||||
|
terminal_slug
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
|
||||||
|
cursor.executemany(sql_statement, records_to_insert)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
print(f"Successfully saved/updated {len(records_to_insert)} records to the database.")
|
||||||
|
|
||||||
|
def job():
|
||||||
|
"""The main job function to be scheduled."""
|
||||||
|
print(f"--- Running job at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ---")
|
||||||
|
api_data = fetch_data_from_api()
|
||||||
|
if api_data:
|
||||||
|
save_data_to_db(api_data)
|
||||||
|
print("--- Job finished ---")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 1. Set up the database and table on the first run.
|
||||||
|
setup_database()
|
||||||
|
|
||||||
|
# 2. Run the job immediately once when the script starts.
|
||||||
|
job()
|
||||||
|
|
||||||
|
# 3. Schedule the job to run every hour.
|
||||||
|
print(f"Scheduling job to run every hour. Press Ctrl+C to exit.")
|
||||||
|
schedule.every().hour.do(job)
|
||||||
|
|
||||||
|
# 4. Run the scheduler loop.
|
||||||
|
while True:
|
||||||
|
schedule.run_pending()
|
||||||
|
time.sleep(1)
|
||||||
162
llm_tools/get_items.py
Normal file
162
llm_tools/get_items.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import requests
|
||||||
|
import sqlite3
|
||||||
|
import time
|
||||||
|
import schedule
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# --- Configuration for Item Prices ---
|
||||||
|
API_URL = "https://api.uexcorp.space/2.0/items_prices_all"
|
||||||
|
with open("uex_api_key", "r") as f:
|
||||||
|
BEARER_TOKEN = f.read().strip()
|
||||||
|
DB_NAME = "items.db" # Using a dedicated DB file
|
||||||
|
TABLE_NAME = "item_prices"
|
||||||
|
|
||||||
|
def setup_item_database():
|
||||||
|
"""
|
||||||
|
Sets up the SQLite database and creates the item_prices table if it doesn't exist.
|
||||||
|
"""
|
||||||
|
conn = sqlite3.connect(DB_NAME)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Schema is derived from the new API documentation.
|
||||||
|
cursor.execute(f'''
|
||||||
|
CREATE TABLE IF NOT EXISTS {TABLE_NAME} (
|
||||||
|
id INTEGER,
|
||||||
|
id_item INTEGER,
|
||||||
|
id_terminal INTEGER,
|
||||||
|
id_category INTEGER,
|
||||||
|
price_buy REAL,
|
||||||
|
price_sell REAL,
|
||||||
|
date_added INTEGER,
|
||||||
|
date_modified INTEGER,
|
||||||
|
item_name TEXT,
|
||||||
|
item_uuid TEXT,
|
||||||
|
terminal_name TEXT,
|
||||||
|
PRIMARY KEY (id_item, id_terminal)
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print(f"Database setup complete. Table '{TABLE_NAME}' is ready.")
|
||||||
|
|
||||||
|
def fetch_item_data_from_api():
|
||||||
|
"""
|
||||||
|
Fetches the latest item price data from the UAX Corp API.
|
||||||
|
Returns the data as a list of dictionaries or None if an error occurs.
|
||||||
|
"""
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {BEARER_TOKEN}"
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = requests.get(API_URL, headers=headers)
|
||||||
|
response.raise_for_status() # Check for HTTP errors
|
||||||
|
|
||||||
|
data = response.json()
|
||||||
|
if 'data' in data:
|
||||||
|
return data['data']
|
||||||
|
return data
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error fetching item data from API: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def sync_data_with_db(data):
|
||||||
|
"""
|
||||||
|
Synchronizes the database with the fetched API data.
|
||||||
|
It de-duplicates the source data, then deletes all old entries and
|
||||||
|
inserts the new ones in a single transaction.
|
||||||
|
"""
|
||||||
|
if not data:
|
||||||
|
print("No data received from API. Database will not be changed.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# --- De-duplication Step ---
|
||||||
|
# The API is returning duplicates for (id_item, id_terminal).
|
||||||
|
# We will process the list and keep only the one with the latest 'date_modified'.
|
||||||
|
unique_items = {}
|
||||||
|
for item in data:
|
||||||
|
key = (item.get('id_item'), item.get('id_terminal'))
|
||||||
|
if key not in unique_items or item.get('date_modified') > unique_items[key].get('date_modified'):
|
||||||
|
unique_items[key] = item
|
||||||
|
|
||||||
|
# The final list of records to insert is the values of our de-duplicated dictionary.
|
||||||
|
clean_data = list(unique_items.values())
|
||||||
|
print(f"Received {len(data)} records from API. After de-duplication, {len(clean_data)} unique records will be processed.")
|
||||||
|
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(DB_NAME)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# --- Start Transaction ---
|
||||||
|
# 1. Delete all existing records from the table.
|
||||||
|
cursor.execute(f"DELETE FROM {TABLE_NAME}")
|
||||||
|
print(f"Cleared all old records from '{TABLE_NAME}'.")
|
||||||
|
|
||||||
|
# 2. Prepare the new, clean records for insertion.
|
||||||
|
records_to_insert = []
|
||||||
|
for item in clean_data: # Use the clean_data list now
|
||||||
|
records_to_insert.append((
|
||||||
|
item.get('id'),
|
||||||
|
item.get('id_item'),
|
||||||
|
item.get('id_terminal'),
|
||||||
|
item.get('id_category'),
|
||||||
|
item.get('price_buy'),
|
||||||
|
item.get('price_sell'),
|
||||||
|
item.get('date_added'),
|
||||||
|
item.get('date_modified'),
|
||||||
|
item.get('item_name'),
|
||||||
|
item.get('item_uuid'),
|
||||||
|
item.get('terminal_name')
|
||||||
|
))
|
||||||
|
|
||||||
|
# 3. Insert all new records.
|
||||||
|
sql_statement = f'''
|
||||||
|
INSERT INTO {TABLE_NAME} (
|
||||||
|
id, id_item, id_terminal, id_category, price_buy, price_sell,
|
||||||
|
date_added, date_modified, item_name, item_uuid, terminal_name
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
cursor.executemany(sql_statement, records_to_insert)
|
||||||
|
|
||||||
|
# --- Commit Transaction ---
|
||||||
|
conn.commit()
|
||||||
|
print(f"Successfully synchronized {len(records_to_insert)} records into the database.")
|
||||||
|
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
print(f"Database error: {e}")
|
||||||
|
if conn:
|
||||||
|
print("Rolling back changes.")
|
||||||
|
conn.rollback()
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def item_sync_job():
|
||||||
|
"""The main job function to be scheduled for syncing item prices."""
|
||||||
|
print(f"--- Running item sync job at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ---")
|
||||||
|
api_data = fetch_item_data_from_api()
|
||||||
|
sync_data_with_db(api_data)
|
||||||
|
print("--- Item sync job finished ---")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# 1. Set up the database and table on the first run.
|
||||||
|
setup_item_database()
|
||||||
|
|
||||||
|
# 2. Run the job immediately once when the script starts.
|
||||||
|
item_sync_job()
|
||||||
|
|
||||||
|
# 3. Schedule the job to run every hour.
|
||||||
|
print(f"Scheduling item sync job to run every hour. Press Ctrl+C to exit.")
|
||||||
|
schedule.every().hour.do(item_sync_job)
|
||||||
|
|
||||||
|
# 4. Run the scheduler loop.
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
schedule.run_pending()
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nExiting scheduler.")
|
||||||
|
break
|
||||||
4
llm_tools/sample_fleetyard_login.ini
Normal file
4
llm_tools/sample_fleetyard_login.ini
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[login]
|
||||||
|
username = YOUR_USERNAME
|
||||||
|
password = YOUR_PASSWORD
|
||||||
|
rememberMe = true
|
||||||
1
llm_tools/sample_uex_api_key
Normal file
1
llm_tools/sample_uex_api_key
Normal file
@ -0,0 +1 @@
|
|||||||
|
YOUR_API_KEY_HERE
|
||||||
792
llm_tools/star_citizen_info_retrieval.py
Normal file
792
llm_tools/star_citizen_info_retrieval.py
Normal file
@ -0,0 +1,792 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import requests, asyncio, json, sqlite3
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from fuzzywuzzy import process
|
||||||
|
from typing import Callable, Any
|
||||||
|
|
||||||
|
|
||||||
|
class EventEmitter:
|
||||||
|
def __init__(self, event_emitter: Callable[[dict], Any] = None):
|
||||||
|
self.event_emitter = event_emitter
|
||||||
|
|
||||||
|
async def progress_update(self, description):
|
||||||
|
await self.emit(description)
|
||||||
|
|
||||||
|
async def error_update(self, description):
|
||||||
|
await self.emit(description, "error", True)
|
||||||
|
|
||||||
|
async def success_update(self, description):
|
||||||
|
await self.emit(description, "success", True)
|
||||||
|
|
||||||
|
async def emit(self, description="Unknown State", status="in_progress", done=False):
|
||||||
|
if self.event_emitter:
|
||||||
|
await self.event_emitter(
|
||||||
|
{
|
||||||
|
"type": "status",
|
||||||
|
"data": {
|
||||||
|
"status": status,
|
||||||
|
"description": description,
|
||||||
|
"done": done,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class get_information:
|
||||||
|
def __init__(self):
|
||||||
|
self.base_url = "https://starcitizen.tools"
|
||||||
|
self.db_path = "/app/sc_databases"
|
||||||
|
|
||||||
|
async def get_all_vehicle_names(self):
|
||||||
|
"""Fetches all vehicle names from the list of pledge vehicles using the MediaWiki API."""
|
||||||
|
api_url = f"{self.base_url}/api.php"
|
||||||
|
vehicle_names = []
|
||||||
|
categories = ["Category:Pledge ships", "Category:Pledge vehicles"]
|
||||||
|
for category in categories:
|
||||||
|
params = {
|
||||||
|
"action": "query",
|
||||||
|
"format": "json",
|
||||||
|
"list": "categorymembers",
|
||||||
|
"cmtitle": category,
|
||||||
|
"cmlimit": "max", # Use max limit (500)
|
||||||
|
"cmprop": "title",
|
||||||
|
}
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
response = await asyncio.to_thread(
|
||||||
|
requests.get, api_url, params=params
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if "query" in data and "categorymembers" in data["query"]:
|
||||||
|
for member in data["query"]["categorymembers"]:
|
||||||
|
vehicle_names.append(member["title"])
|
||||||
|
|
||||||
|
# Check for continuation to get the next page of results
|
||||||
|
if "continue" in data and "cmcontinue" in data["continue"]:
|
||||||
|
params["cmcontinue"] = data["continue"]["cmcontinue"]
|
||||||
|
else:
|
||||||
|
break # No more pages
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error fetching vehicle list for {category}: {e}")
|
||||||
|
break # Stop processing this category
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Error decoding JSON from response for {category}.")
|
||||||
|
break # Stop processing this category
|
||||||
|
|
||||||
|
if not vehicle_names:
|
||||||
|
print("No vehicle names found.")
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Remove duplicates and sort the list
|
||||||
|
return sorted(list(set(vehicle_names)))
|
||||||
|
|
||||||
|
async def get_closest_vehicle_name(self, vehicle_name):
|
||||||
|
"""Finds the closest matching vehicle name using fuzzy matching."""
|
||||||
|
all_vehicle_names = await self.get_all_vehicle_names()
|
||||||
|
# print(f"Total vehicle names found: {len(all_vehicle_names)}")
|
||||||
|
if not all_vehicle_names:
|
||||||
|
return None
|
||||||
|
|
||||||
|
closest_name, _ = process.extractOne(vehicle_name, all_vehicle_names)
|
||||||
|
return closest_name
|
||||||
|
|
||||||
|
async def fetch_infos(self, ship_name):
|
||||||
|
"""Fetches ship information from the Star Citizen wiki using the MediaWiki API."""
|
||||||
|
closest_name = await self.get_closest_vehicle_name(ship_name)
|
||||||
|
if not closest_name:
|
||||||
|
print(f"No matching vehicle found for {ship_name}.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Use the closest name found for the API call
|
||||||
|
page_title = closest_name.replace(" ", "_")
|
||||||
|
api_url = f"{self.base_url}/api.php"
|
||||||
|
params = {
|
||||||
|
"action": "parse",
|
||||||
|
"page": page_title,
|
||||||
|
"format": "json",
|
||||||
|
"prop": "text", # We only need the parsed HTML content
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await asyncio.to_thread(requests.get, api_url, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if "error" in data:
|
||||||
|
print(f"API Error for {page_title}: {data['error']['info']}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
html_content = data.get("parse", {}).get("text", {}).get("*", "")
|
||||||
|
if not html_content:
|
||||||
|
print(f"No content found for {page_title}.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error fetching data for {page_title}: {e}")
|
||||||
|
return None
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Error decoding JSON from response for {page_title}.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
soup = BeautifulSoup(html_content, "html.parser")
|
||||||
|
info = {}
|
||||||
|
|
||||||
|
# Extracting ship information from the parsed HTML
|
||||||
|
info["general"] = await self._extract_infobox_data(soup)
|
||||||
|
info["specifications"] = await self._extract_specifications(soup)
|
||||||
|
|
||||||
|
return info
|
||||||
|
|
||||||
|
async def _extract_infobox_data(self, soup):
|
||||||
|
"""Extracts data from the infobox."""
|
||||||
|
infobox_data = {}
|
||||||
|
infobox = soup.find("details", class_="infobox")
|
||||||
|
if not infobox:
|
||||||
|
return infobox_data
|
||||||
|
|
||||||
|
items = infobox.find_all("div", class_="infobox__item")
|
||||||
|
for item in items:
|
||||||
|
label_tag = item.find("div", class_="infobox__label")
|
||||||
|
data_tag = item.find("div", class_="infobox__data")
|
||||||
|
|
||||||
|
if label_tag and data_tag:
|
||||||
|
label = label_tag.get_text(strip=True)
|
||||||
|
# For loaners, get all ship names
|
||||||
|
if "loaner" in label.lower():
|
||||||
|
value = [a.get_text(strip=True) for a in data_tag.find_all("a")]
|
||||||
|
else:
|
||||||
|
value = data_tag.get_text(separator=" ", strip=True)
|
||||||
|
|
||||||
|
infobox_data[label] = value
|
||||||
|
return infobox_data
|
||||||
|
|
||||||
|
async def _extract_specifications(self, soup):
|
||||||
|
"""Extracts data from the specifications tabs."""
|
||||||
|
specifications = {}
|
||||||
|
|
||||||
|
# Find all specification tabs like "Avionics & Systems", "Weaponry", etc.
|
||||||
|
tabs = soup.select("div.tabber > section > article.tabber__panel")
|
||||||
|
|
||||||
|
for panel in tabs:
|
||||||
|
panel_id = panel.get("id", "")
|
||||||
|
tab_name_tag = soup.find("a", {"aria-controls": panel_id})
|
||||||
|
if not tab_name_tag:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tab_name = tab_name_tag.get_text(strip=True)
|
||||||
|
specifications[tab_name] = {}
|
||||||
|
|
||||||
|
# Find all component groups in the panel
|
||||||
|
component_groups = panel.find_all(
|
||||||
|
"div", class_="template-components__section"
|
||||||
|
)
|
||||||
|
for group in component_groups:
|
||||||
|
label_tag = group.find("div", class_="template-components__label")
|
||||||
|
if not label_tag:
|
||||||
|
continue
|
||||||
|
|
||||||
|
category = label_tag.get_text(strip=True)
|
||||||
|
components = []
|
||||||
|
|
||||||
|
# Find all component cards in the group
|
||||||
|
component_cards = group.select(".template-component__card")
|
||||||
|
for card in component_cards:
|
||||||
|
count_tag = card.select_one(".template-component__count")
|
||||||
|
size_tag = card.select_one(".template-component__size")
|
||||||
|
title_tag = card.select_one(".template-component__title")
|
||||||
|
|
||||||
|
if count_tag and size_tag and title_tag:
|
||||||
|
count = count_tag.get_text(strip=True)
|
||||||
|
size = size_tag.get_text(strip=True)
|
||||||
|
title = title_tag.get_text(strip=True)
|
||||||
|
components.append(f"{count} {size} {title}")
|
||||||
|
|
||||||
|
if components:
|
||||||
|
# If the category already exists, append to it (for Thrusters)
|
||||||
|
if category in specifications[tab_name]:
|
||||||
|
specifications[tab_name][category].extend(components)
|
||||||
|
else:
|
||||||
|
specifications[tab_name][category] = components
|
||||||
|
|
||||||
|
return specifications
|
||||||
|
|
||||||
|
async def fetch_all_commodity_names(self):
|
||||||
|
"""
|
||||||
|
Fetches all commodity names from the database and sort them uniquely and returns a string.
|
||||||
|
"""
|
||||||
|
conn = sqlite3.connect(self.db_path + "/commodities.db")
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT DISTINCT commodity_name FROM commodity_prices")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
return_string = "\n".join([row[0] for row in rows])
|
||||||
|
return return_string
|
||||||
|
|
||||||
|
async def fetch_all_item_names(self):
|
||||||
|
"""
|
||||||
|
Fetches all item names from the database and sort them uniquely and returns a string.
|
||||||
|
"""
|
||||||
|
conn = sqlite3.connect(self.db_path + "/items.db")
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT DISTINCT item_name FROM item_prices")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
return_string = "\n".join([row[0] for row in rows])
|
||||||
|
return return_string
|
||||||
|
|
||||||
|
async def get_all_ship_names_from_fleetyard_db(self):
|
||||||
|
"""
|
||||||
|
Fetches all ship names from the fleet.db database and returns a string.
|
||||||
|
"""
|
||||||
|
conn = sqlite3.connect(self.db_path + "/fleet.db")
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT DISTINCT name FROM ship_owner_list")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
return_string = "\n".join([row[0] for row in rows])
|
||||||
|
return return_string
|
||||||
|
|
||||||
|
class Tools:
|
||||||
|
def __init__(self):
|
||||||
|
self.db_path = "/app/sc_databases"
|
||||||
|
|
||||||
|
async def get_ship_details(
|
||||||
|
self, ship_name: str, __event_emitter__: Callable[[dict], Any] = None
|
||||||
|
):
|
||||||
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
# The API call in fetch_infos now handles fuzzy matching and name formatting.
|
||||||
|
# ship_name = await get_information().get_closest_vehicle_name(ship_name)
|
||||||
|
# ship_name = ship_name.title().replace(" ", "_")
|
||||||
|
|
||||||
|
await emitter.progress_update("Fetching ship information for " + ship_name)
|
||||||
|
info = await get_information().fetch_infos(ship_name)
|
||||||
|
|
||||||
|
if info:
|
||||||
|
await emitter.success_update(
|
||||||
|
"Successfully fetched ship information for " + ship_name
|
||||||
|
)
|
||||||
|
await emitter.progress_update("Processing retrieved information...")
|
||||||
|
output_lines = []
|
||||||
|
# Build the output string
|
||||||
|
output_lines.append(f"Information for {ship_name}:")
|
||||||
|
if info.get("general"):
|
||||||
|
await emitter.progress_update("Processing general information...")
|
||||||
|
output_lines.append("\n--- General Information ---")
|
||||||
|
for key, value in info["general"].items():
|
||||||
|
if isinstance(value, list):
|
||||||
|
output_lines.append(f"{key}: {', '.join(value)}")
|
||||||
|
else:
|
||||||
|
if "Size" in key:
|
||||||
|
# Only print the first word for size-related keys
|
||||||
|
value = value.split()[0] if value else ""
|
||||||
|
if "Stowage" in key:
|
||||||
|
# Replace 'Stowage' with 'Storage':
|
||||||
|
key = key.replace("Stowage", "Storage")
|
||||||
|
output_lines.append(f"{key}: {value}")
|
||||||
|
|
||||||
|
if info.get("specifications"):
|
||||||
|
await emitter.progress_update("Processing specifications...")
|
||||||
|
output_lines.append("\n--- Specifications ---")
|
||||||
|
for spec_area, details in info["specifications"].items():
|
||||||
|
if not details:
|
||||||
|
continue
|
||||||
|
output_lines.append(f"\n[{spec_area}]")
|
||||||
|
for category, items in details.items():
|
||||||
|
output_lines.append(f" {category}:")
|
||||||
|
for item in items:
|
||||||
|
output_lines.append(f" - {item}")
|
||||||
|
|
||||||
|
final_output = "\n".join(output_lines)
|
||||||
|
print(final_output)
|
||||||
|
await emitter.success_update(final_output)
|
||||||
|
return final_output
|
||||||
|
else:
|
||||||
|
error_message = f"No information found for {ship_name}."
|
||||||
|
print(error_message)
|
||||||
|
await emitter.error_update(error_message)
|
||||||
|
return error_message
|
||||||
|
|
||||||
|
async def compare_ships(
|
||||||
|
self,
|
||||||
|
ship_name1: str,
|
||||||
|
ship_name2: str,
|
||||||
|
__event_emitter__: Callable[[dict], Any] = None,
|
||||||
|
):
|
||||||
|
# ship_name1 = ship_name1.title().replace(" ", "_")
|
||||||
|
# ship_name2 = ship_name2.title().replace(" ", "_")
|
||||||
|
|
||||||
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
await emitter.progress_update(
|
||||||
|
f"Fetching ship information for {ship_name1} and {ship_name2}"
|
||||||
|
)
|
||||||
|
info1 = await get_information().fetch_infos(ship_name1)
|
||||||
|
if info1:
|
||||||
|
await emitter.success_update(
|
||||||
|
f"Successfully fetched ship information for {ship_name1}"
|
||||||
|
)
|
||||||
|
output_lines = [f"Information for {ship_name1}:"]
|
||||||
|
if info1.get("general"):
|
||||||
|
await emitter.progress_update(
|
||||||
|
"Processing general information for " + ship_name1
|
||||||
|
)
|
||||||
|
output_lines.append("\n--- General Information ---")
|
||||||
|
for key, value in info1["general"].items():
|
||||||
|
if isinstance(value, list):
|
||||||
|
output_lines.append(f"{key}: {', '.join(value)}")
|
||||||
|
else:
|
||||||
|
if "Size" in key:
|
||||||
|
value = value.split()[0] if value else ""
|
||||||
|
if "Stowage" in key:
|
||||||
|
key = key.replace("Stowage", "Storage")
|
||||||
|
output_lines.append(f"{key}: {value}")
|
||||||
|
|
||||||
|
if info1.get("specifications"):
|
||||||
|
await emitter.progress_update(
|
||||||
|
"Processing specifications for " + ship_name1
|
||||||
|
)
|
||||||
|
output_lines.append("\n--- Specifications ---")
|
||||||
|
for spec_area, details in info1["specifications"].items():
|
||||||
|
if not details:
|
||||||
|
continue
|
||||||
|
output_lines.append(f"\n[{spec_area}]")
|
||||||
|
for category, items in details.items():
|
||||||
|
output_lines.append(f" {category}:")
|
||||||
|
for item in items:
|
||||||
|
output_lines.append(f" - {item}")
|
||||||
|
final_output1 = "\n".join(output_lines)
|
||||||
|
|
||||||
|
info2 = await get_information().fetch_infos(ship_name2)
|
||||||
|
if info2:
|
||||||
|
await emitter.success_update(
|
||||||
|
f"Successfully fetched ship information for {ship_name2}"
|
||||||
|
)
|
||||||
|
output_lines = [f"Information for {ship_name2}:"]
|
||||||
|
if info2.get("general"):
|
||||||
|
await emitter.progress_update(
|
||||||
|
"Processing general information for " + ship_name2
|
||||||
|
)
|
||||||
|
output_lines.append("\n--- General Information ---")
|
||||||
|
for key, value in info2["general"].items():
|
||||||
|
if isinstance(value, list):
|
||||||
|
output_lines.append(f"{key}: {', '.join(value)}")
|
||||||
|
else:
|
||||||
|
if "Size" in key:
|
||||||
|
value = value.split()[0] if value else ""
|
||||||
|
if "Stowage" in key:
|
||||||
|
key = key.replace("Stowage", "Storage")
|
||||||
|
output_lines.append(f"{key}: {value}")
|
||||||
|
if info2.get("specifications"):
|
||||||
|
await emitter.progress_update(
|
||||||
|
"Processing specifications for " + ship_name2
|
||||||
|
)
|
||||||
|
output_lines.append("\n--- Specifications ---")
|
||||||
|
for spec_area, details in info2["specifications"].items():
|
||||||
|
if not details:
|
||||||
|
continue
|
||||||
|
output_lines.append(f"\n[{spec_area}]")
|
||||||
|
for category, items in details.items():
|
||||||
|
output_lines.append(f" {category}:")
|
||||||
|
for item in items:
|
||||||
|
output_lines.append(f" - {item}")
|
||||||
|
|
||||||
|
final_output2 = "\n".join(output_lines)
|
||||||
|
await emitter.success_update(final_output2)
|
||||||
|
print(final_output1 + "\n\n" + final_output2)
|
||||||
|
return final_output1 + "\n\n" + final_output2
|
||||||
|
|
||||||
|
async def get_commodity_prices(
|
||||||
|
self, commodity_name: str, __event_emitter__: Callable[[dict], Any] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Fetch commodities from the database by name.
|
||||||
|
|
||||||
|
commodity_name: The name of the commodity to fetch.
|
||||||
|
"""
|
||||||
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
result_string = f"No information found for commodity '{commodity_name}'."
|
||||||
|
# First, check for spelling issues and compare it to the list of all commodity names available
|
||||||
|
try:
|
||||||
|
await emitter.progress_update(
|
||||||
|
f"Fetching commodity names from the database to find a match for '{commodity_name}'"
|
||||||
|
)
|
||||||
|
all_names = await get_information().fetch_all_commodity_names()
|
||||||
|
# The names are returned as a single string, split it into a list
|
||||||
|
names_list = all_names.splitlines()
|
||||||
|
best_match = process.extractOne(commodity_name, names_list)
|
||||||
|
if (
|
||||||
|
best_match and best_match[1] > 60
|
||||||
|
): # If the match is above 60% confidence
|
||||||
|
matched_commodity_name = best_match[0]
|
||||||
|
await emitter.success_update(
|
||||||
|
f"Found a close match for '{commodity_name}': {matched_commodity_name}"
|
||||||
|
)
|
||||||
|
conn = sqlite3.connect(self.db_path + "/commodities.db")
|
||||||
|
cursor = conn.cursor()
|
||||||
|
await emitter.progress_update(
|
||||||
|
f"Fetching buy and sell prices for '{matched_commodity_name}'"
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT price_buy, price_sell, terminal_name, commodity_name FROM commodity_prices WHERE commodity_name = ?",
|
||||||
|
(matched_commodity_name,),
|
||||||
|
)
|
||||||
|
await emitter.progress_update(
|
||||||
|
f"Processing results for '{matched_commodity_name}'"
|
||||||
|
)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
if rows:
|
||||||
|
output_lines = []
|
||||||
|
for row in rows:
|
||||||
|
buy_price = (
|
||||||
|
"Not buyable"
|
||||||
|
if int(row[0]) == 0
|
||||||
|
else f"{int(row[0])} aUEC"
|
||||||
|
)
|
||||||
|
sell_price = (
|
||||||
|
"not sellable"
|
||||||
|
if int(row[1]) == 0
|
||||||
|
else f"{int(row[1])} aUEC"
|
||||||
|
)
|
||||||
|
output_lines.append(
|
||||||
|
f"Item: {row[3]}, Buy Price: {buy_price} aUEC, Sell Price: {sell_price} aUEC, Terminal: {row[2]}"
|
||||||
|
)
|
||||||
|
result_string = "\n".join(output_lines)
|
||||||
|
await emitter.success_update(
|
||||||
|
f"Successfully fetched buy and sell prices for '{matched_commodity_name}'"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result_string = (
|
||||||
|
f"No price data found for '{matched_commodity_name}'."
|
||||||
|
)
|
||||||
|
await emitter.error_update(result_string)
|
||||||
|
else:
|
||||||
|
result_string = f"Could not find a confident match for commodity '{commodity_name}'. Best guess was '{best_match[0]}' with {best_match[1]}% confidence."
|
||||||
|
await emitter.error_update(result_string)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_message = f"An error occurred while fetching information for {commodity_name}: {str(e)}"
|
||||||
|
await emitter.error_update(error_message)
|
||||||
|
result_string = error_message
|
||||||
|
|
||||||
|
print(result_string)
|
||||||
|
return result_string
|
||||||
|
|
||||||
|
async def get_item_prices(
|
||||||
|
self, item_name: str, __event_emitter__: Callable[[dict], Any] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Fetch item prices from the database by name.
|
||||||
|
item_name: The name of the item to fetch.
|
||||||
|
"""
|
||||||
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
result_string = f"No information found for item '{item_name}'."
|
||||||
|
# First, check for spelling issues and compare it to the list of all item names available
|
||||||
|
try:
|
||||||
|
await emitter.progress_update(
|
||||||
|
f"Fetching item names from the database to find a match for '{item_name}'"
|
||||||
|
)
|
||||||
|
all_names = await get_information().fetch_all_item_names()
|
||||||
|
# The names are returned as a single string, split it into a list
|
||||||
|
names_list = all_names.splitlines()
|
||||||
|
best_match = process.extractOne(item_name, names_list)
|
||||||
|
if best_match and best_match[1] > 60:
|
||||||
|
matched_item_name = best_match[0]
|
||||||
|
await emitter.success_update(
|
||||||
|
f"Found a close match for '{item_name}': {matched_item_name}"
|
||||||
|
)
|
||||||
|
conn = sqlite3.connect(self.db_path + "/items.db")
|
||||||
|
cursor = conn.cursor()
|
||||||
|
await emitter.progress_update(
|
||||||
|
f"Fetching buy and sell prices for '{matched_item_name}'"
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT price_buy, price_sell, terminal_name, item_name FROM item_prices WHERE item_name = ?",
|
||||||
|
(matched_item_name,),
|
||||||
|
)
|
||||||
|
await emitter.progress_update(
|
||||||
|
f"Processing results for '{matched_item_name}'"
|
||||||
|
)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
if rows:
|
||||||
|
output_lines = []
|
||||||
|
for row in rows:
|
||||||
|
buy_price = (
|
||||||
|
"Not buyable"
|
||||||
|
if int(row[0]) == 0
|
||||||
|
else f"{int(row[0])} aUEC"
|
||||||
|
)
|
||||||
|
sell_price = (
|
||||||
|
"not sellable"
|
||||||
|
if int(row[1]) == 0
|
||||||
|
else f"{int(row[1])} aUEC"
|
||||||
|
)
|
||||||
|
output_lines.append(
|
||||||
|
f"Item: {row[3]}, Buy Price: {buy_price}, Sell Price: {sell_price}, Terminal: {row[2]}"
|
||||||
|
)
|
||||||
|
result_string = "\n".join(output_lines)
|
||||||
|
await emitter.success_update(
|
||||||
|
f"Successfully fetched buy and sell prices for '{matched_item_name}'"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result_string = f"No price data found for '{matched_item_name}'."
|
||||||
|
await emitter.error_update(result_string)
|
||||||
|
else:
|
||||||
|
result_string = f"Could not find a confident match for item '{item_name}'. Best guess was '{best_match[0]}' with {best_match[1]}% confidence."
|
||||||
|
await emitter.error_update(result_string)
|
||||||
|
except Exception as e:
|
||||||
|
error_message = f"An error occurred while fetching information for {item_name}: {str(e)}"
|
||||||
|
await emitter.error_update(error_message)
|
||||||
|
result_string = error_message
|
||||||
|
print(result_string)
|
||||||
|
return result_string
|
||||||
|
|
||||||
|
async def get_ship_owners(self, ship_name: str, __event_emitter__: Callable[[dict], Any] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Fetches the owners of a specific ship from the fleet.db sqlite database.
|
||||||
|
|
||||||
|
ship_name: The name of the ship to fetch owners for.
|
||||||
|
"""
|
||||||
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
result_string = f"No owners found for ship '{ship_name}'."
|
||||||
|
try:
|
||||||
|
await emitter.progress_update(
|
||||||
|
f"Fetching owners for ship '{ship_name}' from the database"
|
||||||
|
)
|
||||||
|
available_ships = await get_information().get_all_ship_names_from_fleetyard_db()
|
||||||
|
# The names are returned as a single string, split it into a list
|
||||||
|
ships_list = available_ships.splitlines()
|
||||||
|
best_match = process.extractOne(ship_name, ships_list)
|
||||||
|
if best_match and best_match[1] > 60:
|
||||||
|
matched_ship_name = best_match[0]
|
||||||
|
await emitter.success_update(
|
||||||
|
f"Found a close match for '{ship_name}': {matched_ship_name}"
|
||||||
|
)
|
||||||
|
print(f'found a close match for "{ship_name}": {matched_ship_name}')
|
||||||
|
conn = sqlite3.connect(self.db_path + "/fleet.db")
|
||||||
|
cursor = conn.cursor()
|
||||||
|
await emitter.progress_update(
|
||||||
|
f"Fetching owners for ship '{matched_ship_name}' from the database"
|
||||||
|
)
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT manufacturerName, name, usernames FROM ship_owner_list WHERE name = ?",
|
||||||
|
(matched_ship_name,),
|
||||||
|
)
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
if rows:
|
||||||
|
owners = [row[2] for row in rows]
|
||||||
|
manufacturer_name = rows[0][0]
|
||||||
|
matched_ship_name = rows[0][1]
|
||||||
|
result_string = f"Please report these to the user in a bulletpoint list:\nOwners of ship {manufacturer_name} {matched_ship_name}: {', '.join(owners)}"
|
||||||
|
except Exception as e:
|
||||||
|
error_message = f"An error occurred while fetching owners for {ship_name}: {str(e)}"
|
||||||
|
await emitter.error_update(error_message)
|
||||||
|
result_string = error_message
|
||||||
|
await emitter.progress_update(result_string)
|
||||||
|
print(result_string)
|
||||||
|
return result_string
|
||||||
|
|
||||||
|
async def list_purchasable_ships(
|
||||||
|
self, __event_emitter__: Callable[[dict], Any] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Fetches all buyable ships, their prices, and locations from the Star Citizen Tools wiki.
|
||||||
|
"""
|
||||||
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
api_url = "https://starcitizen.tools/api.php"
|
||||||
|
ship_data = {}
|
||||||
|
page_title = "Purchasing_ships"
|
||||||
|
|
||||||
|
await emitter.progress_update(f"Fetching data from {page_title}...")
|
||||||
|
params = {
|
||||||
|
"action": "parse",
|
||||||
|
"page": page_title,
|
||||||
|
"format": "json",
|
||||||
|
"prop": "text",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = await asyncio.to_thread(requests.get, api_url, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if "error" in data:
|
||||||
|
await emitter.error_update(
|
||||||
|
f"API Error for {page_title}: {data['error']['info']}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
html_content = data.get("parse", {}).get("text", {}).get("*", "")
|
||||||
|
if not html_content:
|
||||||
|
await emitter.error_update(f"No content found for {page_title}.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await emitter.progress_update(f"Parsing data from {page_title}...")
|
||||||
|
soup = BeautifulSoup(html_content, "html.parser")
|
||||||
|
tables = soup.find_all("table", class_="wikitable")
|
||||||
|
|
||||||
|
for table in tables:
|
||||||
|
header_row = table.find("tr")
|
||||||
|
if not header_row:
|
||||||
|
continue
|
||||||
|
headers = [th.get_text(strip=True) for th in header_row.find_all("th")]
|
||||||
|
|
||||||
|
rows = table.find_all("tr")[1:]
|
||||||
|
for row in rows:
|
||||||
|
cells = row.find_all("td")
|
||||||
|
if not cells or len(cells) < 3:
|
||||||
|
continue
|
||||||
|
|
||||||
|
ship_name_tag = cells[1].find("a")
|
||||||
|
if not ship_name_tag or not ship_name_tag.get("title"):
|
||||||
|
continue
|
||||||
|
ship_name = ship_name_tag.get("title").strip()
|
||||||
|
price = cells[2].get_text(strip=True)
|
||||||
|
|
||||||
|
if ship_name not in ship_data:
|
||||||
|
ship_data[ship_name] = []
|
||||||
|
|
||||||
|
location_headers = headers[3:]
|
||||||
|
for i, cell in enumerate(cells[3:]):
|
||||||
|
if "✔" in cell.get_text():
|
||||||
|
location = location_headers[i]
|
||||||
|
ship_data[ship_name].append(
|
||||||
|
{"price": price + " aUEC (alpha United Earth Credits)", "location": location}
|
||||||
|
)
|
||||||
|
|
||||||
|
await emitter.success_update(f"Successfully processed {page_title}.")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
await emitter.error_update(f"Error fetching data for {page_title}: {e}")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
await emitter.error_update(f"Error decoding JSON for {page_title}.")
|
||||||
|
|
||||||
|
output_lines = []
|
||||||
|
for ship_name, locations in sorted(ship_data.items()):
|
||||||
|
output_lines.append(f"\n--- {ship_name} ---")
|
||||||
|
output_lines.append("Buyable at:")
|
||||||
|
for item in locations:
|
||||||
|
output_lines.append(
|
||||||
|
f" - Location: {item['location']}, Price: {item['price']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
final_output = "\n".join(output_lines)
|
||||||
|
await emitter.success_update(f"Found {len(ship_data)} unique buyable ships.")
|
||||||
|
print(final_output)
|
||||||
|
return final_output
|
||||||
|
|
||||||
|
async def list_rentable_ships(
|
||||||
|
self, __event_emitter__: Callable[[dict], Any] = None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Fetches all rentable ships, their prices, and locations from the Star Citizen Tools wiki.
|
||||||
|
"""
|
||||||
|
emitter = EventEmitter(__event_emitter__)
|
||||||
|
api_url = "https://starcitizen.tools/api.php"
|
||||||
|
ship_prices = {}
|
||||||
|
ship_locations = {}
|
||||||
|
page_title = "Ship_renting"
|
||||||
|
|
||||||
|
await emitter.progress_update(f"Fetching data from {page_title}...")
|
||||||
|
params = {
|
||||||
|
"action": "parse",
|
||||||
|
"page": page_title,
|
||||||
|
"format": "json",
|
||||||
|
"prop": "text",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = await asyncio.to_thread(requests.get, api_url, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
if "error" in data:
|
||||||
|
await emitter.error_update(
|
||||||
|
f"API Error for {page_title}: {data['error']['info']}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
html_content = data.get("parse", {}).get("text", {}).get("*", "")
|
||||||
|
if not html_content:
|
||||||
|
await emitter.error_update(f"No content found for {page_title}.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await emitter.progress_update(f"Parsing data from {page_title}...")
|
||||||
|
soup = BeautifulSoup(html_content, "html.parser")
|
||||||
|
tables = soup.find_all("table", class_="wikitable")
|
||||||
|
|
||||||
|
for table in tables:
|
||||||
|
header_row = table.find("tr")
|
||||||
|
if not header_row:
|
||||||
|
continue
|
||||||
|
headers = [th.get_text(strip=True) for th in header_row.find_all("th")]
|
||||||
|
rows = table.find_all("tr")[1:]
|
||||||
|
|
||||||
|
# Table 1: Ship rental prices
|
||||||
|
if "1 Day" in headers and "Location" in headers:
|
||||||
|
for row in rows:
|
||||||
|
cells = row.find_all("td")
|
||||||
|
if len(cells) < 8:
|
||||||
|
continue
|
||||||
|
ship_name_tag = cells[1].find("a")
|
||||||
|
if not ship_name_tag or not ship_name_tag.get("title"):
|
||||||
|
continue
|
||||||
|
ship_name = ship_name_tag.get("title").strip()
|
||||||
|
ship_prices[ship_name] = {
|
||||||
|
"1_day": cells[3].get_text(strip=True),
|
||||||
|
"3_days": cells[4].get_text(strip=True),
|
||||||
|
"7_days": cells[5].get_text(strip=True),
|
||||||
|
"30_days": cells[6].get_text(strip=True),
|
||||||
|
}
|
||||||
|
# Table 2: Ship rental locations
|
||||||
|
elif "Area18" in headers:
|
||||||
|
location_headers = headers[3:]
|
||||||
|
for row in rows:
|
||||||
|
cells = row.find_all("td")
|
||||||
|
if len(cells) < 4:
|
||||||
|
continue
|
||||||
|
ship_name_tag = cells[1].find("a")
|
||||||
|
if not ship_name_tag or not ship_name_tag.get("title"):
|
||||||
|
continue
|
||||||
|
ship_name = ship_name_tag.get("title").strip()
|
||||||
|
if ship_name not in ship_locations:
|
||||||
|
ship_locations[ship_name] = []
|
||||||
|
for i, cell in enumerate(cells[3:]):
|
||||||
|
if "✔" in cell.get_text():
|
||||||
|
ship_locations[ship_name].append(location_headers[i])
|
||||||
|
|
||||||
|
await emitter.success_update(f"Successfully processed {page_title}.")
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
await emitter.error_update(f"Error fetching data for {page_title}: {e}")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
await emitter.error_update(f"Error decoding JSON for {page_title}.")
|
||||||
|
|
||||||
|
output_lines = []
|
||||||
|
for ship_name, locations in sorted(ship_locations.items()):
|
||||||
|
if not locations:
|
||||||
|
continue
|
||||||
|
output_lines.append(f"\n--- {ship_name} ---")
|
||||||
|
output_lines.append("Rentable at:")
|
||||||
|
prices = ship_prices.get(ship_name, {})
|
||||||
|
for location in locations:
|
||||||
|
output_lines.append(f" - Location: {location}")
|
||||||
|
if prices:
|
||||||
|
output_lines.append(
|
||||||
|
f" - 1 Day: {prices.get('1_day', 'N/A')}, 3 Days: {prices.get('3_days', 'N/A')}, 7 Days: {prices.get('7_days', 'N/A')}, 30 Days: {prices.get('30_days', 'N/A')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
final_output = "\n".join(output_lines)
|
||||||
|
await emitter.success_update(
|
||||||
|
f"Found {len(ship_locations)} unique rentable ships."
|
||||||
|
)
|
||||||
|
print(final_output)
|
||||||
|
return final_output
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
info_printer = Tools()
|
||||||
|
asyncio.run(info_printer.get_ship_owners("Perseus"))
|
||||||
Loading…
x
Reference in New Issue
Block a user