23 Commits

Author SHA1 Message Date
uberwald adf9b0b2e6 Réglage de la difficulté par défaut
Release Creation / build (release) Successful in 1m16s
2026-05-29 19:26:31 +02:00
uberwald 5c2a364832 Réglage de la difficulté par défaut
Release Creation / build (release) Successful in 1m13s
2026-05-29 18:28:09 +02:00
uberwald ec0e522145 Add ranged attacks for monsters
Release Creation / build (release) Successful in 1m13s
2026-04-29 22:24:42 +02:00
uberwald 8123b53f75 Import de personnages du précédent système
Release Creation / build (release) Successful in 1m21s
2026-04-27 22:29:49 +02:00
uberwald 58478d56ea Amélioration du rendu des polices
Release Creation / build (release) Successful in 1m22s
2026-04-27 21:54:04 +02:00
uberwald a99eeaccba Corrections sur font, again v3
Release Creation / build (release) Successful in 1m35s
2026-04-27 17:40:59 +02:00
uberwald 2ab380786f Correction sur O dans police de titre, step2
Release Creation / build (release) Successful in 1m12s
2026-04-27 06:39:11 +02:00
uberwald c3ce628e24 Ameliorations CSS et font diverses
Release Creation / build (release) Successful in 1m25s
2026-04-26 22:44:23 +02:00
uberwald 8e5fb9aca1 Correction sur jauge de destin
Release Creation / build (release) Successful in 1m26s
2026-04-16 22:26:44 +02:00
uberwald 1b2a74969d Update READM.md 2026-04-16 08:39:52 +02:00
uberwald 98a6a41078 Ajout README.md 2026-04-15 11:53:52 +02:00
uberwald c73136b3c9 Ajout README.md 2026-04-15 11:41:44 +02:00
uberwald 49996104ce Roll and styles update
Release Creation / build (release) Failing after 1m17s
2026-04-15 02:16:51 +02:00
uberwald b3cf0b0aa1 Corrections après tests de combat
Release Creation / build (release) Failing after 1m34s
2026-04-14 18:59:09 +02:00
uberwald 63c0153860 FIx v13/v14
Release Creation / build (release) Failing after 1m17s
2026-04-14 00:56:25 +02:00
uberwald 8bfbdedf43 Upgrade compat 2026-04-13 17:33:06 +02:00
uberwald 6d2fca9fc2 Cleanup 2026-04-13 16:34:38 +02:00
uberwald b26ce2f114 Diverses corrections autour du combat 2026-04-13 16:23:31 +02:00
uberwald 8b75a15e3f Diverses corrections autour du combat
Release Creation / build (release) Failing after 1m5s
2026-04-13 15:55:40 +02:00
uberwald d69144f506 Corrections diverses autout du combat 2026-04-13 14:19:24 +02:00
uberwald 44cc07db73 Portraits et corrections sur valeurs des PNJ
Release Creation / build (release) Failing after 1m24s
2026-04-12 11:52:17 +02:00
uberwald 7a2be0cc0e Update compendium and welcom chat message
Release Creation / build (release) Failing after 1m41s
2026-04-11 15:29:44 +02:00
uberwald 3358dea306 Corrections sur factions, aspects, degats et fiches PNJs 2026-04-11 15:02:46 +02:00
152 changed files with 12813 additions and 649 deletions
+1 -1
View File
@@ -68,4 +68,4 @@ jobs:
manifest: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/latest/system.json' manifest: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/latest/system.json'
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-celestopol-${{github.event.release.tag_name}}.zip' notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-celestopol-${{github.event.release.tag_name}}.zip'
compatibility-minimum: '13' compatibility-minimum: '13'
compatibility-verified: '13' compatibility-verified: '14'
+1
View File
@@ -13,3 +13,4 @@ css/*.css
# Règles (PDFs privés) # Règles (PDFs privés)
__regles/ __regles/
*.pdf *.pdf
.github/
+84
View File
@@ -0,0 +1,84 @@
# Célestopol 1922 — Système FoundryVTT
Système [Foundry VTT](https://foundryvtt.com) pour **Célestopol 1922**, le jeu de rôle d'[Antre Monde Éditions](https://antremonde.fr).
Copyright 2025-2026 Antre Monde Editions All rights reserved
Celestopol 1922 is a game written by Emmanuel Chastellière and Julien Arnaud. The authors retain their moral rights to this work in both print and digital formats.
This system for FoundryVTT has been approved and authorized by Antre-Monde Edition
---
## ✨ Fonctionnalités
### Personnages joueurs (PJ)
- **4 attributs** (Âme, Corps, Cœur, Esprit) × **4 compétences** chacun (16 compétences au total)
- Gestion des **Blessures** (8 niveaux avec malus automatique aux jets)
- Suivi du **Destin** et du **Spleen** (jauges à 8 crans)
- **Anomalies** : 9 types, déclenchées sur certains résultats de dé
- **Factions** : 8 factions standard + 2 personnalisées, avec aspects liés
- **Équipement** : armes, armures, équipements génériques
- Onglets dédiés : Principal · Compétences · Factions · Équipement · Combat · Biographie
### Personnages non-joueurs (PNJ)
- Fiche simplifiée : compétences, blessures, équipement, biographie
### Items
- **Arme** : mêlée ou à distance, modificateurs de portée
- **Armure** : protection et malus éventuels
- **Aspect de faction** : lié à une faction, bonus / usage limité
- **Anomalie** : type, usages, description
- **Équipement** générique
### Mécanique de jet de désde démonstration
- **Pool de d6** : `max(1, compétence + malus_blessures)` dés
- **Bonus Phase de Lune** : automatique selon la phase en cours (0 à +3)
- **Modificateurs** : difficulté et bonus libres via DialogV2
- Seuil de succès standard : **7**
- Résultats spéciaux : **Brio** (+destin), **Contrecoup** (destin), **Triomphe** (+anomalie/spleen), **Catastrophe** (anomalie/+spleen)
### Dé de la Lune
- Jet de dé de lune indépendant (menu contextuel du token)
- 8 faces : Nouvelle Lune, Premiers Croissants, Dernier Croissant, Gibbeuses ×2, Pleine Lune, Lune de Feu, Lune Noire
- Sur certains résultats : **choix de contrepartie** via pop-up (perte d'anomalie, spleen, ou échec catastrophique)
- Carte de résultat avec boutons d'application directe
### Combat
- **Initiative Célestopol** : jet d'initiative avec dé spécial, insertion dans le tracker
- Attaque avec calcul automatique dommages / protection
- Chat de combat avec récapitulatif des dégâts et blessures appliqués
### Compendiums inclus
| Pack | Contenu |
| ------------ | ------------------------------ |
| Anomalies | Les 8 anomalies du jeu de base |
| Prétirés | 8 personnages prêts à jouer |
| Aides de jeu | Aide du système |
| Scènes | Scènes utiles |
## 🎨 Charte graphique
- Police de titre : **CopaseticNF** (Regular + Bold)
- Vert sombre en-tête : `#0c4c0c`
- Orange texte : `#e07b00`
- Fond parchemin : `#f0e8d4`
- Texture en-tête : `assets/ui/fond_cadrille.jpg`
---
## 👤 Auteur
**LeRatierBretonnien / Uberwald**
- Discord : `LeRatierBretonnien`
- Site : [uberwald.me](https://www.uberwald.me)
---
## 📜 Licence
Copyright 2025 Antre Monde Editions All rights reserved
Celestopol 1922 is a game written by Emmanuel Chastellière and Julien Arnaud. The authors retain their moral rights to this work in both print and digital formats.
This system for FoundryVTT has been approved and authorized by Antre-Monde Edition.
+368
View File
@@ -0,0 +1,368 @@
#!/usr/bin/env python3
"""
convert-old-system.py
Convertit les fichiers JSON d'acteurs exportés de l'ancien système Célestopol 1922
(system id: celestopol1922) vers le format du nouveau système (fvtt-celestopol).
Usage:
python3 convert-old-system.py <fichier.json> [fichier2.json ...]
Le fichier converti est écrit à côté du fichier source sous le nom :
<nom>-converted.json
"""
import json
import os
import re
import sys
import uuid
# ─── Mapping des types d'anomalies (préfixe "CEL1922.opt." → clé nouveau système) ─────
ANOMALY_TYPE_MAP = {
"none": "none",
"entropie": "entropie",
"communicationaveclesmorts": "communicationaveclesmorts",
"illusion": "illusion",
"suggestion": "suggestion",
"tarotdivinatoire": "tarotdivinatoire",
"telekinesie": "telekinesie",
"telepathie": "telepathie",
"voyageastral": "voyageastral",
}
# Clé anomaly vide → "none"
VALID_ANOMALY_TYPES = set(ANOMALY_TYPE_MAP.values())
# ─── Domaines (stats) ──────────────────────────────────────────────────────────────────
STATS = ["ame", "corps", "coeur", "esprit"]
# Compétences par domaine (dans l'ordre du nouveau système)
SKILLS = {
"ame": ["artifice", "attraction", "coercition", "faveur"],
"corps": ["echauffouree", "effacement", "mobilite", "prouesse"],
"coeur": ["appreciation", "arts", "inspiration", "traque"],
"esprit": ["instruction", "mtechnologique", "raisonnement", "traitement"],
}
def make_id():
"""Génère un identifiant Foundry-compatible (16 chars hex)."""
return uuid.uuid4().hex[:16]
def strip_prefix(label: str) -> str:
"""Extrait la clé depuis 'CEL1922.opt.<clé>'."""
return label.split(".")[-1]
def resolve_anomaly_type(old_system: dict) -> str:
"""
Résout le type d'anomalie depuis l'ancien système.
old_system.anomaly est un int (index dans anomalytypes[]).
"""
anomalytypes = old_system.get("skill", {}).get("anomalytypes", [])
idx = old_system.get("anomaly", 0)
try:
idx = int(idx)
except (ValueError, TypeError):
idx = 0
if idx == 0 or not anomalytypes:
return "none"
if idx < len(anomalytypes):
raw = strip_prefix(anomalytypes[idx])
return ANOMALY_TYPE_MAP.get(raw, "none")
return "none"
def resolve_anomaly_type_from_name(item_name: str) -> str:
"""
Infère le type d'anomalie depuis le nom de l'item (ex: 'Entropie 1''entropie').
Fallback si le mapping par index est insuffisant.
"""
# Normalise : minuscules, retire accents, retire espaces et chiffres
name_clean = item_name.lower().strip()
name_clean = re.sub(r"\s*\d+\s*$", "", name_clean).strip() # retire le niveau en fin
name_clean = re.sub(r"\s+", "", name_clean) # retire tous les espaces
# Normalisation des accents courants
replacements = [
("é", "e"), ("è", "e"), ("ê", "e"), ("ë", "e"),
("à", "a"), ("â", "a"), ("ä", "a"),
("î", "i"), ("ï", "i"),
("ô", "o"), ("ö", "o"),
("û", "u"), ("ù", "u"), ("ü", "u"),
("ç", "c"),
]
for src, dst in replacements:
name_clean = name_clean.replace(src, dst)
for key in ANOMALY_TYPE_MAP:
if key == "none":
continue
if key in name_clean or name_clean in key:
return key
return "none"
def convert_stats_character(old_skill: dict, warnings: list) -> dict:
"""Convertit system.skill (ancien) → system.stats (nouveau) pour un PJ."""
stats = {}
for stat in STATS:
old_stat = old_skill.get(stat, {})
new_stat = {
"label": stat,
"res": int(old_stat.get("res", 0) or 0),
}
for skill in SKILLS[stat]:
old_sk = old_stat.get(skill, {})
val = old_sk.get("value", 0)
try:
val = int(val) if val is not None else 0
except (ValueError, TypeError):
warnings.append(f"Valeur de compétence invalide pour {stat}.{skill}: {val!r} → 0")
val = 0
new_stat[skill] = {"label": skill, "value": val}
stats[stat] = new_stat
return stats
def convert_stats_npc(old_skill: dict, warnings: list) -> dict:
"""Convertit system.skill (ancien) → system.stats (nouveau) pour un PNJ."""
stats = {}
for stat in STATS:
old_stat = old_skill.get(stat, {})
res = int(old_stat.get("res", 0) or 0)
stats[stat] = {
"label": stat,
"res": res,
"actuel": res, # actuel = res par défaut
}
return stats
def convert_attributs(old_attributs: dict) -> dict:
"""Convertit les attributs du vieux format plat vers SchemaField {value, max}."""
return {
"entregent": {
"value": int(old_attributs.get("entregent", 0) or 0),
"max": int(old_attributs.get("entregentmax", 0) or 0),
},
"fortune": {
"value": int(old_attributs.get("fortune", 0) or 0),
"max": int(old_attributs.get("fortunemax", 0) or 0),
},
"reve": {
"value": int(old_attributs.get("reve", 0) or 0),
"max": int(old_attributs.get("revemax", 0) or 0),
},
"vision": {
"value": int(old_attributs.get("vision", 0) or 0),
"max": int(old_attributs.get("visionmax", 0) or 0),
},
}
def convert_item(item: dict, warnings: list) -> dict | None:
"""
Convertit un item de l'ancien format vers le nouveau.
Retourne None si l'item doit être ignoré (type inconnu sans données utiles).
"""
old_type = item.get("type", "")
old_sys = item.get("system", {})
name = item.get("name", "Sans nom")
new_item = {
"_id": make_id(),
"name": name,
"img": item.get("img", "icons/svg/item-bag.svg"),
"effects": [],
"flags": {},
"sort": 0,
}
if old_type == "anomaly":
level = 0
try:
level = int(old_sys.get("value", 1) or 1)
except (ValueError, TypeError):
level = 1
level = max(1, min(4, level))
# Résolution du subtype : l'ancien système le stocke mal ("weapon"), on l'infère du nom
subtype = resolve_anomaly_type_from_name(name)
if subtype == "none":
warnings.append(f"Impossible de déterminer le type d'anomalie depuis '{name}', 'none' utilisé")
new_item["type"] = "anomaly"
new_item["system"] = {
"subtype": subtype,
"level": level,
"usesRemaining": level,
"technique": old_sys.get("technique", "") or "",
"narratif": old_sys.get("narratif", "") or "",
}
elif old_type == "aspect":
valeur = 0
try:
valeur = int(old_sys.get("value", 0) or 0)
except (ValueError, TypeError):
valeur = 0
new_item["type"] = "aspect"
new_item["system"] = {
"valeur": valeur,
"description": old_sys.get("narratif", "") or old_sys.get("technique", "") or "",
}
elif old_type == "item":
old_subtype = (old_sys.get("subtype") or "").lower().strip()
damage = old_sys.get("damage", "") or ""
protect = old_sys.get("protection", "") or ""
if old_subtype == "weapon" or (damage and not protect):
new_item["type"] = "weapon"
new_item["system"] = {
"type": "melee",
"degats": "0",
"portee": "contact",
"equipped": False,
"description": old_sys.get("technique", "") or old_sys.get("narratif", "") or "",
}
if damage:
warnings.append(f"Item '{name}' : valeur damage='{damage}' non convertie automatiquement, à saisir manuellement")
elif old_subtype == "armor" or (protect and not damage):
new_item["type"] = "armure"
new_item["system"] = {
"protection": 1,
"malus": 1,
"equipped": False,
"description": old_sys.get("technique", "") or old_sys.get("narratif", "") or "",
}
if protect:
warnings.append(f"Item '{name}' : valeur protection='{protect}' non convertie automatiquement, à saisir manuellement")
else:
# Équipement générique
new_item["type"] = "equipment"
new_item["system"] = {
"description": old_sys.get("technique", "") or old_sys.get("narratif", "") or "",
}
else:
warnings.append(f"Type d'item inconnu '{old_type}' pour '{name}', ignoré")
return None
return new_item
def convert_actor(old: dict) -> tuple[dict, list[str]]:
"""Convertit un acteur complet. Retourne (new_actor, warnings)."""
warnings = []
actor_type = old.get("type", "character")
old_sys = old.get("system", {})
old_skill = old_sys.get("skill", {})
new_sys = {}
# ── Champs communs ──────────────────────────────────────────────────────────────
new_sys["concept"] = old_sys.get("concept", "") or ""
new_sys["description"] = old_sys.get("description", "") or "" # gardé dans metier/concept
new_sys["metier"] = old_sys.get("metier", "") or old_sys.get("concept", "") or ""
new_sys["faction"] = old_sys.get("faction", "") or ""
# ── Blessures / Destin / Spleen ─────────────────────────────────────────────────
new_sys["blessures"] = {"lvl": int(old_sys.get("blessures", {}).get("lvl", 0) or 0)}
new_sys["destin"] = {"lvl": int(old_sys.get("destin", {}).get("lvl", 0) or 0)}
new_sys["spleen"] = {"lvl": int(old_sys.get("spleen", {}).get("lvl", 0) or 0)}
# ── Stats ───────────────────────────────────────────────────────────────────────
if actor_type == "character":
new_sys["stats"] = convert_stats_character(old_skill, warnings)
# Anomalie : type depuis l'index, niveau depuis anomalyval
anomaly_type = resolve_anomaly_type(old_sys)
anomaly_val = int(old_sys.get("anomalyval", 0) or 0)
new_sys["anomaly"] = {"type": anomaly_type, "value": anomaly_val}
# Attributs
new_sys["attributs"] = convert_attributs(old_sys.get("attributs", {}))
# Biographie → historique
new_sys["historique"] = old_sys.get("description", "") or ""
new_sys["descriptionPhysique"] = ""
new_sys["descriptionPsychologique"] = ""
new_sys["initiative"] = 0
# Factions (vide)
new_sys["factions"] = old_sys.get("factions", {})
elif actor_type == "npc":
new_sys["stats"] = convert_stats_npc(old_skill, warnings)
new_sys["npcType"] = old_sys.get("npcType", "standard") or "standard"
new_sys["historique"] = old_sys.get("description", "") or ""
# ── Items ───────────────────────────────────────────────────────────────────────
new_items = []
for item in old.get("items", []):
converted = convert_item(item, warnings)
if converted:
new_items.append(converted)
new_actor = {
"_id": make_id(),
"name": old.get("name", "Personnage sans nom"),
"type": actor_type,
"img": old.get("img", "icons/svg/mystery-man.svg"),
"system": new_sys,
"items": new_items,
"effects": [],
"folder": None,
"flags": {},
"prototypeToken": old.get("prototypeToken", {}),
}
return new_actor, warnings
def convert_file(path: str) -> None:
"""Lit path, convertit, écrit <base>-converted.json."""
print(f"\n{'' * 60}")
print(f"Traitement : {path}")
with open(path, "r", encoding="utf-8") as f:
old = json.load(f)
new_actor, warnings = convert_actor(old)
for w in warnings:
print(f"{w}")
base, _ = os.path.splitext(path)
out_path = f"{base}-converted.json"
with open(out_path, "w", encoding="utf-8") as f:
json.dump(new_actor, f, ensure_ascii=False, indent=2)
print(f" ✓ Écrit : {out_path}")
print(f" type={new_actor['type']} items={len(new_actor['items'])} avertissements={len(warnings)}")
def main():
if len(sys.argv) < 2:
print("Usage: python3 convert-old-system.py <fichier.json> [fichier2.json ...]")
sys.exit(1)
for path in sys.argv[1:]:
if not os.path.isfile(path):
print(f"Fichier introuvable : {path}", file=sys.stderr)
continue
convert_file(path)
print(f"\n{'' * 60}")
print("Conversion terminée.")
if __name__ == "__main__":
main()
@@ -0,0 +1,143 @@
{
"_id": "e9e583fc41454c90",
"name": "Ajax",
"type": "npc",
"img": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/tokenizer/celestopol/ajax.Avatar.webp?1719437847752",
"system": {
"concept": "",
"description": "",
"metier": "",
"faction": "",
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"stats": {
"ame": {
"label": "ame",
"res": 0,
"actuel": 0
},
"corps": {
"label": "corps",
"res": 0,
"actuel": 0
},
"coeur": {
"label": "coeur",
"res": 0,
"actuel": 0
},
"esprit": {
"label": "esprit",
"res": 0,
"actuel": 0
}
},
"npcType": "standard",
"historique": ""
},
"items": [],
"effects": [],
"folder": null,
"flags": {},
"prototypeToken": {
"name": "Ajax",
"displayName": 0,
"actorLink": false,
"appendNumber": false,
"prependAdjective": false,
"texture": {
"src": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/tokenizer/celestopol/ajax.Token.webp?1719437847752",
"scaleX": 1,
"scaleY": 1,
"offsetX": 0,
"offsetY": 0,
"rotation": 0,
"anchorX": 0.5,
"anchorY": 0.5,
"fit": "contain",
"tint": "#ffffff",
"alphaThreshold": 0.75
},
"width": 1,
"height": 1,
"lockRotation": false,
"rotation": 0,
"alpha": 1,
"disposition": -1,
"displayBars": 0,
"bar1": {
"attribute": null
},
"bar2": {
"attribute": null
},
"light": {
"alpha": 0.5,
"angle": 360,
"bright": 0,
"coloration": 1,
"dim": 0,
"attenuation": 0.5,
"luminosity": 0.5,
"saturation": 0,
"contrast": 0,
"shadows": 0,
"animation": {
"type": null,
"speed": 5,
"intensity": 5,
"reverse": false
},
"darkness": {
"min": 0,
"max": 1
},
"negative": false,
"priority": 0,
"color": null
},
"sight": {
"enabled": true,
"range": 30,
"angle": 360,
"visionMode": "basic",
"color": null,
"attenuation": 0.1,
"brightness": 0,
"saturation": 0,
"contrast": 0
},
"detectionModes": [],
"flags": {},
"randomImg": false,
"occludable": {
"radius": 0
},
"ring": {
"enabled": false,
"colors": {
"ring": null,
"background": null
},
"effects": 1,
"subject": {
"scale": 1,
"texture": null
}
},
"turnMarker": {
"mode": 1,
"animation": null,
"src": null,
"disposition": false
},
"movementAction": null
}
}
@@ -0,0 +1,459 @@
{
"folder": "fbRXVyFLLyOpuiTd",
"name": "Ajax",
"type": "npc",
"img": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/tokenizer/celestopol/ajax.Avatar.webp?1719437847752",
"system": {
"concept": "",
"anomaly": "0",
"anomalyval": 0,
"anomalymax": "0",
"initiative": 0,
"description": "",
"prefs": {
"typeofthrow": {
"check": true,
"choice": "0"
}
},
"prompt": {
"typeofthrow": {
"check": true,
"choice": "0"
},
"configure": {
"numberofdice": 0,
"aspect": 0,
"bonus": 0,
"bonusauspiciousdice": 0,
"typeofthrow": 0,
"aspectskill": 0,
"bonusmalusskill": 0,
"aspectspeciality": 0,
"rolldifficulty": 0,
"bonusmalusspeciality": 0
}
},
"skill": {
"skilltypes": [
"CEL1922.opt.ame",
"CEL1922.opt.attraction",
"CEL1922.opt.artifice",
"CEL1922.opt.coercition",
"CEL1922.opt.faveur",
"CEL1922.opt.corps",
"CEL1922.opt.echauffouree",
"CEL1922.opt.effacement",
"CEL1922.opt.prouesse",
"CEL1922.opt.mobilite",
"CEL1922.opt.coeur",
"CEL1922.opt.appreciation",
"CEL1922.opt.arts",
"CEL1922.opt.inspiration",
"CEL1922.opt.traque",
"CEL1922.opt.esprit",
"CEL1922.opt.instruction",
"CEL1922.opt.mtechnologique",
"CEL1922.opt.raisonnement",
"CEL1922.opt.traitement"
],
"moondicetypes": [
"CEL1922.opt.nouvellelune",
"CEL1922.opt.premiercroissant",
"CEL1922.opt.premierquartier",
"CEL1922.opt.lunegibbeuse",
"CEL1922.opt.lunevoutee",
"CEL1922.opt.derniercroissant",
"CEL1922.opt.dernierquartier",
"CEL1922.opt.pleinelune"
],
"woundstypes": [
"CEL1922.opt.none",
"CEL1922.opt.anodin",
"CEL1922.opt.derisoire",
"CEL1922.opt.negligeable",
"CEL1922.opt.superficiel",
"CEL1922.opt.leger",
"CEL1922.opt.modere",
"CEL1922.opt.grave",
"CEL1922.opt.dramatique"
],
"woundsmalus": [
0,
0,
0,
-1,
-1,
-2,
-2,
-3,
-999
],
"woundsrecup": [
"CEL1922.opt.none",
"CEL1922.1 minute",
"CEL1922.1 minute",
"CEL1922.10 minutes",
"CEL1922.10 minutes",
"CEL1922.30 minutes",
"CEL1922.30 minutes",
"CEL1922.1 day",
"CEL1922.Out of Fiction"
],
"destinytypes": [
"CEL1922.opt.none",
"CEL1922.opt.libel1",
"CEL1922.opt.libel2",
"CEL1922.opt.libel3",
"CEL1922.opt.libel4",
"CEL1922.opt.libel5",
"CEL1922.opt.libel6",
"CEL1922.opt.libel7",
"CEL1922.opt.libel8"
],
"spleentypes": [
"CEL1922.opt.none",
"CEL1922.opt.libel1",
"CEL1922.opt.libel2",
"CEL1922.opt.libel3",
"CEL1922.opt.libel4",
"CEL1922.opt.libel5",
"CEL1922.opt.libel6",
"CEL1922.opt.libel7",
"CEL1922.opt.libel8"
],
"anomalytypes": [
"CEL1922.opt.none",
"CEL1922.opt.entropie",
"CEL1922.opt.communicationaveclesmorts",
"CEL1922.opt.illusion",
"CEL1922.opt.suggestion",
"CEL1922.opt.tarotdivinatoire",
"CEL1922.opt.telekinesie",
"CEL1922.opt.telepathie",
"CEL1922.opt.voyageastral"
],
"ame": {
"res": 0,
"actuel": 0,
"artifice": {
"value": 0
},
"attraction": {
"value": 0
},
"coercition": {
"value": 0
},
"faveur": {
"value": 0
}
},
"corps": {
"res": 0,
"actuel": 0,
"echauffouree": {
"value": 0
},
"effacement": {
"value": 0
},
"prouesse": {
"value": 0
},
"mobilite": {
"value": 0
}
},
"coeur": {
"res": 0,
"actuel": 0,
"appreciation": {
"value": 0
},
"arts": {
"value": 0
},
"inspiration": {
"value": 0
},
"traque": {
"value": 0
}
},
"esprit": {
"res": 0,
"actuel": 0,
"instruction": {
"value": 0
},
"mtechnologique": {
"value": 0
},
"raisonnement": {
"value": 0
},
"traitement": {
"value": 0
}
},
"aspecttypes": [
"CEL1922.opt.none",
"CEL1922.opt.aspect1",
"CEL1922.opt.aspect2",
"CEL1922.opt.aspect3",
"CEL1922.opt.aspect4",
"CEL1922.opt.aspect5",
"CEL1922.opt.aspect6",
"CEL1922.opt.aspect7",
"CEL1922.opt.aspect8"
]
},
"blessures": {
"lvl": 0,
"blessure_1": {
"check": false,
"bonus": 0,
"malus": 0
},
"blessure_2": {
"check": false,
"bonus": 0,
"malus": 0
},
"blessure_3": {
"check": false,
"bonus": 0,
"malus": -1
},
"blessure_4": {
"check": false,
"bonus": 0,
"malus": -1
},
"blessure_5": {
"check": false,
"bonus": 0,
"malus": -2
},
"blessure_6": {
"check": false,
"bonus": 0,
"malus": -2
},
"blessure_7": {
"check": false,
"bonus": 0,
"malus": -3
},
"blessure_8": {
"check": false,
"bonus": 0,
"malus": -999
}
},
"destin": {
"lvl": 0,
"destin_1": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_2": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_3": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_4": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_5": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_6": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_7": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_8": {
"check": false,
"bonus": 0,
"malus": 0
}
},
"spleen": {
"lvl": 0,
"spleen_1": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_2": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_3": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_4": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_5": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_6": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_7": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_8": {
"check": false,
"bonus": 0,
"malus": 0
}
}
},
"prototypeToken": {
"name": "Ajax",
"displayName": 0,
"actorLink": false,
"appendNumber": false,
"prependAdjective": false,
"texture": {
"src": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/tokenizer/celestopol/ajax.Token.webp?1719437847752",
"scaleX": 1,
"scaleY": 1,
"offsetX": 0,
"offsetY": 0,
"rotation": 0,
"anchorX": 0.5,
"anchorY": 0.5,
"fit": "contain",
"tint": "#ffffff",
"alphaThreshold": 0.75
},
"width": 1,
"height": 1,
"lockRotation": false,
"rotation": 0,
"alpha": 1,
"disposition": -1,
"displayBars": 0,
"bar1": {
"attribute": null
},
"bar2": {
"attribute": null
},
"light": {
"alpha": 0.5,
"angle": 360,
"bright": 0,
"coloration": 1,
"dim": 0,
"attenuation": 0.5,
"luminosity": 0.5,
"saturation": 0,
"contrast": 0,
"shadows": 0,
"animation": {
"type": null,
"speed": 5,
"intensity": 5,
"reverse": false
},
"darkness": {
"min": 0,
"max": 1
},
"negative": false,
"priority": 0,
"color": null
},
"sight": {
"enabled": true,
"range": 30,
"angle": 360,
"visionMode": "basic",
"color": null,
"attenuation": 0.1,
"brightness": 0,
"saturation": 0,
"contrast": 0
},
"detectionModes": [],
"flags": {},
"randomImg": false,
"occludable": {
"radius": 0
},
"ring": {
"enabled": false,
"colors": {
"ring": null,
"background": null
},
"effects": 1,
"subject": {
"scale": 1,
"texture": null
}
},
"turnMarker": {
"mode": 1,
"animation": null,
"src": null,
"disposition": false
},
"movementAction": null
},
"items": [],
"effects": [],
"flags": {},
"_stats": {
"systemId": "celestopol1922",
"systemVersion": "0.0.7bêta",
"coreVersion": "13.348",
"createdTime": 1719437808862,
"modifiedTime": 1719437944369,
"lastModifiedBy": "6VFjkRpqiseDFIh9",
"compendiumSource": null,
"duplicateSource": null,
"exportSource": {
"worldId": "celestopol-1922",
"uuid": "Actor.QcGcXhb9FvHCb3uf",
"coreVersion": "13.351",
"systemId": "celestopol1922",
"systemVersion": "1.2.0"
}
},
"ownership": {
"default": 0
}
}
@@ -0,0 +1,296 @@
{
"_id": "04f6df7b17f747c6",
"name": "BAO WANG",
"type": "character",
"img": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/systems/celestopol1922/images/pj/bao_wang.png?1719410935795",
"system": {
"concept": "LESCROC",
"description": "<h1>BAO WANG : LESCROC </h1><p></p><p><span class=\"fontstyle0\">Ancien employé du casino flottant la Libellule, Bao Wang officiait comme videur.</span></p><p></p><p><span class=\"fontstyle0\">Après une adolescence de petite frappe dans les faubourgs de Shanghai, il parvint à sintroduire dans un obus-traversier pour rejoindre Célestopol, autant attiré par les perspectives offertes par la cité lunaire que par le besoin de se faire oublier des services de police de sa ville natale.</span></p><p></p><p><span class=\"fontstyle0\">Problème : Bao répugne à se battre et préfère largement la finesse à la violence. De son ancienne vie, il conserve un goût prononcé pour le jeu et la gente féminine. Par ailleurs, Bao a également un certain penchant pour les cabarets clandestins dans lesquels il aime à se travestir à loccasion. Cest dans lun de ces lieux interlopes de la cité que le jeune Chinois a rencontré Ernest. Ils ont sympathisé et le vétéran a su voir un certain potentiel chez Bao. Ce dernier, passablement désœuvré et fauché, accepta sans trop y croire dintégrer lagence du Lys blanc.</span></p><p></p><p><span class=\"fontstyle0\">Son sens de la débrouillardise et son passé houleux parfois bien utile lui octroient des compétences uniques et font désormais de lui un agent indispensable. Le jeune homme sinvestit dailleurs sincèrement dans son travail. Toujours tiré à quatre épingles, Bao est reconnu parmi ses coéquipiers comme le plus nonchalant du groupe.</span> <br style=\"font-style:normal;font-variant:normal;font-weight:normal;letter-spacing:normal;line-height:normal;orphans:2;text-align:-webkit-auto;text-indent:0px;text-transform:none;white-space:normal;widows:2;word-spacing:0px;-webkit-text-size-adjust:auto;-webkit-text-stroke-width:0px\" /></p>",
"metier": "LESCROC",
"faction": "",
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"stats": {
"ame": {
"label": "ame",
"res": 2,
"artifice": {
"label": "artifice",
"value": 4
},
"attraction": {
"label": "attraction",
"value": 2
},
"coercition": {
"label": "coercition",
"value": 2
},
"faveur": {
"label": "faveur",
"value": 2
}
},
"corps": {
"label": "corps",
"res": 4,
"echauffouree": {
"label": "echauffouree",
"value": 0
},
"effacement": {
"label": "effacement",
"value": 4
},
"mobilite": {
"label": "mobilite",
"value": 2
},
"prouesse": {
"label": "prouesse",
"value": 2
}
},
"coeur": {
"label": "coeur",
"res": 0,
"appreciation": {
"label": "appreciation",
"value": 2
},
"arts": {
"label": "arts",
"value": 0
},
"inspiration": {
"label": "inspiration",
"value": 1
},
"traque": {
"label": "traque",
"value": 3
}
},
"esprit": {
"label": "esprit",
"res": 0,
"instruction": {
"label": "instruction",
"value": 1
},
"mtechnologique": {
"label": "mtechnologique",
"value": 2
},
"raisonnement": {
"label": "raisonnement",
"value": 1
},
"traitement": {
"label": "traitement",
"value": 2
}
}
},
"anomaly": {
"type": "entropie",
"value": 1
},
"attributs": {
"entregent": {
"value": 0,
"max": 0
},
"fortune": {
"value": 0,
"max": 0
},
"reve": {
"value": 0,
"max": 0
},
"vision": {
"value": 0,
"max": 0
}
},
"historique": "<h1>BAO WANG : LESCROC </h1><p></p><p><span class=\"fontstyle0\">Ancien employé du casino flottant la Libellule, Bao Wang officiait comme videur.</span></p><p></p><p><span class=\"fontstyle0\">Après une adolescence de petite frappe dans les faubourgs de Shanghai, il parvint à sintroduire dans un obus-traversier pour rejoindre Célestopol, autant attiré par les perspectives offertes par la cité lunaire que par le besoin de se faire oublier des services de police de sa ville natale.</span></p><p></p><p><span class=\"fontstyle0\">Problème : Bao répugne à se battre et préfère largement la finesse à la violence. De son ancienne vie, il conserve un goût prononcé pour le jeu et la gente féminine. Par ailleurs, Bao a également un certain penchant pour les cabarets clandestins dans lesquels il aime à se travestir à loccasion. Cest dans lun de ces lieux interlopes de la cité que le jeune Chinois a rencontré Ernest. Ils ont sympathisé et le vétéran a su voir un certain potentiel chez Bao. Ce dernier, passablement désœuvré et fauché, accepta sans trop y croire dintégrer lagence du Lys blanc.</span></p><p></p><p><span class=\"fontstyle0\">Son sens de la débrouillardise et son passé houleux parfois bien utile lui octroient des compétences uniques et font désormais de lui un agent indispensable. Le jeune homme sinvestit dailleurs sincèrement dans son travail. Toujours tiré à quatre épingles, Bao est reconnu parmi ses coéquipiers comme le plus nonchalant du groupe.</span> <br style=\"font-style:normal;font-variant:normal;font-weight:normal;letter-spacing:normal;line-height:normal;orphans:2;text-align:-webkit-auto;text-indent:0px;text-transform:none;white-space:normal;widows:2;word-spacing:0px;-webkit-text-size-adjust:auto;-webkit-text-stroke-width:0px\" /></p>",
"descriptionPhysique": "",
"descriptionPsychologique": "",
"initiative": 0,
"factions": {
"pinkerton": 0,
"police": 0,
"okhrana": 0,
"lunanovatek": 0,
"oto": 0,
"syndicats": 0,
"vorovskoymir": 0,
"cour": 0,
"perso": 0,
"libel": "",
"perso2": 0,
"libel2": ""
}
},
"items": [
{
"_id": "23315ed101e248c8",
"name": "Entropie 1",
"img": "https://assets.forge-vtt.com/bazaar/core/icons/svg/item-bag.svg",
"effects": [],
"flags": {},
"sort": 0,
"type": "anomaly",
"system": {
"subtype": "entropie",
"level": 1,
"usesRemaining": 1,
"technique": "<p><span class=\"fontstyle0\">Bao peut relancer le dé de la Lune une fois lors dun même scénario et choisit de conserver le résultat quil préfère (cette faculté ne fonctionne pas sur les tests de chance).</span></p>",
"narratif": "<p><span class=\"fontstyle0\">Bao peut influer sur le hasard de manière mineure. </span><span class=\"fontstyle2\">Exemple : <em>avoir une bonne main en jouant au poker à la distribution, que le feu soit au vert en tournant au coin de la rue, etc.</em></span></p>"
}
},
{
"_id": "4edb4b80b9514a7b",
"name": "Belle gueule 2",
"img": "https://assets.forge-vtt.com/bazaar/core/icons/svg/item-bag.svg",
"effects": [],
"flags": {},
"sort": 0,
"type": "aspect",
"system": {
"valeur": 2,
"description": ""
}
},
{
"_id": "153b0a094ca4462e",
"name": "Aime le jeu 1",
"img": "https://assets.forge-vtt.com/bazaar/core/icons/svg/item-bag.svg",
"effects": [],
"flags": {},
"sort": 0,
"type": "aspect",
"system": {
"valeur": 1,
"description": ""
}
},
{
"_id": "cbdc76f66bd041a0",
"name": "Nouvel Équipement",
"img": "https://assets.forge-vtt.com/bazaar/core/icons/svg/item-bag.svg",
"effects": [],
"flags": {},
"sort": 0,
"type": "equipment",
"system": {
"description": ""
}
}
],
"effects": [],
"folder": null,
"flags": {},
"prototypeToken": {
"name": "BAO WANG",
"displayName": 0,
"actorLink": false,
"appendNumber": false,
"prependAdjective": false,
"texture": {
"src": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/tokenizer/celestopol/bao_wang.Token.webp?1719410935795",
"scaleX": 1,
"scaleY": 1,
"offsetX": 0,
"offsetY": 0,
"rotation": 0,
"tint": "#ffffff",
"anchorX": 0.5,
"anchorY": 0.5,
"fit": "contain",
"alphaThreshold": 0.75
},
"width": 1,
"height": 1,
"lockRotation": false,
"rotation": 0,
"alpha": 1,
"disposition": -1,
"displayBars": 0,
"bar1": {
"attribute": null
},
"bar2": {
"attribute": null
},
"light": {
"alpha": 0.5,
"angle": 360,
"bright": 0,
"coloration": 1,
"dim": 0,
"attenuation": 0.5,
"luminosity": 0.5,
"saturation": 0,
"contrast": 0,
"shadows": 0,
"animation": {
"type": null,
"speed": 5,
"intensity": 5,
"reverse": false
},
"darkness": {
"min": 0,
"max": 1
},
"color": null,
"negative": false,
"priority": 0
},
"sight": {
"enabled": false,
"range": 0,
"angle": 360,
"visionMode": "basic",
"attenuation": 0.1,
"brightness": 0,
"saturation": 0,
"contrast": 0,
"color": null
},
"detectionModes": [],
"flags": {},
"randomImg": false,
"occludable": {
"radius": 0
},
"ring": {
"enabled": false,
"colors": {
"ring": null,
"background": null
},
"effects": 1,
"subject": {
"scale": 1,
"texture": null
}
},
"turnMarker": {
"mode": 1,
"animation": null,
"src": null,
"disposition": false
},
"movementAction": null
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,143 @@
{
"_id": "fabe178df844475b",
"name": "Citoyen",
"type": "npc",
"img": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/tokenizer/celestopol/citoyen.Avatar.webp?1719438184882",
"system": {
"concept": "",
"description": "",
"metier": "",
"faction": "",
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"stats": {
"ame": {
"label": "ame",
"res": 0,
"actuel": 0
},
"corps": {
"label": "corps",
"res": 0,
"actuel": 0
},
"coeur": {
"label": "coeur",
"res": 0,
"actuel": 0
},
"esprit": {
"label": "esprit",
"res": 0,
"actuel": 0
}
},
"npcType": "standard",
"historique": ""
},
"items": [],
"effects": [],
"folder": null,
"flags": {},
"prototypeToken": {
"name": "Citoyen",
"displayName": 0,
"actorLink": false,
"appendNumber": false,
"prependAdjective": false,
"texture": {
"src": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/tokenizer/celestopol/citoyen.Token.webp?1719438184882",
"scaleX": 1,
"scaleY": 1,
"offsetX": 0,
"offsetY": 0,
"rotation": 0,
"anchorX": 0.5,
"anchorY": 0.5,
"fit": "contain",
"tint": "#ffffff",
"alphaThreshold": 0.75
},
"width": 1,
"height": 1,
"lockRotation": false,
"rotation": 0,
"alpha": 1,
"disposition": -1,
"displayBars": 0,
"bar1": {
"attribute": null
},
"bar2": {
"attribute": null
},
"light": {
"alpha": 0.5,
"angle": 360,
"bright": 0,
"coloration": 1,
"dim": 0,
"attenuation": 0.5,
"luminosity": 0.5,
"saturation": 0,
"contrast": 0,
"shadows": 0,
"animation": {
"type": null,
"speed": 5,
"intensity": 5,
"reverse": false
},
"darkness": {
"min": 0,
"max": 1
},
"negative": false,
"priority": 0,
"color": null
},
"sight": {
"enabled": true,
"range": 30,
"angle": 360,
"visionMode": "basic",
"color": null,
"attenuation": 0.1,
"brightness": 0,
"saturation": 0,
"contrast": 0
},
"detectionModes": [],
"flags": {},
"randomImg": false,
"occludable": {
"radius": 0
},
"ring": {
"enabled": false,
"colors": {
"ring": null,
"background": null
},
"effects": 1,
"subject": {
"scale": 1,
"texture": null
}
},
"turnMarker": {
"mode": 1,
"animation": null,
"src": null,
"disposition": false
},
"movementAction": null
}
}
@@ -0,0 +1,459 @@
{
"folder": "5aDs3Gbi2cHy35X9",
"name": "Citoyen",
"type": "npc",
"img": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/tokenizer/celestopol/citoyen.Avatar.webp?1719438184882",
"system": {
"concept": "",
"anomaly": "0",
"anomalyval": 0,
"anomalymax": "0",
"initiative": 0,
"description": "",
"prefs": {
"typeofthrow": {
"check": true,
"choice": "0"
}
},
"prompt": {
"typeofthrow": {
"check": true,
"choice": "0"
},
"configure": {
"numberofdice": 0,
"aspect": 0,
"bonus": 0,
"bonusauspiciousdice": 0,
"typeofthrow": 0,
"aspectskill": 0,
"bonusmalusskill": 0,
"aspectspeciality": 0,
"rolldifficulty": 0,
"bonusmalusspeciality": 0
}
},
"skill": {
"skilltypes": [
"CEL1922.opt.ame",
"CEL1922.opt.attraction",
"CEL1922.opt.artifice",
"CEL1922.opt.coercition",
"CEL1922.opt.faveur",
"CEL1922.opt.corps",
"CEL1922.opt.echauffouree",
"CEL1922.opt.effacement",
"CEL1922.opt.prouesse",
"CEL1922.opt.mobilite",
"CEL1922.opt.coeur",
"CEL1922.opt.appreciation",
"CEL1922.opt.arts",
"CEL1922.opt.inspiration",
"CEL1922.opt.traque",
"CEL1922.opt.esprit",
"CEL1922.opt.instruction",
"CEL1922.opt.mtechnologique",
"CEL1922.opt.raisonnement",
"CEL1922.opt.traitement"
],
"moondicetypes": [
"CEL1922.opt.nouvellelune",
"CEL1922.opt.premiercroissant",
"CEL1922.opt.premierquartier",
"CEL1922.opt.lunegibbeuse",
"CEL1922.opt.lunevoutee",
"CEL1922.opt.derniercroissant",
"CEL1922.opt.dernierquartier",
"CEL1922.opt.pleinelune"
],
"woundstypes": [
"CEL1922.opt.none",
"CEL1922.opt.anodin",
"CEL1922.opt.derisoire",
"CEL1922.opt.negligeable",
"CEL1922.opt.superficiel",
"CEL1922.opt.leger",
"CEL1922.opt.modere",
"CEL1922.opt.grave",
"CEL1922.opt.dramatique"
],
"woundsmalus": [
0,
0,
0,
-1,
-1,
-2,
-2,
-3,
-999
],
"woundsrecup": [
"CEL1922.opt.none",
"CEL1922.1 minute",
"CEL1922.1 minute",
"CEL1922.10 minutes",
"CEL1922.10 minutes",
"CEL1922.30 minutes",
"CEL1922.30 minutes",
"CEL1922.1 day",
"CEL1922.Out of Fiction"
],
"destinytypes": [
"CEL1922.opt.none",
"CEL1922.opt.libel1",
"CEL1922.opt.libel2",
"CEL1922.opt.libel3",
"CEL1922.opt.libel4",
"CEL1922.opt.libel5",
"CEL1922.opt.libel6",
"CEL1922.opt.libel7",
"CEL1922.opt.libel8"
],
"spleentypes": [
"CEL1922.opt.none",
"CEL1922.opt.libel1",
"CEL1922.opt.libel2",
"CEL1922.opt.libel3",
"CEL1922.opt.libel4",
"CEL1922.opt.libel5",
"CEL1922.opt.libel6",
"CEL1922.opt.libel7",
"CEL1922.opt.libel8"
],
"anomalytypes": [
"CEL1922.opt.none",
"CEL1922.opt.entropie",
"CEL1922.opt.communicationaveclesmorts",
"CEL1922.opt.illusion",
"CEL1922.opt.suggestion",
"CEL1922.opt.tarotdivinatoire",
"CEL1922.opt.telekinesie",
"CEL1922.opt.telepathie",
"CEL1922.opt.voyageastral"
],
"ame": {
"res": 0,
"actuel": 0,
"artifice": {
"value": 0
},
"attraction": {
"value": 0
},
"coercition": {
"value": 0
},
"faveur": {
"value": 0
}
},
"corps": {
"res": 0,
"actuel": 0,
"echauffouree": {
"value": 0
},
"effacement": {
"value": 0
},
"prouesse": {
"value": 0
},
"mobilite": {
"value": 0
}
},
"coeur": {
"res": 0,
"actuel": 0,
"appreciation": {
"value": 0
},
"arts": {
"value": 0
},
"inspiration": {
"value": 0
},
"traque": {
"value": 0
}
},
"esprit": {
"res": 0,
"actuel": 0,
"instruction": {
"value": 0
},
"mtechnologique": {
"value": 0
},
"raisonnement": {
"value": 0
},
"traitement": {
"value": 0
}
},
"aspecttypes": [
"CEL1922.opt.none",
"CEL1922.opt.aspect1",
"CEL1922.opt.aspect2",
"CEL1922.opt.aspect3",
"CEL1922.opt.aspect4",
"CEL1922.opt.aspect5",
"CEL1922.opt.aspect6",
"CEL1922.opt.aspect7",
"CEL1922.opt.aspect8"
]
},
"blessures": {
"lvl": 0,
"blessure_1": {
"check": false,
"bonus": 0,
"malus": 0
},
"blessure_2": {
"check": false,
"bonus": 0,
"malus": 0
},
"blessure_3": {
"check": false,
"bonus": 0,
"malus": -1
},
"blessure_4": {
"check": false,
"bonus": 0,
"malus": -1
},
"blessure_5": {
"check": false,
"bonus": 0,
"malus": -2
},
"blessure_6": {
"check": false,
"bonus": 0,
"malus": -2
},
"blessure_7": {
"check": false,
"bonus": 0,
"malus": -3
},
"blessure_8": {
"check": false,
"bonus": 0,
"malus": -999
}
},
"destin": {
"lvl": 0,
"destin_1": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_2": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_3": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_4": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_5": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_6": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_7": {
"check": false,
"bonus": 0,
"malus": 0
},
"destin_8": {
"check": false,
"bonus": 0,
"malus": 0
}
},
"spleen": {
"lvl": 0,
"spleen_1": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_2": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_3": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_4": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_5": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_6": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_7": {
"check": false,
"bonus": 0,
"malus": 0
},
"spleen_8": {
"check": false,
"bonus": 0,
"malus": 0
}
}
},
"prototypeToken": {
"name": "Citoyen",
"displayName": 0,
"actorLink": false,
"appendNumber": false,
"prependAdjective": false,
"texture": {
"src": "https://assets.forge-vtt.com/630dd9fa56bd61d804eb1dec/tokenizer/celestopol/citoyen.Token.webp?1719438184882",
"scaleX": 1,
"scaleY": 1,
"offsetX": 0,
"offsetY": 0,
"rotation": 0,
"anchorX": 0.5,
"anchorY": 0.5,
"fit": "contain",
"tint": "#ffffff",
"alphaThreshold": 0.75
},
"width": 1,
"height": 1,
"lockRotation": false,
"rotation": 0,
"alpha": 1,
"disposition": -1,
"displayBars": 0,
"bar1": {
"attribute": null
},
"bar2": {
"attribute": null
},
"light": {
"alpha": 0.5,
"angle": 360,
"bright": 0,
"coloration": 1,
"dim": 0,
"attenuation": 0.5,
"luminosity": 0.5,
"saturation": 0,
"contrast": 0,
"shadows": 0,
"animation": {
"type": null,
"speed": 5,
"intensity": 5,
"reverse": false
},
"darkness": {
"min": 0,
"max": 1
},
"negative": false,
"priority": 0,
"color": null
},
"sight": {
"enabled": true,
"range": 30,
"angle": 360,
"visionMode": "basic",
"color": null,
"attenuation": 0.1,
"brightness": 0,
"saturation": 0,
"contrast": 0
},
"detectionModes": [],
"flags": {},
"randomImg": false,
"occludable": {
"radius": 0
},
"ring": {
"enabled": false,
"colors": {
"ring": null,
"background": null
},
"effects": 1,
"subject": {
"scale": 1,
"texture": null
}
},
"turnMarker": {
"mode": 1,
"animation": null,
"src": null,
"disposition": false
},
"movementAction": null
},
"items": [],
"effects": [],
"flags": {},
"_stats": {
"systemId": "celestopol1922",
"systemVersion": "0.0.7bêta",
"coreVersion": "13.348",
"createdTime": 1719438039685,
"modifiedTime": 1719438184895,
"lastModifiedBy": "6VFjkRpqiseDFIh9",
"compendiumSource": null,
"duplicateSource": null,
"exportSource": {
"worldId": "celestopol-1922",
"uuid": "Actor.H4qPWYC8kbacuBfm",
"coreVersion": "13.351",
"systemId": "celestopol1922",
"systemVersion": "1.2.0"
}
},
"ownership": {
"default": 0
}
}
+228
View File
@@ -0,0 +1,228 @@
{
"_id": "preBaoWang00001",
"_key": "!actors!preBaoWang00001",
"name": "Bao Wang",
"type": "character",
"img": "systems/fvtt-celestopol/assets/ui/logo_jeu.png",
"system": {
"concept": "Lescroc",
"metier": "Videur",
"faction": "Agence du Lys blanc",
"initiative": 7,
"anomaly": {
"type": "entropie",
"value": 2
},
"stats": {
"ame": {
"label": "CELESTOPOL.Stat.ame",
"res": 0,
"artifice": {
"label": "CELESTOPOL.Skill.artifice",
"value": 1
},
"attraction": {
"label": "CELESTOPOL.Skill.attraction",
"value": 2
},
"coercition": {
"label": "CELESTOPOL.Skill.coercition",
"value": 2
},
"faveur": {
"label": "CELESTOPOL.Skill.faveur",
"value": 1
}
},
"corps": {
"label": "CELESTOPOL.Stat.corps",
"res": 0,
"echauffouree": {
"label": "CELESTOPOL.Skill.echauffouree",
"value": 1
},
"effacement": {
"label": "CELESTOPOL.Skill.effacement",
"value": 3
},
"mobilite": {
"label": "CELESTOPOL.Skill.mobilite",
"value": 2
},
"prouesse": {
"label": "CELESTOPOL.Skill.prouesse",
"value": 2
}
},
"coeur": {
"label": "CELESTOPOL.Stat.coeur",
"res": 0,
"appreciation": {
"label": "CELESTOPOL.Skill.appreciation",
"value": 1
},
"arts": {
"label": "CELESTOPOL.Skill.arts",
"value": 1
},
"inspiration": {
"label": "CELESTOPOL.Skill.inspiration",
"value": 1
},
"traque": {
"label": "CELESTOPOL.Skill.traque",
"value": 3
}
},
"esprit": {
"label": "CELESTOPOL.Stat.esprit",
"res": 0,
"instruction": {
"label": "CELESTOPOL.Skill.instruction",
"value": 1
},
"mtechnologique": {
"label": "CELESTOPOL.Skill.mtechnologique",
"value": 0
},
"raisonnement": {
"label": "CELESTOPOL.Skill.raisonnement",
"value": 2
},
"traitement": {
"label": "CELESTOPOL.Skill.traitement",
"value": 1
}
}
},
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"attributs": {
"entregent": {
"value": 0,
"max": 4
},
"fortune": {
"value": 2,
"max": 4
},
"reve": {
"value": 0,
"max": 4
},
"vision": {
"value": 0,
"max": 4
}
},
"factions": {
"pinkerton": {
"value": 0
},
"police": {
"value": 0
},
"okhrana": {
"value": 0
},
"lunanovatek": {
"value": 0
},
"oto": {
"value": 0
},
"syndicats": {
"value": 0
},
"vorovskoymir": {
"value": 0
},
"cour": {
"value": 0
},
"perso1": {
"label": "",
"value": 0
},
"perso2": {
"label": "",
"value": 0
}
},
"prefs": {
"rollMoonDie": false,
"difficulty": "normal"
},
"xp": {
"actuel": 0,
"log": []
},
"descriptionPhysique": "<p>Toujours tiré à quatre épingles, Bao est reconnu par ses coéquipiers comme le plus nonchalant du groupe.</p>",
"descriptionPsychologique": "<p>Ancienne petite frappe de Shanghai devenue videur au casino flottant La Libellule, Bao a rejoint Célestopol pour disparaître des radars de la police chinoise.</p><p>Peu enclin à la violence, joueur, séducteur et débrouillard, il a été recruté par Ernest pour mettre son passé houleux au service de lagence du Lys blanc.</p>",
"historique": "",
"portraitImage": "",
"notes": "<p>Compétences reconstruites à partir des résistances, des aspects et de l'archétype du prétiré, la source PDF ne fournissant pas de dots de spécialisation différenciés de manière exploitable.</p>",
"biodata": {
"age": "32 ans",
"genre": "Homme",
"taille": "",
"yeux": "",
"naissance": "",
"cheveux": "",
"origine": "Chine"
}
},
"items": [
{
"_id": "preBaoWangAnom",
"name": "Entropie",
"type": "anomaly",
"img": "systems/fvtt-celestopol/assets/icons/anomaly.svg",
"system": {
"subtype": "entropie",
"level": 2,
"usesRemaining": 1,
"technique": "<p>Durant un scénario, Bao peut <strong>relancer le Dé de Lune une seule fois</strong> et conserver le résultat quil préfère.</p><p>Cette capacité ne sapplique pas aux tests de chance.</p>",
"narratif": "<p>Le protagoniste peut <strong>influencer le hasard</strong> à sa façon, en déclenchant ou en évitant de petits événements aléatoires dans son environnement proche. Ces manifestations sont mineures, subtiles, et ne semblent jamais surnaturelles aux yeux des témoins.</p>",
"exemples": "<ul><li>Obtenir une bonne main au jeu de cartes lors de la distribution.</li><li>Voir le feu passer au vert en tournant le coin de la rue.</li><li>Faire tomber le verre d'un convive gênant au bon moment.</li></ul>"
},
"_key": "!actors.items!preBaoWang00001.preBaoWangAnom"
},
{
"_id": "preBaoWangAsp1",
"name": "Aime le jeu",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Aime le jeu</strong>.</p>"
},
"_key": "!actors.items!preBaoWang00001.preBaoWangAsp1"
},
{
"_id": "preBaoWangAsp2",
"name": "Belle gueule",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 2,
"description": "<p>Aspect du prétiré : <strong>Belle gueule</strong>.</p>"
},
"_key": "!actors.items!preBaoWang00001.preBaoWangAsp2"
}
],
"effects": [],
"folder": null,
"sort": 0,
"ownership": {
"default": 0
},
"flags": {}
}
+228
View File
@@ -0,0 +1,228 @@
{
"_id": "preElemiah0001",
"_key": "!actors!preElemiah0001",
"name": "Elemiah Cowen",
"type": "character",
"img": "systems/fvtt-celestopol/assets/ui/logo_jeu.png",
"system": {
"concept": "Louvrier",
"metier": "Manutentionnaire",
"faction": "Agence du Lys blanc",
"initiative": 7,
"anomaly": {
"type": "tarotdivinatoire",
"value": 2
},
"stats": {
"ame": {
"label": "CELESTOPOL.Stat.ame",
"res": 0,
"artifice": {
"label": "CELESTOPOL.Skill.artifice",
"value": 1
},
"attraction": {
"label": "CELESTOPOL.Skill.attraction",
"value": 1
},
"coercition": {
"label": "CELESTOPOL.Skill.coercition",
"value": 1
},
"faveur": {
"label": "CELESTOPOL.Skill.faveur",
"value": 1
}
},
"corps": {
"label": "CELESTOPOL.Stat.corps",
"res": 0,
"echauffouree": {
"label": "CELESTOPOL.Skill.echauffouree",
"value": 2
},
"effacement": {
"label": "CELESTOPOL.Skill.effacement",
"value": 1
},
"mobilite": {
"label": "CELESTOPOL.Skill.mobilite",
"value": 1
},
"prouesse": {
"label": "CELESTOPOL.Skill.prouesse",
"value": 5
}
},
"coeur": {
"label": "CELESTOPOL.Stat.coeur",
"res": 0,
"appreciation": {
"label": "CELESTOPOL.Skill.appreciation",
"value": 2
},
"arts": {
"label": "CELESTOPOL.Skill.arts",
"value": 1
},
"inspiration": {
"label": "CELESTOPOL.Skill.inspiration",
"value": 2
},
"traque": {
"label": "CELESTOPOL.Skill.traque",
"value": 2
}
},
"esprit": {
"label": "CELESTOPOL.Stat.esprit",
"res": 0,
"instruction": {
"label": "CELESTOPOL.Skill.instruction",
"value": 2
},
"mtechnologique": {
"label": "CELESTOPOL.Skill.mtechnologique",
"value": 1
},
"raisonnement": {
"label": "CELESTOPOL.Skill.raisonnement",
"value": 2
},
"traitement": {
"label": "CELESTOPOL.Skill.traitement",
"value": 3
}
}
},
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"attributs": {
"entregent": {
"value": 1,
"max": 4
},
"fortune": {
"value": 0,
"max": 4
},
"reve": {
"value": 1,
"max": 4
},
"vision": {
"value": 0,
"max": 4
}
},
"factions": {
"pinkerton": {
"value": 0
},
"police": {
"value": 0
},
"okhrana": {
"value": 0
},
"lunanovatek": {
"value": 0
},
"oto": {
"value": 0
},
"syndicats": {
"value": 0
},
"vorovskoymir": {
"value": 0
},
"cour": {
"value": 0
},
"perso1": {
"label": "",
"value": 0
},
"perso2": {
"label": "",
"value": 0
}
},
"prefs": {
"rollMoonDie": false,
"difficulty": "normal"
},
"xp": {
"actuel": 0,
"log": []
},
"descriptionPhysique": "<p>Ancien ouvrier dune trentaine dannées, Elemiah impressionne par son torse large, ses sourcils broussailleux, sa moustache travaillée et labsence de deux doigts à la main gauche.</p>",
"descriptionPsychologique": "<p>Après la mort de son frère Rubben dans un accident industriel, Elemiah sest juré de soutenir les miséreux de Célestopol.</p><p>Taiseux mais profondément solidaire, il s’émerveille désormais de larchitecture et des automates de la cité tout en travaillant aux galeries Sabline.</p>",
"historique": "",
"portraitImage": "",
"notes": "<p>Compétences reconstruites à partir des résistances, des aspects et de l'archétype du prétiré, la source PDF ne fournissant pas de dots de spécialisation différenciés de manière exploitable.</p>",
"biodata": {
"age": "28 ans",
"genre": "Homme",
"taille": "",
"yeux": "",
"naissance": "",
"cheveux": "",
"origine": "Célestopol"
}
},
"items": [
{
"_id": "preElemiahAnom",
"name": "Tarot divinatoire",
"type": "anomaly",
"img": "systems/fvtt-celestopol/assets/icons/anomaly.svg",
"system": {
"subtype": "tarotdivinatoire",
"level": 2,
"usesRemaining": 2,
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation du Cœur</strong> (Appréciation, Arts, Inspiration, Traque), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
"narratif": "<p>En <strong>tirant les cartes</strong>, le protagoniste peut apprendre une information sur une personne concernant son <em>passé, son présent ou son avenir</em>. L'information reste sujette à interprétation et le narrateur peut choisir de la formuler de façon symbolique ou métaphorique.</p>",
"exemples": "<ul><li>Deviner où se trouvera une cible le lendemain.</li><li>Connaître les antécédents douloureux d'un voisin mystérieux.</li><li>Obtenir une image symbolique du danger qui attend un allié.</li></ul>"
},
"_key": "!actors.items!preElemiah0001.preElemiahAnom"
},
{
"_id": "preElemiahAsp1",
"name": "Digne de confiance",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 2,
"description": "<p>Aspect du prétiré : <strong>Digne de confiance</strong>.</p>"
},
"_key": "!actors.items!preElemiah0001.preElemiahAsp1"
},
{
"_id": "preElemiahAsp2",
"name": "Robuste",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Robuste</strong>.</p>"
},
"_key": "!actors.items!preElemiah0001.preElemiahAsp2"
}
],
"effects": [],
"folder": null,
"sort": 0,
"ownership": {
"default": 0
},
"flags": {}
}
+228
View File
@@ -0,0 +1,228 @@
{
"_id": "preMaribel0001",
"_key": "!actors!preMaribel0001",
"name": "Maribel Vargas",
"type": "character",
"img": "systems/fvtt-celestopol/assets/ui/logo_jeu.png",
"system": {
"concept": "La scientifique",
"metier": "Mécanicienne",
"faction": "Agence du Lys blanc",
"initiative": 7,
"anomaly": {
"type": "communicationaveclesmorts",
"value": 2
},
"stats": {
"ame": {
"label": "CELESTOPOL.Stat.ame",
"res": 0,
"artifice": {
"label": "CELESTOPOL.Skill.artifice",
"value": 1
},
"attraction": {
"label": "CELESTOPOL.Skill.attraction",
"value": 1
},
"coercition": {
"label": "CELESTOPOL.Skill.coercition",
"value": 1
},
"faveur": {
"label": "CELESTOPOL.Skill.faveur",
"value": 1
}
},
"corps": {
"label": "CELESTOPOL.Stat.corps",
"res": 0,
"echauffouree": {
"label": "CELESTOPOL.Skill.echauffouree",
"value": 1
},
"effacement": {
"label": "CELESTOPOL.Skill.effacement",
"value": 1
},
"mobilite": {
"label": "CELESTOPOL.Skill.mobilite",
"value": 2
},
"prouesse": {
"label": "CELESTOPOL.Skill.prouesse",
"value": 1
}
},
"coeur": {
"label": "CELESTOPOL.Stat.coeur",
"res": 0,
"appreciation": {
"label": "CELESTOPOL.Skill.appreciation",
"value": 2
},
"arts": {
"label": "CELESTOPOL.Skill.arts",
"value": 2
},
"inspiration": {
"label": "CELESTOPOL.Skill.inspiration",
"value": 1
},
"traque": {
"label": "CELESTOPOL.Skill.traque",
"value": 1
}
},
"esprit": {
"label": "CELESTOPOL.Stat.esprit",
"res": 0,
"instruction": {
"label": "CELESTOPOL.Skill.instruction",
"value": 2
},
"mtechnologique": {
"label": "CELESTOPOL.Skill.mtechnologique",
"value": 4
},
"raisonnement": {
"label": "CELESTOPOL.Skill.raisonnement",
"value": 3
},
"traitement": {
"label": "CELESTOPOL.Skill.traitement",
"value": 2
}
}
},
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"attributs": {
"entregent": {
"value": 0,
"max": 4
},
"fortune": {
"value": 0,
"max": 4
},
"reve": {
"value": 2,
"max": 4
},
"vision": {
"value": 0,
"max": 4
}
},
"factions": {
"pinkerton": {
"value": 0
},
"police": {
"value": 0
},
"okhrana": {
"value": 0
},
"lunanovatek": {
"value": 0
},
"oto": {
"value": 0
},
"syndicats": {
"value": 0
},
"vorovskoymir": {
"value": 0
},
"cour": {
"value": 0
},
"perso1": {
"label": "",
"value": 0
},
"perso2": {
"label": "",
"value": 0
}
},
"prefs": {
"rollMoonDie": false,
"difficulty": "normal"
},
"xp": {
"actuel": 0,
"log": []
},
"descriptionPhysique": "<p>Jeune scientifique mexicaine, Maribel possède lallure vive et appliquée de celles qui pensent plus vite quelles ne parlent.</p>",
"descriptionPsychologique": "<p>Mécanicienne brillante et major de promotion de luniversité de Célestopol, Maribel sest spécialisée dans lingénierie de pointe et le sélénium.</p><p>Longtemps freinée par son origine et son genre, elle a finalement trouvé sa place auprès dErnest, même si son habitude de parler seule amuse encore le reste de l’équipe.</p>",
"historique": "",
"portraitImage": "",
"notes": "<p>Compétences reconstruites à partir des résistances, des aspects et de l'archétype du prétiré, la source PDF ne fournissant pas de dots de spécialisation différenciés de manière exploitable.</p>",
"biodata": {
"age": "25 ans",
"genre": "Femme",
"taille": "",
"yeux": "",
"naissance": "",
"cheveux": "",
"origine": "Mexique"
}
},
"items": [
{
"_id": "preMaribelAnom",
"name": "Communication avec les morts",
"type": "anomaly",
"img": "systems/fvtt-celestopol/assets/icons/anomaly.svg",
"system": {
"subtype": "communicationaveclesmorts",
"level": 2,
"usesRemaining": 2,
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation de l'Esprit</strong> (Instruction, Merveilleux technologique, Raisonnement, Traitement), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
"narratif": "<p>Le protagoniste entre <strong>en contact avec l'esprit d'un défunt</strong>. Il peut lui poser une <em>question fermée</em> (réponse par oui ou par non uniquement). Le contact est bref et les réponses peuvent être fragmentées ou métaphoriques, à la discrétion du narrateur.</p>",
"exemples": "<ul><li>Interroger la victime d'un meurtre sur l'identité de son agresseur.</li><li>Consulter l'esprit d'un ancêtre pour retrouver un objet caché.</li><li>Demander à un fantôme si quelqu'un l'a aidé à mourir.</li></ul>"
},
"_key": "!actors.items!preMaribel0001.preMaribelAnom"
},
{
"_id": "preMaribelAsp1",
"name": "Déterminée",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Déterminée</strong>.</p>"
},
"_key": "!actors.items!preMaribel0001.preMaribelAsp1"
},
{
"_id": "preMaribelAsp2",
"name": "Sagace",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 2,
"description": "<p>Aspect du prétiré : <strong>Sagace</strong>.</p>"
},
"_key": "!actors.items!preMaribel0001.preMaribelAsp2"
}
],
"effects": [],
"folder": null,
"sort": 0,
"ownership": {
"default": 0
},
"flags": {}
}
+228
View File
@@ -0,0 +1,228 @@
{
"_id": "preNadeja00001",
"_key": "!actors!preNadeja00001",
"name": "Nadeja Danilo",
"type": "character",
"img": "systems/fvtt-celestopol/assets/ui/logo_jeu.png",
"system": {
"concept": "La pilote",
"metier": "Pilote",
"faction": "Agence du Lys blanc",
"initiative": 10,
"anomaly": {
"type": "telekinesie",
"value": 2
},
"stats": {
"ame": {
"label": "CELESTOPOL.Stat.ame",
"res": 0,
"artifice": {
"label": "CELESTOPOL.Skill.artifice",
"value": 1
},
"attraction": {
"label": "CELESTOPOL.Skill.attraction",
"value": 1
},
"coercition": {
"label": "CELESTOPOL.Skill.coercition",
"value": 1
},
"faveur": {
"label": "CELESTOPOL.Skill.faveur",
"value": 1
}
},
"corps": {
"label": "CELESTOPOL.Stat.corps",
"res": 0,
"echauffouree": {
"label": "CELESTOPOL.Skill.echauffouree",
"value": 1
},
"effacement": {
"label": "CELESTOPOL.Skill.effacement",
"value": 1
},
"mobilite": {
"label": "CELESTOPOL.Skill.mobilite",
"value": 3
},
"prouesse": {
"label": "CELESTOPOL.Skill.prouesse",
"value": 2
}
},
"coeur": {
"label": "CELESTOPOL.Stat.coeur",
"res": 0,
"appreciation": {
"label": "CELESTOPOL.Skill.appreciation",
"value": 1
},
"arts": {
"label": "CELESTOPOL.Skill.arts",
"value": 1
},
"inspiration": {
"label": "CELESTOPOL.Skill.inspiration",
"value": 3
},
"traque": {
"label": "CELESTOPOL.Skill.traque",
"value": 2
}
},
"esprit": {
"label": "CELESTOPOL.Stat.esprit",
"res": 0,
"instruction": {
"label": "CELESTOPOL.Skill.instruction",
"value": 2
},
"mtechnologique": {
"label": "CELESTOPOL.Skill.mtechnologique",
"value": 1
},
"raisonnement": {
"label": "CELESTOPOL.Skill.raisonnement",
"value": 3
},
"traitement": {
"label": "CELESTOPOL.Skill.traitement",
"value": 2
}
}
},
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"attributs": {
"entregent": {
"value": 0,
"max": 4
},
"fortune": {
"value": 0,
"max": 4
},
"reve": {
"value": 1,
"max": 4
},
"vision": {
"value": 1,
"max": 4
}
},
"factions": {
"pinkerton": {
"value": 0
},
"police": {
"value": 0
},
"okhrana": {
"value": 0
},
"lunanovatek": {
"value": 0
},
"oto": {
"value": 0
},
"syndicats": {
"value": 0
},
"vorovskoymir": {
"value": 0
},
"cour": {
"value": 0
},
"perso1": {
"label": "",
"value": 0
},
"perso2": {
"label": "",
"value": 0
}
},
"prefs": {
"rollMoonDie": false,
"difficulty": "normal"
},
"xp": {
"actuel": 0,
"log": []
},
"descriptionPhysique": "<p>Nadeja donne dabord limage dune femme sérieuse et farouche, toujours parfaitement concentrée.</p>",
"descriptionPsychologique": "<p>Née à Célestopol dans une famille duniversitaires, Nadeja sest tournée très tôt vers les étoiles et les destinations lointaines.</p><p>Pilote extrêmement douée, elle travaille pour Columbia après avoir subi le sexisme du milieu aéronautique et supporte mal le manque de respect.</p>",
"historique": "",
"portraitImage": "",
"notes": "<p>Compétences reconstruites à partir des résistances, des aspects et de l'archétype du prétiré, la source PDF ne fournissant pas de dots de spécialisation différenciés de manière exploitable.</p>",
"biodata": {
"age": "35 ans",
"genre": "Femme",
"taille": "",
"yeux": "",
"naissance": "",
"cheveux": "",
"origine": "Célestopol"
}
},
"items": [
{
"_id": "preNadeja0Anom",
"name": "Télékinésie",
"type": "anomaly",
"img": "systems/fvtt-celestopol/assets/icons/anomaly.svg",
"system": {
"subtype": "telekinesie",
"level": 2,
"usesRemaining": 2,
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation du Corps</strong> (Échauffourée, Effacement, Mobilité, Prouesse), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
"narratif": "<p>Dans un rayon de <strong>8 mètres</strong>, le protagoniste peut <strong>déplacer par la pensée</strong> un petit objet léger sans attaches, sur <strong>4 mètres</strong> (dans n'importe quelle direction) pendant <strong>2 tours</strong>. L'objet doit être visible et accessible par le regard.</p>",
"exemples": "<ul><li>Déplacer une cuillère pour la faire tomber d'une table au bon moment.</li><li>Faire léviter un jeu de tarot ou un trousseau de clés.</li><li>Pousser doucement un verre pour attirer l'attention d'un interlocuteur.</li></ul>"
},
"_key": "!actors.items!preNadeja00001.preNadeja0Anom"
},
{
"_id": "preNadeja0Asp1",
"name": "Résiliente",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Résiliente</strong>.</p>"
},
"_key": "!actors.items!preNadeja00001.preNadeja0Asp1"
},
{
"_id": "preNadeja0Asp2",
"name": "Tête froide",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 2,
"description": "<p>Aspect du prétiré : <strong>Tête froide</strong>.</p>"
},
"_key": "!actors.items!preNadeja00001.preNadeja0Asp2"
}
],
"effects": [],
"folder": null,
"sort": 0,
"ownership": {
"default": 0
},
"flags": {}
}
+228
View File
@@ -0,0 +1,228 @@
{
"_id": "preNielsBielke1",
"_key": "!actors!preNielsBielke1",
"name": "Niels Bielke",
"type": "character",
"img": "systems/fvtt-celestopol/assets/ui/logo_jeu.png",
"system": {
"concept": "LArtiste maudit",
"metier": "Acteur",
"faction": "Agence du Lys blanc",
"initiative": 8,
"anomaly": {
"type": "suggestion",
"value": 2
},
"stats": {
"ame": {
"label": "CELESTOPOL.Stat.ame",
"res": 0,
"artifice": {
"label": "CELESTOPOL.Skill.artifice",
"value": 1
},
"attraction": {
"label": "CELESTOPOL.Skill.attraction",
"value": 3
},
"coercition": {
"label": "CELESTOPOL.Skill.coercition",
"value": 2
},
"faveur": {
"label": "CELESTOPOL.Skill.faveur",
"value": 1
}
},
"corps": {
"label": "CELESTOPOL.Stat.corps",
"res": 0,
"echauffouree": {
"label": "CELESTOPOL.Skill.echauffouree",
"value": 0
},
"effacement": {
"label": "CELESTOPOL.Skill.effacement",
"value": 1
},
"mobilite": {
"label": "CELESTOPOL.Skill.mobilite",
"value": 2
},
"prouesse": {
"label": "CELESTOPOL.Skill.prouesse",
"value": 1
}
},
"coeur": {
"label": "CELESTOPOL.Stat.coeur",
"res": 0,
"appreciation": {
"label": "CELESTOPOL.Skill.appreciation",
"value": 1
},
"arts": {
"label": "CELESTOPOL.Skill.arts",
"value": 2
},
"inspiration": {
"label": "CELESTOPOL.Skill.inspiration",
"value": 2
},
"traque": {
"label": "CELESTOPOL.Skill.traque",
"value": 1
}
},
"esprit": {
"label": "CELESTOPOL.Stat.esprit",
"res": 0,
"instruction": {
"label": "CELESTOPOL.Skill.instruction",
"value": 2
},
"mtechnologique": {
"label": "CELESTOPOL.Skill.mtechnologique",
"value": 0
},
"raisonnement": {
"label": "CELESTOPOL.Skill.raisonnement",
"value": 1
},
"traitement": {
"label": "CELESTOPOL.Skill.traitement",
"value": 3
}
}
},
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"attributs": {
"entregent": {
"value": 0,
"max": 4
},
"fortune": {
"value": 0,
"max": 4
},
"reve": {
"value": 2,
"max": 4
},
"vision": {
"value": 0,
"max": 4
}
},
"factions": {
"pinkerton": {
"value": 0
},
"police": {
"value": 0
},
"okhrana": {
"value": 0
},
"lunanovatek": {
"value": 0
},
"oto": {
"value": 0
},
"syndicats": {
"value": 0
},
"vorovskoymir": {
"value": 0
},
"cour": {
"value": 0
},
"perso1": {
"label": "",
"value": 0
},
"perso2": {
"label": "",
"value": 0
}
},
"prefs": {
"rollMoonDie": false,
"difficulty": "normal"
},
"xp": {
"actuel": 0,
"log": []
},
"descriptionPhysique": "<p>Niels approche de la quarantaine. Malgré une allure en apparence négligée et un tic nerveux persistant, il conserve un charisme et une aura certains.</p>",
"descriptionPsychologique": "<p>Ancienne vedette des scènes suédoises, Niels a quitté la Terre pour conquérir Célestopol avant de sombrer dans la disgrâce et lalcool.</p><p>Sauvé par Ernest alors quil était au bord du gouffre, il sert désormais l’équipe du Lys blanc avec la volonté de payer sa dette et de tenir sa parole de ne plus toucher à lalcool.</p>",
"historique": "",
"portraitImage": "",
"notes": "<p>Compétences reconstruites à partir des résistances, des aspects et de l'archétype du prétiré, la source PDF ne fournissant pas de dots de spécialisation différenciés de manière exploitable.</p>",
"biodata": {
"age": "38 ans",
"genre": "Homme",
"taille": "",
"yeux": "",
"naissance": "",
"cheveux": "",
"origine": "Suède"
}
},
"items": [
{
"_id": "preNielsBiAnom",
"name": "Suggestion",
"type": "anomaly",
"img": "systems/fvtt-celestopol/assets/icons/anomaly.svg",
"system": {
"subtype": "suggestion",
"level": 2,
"usesRemaining": 2,
"technique": "<p>Durant un scénario, lors d'un test d'une <strong>Spécialisation de l'Âme</strong> (Artifice, Attraction, Coercition, Faveur), le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
"narratif": "<p>Le protagoniste est capable d'<strong>influencer la prise de décision</strong> d'une personne en lui parlant à voix haute et en la regardant dans les yeux. Cette décision doit avoir un <em>impact immédiat</em> sur l'action de la personne concernée.</p><p>Cette capacité fonctionne également sur les <strong>automates sophistiqués de 4e et 5e génération</strong>.</p>",
"exemples": "<ul><li>Convaincre un garde de laisser passer sans vérifier les laissez-passer.</li><li>Pousser un prisonnier à donner son nom ou à s'asseoir.</li><li>Inciter un chauffeur de taxi à emprunter un itinéraire détourné.</li></ul>"
},
"_key": "!actors.items!preNielsBielke1.preNielsBiAnom"
},
{
"_id": "preNielsBiAsp1",
"name": "Charismatique",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 2,
"description": "<p>Aspect du prétiré : <strong>Charismatique</strong>.</p>"
},
"_key": "!actors.items!preNielsBielke1.preNielsBiAsp1"
},
{
"_id": "preNielsBiAsp2",
"name": "Sensible",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Sensible</strong>.</p>"
},
"_key": "!actors.items!preNielsBielke1.preNielsBiAsp2"
}
],
"effects": [],
"folder": null,
"sort": 0,
"ownership": {
"default": 0
},
"flags": {}
}
+250
View File
@@ -0,0 +1,250 @@
{
"_id": "prePolyphme0001",
"_key": "!actors!prePolyphme0001",
"name": "Polyphème",
"type": "character",
"img": "systems/fvtt-celestopol/assets/ui/logo_jeu.png",
"system": {
"concept": "Le chroniqueur",
"metier": "Assistant-archiviste",
"faction": "Agence du Lys blanc",
"initiative": 7,
"anomaly": {
"type": "voyageastral",
"value": 2
},
"stats": {
"ame": {
"label": "CELESTOPOL.Stat.ame",
"res": 0,
"artifice": {
"label": "CELESTOPOL.Skill.artifice",
"value": 1
},
"attraction": {
"label": "CELESTOPOL.Skill.attraction",
"value": 0
},
"coercition": {
"label": "CELESTOPOL.Skill.coercition",
"value": 1
},
"faveur": {
"label": "CELESTOPOL.Skill.faveur",
"value": 1
}
},
"corps": {
"label": "CELESTOPOL.Stat.corps",
"res": 0,
"echauffouree": {
"label": "CELESTOPOL.Skill.echauffouree",
"value": 1
},
"effacement": {
"label": "CELESTOPOL.Skill.effacement",
"value": 1
},
"mobilite": {
"label": "CELESTOPOL.Skill.mobilite",
"value": 2
},
"prouesse": {
"label": "CELESTOPOL.Skill.prouesse",
"value": 2
}
},
"coeur": {
"label": "CELESTOPOL.Stat.coeur",
"res": 0,
"appreciation": {
"label": "CELESTOPOL.Skill.appreciation",
"value": 2
},
"arts": {
"label": "CELESTOPOL.Skill.arts",
"value": 2
},
"inspiration": {
"label": "CELESTOPOL.Skill.inspiration",
"value": 1
},
"traque": {
"label": "CELESTOPOL.Skill.traque",
"value": 2
}
},
"esprit": {
"label": "CELESTOPOL.Stat.esprit",
"res": 0,
"instruction": {
"label": "CELESTOPOL.Skill.instruction",
"value": 1
},
"mtechnologique": {
"label": "CELESTOPOL.Skill.mtechnologique",
"value": 4
},
"raisonnement": {
"label": "CELESTOPOL.Skill.raisonnement",
"value": 2
},
"traitement": {
"label": "CELESTOPOL.Skill.traitement",
"value": 2
}
}
},
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"attributs": {
"entregent": {
"value": 0,
"max": 4
},
"fortune": {
"value": 0,
"max": 4
},
"reve": {
"value": 0,
"max": 4
},
"vision": {
"value": 2,
"max": 4
}
},
"factions": {
"pinkerton": {
"value": 0
},
"police": {
"value": 0
},
"okhrana": {
"value": 0
},
"lunanovatek": {
"value": 0
},
"oto": {
"value": 0
},
"syndicats": {
"value": 0
},
"vorovskoymir": {
"value": 0
},
"cour": {
"value": 0
},
"perso1": {
"label": "",
"value": 0
},
"perso2": {
"label": "",
"value": 0
}
},
"prefs": {
"rollMoonDie": false,
"difficulty": "normal"
},
"xp": {
"actuel": 0,
"log": []
},
"descriptionPhysique": "<p>Automate de 4e génération remis en état par Ernest, Polyphème se distingue par son apparence artificielle et son obsession de ne pas avoir le « bon visage ».</p>",
"descriptionPsychologique": "<p>Retrouvé à demi détruit au fond dune impasse, Polyphème a été réparé puis intégré à lagence du Lys blanc comme assistant-archiviste.</p><p>Parfaitement intégré à l’équipe, il demeure hanté par la violence subie avant sa remise en état et par labsence de souvenirs de sa vie passée.</p>",
"historique": "",
"portraitImage": "",
"notes": "<p>Compétences reconstruites à partir des résistances, des aspects et de l'archétype du prétiré, la source PDF ne fournissant pas de dots de spécialisation différenciés de manière exploitable.</p>",
"biodata": {
"age": "Inconnu",
"genre": "Automate",
"taille": "",
"yeux": "",
"naissance": "",
"cheveux": "",
"origine": "Inconnue"
}
},
"items": [
{
"_id": "prePolyphmAnom",
"name": "Voyage astral",
"type": "anomaly",
"img": "systems/fvtt-celestopol/assets/icons/anomaly.svg",
"system": {
"subtype": "voyageastral",
"level": 2,
"usesRemaining": 2,
"technique": "<p>Durant un scénario, lors d'un test d'<strong>Appréciation, Merveilleux technologique, Traitement ou Traque</strong>, le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
"narratif": "<p>L'<strong>esprit du protagoniste quitte son enveloppe corporelle</strong> et se déplace de <strong>8 mètres par tour</strong> pendant <strong>4 tours</strong>, dans n'importe quelle direction. L'esprit est <em>invisible</em> et peut traverser tous les obstacles matériels. Les sens du protagoniste restent les mêmes durant le voyage.</p><p>Le corps reste immobile et vulnérable durant le voyage.</p>",
"exemples": "<ul><li>Accéder aux toits d'une maison pour effectuer une reconnaissance sans risque physique.</li><li>Inspecter une pièce adjacente verrouillée avant d'y pénétrer.</li><li>Voir à quelle distance un éboulement bloque le passage dans un tunnel et s'il y a des survivants.</li></ul>"
},
"_key": "!actors.items!prePolyphme0001.prePolyphmAnom"
},
{
"_id": "prePolyphmAsp1",
"name": "Difficile à lire",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 2,
"description": "<p>Aspect du prétiré : <strong>Difficile à lire</strong>.</p>"
},
"_key": "!actors.items!prePolyphme0001.prePolyphmAsp1"
},
{
"_id": "prePolyphmAsp2",
"name": "Étrangeté",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 2,
"description": "<p>Aspect du prétiré : <strong>Étrangeté</strong>.</p>"
},
"_key": "!actors.items!prePolyphme0001.prePolyphmAsp2"
},
{
"_id": "prePolyphmAsp3",
"name": "Mémoire eidétique",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 2,
"description": "<p>Aspect du prétiré : <strong>Mémoire eidétique</strong>.</p>"
},
"_key": "!actors.items!prePolyphme0001.prePolyphmAsp3"
},
{
"_id": "prePolyphmAsp4",
"name": "Vision aiguisée",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Vision aiguisée</strong>.</p>"
},
"_key": "!actors.items!prePolyphme0001.prePolyphmAsp4"
}
],
"effects": [],
"folder": null,
"sort": 0,
"ownership": {
"default": 0
},
"flags": {}
}
+228
View File
@@ -0,0 +1,228 @@
{
"_id": "preSedami000001",
"_key": "!actors!preSedami000001",
"name": "Sèdami Alassane",
"type": "character",
"img": "systems/fvtt-celestopol/assets/ui/logo_jeu.png",
"system": {
"concept": "La diplomate",
"metier": "Guerrière / diplomate",
"faction": "Agence du Lys blanc",
"initiative": 9,
"anomaly": {
"type": "telepathie",
"value": 2
},
"stats": {
"ame": {
"label": "CELESTOPOL.Stat.ame",
"res": 0,
"artifice": {
"label": "CELESTOPOL.Skill.artifice",
"value": 1
},
"attraction": {
"label": "CELESTOPOL.Skill.attraction",
"value": 3
},
"coercition": {
"label": "CELESTOPOL.Skill.coercition",
"value": 3
},
"faveur": {
"label": "CELESTOPOL.Skill.faveur",
"value": 2
}
},
"corps": {
"label": "CELESTOPOL.Stat.corps",
"res": 0,
"echauffouree": {
"label": "CELESTOPOL.Skill.echauffouree",
"value": 4
},
"effacement": {
"label": "CELESTOPOL.Skill.effacement",
"value": 1
},
"mobilite": {
"label": "CELESTOPOL.Skill.mobilite",
"value": 2
},
"prouesse": {
"label": "CELESTOPOL.Skill.prouesse",
"value": 3
}
},
"coeur": {
"label": "CELESTOPOL.Stat.coeur",
"res": 0,
"appreciation": {
"label": "CELESTOPOL.Skill.appreciation",
"value": 2
},
"arts": {
"label": "CELESTOPOL.Skill.arts",
"value": 1
},
"inspiration": {
"label": "CELESTOPOL.Skill.inspiration",
"value": 3
},
"traque": {
"label": "CELESTOPOL.Skill.traque",
"value": 2
}
},
"esprit": {
"label": "CELESTOPOL.Stat.esprit",
"res": 0,
"instruction": {
"label": "CELESTOPOL.Skill.instruction",
"value": 1
},
"mtechnologique": {
"label": "CELESTOPOL.Skill.mtechnologique",
"value": 0
},
"raisonnement": {
"label": "CELESTOPOL.Skill.raisonnement",
"value": 2
},
"traitement": {
"label": "CELESTOPOL.Skill.traitement",
"value": 1
}
}
},
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"attributs": {
"entregent": {
"value": 1,
"max": 4
},
"fortune": {
"value": 1,
"max": 4
},
"reve": {
"value": 0,
"max": 4
},
"vision": {
"value": 0,
"max": 4
}
},
"factions": {
"pinkerton": {
"value": 0
},
"police": {
"value": 0
},
"okhrana": {
"value": 0
},
"lunanovatek": {
"value": 0
},
"oto": {
"value": 0
},
"syndicats": {
"value": 0
},
"vorovskoymir": {
"value": 0
},
"cour": {
"value": 0
},
"perso1": {
"label": "",
"value": 0
},
"perso2": {
"label": "",
"value": 0
}
},
"prefs": {
"rollMoonDie": false,
"difficulty": "normal"
},
"xp": {
"actuel": 0,
"log": []
},
"descriptionPhysique": "<p>Sèdami est une femme d’âge mûr, confiante dans ses capacités et dotée dun esprit très ouvert.</p>",
"descriptionPsychologique": "<p>Ancienne officière des Mino du Dahomey, Sèdami sest illustrée au combat comme en stratégie avant de devenir diplomate.</p><p>Fascinée par Célestopol lors dune mission de courtoisie, elle a choisi de rester vivre sur la Lune, malgré la nostalgie de son pays natal.</p>",
"historique": "",
"portraitImage": "",
"notes": "<p>Compétences reconstruites à partir des résistances, des aspects et de l'archétype du prétiré, la source PDF ne fournissant pas de dots de spécialisation différenciés de manière exploitable.</p>",
"biodata": {
"age": "58 ans",
"genre": "Femme",
"taille": "",
"yeux": "",
"naissance": "",
"cheveux": "",
"origine": "Dahomey"
}
},
"items": [
{
"_id": "preSedami0Anom",
"name": "Télépathie",
"type": "anomaly",
"img": "systems/fvtt-celestopol/assets/icons/anomaly.svg",
"system": {
"subtype": "telepathie",
"level": 2,
"usesRemaining": 2,
"technique": "<p>Durant un scénario, lors d'un test d'<strong>Appréciation, Attraction, Échauffourée ou Faveur</strong>, le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p><p>Cette capacité fonctionne également sur les <strong>automates sophistiqués de 4e et 5e génération</strong>.</p>",
"narratif": "<p>Le protagoniste est capable de <strong>percevoir les pensées superficielles</strong> d'un tiers. Il peut comprendre l'état émotionnel d'une personne ou capter une image ou un mot dans son esprit (à la discrétion du narrateur), simplement en <em>l'observant</em>.</p>",
"exemples": "<ul><li>Percevoir l'image d'un cristal de cyanure dans l'esprit d'un serviteur soupçonné de tentative de meurtre.</li><li>Détecter, malgré un visage parfaitement contrôlé, qu'un magistrat est en réalité terrifié.</li><li>Ressentir la culpabilité d'un homme qui ment avec aplomb.</li></ul>"
},
"_key": "!actors.items!preSedami000001.preSedami0Anom"
},
{
"_id": "preSedami0Asp1",
"name": "Comportementaliste",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Comportementaliste</strong>.</p>"
},
"_key": "!actors.items!preSedami000001.preSedami0Asp1"
},
{
"_id": "preSedami0Asp2",
"name": "Stratège",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 2,
"description": "<p>Aspect du prétiré : <strong>Stratège</strong>.</p>"
},
"_key": "!actors.items!preSedami000001.preSedami0Asp2"
}
],
"effects": [],
"folder": null,
"sort": 0,
"ownership": {
"default": 0
},
"flags": {}
}
+239
View File
@@ -0,0 +1,239 @@
{
"_id": "preWiktoria001",
"_key": "!actors!preWiktoria001",
"name": "Wiktoria Raźny",
"type": "character",
"img": "systems/fvtt-celestopol/assets/ui/logo_jeu.png",
"system": {
"concept": "La vétérane",
"metier": "Soldate",
"faction": "Agence du Lys blanc",
"initiative": 6,
"anomaly": {
"type": "illusion",
"value": 2
},
"stats": {
"ame": {
"label": "CELESTOPOL.Stat.ame",
"res": 0,
"artifice": {
"label": "CELESTOPOL.Skill.artifice",
"value": 1
},
"attraction": {
"label": "CELESTOPOL.Skill.attraction",
"value": 1
},
"coercition": {
"label": "CELESTOPOL.Skill.coercition",
"value": 3
},
"faveur": {
"label": "CELESTOPOL.Skill.faveur",
"value": 1
}
},
"corps": {
"label": "CELESTOPOL.Stat.corps",
"res": 0,
"echauffouree": {
"label": "CELESTOPOL.Skill.echauffouree",
"value": 2
},
"effacement": {
"label": "CELESTOPOL.Skill.effacement",
"value": 3
},
"mobilite": {
"label": "CELESTOPOL.Skill.mobilite",
"value": 1
},
"prouesse": {
"label": "CELESTOPOL.Skill.prouesse",
"value": 2
}
},
"coeur": {
"label": "CELESTOPOL.Stat.coeur",
"res": 0,
"appreciation": {
"label": "CELESTOPOL.Skill.appreciation",
"value": 1
},
"arts": {
"label": "CELESTOPOL.Skill.arts",
"value": 1
},
"inspiration": {
"label": "CELESTOPOL.Skill.inspiration",
"value": 1
},
"traque": {
"label": "CELESTOPOL.Skill.traque",
"value": 3
}
},
"esprit": {
"label": "CELESTOPOL.Stat.esprit",
"res": 0,
"instruction": {
"label": "CELESTOPOL.Skill.instruction",
"value": 1
},
"mtechnologique": {
"label": "CELESTOPOL.Skill.mtechnologique",
"value": 0
},
"raisonnement": {
"label": "CELESTOPOL.Skill.raisonnement",
"value": 2
},
"traitement": {
"label": "CELESTOPOL.Skill.traitement",
"value": 2
}
}
},
"blessures": {
"lvl": 0
},
"destin": {
"lvl": 0
},
"spleen": {
"lvl": 0
},
"attributs": {
"entregent": {
"value": 2,
"max": 4
},
"fortune": {
"value": 0,
"max": 4
},
"reve": {
"value": 0,
"max": 4
},
"vision": {
"value": 0,
"max": 4
}
},
"factions": {
"pinkerton": {
"value": 0
},
"police": {
"value": 0
},
"okhrana": {
"value": 0
},
"lunanovatek": {
"value": 0
},
"oto": {
"value": 0
},
"syndicats": {
"value": 0
},
"vorovskoymir": {
"value": 0
},
"cour": {
"value": 0
},
"perso1": {
"label": "",
"value": 0
},
"perso2": {
"label": "",
"value": 0
}
},
"prefs": {
"rollMoonDie": false,
"difficulty": "normal"
},
"xp": {
"actuel": 0,
"log": []
},
"descriptionPhysique": "<p>Wiktoria est une Polonaise dégingandée portant un œil de verre à la place de l’œil perdu durant la guerre.</p>",
"descriptionPsychologique": "<p>Ancienne soldate de la Seconde Guerre de Crimée, Wiktoria a quitté la Pologne par dégoût après avoir été rejetée pour son handicap.</p><p>Amie dErnest et agente du Lys blanc, elle aime les soirées mondaines où elle glane des informations utiles tout en cultivant un goût affirmé pour la compagnie et l’élégance.</p>",
"historique": "",
"portraitImage": "",
"notes": "<p>Compétences reconstruites à partir des résistances, des aspects et de l'archétype du prétiré, la source PDF ne fournissant pas de dots de spécialisation différenciés de manière exploitable.</p>",
"biodata": {
"age": "39 ans",
"genre": "Femme",
"taille": "",
"yeux": "",
"naissance": "",
"cheveux": "",
"origine": "Pologne"
}
},
"items": [
{
"_id": "preWiktoriAnom",
"name": "Illusion",
"type": "anomaly",
"img": "systems/fvtt-celestopol/assets/icons/anomaly.svg",
"system": {
"subtype": "illusion",
"level": 2,
"usesRemaining": 2,
"technique": "<p>Durant un scénario, lors d'un test de <strong>Coercition, Échauffourée, Effacement ou Traque</strong>, le protagoniste gagne la possibilité de relancer les 2d8 un nombre de fois égal à son Niveau d'Anomalie.</p><p>Il doit conserver le dernier résultat.</p>",
"narratif": "<p>Le protagoniste peut <strong>générer une petite illusion mineure</strong> — visuelle, auditive ou olfactive, au choix — sans détail ni précision, pour une durée d'<strong>une minute</strong>. L'illusion ne peut représenter un être vivant en détail et ne résiste pas à un examen rapproché.</p>",
"exemples": "<ul><li>Le son d'un chat qui miaule ou d'un livre qui tombe dans la pièce voisine.</li><li>Un reflet métallique ou une ombre fugace au bout d'un couloir.</li><li>L'odeur de la pluie ou d'une fumée inquiétante.</li></ul>"
},
"_key": "!actors.items!preWiktoria001.preWiktoriAnom"
},
{
"_id": "preWiktoriAsp1",
"name": "Affable",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Affable</strong>.</p>"
},
"_key": "!actors.items!preWiktoria001.preWiktoriAsp1"
},
{
"_id": "preWiktoriAsp2",
"name": "Grande",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Grande</strong>.</p>"
},
"_key": "!actors.items!preWiktoria001.preWiktoriAsp2"
},
{
"_id": "preWiktoriAsp3",
"name": "Expérience militaire",
"type": "aspect",
"img": "systems/fvtt-celestopol/assets/icons/aspect.svg",
"system": {
"valeur": 1,
"description": "<p>Aspect du prétiré : <strong>Expérience militaire</strong>.</p>"
},
"_key": "!actors.items!preWiktoria001.preWiktoriAsp3"
}
],
"effects": [],
"folder": null,
"sort": 0,
"ownership": {
"default": 0
},
"flags": {}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

+154
View File
@@ -0,0 +1,154 @@
import fitz, re, json
pdf_path = '/home/morr/work/uberwald/fvtt-celestopol/__regles/Célestopol 1922 Fiches de prêts à jouer v1_cdjdr.pdf'
doc = fitz.open(pdf_path)
def is_green(color, tol=0.008):
if not color: return False
return (abs(color[0]-0.131) < tol and abs(color[1]-0.284) < tol and abs(color[2]-0.160) < tol)
SKILL_LIST = ['ARTIFICE','ATTRACTION','COERCITION','FAVEUR',
'ÉCHAUFFOURÉE','EFFACEMENT','MOBILITÉ','PROUESSE',
'APPRÉCIATION','ARTS','INSPIRATION','TRAQUE',
'INSTRUCTION','MERV.TECH.','RAISONNEMENT','TRAITEMENT']
def norm(s):
return s.strip().upper().replace('É','E').replace('È','E').replace('Ê','E').replace('Ô','O').replace('Â','A').replace('Î','I').replace('Œ','OE').replace('.','')
def get_skill_values(page):
words = page.get_text("words")
skill_y = {}
for w in words:
wn = norm(w[4])
for sk in SKILL_LIST:
if wn == norm(sk) and w[0] < 430:
skill_y[sk] = (w[1]+w[3])/2
# MERV.TECH. special
for w in words:
if 'MERV' in w[4].upper() and w[0] < 430:
skill_y['MERV.TECH.'] = (w[1]+w[3])/2
green_circles = []
for d in page.get_drawings():
if is_green(d.get('fill')):
rect = d['rect']
ws = rect.x1 - rect.x0
if 5.5 < ws < 8.5:
green_circles.append(((rect.x0+rect.x1)/2, (rect.y0+rect.y1)/2))
skills = {}
for sk in SKILL_LIST:
sy = skill_y.get(sk, None)
if sy is not None:
skills[sk] = sum(1 for cx, cy in green_circles if abs(cy - sy) < 7)
else:
skills[sk] = 0
return skills
def get_resistances(page):
words = page.get_text("words")
domain_y = {}
for w in sorted(words, key=lambda x: x[1]):
t = norm(w[4])
x0 = w[0]
if t == 'AME' and 300 < x0 < 500:
domain_y['ame'] = w[1]
elif t == 'CORPS' and 300 < x0 < 500:
domain_y['corps'] = w[1]
elif t == 'COEUR' and 300 < x0 < 500:
domain_y['coeur'] = w[1]
elif t == 'ESPRIT' and 300 < x0 < 500:
domain_y['esprit'] = w[1]
res = {}
for dom, dy in domain_y.items():
for w in words:
if w[4].strip().isdigit() and w[0] > 480 and abs(w[1]-dy) < 35:
res[dom] = int(w[4].strip())
break
return res
def get_anomalie_name(stats_page):
# Extract from text: the anomalie name appears in the bottom-right of the page
# Parse cleanly using raw text
text = stats_page.get_text("text")
lines = [l.strip() for l in text.split('\n') if l.strip()]
skip_words = {'ANOMALIE','NV','RÉSISTANCE','RESISTANCE'}
skip_starts = ['pour ','lors ','gagner ','trouver ','éviter ','sortir ','obtenir ',
'lors d', 'Vider ', 'Gain ', 'en pui', 'pour ne', 'pour ré']
for i, line in enumerate(lines):
if 'ANOMALIE' in line.upper() or 'NV' in line:
# Look in next few lines for the name
for j in range(i+1, min(i+10, len(lines))):
l = lines[j]
if not any(l.startswith(s) for s in skip_starts) and l not in skip_words:
if l and l[0].isupper() and len(l) > 1:
return l
return "?"
def get_anomalie_niveau(stats_page):
words = stats_page.get_text("words")
for w in sorted(words, key=lambda x: (x[1],x[0])):
if w[4].strip().isdigit() and w[0] > 480 and w[1] > 650:
return int(w[4].strip())
return None
def get_char_base_info(stats_page):
blocks = stats_page.get_text("dict")["blocks"]
name = None
for block in blocks:
for line in block.get("lines", []):
for span in line.get("spans", []):
if span.get("size", 0) > 11 and 'Bold' in span.get("font",""):
y = span["origin"][1]
t = span["text"].strip()
if t and len(t) > 3 and 150 < y < 250:
name = t
return {'name': name}
def get_raw_text(page):
html = page.get_text("html")
clean = re.sub(r'<[^>]+>', ' ', html)
clean = re.sub(r'&#x([0-9a-fA-F]+);', lambda m: chr(int(m.group(1),16)), clean)
clean = re.sub(r'&#([0-9]+);', lambda m: chr(int(m.group(1))), clean)
return re.sub(r'\s+', ' ', clean).strip()
def parse_aspects_page(asp_page):
text = asp_page.get_text("text")
lines = [l.strip() for l in text.split('\n') if l.strip()]
return lines
# Characters: (name_idx, anom_desc_idx, stats_idx, aspects_idx)
CHARACTERS = [
(0, 1, 2, 3),
(4, 5, 6, 7),
(8, 9, 10, 11),
(12, 13, 14, 15),
(16, 17, 18, 19),
(20, 21, 22, 23),
(24, 25, 26, 27),
(28, 29, 30, 31),
]
for n_idx, a_idx, s_idx, asp_idx in CHARACTERS:
sp = doc[s_idx]
skills = get_skill_values(sp)
res = get_resistances(sp)
anom_name = get_anomalie_name(sp)
anom_nv = get_anomalie_niveau(sp)
char_info = get_char_base_info(sp)
anom_desc = get_raw_text(doc[a_idx])
asp_lines = parse_aspects_page(doc[asp_idx])
print(f"\n{'='*70}")
print(f"NAME: {char_info.get('name','?')}")
print(f"SKILLS: {json.dumps(skills, ensure_ascii=False)}")
print(f"RESISTANCES: {res}")
print(f"ANOMALIE: {anom_name} nv{anom_nv}")
print(f"ANOM DESC (first 300 chars): {anom_desc[:300]}")
print("ASPECTS LINES:")
for i,l in enumerate(asp_lines[:60]): print(f" {i:2d}: {l}")
doc.close()
print("\nDONE")
+1304 -7
View File
File diff suppressed because it is too large Load Diff
+188 -10
View File
@@ -20,6 +20,14 @@
"anomaly": "Anomalie", "anomaly": "Anomalie",
"descriptionPhysique": "Description physique", "descriptionPhysique": "Description physique",
"descriptionPsychologique": "Description psychologique", "descriptionPsychologique": "Description psychologique",
"historique": "Historique",
"portraitImage": "Image de portrait",
"portraitImagePlaceholder": "Chemin vers une image verticale…",
"portraitImageHint": "Cette image est indépendante du portrait affiché dans len-tête de la fiche.",
"portraitImageEmpty": "Aucun portrait biographique distinct nest encore renseigné.",
"portraitImageMissing": "Aucun portrait biographique distinct nest disponible pour cette fiche.",
"sendPortraitToChat": "Envoyer dans le tchat",
"portraitChatTitle": "Portrait",
"notes": "Notes", "notes": "Notes",
"metier": "Métier", "metier": "Métier",
"origine": "Origine", "origine": "Origine",
@@ -50,7 +58,25 @@
"instruction": "Instruction", "instruction": "Instruction",
"mtechnologique": "Merveilleux technologique", "mtechnologique": "Merveilleux technologique",
"raisonnement": "Raisonnement", "raisonnement": "Raisonnement",
"traitement": "Traitement" "traitement": "Traitement",
"tooltip": {
"artifice": "Art du badinage et de la conversation subtile. Permet de se glisser dans une discussion pour glaner des renseignements ou noyer des informations mensongères. Favorise la manipulation et l'incitation plutôt que la contrainte.",
"attraction": "Capacité à orienter les émotions d'un interlocuteur. Sert à plaire, séduire, mais aussi à passer inaperçu lors d'une soirée mondaine.",
"coercition": "Art d'intimider par le regard ou les mots. Couvre également la capacité à donner des ordres dans un contexte militaire ou hiérarchique.",
"faveur": "Rapport de force oral : convaincre, persuader, négocier. S'appuie sur un discours argumentatif pour imposer son point de vue ou emporter l'adhésion.",
"echauffouree": "Techniques de combat à mains nues, avec une arme improvisée, une arme blanche ou des armes à feu et à distance.",
"effacement": "Capacité à être discret et à ne pas se faire repérer. Couvre le camouflage et l'escamotage. Requiert souvent de demeurer immobile.",
"mobilite": "Réflexes et vivacité. Sert pour la conduite, le pilotage, l'équitation, et représente l'initiative d'un protagoniste.",
"prouesse": "Efforts sportifs et physiques : escalade, natation, sprint, force brute et endurance.",
"appreciation": "Observer son environnement de façon exhaustive. Permet de repérer des intrus, des pièges, des guets-apens et des indices.",
"arts": "Domaines artistiques au sens large : musique, littérature, théâtre, prestidigitation. Permet de captiver un auditoire ou de créer des œuvres.",
"inspiration": "Intuition et inconscient : écouter sa petite voix intérieure sans démarche logique préalable. Permet aussi de lire le comportement d'une personne.",
"traque": "Filer un individu ou un groupe, pister et s'orienter. Ouvre également vers les techniques de survie.",
"instruction": "Savoirs encyclopédiques : sciences humaines, sciences dures, sciences sociales et linguistique. Chaque tranche de 2 points permet de maîtriser une langue supplémentaire.",
"mtechnologique": "Technologies extraordinaires de Célestopol : mécanique, automates, ingénierie au sélénium, horlogerie, serrurerie et artisanat.",
"raisonnement": "Capacités cognitives : déduction froide, logique pragmatique, analyse des faits. Utile pour percevoir des liens dans une enquête.",
"traitement": "Techniques médicales et thérapeutiques : premiers secours, chirurgie, pharmacopée, psychanalyse et psychothérapie."
}
}, },
"Anomaly": { "Anomaly": {
"type": "Type d'anomalie", "type": "Type d'anomalie",
@@ -82,7 +108,17 @@
"Faction": { "Faction": {
"label": "Faction", "label": "Faction",
"relation": "Niveau de Relation", "relation": "Niveau de Relation",
"legendTitle": "Rappel des relations",
"custom": "Faction personnalisée…", "custom": "Faction personnalisée…",
"levelAllies": "Alliés",
"levelAmicaux": "Amicaux",
"levelPartenaires": "Partenaires",
"levelBienveillants": "Bienveillants",
"levelNeutres": "Neutres",
"levelMefiants": "Méfiants",
"levelHostiles": "Hostiles",
"levelRivaux": "Rivaux",
"levelEnnemis": "Ennemis",
"pinkerton": "Agence Pinkerton", "pinkerton": "Agence Pinkerton",
"police": "Police secrète du duc", "police": "Police secrète du duc",
"okhrana": "Okhrana", "okhrana": "Okhrana",
@@ -92,6 +128,74 @@
"vorovskoymir": "Vorovskoy Mir", "vorovskoymir": "Vorovskoy Mir",
"cour": "Cour des merveilles" "cour": "Cour des merveilles"
}, },
"FactionAspect": {
"title": "Aspects de faction",
"manage": "Gérer",
"managerTitle": "Gestion des Aspects de faction",
"managerState": "État du scénario",
"managerSettings": "Paramètres de groupe",
"officialSourcesTitle": "Factions officielles reconnues",
"officialSourcesHint": "En usage normal, les aspects disponibles proviennent automatiquement des factions principales des protagonistes.",
"officialSourcesEmpty": "Aucune faction officielle reconnue. Renseignez la faction principale des PJ sur leur fiche, ou activez une cellule indépendante si le groupe joue sa propre structure.",
"availablePoolTitle": "Aspects actuellement mobilisables",
"availablePoolHint": "Les joueurs choisissent collectivement un aspect disponible et la valeur qu'ils veulent lui attribuer, dans la limite de la réserve du scénario.",
"globalPoolLabel": "Pool global du scénario (MJ)",
"globalPoolHint": "Le MJ ajuste ici uniquement la réserve totale disponible pour le groupe au cours du scénario.",
"pointsMax": "Points disponibles",
"pointsSpent": "Points mobilisés",
"pointsRemaining": "Points restants",
"sources": "Sources disponibles",
"noSource": "Aucune faction de protagoniste reconnue pour le moment.",
"activeTitle": "Aspects mobilisés",
"noneActive": "Aucun aspect de faction mobilisé.",
"activateTitle": "Mobiliser un aspect",
"activateHint": "Une fois choisi par les joueurs et mobilisé ici, l'aspect reste figé pour tout le scénario, jusqu'au bouton « Nouveau scénario » ou à une correction manuelle du MJ.",
"activateAspect": "Aspect à mobiliser",
"activateValue": "Valeur",
"activateButton": "Mobiliser",
"removeAspect": "Retirer un aspect mobilisé",
"removeButton": "Retirer",
"customCell": "Cellule indépendante",
"customCellSection": "Cellule indépendante (optionnel)",
"customCellEnabled": "Utiliser une cellule indépendante",
"customCellHint": "Utilisez cette option uniquement si le groupe agit comme sa propre cellule ou si vous voulez compléter ou remplacer les factions officielles reconnues.",
"customCellName": "Nom de la cellule",
"customCellMode": "Mode de combinaison",
"modeReplace": "Remplace les factions officielles",
"modeExtend": "S'ajoute aux factions officielles",
"save": "Enregistrer",
"saved": "Paramètres des Aspects de faction enregistrés.",
"resetScenario": "Nouveau scénario",
"resetDone": "Les Aspects de faction mobilisés ont été réinitialisés.",
"activated": "{aspect} mobilisé à +{value}.",
"removed": "{aspect} (+{value}) retiré de la réserve mobilisée.",
"alreadyActive": "Cet aspect de faction est déjà mobilisé pour ce scénario.",
"invalidAspect": "Choisissez un aspect de faction disponible à mobiliser.",
"invalidRemove": "Choisissez un aspect de faction mobilisé à retirer.",
"notEnoughPoints": "La réserve d'Aspects de faction ne suffit pas pour cette mobilisation.",
"pointsBelowSpent": "Le total disponible ne peut pas être inférieur aux points déjà mobilisés.",
"customCellAspectCount": "Une cellule indépendante doit proposer entre 4 et 8 aspects.",
"gmOnly": "Seul le MJ peut gérer les Aspects de faction.",
"noAspectAvailable": "Aucun aspect disponible à mobiliser",
"legacyFactionValue": "Valeur héritée",
"rollLabel": "Aspect de faction",
"noneOption": "Aucun aspect de faction",
"bonnesadresses": "Bonnes adresses",
"contrebande": "Contrebande",
"corruption": "Corruption",
"diversion": "Diversion",
"falsification": "Falsification",
"passedroit": "Passe-droit",
"renforts": "Renforts",
"renseignements": "Renseignements",
"ressources": "Ressources",
"surveillance": "Surveillance",
"resetButton": "Réinitialiser",
"resetTitle": "Réinitialiser les Aspects de faction",
"resetConfirm": "Voulez-vous réinitialiser tous les Aspects de faction mobilisés ? Cette action efface la réserve de scénario en cours.",
"resetConfirmYes": "Réinitialiser",
"resetSuccess": "Aspects de faction réinitialisés pour le nouveau scénario."
},
"Track": { "Track": {
"blessures": "Blessures", "blessures": "Blessures",
"destin": "Destin", "destin": "Destin",
@@ -99,7 +203,7 @@
"level": "Niveau", "level": "Niveau",
"currentMalus": "Malus actuel", "currentMalus": "Malus actuel",
"blessuresTooltip": "Niveaux de blessures :\n12 : Anodin / Négligeable → aucun malus (1 min)\n34 : Dérisoire / Superficiel → 1 (10 min)\n56 : Léger / Modéré → 2 (30 min)\n7 : Grave → 3 (1 journée)\n8 : Dramatique → hors combat", "blessuresTooltip": "Niveaux de blessures :\n12 : Anodin / Négligeable → aucun malus (1 min)\n34 : Dérisoire / Superficiel → 1 (10 min)\n56 : Léger / Modéré → 2 (30 min)\n7 : Grave → 3 (1 journée)\n8 : Dramatique → hors combat",
"spleenTooltip": "Le Spleen représente l'usure morale du protagoniste.\nLorsqu'il atteint son maximum, le protagoniste sombre dans la mélancolie et peut se retirer du scénario.", "spleenTooltip": "Le Spleen représente l'usure morale du protagoniste.\nLa jauge de Spleen augmente avec les actions suivantes :\n• Lors dun test de Spécialisation, en obtenant un échec et une Pleine lune sur le dé de la Lune.\n• Après un test de Spécialisation, pour transformer un échec en réussite, même après lutilisation éventuelle dune Anomalie.\n• Avant un test de résistance, pour réussir automatiquement le test.\n• Pour ne pas subir une blessure Dramatique.\n• En choisissant de puiser dans ses ressources.\nLorsque la jauge est remplie, le Protagoniste subit une Séquelle Dramatique et risque, à terme, de passer définitivement Hors Fiction.",
"destinTooltip": "Usages du Destin :\n• Réaliser un test avec 3d8\n• Gagner l'initiative lors d'un combat\n• Trouver l'ensemble des indices\n• Éviter une blessure\n• Sortir de l'inconscience\n• Obtenir un Triomphe" "destinTooltip": "Usages du Destin :\n• Réaliser un test avec 3d8\n• Gagner l'initiative lors d'un combat\n• Trouver l'ensemble des indices\n• Éviter une blessure\n• Sortir de l'inconscience\n• Obtenir un Triomphe"
}, },
"Wound": { "Wound": {
@@ -121,21 +225,44 @@
"Combat": { "Combat": {
"initiative": "Initiative", "initiative": "Initiative",
"attack": "Attaquer", "attack": "Attaquer",
"unarmedAttack": "Attaque à main nue",
"baseActions": "Actions de base",
"baseRangedDefense": "Esquive d'attaque à distance",
"rangedAttack": "Attaque à distance",
"corpsPnj": "Corps du PNJ", "corpsPnj": "Corps du PNJ",
"tie": "ÉGALITÉ", "tie": "ÉGALITÉ",
"tieDesc": "Personne n'est blessé", "tieDesc": "Personne n'est blessé",
"successHit": "PNJ touché1 blessure", "successHit": "Attaque réussiecible touchée",
"failureHit": "Joueur touché — 1 blessure (mêlée)", "failureHit": "Joueur touché — dégâts de l'arme appliqués (mêlée)",
"distanceNoWound": "Raté — pas de riposte", "distanceNoWound": "Raté — pas de riposte",
"weaponDamage": "dégâts supplémentaires", "weaponDamage": "dégâts supplémentaires",
"playerWounded": "Blessure infligée au joueur (mêlée)", "playerWounded": "Blessures infligées au joueur (mêlée)",
"rangedDefenseTitle": "Esquiver (Mobilité)", "rangedDefenseTitle": "Esquiver (Mobilité)",
"rangedDefenseTag": "Défense à distance", "rangedDefenseTag": "Défense à distance",
"rangedDefenseSuccess": "Attaque esquivée — pas de blessure", "rangedDefenseSuccess": "Attaque esquivée — pas de blessure",
"rangedDefenseFailure": "Touché par le PNJ — 1 blessure", "rangedDefenseFailure": "Touché par le PNJ — dégâts de l'arme appliqués",
"rangedDefensePlayerWounded": "Blessure infligée par attaque à distance", "rangedDefensePlayerWounded": "Blessures infligées par attaque à distance",
"targetLabel": "Cible", "targetLabel": "Cible",
"targetAuto": "Saisir manuellement", "targetAuto": "Saisir manuellement",
"targetCharacterLabel": "Personnage visé",
"targetCharacterAuto": "Aucun personnage présélectionné",
"damageLabel": "Dégâts infligés",
"damageUnit": "blessure(s)",
"damageManual": "Dégâts variables : application manuelle par le MJ.",
"damageArmorReduction": "Protection d'armure",
"damageApplied": "Blessures après armure",
"applyDamage": "Appliquer les blessures",
"damageApplying": "Application...",
"damageApplyingNotice": "Application des blessures en cours...",
"damageAppliedDone": "Blessures appliquées",
"damageAppliedCard": "{actor} subit {wounds} blessure(s) après armure ({armor}).",
"damageNoEffectCard": "{actor} ne subit aucune blessure : l'armure absorbe l'attaque ({armor}).",
"selectCharacter": "Choisir une cible",
"selectCharacterFirst": "Sélectionnez une cible avant d'appliquer les blessures.",
"noCharacterTargetAvailable": "Aucune cible de la scène active disponible pour appliquer les blessures.",
"damageRequestSent": "Demande d'application des blessures envoyée au MJ.",
"damageAppliedNotify": "{actor} : {wounds} blessure(s) appliquée(s) après armure ({armor}).",
"damageNoEffectNotify": "{actor} : l'armure absorbe entièrement l'attaque ({armor}).",
"rangedMod": "Modificateur de tir", "rangedMod": "Modificateur de tir",
"rangedModNone": "Aucun modificateur", "rangedModNone": "Aucun modificateur",
"rangedModAim": "Visée (dépense 1 tour) +2", "rangedModAim": "Visée (dépense 1 tour) +2",
@@ -148,6 +275,7 @@
"competences": "Domaines", "competences": "Domaines",
"blessures": "Jauges", "blessures": "Jauges",
"factions": "Factions", "factions": "Factions",
"combat": "Combat",
"equipement": "Équipement", "equipement": "Équipement",
"biography": "Biographie", "biography": "Biographie",
"description": "Description", "description": "Description",
@@ -210,7 +338,8 @@
"resistanceTest": "Test de résistance", "resistanceTest": "Test de résistance",
"resistanceClickToRoll": "Lancer un test de résistance", "resistanceClickToRoll": "Lancer un test de résistance",
"woundTaken": "Blessure cochée suite à l'échec", "woundTaken": "Blessure cochée suite à l'échec",
"autoSuccess": "Réussite automatique" "autoSuccess": "Réussite automatique",
"usedFactionAspect": "Aspect de faction mobilisé"
}, },
"Modifier": { "Modifier": {
"evident": "Évident — Réussite automatique", "evident": "Évident — Réussite automatique",
@@ -244,7 +373,22 @@
"mauvaiseFortune": "🔴 Mauvaise Fortune", "mauvaiseFortune": "🔴 Mauvaise Fortune",
"chanceInterpret": "Chance", "chanceInterpret": "Chance",
"narrativeInterpret": "Narratif", "narrativeInterpret": "Narratif",
"quantiteHint": "Valeur" "quantiteHint": "Valeur",
"applyChoose": "Choisissez la contrepartie :",
"effectNarrativeOnly": "(Narratif — sans effet mécanique)",
"effectRegainAnomaly": "🌟 Regain d'1 Anomalie",
"effectLoseSpleen": "💚 Perte d'1 Spleen",
"effectGainDestin": "⭐ Gain de 2 Destin",
"effectLoseDestin": "⭐ Perte de 2 Destin",
"effectLoseAnomaly": "❌ Perte d'1 Anomalie",
"effectGainSpleen": "💔 Gain d'1 Spleen",
"effectApplied": "Contrepartie appliquée : {label}",
"noAnomaly": "Ce personnage n'a pas d'anomalie",
"anomalyFull": "L'anomalie est déjà au maximum d'utilisations",
"anomalyEmpty": "L'anomalie n'a plus d'utilisation disponible",
"spleenEmpty": "Le Spleen est déjà à 0",
"spleenFull": "Le Spleen est déjà au maximum",
"actorNotFound": "Personnage introuvable"
}, },
"Difficulty": { "Difficulty": {
"unknown": "Aucun seuil", "unknown": "Aucun seuil",
@@ -309,8 +453,40 @@
"rollMoonDieByDefault": { "rollMoonDieByDefault": {
"name": "Lancer le dé de la lune par défaut", "name": "Lancer le dé de la lune par défaut",
"hint": "Cocher automatiquement 'Lancer le dé de la lune' dans les fenêtres de jet" "hint": "Cocher automatiquement 'Lancer le dé de la lune' dans les fenêtres de jet"
},
"defaultRollThreshold": {
"name": "Seuil de difficulté par défaut",
"hint": "Valeur du seuil utilisée par défaut pour les jets de compétence hors combat"
},
"migrateOldSystem": {
"name": "Migration depuis l'ancien système",
"hint": "Importer un personnage exporté depuis l'ancien système Célestopol 1922 (celestopol1922).",
"label": "Migrer un personnage…"
} }
}, },
"Migration": {
"title": "Migration ancien système Célestopol",
"instructions": "Sélectionnez un fichier JSON exporté depuis l'ancien système Célestopol 1922. Le personnage sera converti et créé automatiquement dans la liste des acteurs.",
"fileLabel": "Fichier JSON :",
"importBtn": "Convertir et importer",
"success": "'{name}' importé avec succès ({count} item(s)).",
"warnings": "{count} avertissement(s) — consultez la console (F12) pour le détail.",
"errorParse": "Impossible de lire le fichier JSON. Vérifiez qu'il s'agit bien d'un export Célestopol valide.",
"errorCreate": "Échec de la création de l'acteur. Consultez la console (F12) pour le détail."
},
"Welcome": {
"title": "Bienvenue dans Célestopol 1922",
"intro": "Bienvenue dans le système FoundryVTT de Célestopol 1922.",
"helpLabel": "Aide de jeu",
"helpCompendium": "Une aide de jeu est disponible dans le compendium : {help}.",
"bookLabel": "Livre de base",
"helpFallback": "Célestopol 1922 — Aides de jeu",
"bookLinkLabel": "Voir le livre de base sur le site dAntre-Monde Éditions"
},
"Pregens": {
"folderName": "Prétirés",
"imported": "Célestopol 1922 | Prétirés importés dans le dossier Acteurs."
},
"ChatCard": { "ChatCard": {
"rollFor": "Jet de {skill} ({stat})" "rollFor": "Jet de {skill} ({stat})"
}, },
@@ -330,7 +506,9 @@
"rangeLongue": "Longue portée", "rangeLongue": "Longue portée",
"type": "Type", "type": "Type",
"typeMelee": "Mêlée", "typeMelee": "Mêlée",
"typeDistance": "Distance" "typeDistance": "Distance",
"equip": "Équiper",
"unequip": "Retirer"
}, },
"Armure": { "Armure": {
"protection": "Protection", "protection": "Protection",
+13
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
export { default as CelestopolCharacterSheet } from "./sheets/character-sheet.mjs" export { default as CelestopolCharacterSheet } from "./sheets/character-sheet.mjs"
export { default as CelestopolNPCSheet } from "./sheets/npc-sheet.mjs" export { default as CelestopolNPCSheet } from "./sheets/npc-sheet.mjs"
export { CelestopolAnomalySheet, CelestopolAspectSheet, CelestopolEquipmentSheet, CelestopolWeaponSheet, CelestopolArmureSheet } from "./sheets/item-sheets.mjs" export { CelestopolAnomalySheet, CelestopolAspectSheet, CelestopolEquipmentSheet, CelestopolWeaponSheet, CelestopolArmureSheet } from "./sheets/item-sheets.mjs"
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import { SYSTEM } from "../../config/system.mjs" import { SYSTEM } from "../../config/system.mjs"
const { HandlebarsApplicationMixin } = foundry.applications.api const { HandlebarsApplicationMixin } = foundry.applications.api
@@ -21,15 +34,19 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }], dragDrop: [{ dragSelector: '[data-drag="true"], .rollable', dropSelector: null }],
actions: { actions: {
editImage: CelestopolActorSheet.#onEditImage, editImage: CelestopolActorSheet.#onEditImage,
sendBiographyPortrait: CelestopolActorSheet.#onSendBiographyPortrait,
toggleSheet: CelestopolActorSheet.#onToggleSheet, toggleSheet: CelestopolActorSheet.#onToggleSheet,
edit: CelestopolActorSheet.#onItemEdit, edit: CelestopolActorSheet.#onItemEdit,
delete: CelestopolActorSheet.#onItemDelete, delete: CelestopolActorSheet.#onItemDelete,
attack: CelestopolActorSheet.#onAttack, attack: CelestopolActorSheet.#onAttack,
rangedDefense: CelestopolActorSheet.#onRangedDefense, rangedDefense: CelestopolActorSheet.#onRangedDefense,
unarmedAttack: CelestopolActorSheet.#onUnarmedAttack,
baseRangedDefense: CelestopolActorSheet.#onBaseRangedDefense,
trackBox: CelestopolActorSheet.#onTrackBox, trackBox: CelestopolActorSheet.#onTrackBox,
skillLevel: CelestopolActorSheet.#onSkillLevel, skillLevel: CelestopolActorSheet.#onSkillLevel,
factionLevel: CelestopolActorSheet.#onFactionLevel, factionLevel: CelestopolActorSheet.#onFactionLevel,
toggleArmure: CelestopolActorSheet.#onToggleArmure, toggleArmure: CelestopolActorSheet.#onToggleArmure,
toggleWeapon: CelestopolActorSheet.#onToggleWeapon,
}, },
} }
@@ -46,6 +63,7 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
actor: this.document, actor: this.document,
system: this.document.system, system: this.document.system,
source: this.document.toObject(), source: this.document.toObject(),
isGM: game.user.isGM,
isEditMode: this.isEditMode, isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode, isPlayMode: this.isPlayMode,
isEditable: this.isEditable, isEditable: this.isEditable,
@@ -122,6 +140,35 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
return fp.browse() return fp.browse()
} }
static async #onSendBiographyPortrait() {
const portrait = this.document.system?.portraitImage || ""
if (!portrait) {
ui.notifications.warn(game.i18n.localize("CELESTOPOL.Actor.portraitImageMissing"))
return
}
const rawContent = `
<div class="cel-portrait-message chat-system-card">
<div class="portrait-message-header">
<span class="portrait-message-mark">✦</span>
<span class="portrait-message-title">${game.i18n.localize("CELESTOPOL.Actor.portraitChatTitle")}</span>
</div>
<div class="portrait-message-body">
<div class="portrait-message-name">${foundry.utils.escapeHTML(this.document.name)}</div>
<div class="portrait-message-frame">
<img src="${portrait}" alt="${foundry.utils.escapeHTML(this.document.name)}" class="portrait-message-image">
</div>
</div>
</div>
`
await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor: this.document }),
style: CONST.CHAT_MESSAGE_STYLES.OTHER,
content: await foundry.applications.ux.TextEditor.implementation.enrichHTML(rawContent, { async: true }),
})
}
static #onToggleSheet() { static #onToggleSheet() {
const modes = this.constructor.SHEET_MODES const modes = this.constructor.SHEET_MODES
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
@@ -153,6 +200,16 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
await this.document.system.rollRangedDefense(itemId) await this.document.system.rollRangedDefense(itemId)
} }
static async #onUnarmedAttack() {
if (typeof this.document.system.rollUnarmedAttack !== "function") return
await this.document.system.rollUnarmedAttack()
}
static async #onBaseRangedDefense() {
if (typeof this.document.system.rollRangedDefenseBase !== "function") return
await this.document.system.rollRangedDefenseBase()
}
/** Met à jour une jauge de piste (blessures/destin/spleen) par clic sur une case. */ /** Met à jour une jauge de piste (blessures/destin/spleen) par clic sur une case. */
static #onTrackBox(_event, target) { static #onTrackBox(_event, target) {
if (!this.isEditable) return if (!this.isEditable) return
@@ -181,6 +238,13 @@ export default class CelestopolActorSheet extends HandlebarsApplicationMixin(fou
if (item?.type === "armure") await item.update({ "system.equipped": !item.system.equipped }) if (item?.type === "armure") await item.update({ "system.equipped": !item.system.equipped })
} }
static async #onToggleWeapon(_event, target) {
const uuid = target.closest('[data-item-uuid]')?.dataset.itemUuid
if (!uuid) return
const item = await fromUuid(uuid)
if (item?.type === "weapon") await item.update({ "system.equipped": !item.system.equipped })
}
static #onFactionLevel(_event, target) { static #onFactionLevel(_event, target) {
if (!this.isEditable) return if (!this.isEditable) return
const factionId = target.dataset.faction const factionId = target.dataset.faction
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
const { HandlebarsApplicationMixin } = foundry.applications.api const { HandlebarsApplicationMixin } = foundry.applications.api
export default class CelestopolItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) { export default class CelestopolItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
+53 -2
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import CelestopolActorSheet from "./base-actor-sheet.mjs" import CelestopolActorSheet from "./base-actor-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs" import { SYSTEM } from "../../config/system.mjs"
@@ -18,6 +31,8 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
depenseXp: CelestopolCharacterSheet.#onDepenseXp, depenseXp: CelestopolCharacterSheet.#onDepenseXp,
supprimerXpLog: CelestopolCharacterSheet.#onSupprimerXpLog, supprimerXpLog: CelestopolCharacterSheet.#onSupprimerXpLog,
rollMoonDie: CelestopolCharacterSheet.#onRollMoonDie, rollMoonDie: CelestopolCharacterSheet.#onRollMoonDie,
manageFactionAspects: CelestopolCharacterSheet.#onManageFactionAspects,
resetFactionAspects: CelestopolCharacterSheet.#onResetFactionAspects,
}, },
} }
@@ -28,6 +43,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
competences:{ template: "systems/fvtt-celestopol/templates/character-competences.hbs" }, competences:{ template: "systems/fvtt-celestopol/templates/character-competences.hbs" },
blessures: { template: "systems/fvtt-celestopol/templates/character-blessures.hbs" }, blessures: { template: "systems/fvtt-celestopol/templates/character-blessures.hbs" },
factions: { template: "systems/fvtt-celestopol/templates/character-factions.hbs" }, factions: { template: "systems/fvtt-celestopol/templates/character-factions.hbs" },
combat: { template: "systems/fvtt-celestopol/templates/character-combat.hbs" },
equipement: { template: "systems/fvtt-celestopol/templates/character-equipement.hbs" }, equipement: { template: "systems/fvtt-celestopol/templates/character-equipement.hbs" },
biography: { template: "systems/fvtt-celestopol/templates/character-biography.hbs" }, biography: { template: "systems/fvtt-celestopol/templates/character-biography.hbs" },
} }
@@ -39,6 +55,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
competences:{ id: "competences", group: "sheet", icon: "fa-solid fa-dice-d6", label: "CELESTOPOL.Tab.competences" }, competences:{ id: "competences", group: "sheet", icon: "fa-solid fa-dice-d6", label: "CELESTOPOL.Tab.competences" },
blessures: { id: "blessures", group: "sheet", icon: "fa-solid fa-heart-crack", label: "CELESTOPOL.Tab.blessures" }, blessures: { id: "blessures", group: "sheet", icon: "fa-solid fa-heart-crack", label: "CELESTOPOL.Tab.blessures" },
factions: { id: "factions", group: "sheet", icon: "fa-solid fa-flag", label: "CELESTOPOL.Tab.factions" }, factions: { id: "factions", group: "sheet", icon: "fa-solid fa-flag", label: "CELESTOPOL.Tab.factions" },
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-khanda", label: "CELESTOPOL.Tab.combat" },
equipement: { id: "equipement", group: "sheet", icon: "fa-solid fa-shield-halved",label: "CELESTOPOL.Tab.equipement" }, equipement: { id: "equipement", group: "sheet", icon: "fa-solid fa-shield-halved",label: "CELESTOPOL.Tab.equipement" },
biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "CELESTOPOL.Tab.biography" }, biography: { id: "biography", group: "sheet", icon: "fa-solid fa-book", label: "CELESTOPOL.Tab.biography" },
} }
@@ -58,6 +75,11 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
context.anomalyTypes = SYSTEM.ANOMALY_TYPES context.anomalyTypes = SYSTEM.ANOMALY_TYPES
context.factions = SYSTEM.FACTIONS context.factions = SYSTEM.FACTIONS
context.woundLevels = SYSTEM.WOUND_LEVELS context.woundLevels = SYSTEM.WOUND_LEVELS
context.selectedPrimaryFactionId = game.celestopol?.normalizeFactionId(this.document.system.faction) || ""
context.legacyPrimaryFactionValue = this.document.system.faction && !context.selectedPrimaryFactionId
? `${this.document.system.faction}`.trim()
: ""
context.primaryFactionLabel = game.celestopol?.getFactionDisplayLabel(this.document.system.faction) || this.document.system.faction
return context return context
} }
@@ -94,6 +116,18 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
case "factions": case "factions":
context.tab = context.tabs.factions context.tab = context.tabs.factions
context.factionAspectSummary = game.celestopol?.getFactionAspectSummary(this.document) ?? null
context.factionLegend = [
{ value: "+4", label: game.i18n.localize("CELESTOPOL.Faction.levelAllies") },
{ value: "+3", label: game.i18n.localize("CELESTOPOL.Faction.levelAmicaux") },
{ value: "+2", label: game.i18n.localize("CELESTOPOL.Faction.levelPartenaires") },
{ value: "+1", label: game.i18n.localize("CELESTOPOL.Faction.levelBienveillants") },
{ value: "0", label: game.i18n.localize("CELESTOPOL.Faction.levelNeutres") },
{ value: "-1", label: game.i18n.localize("CELESTOPOL.Faction.levelMefiants") },
{ value: "-2", label: game.i18n.localize("CELESTOPOL.Faction.levelHostiles") },
{ value: "-3", label: game.i18n.localize("CELESTOPOL.Faction.levelRivaux") },
{ value: "-4", label: game.i18n.localize("CELESTOPOL.Faction.levelEnnemis") },
]
context.factionRows = Object.entries(SYSTEM.FACTIONS).map(([id, fDef]) => { context.factionRows = Object.entries(SYSTEM.FACTIONS).map(([id, fDef]) => {
const val = this.document.system.factions[id]?.value ?? 0 const val = this.document.system.factions[id]?.value ?? 0
return { return {
@@ -125,21 +159,29 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
}) })
break break
case "combat":
context.tab = context.tabs.combat
context.weapons = doc.itemTypes.weapon.sort((a, b) => a.name.localeCompare(b.name))
context.armures = doc.itemTypes.armure.sort((a, b) => a.name.localeCompare(b.name))
break
case "biography": case "biography":
context.tab = context.tabs.biography context.tab = context.tabs.biography
context.xpLogEmpty = (doc.system.xp?.log?.length ?? 0) === 0 context.xpLogEmpty = (doc.system.xp?.log?.length ?? 0) === 0
context.biographyPortrait = doc.system.portraitImage || ""
context.hasBiographyPortrait = !!doc.system.portraitImage
context.enrichedDescriptionPhysique = await foundry.applications.ux.TextEditor.implementation.enrichHTML( context.enrichedDescriptionPhysique = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
doc.system.descriptionPhysique, { relativeTo: this.document }) doc.system.descriptionPhysique, { relativeTo: this.document })
context.enrichedDescriptionPsychologique = await foundry.applications.ux.TextEditor.implementation.enrichHTML( context.enrichedDescriptionPsychologique = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
doc.system.descriptionPsychologique, { relativeTo: this.document }) doc.system.descriptionPsychologique, { relativeTo: this.document })
context.enrichedHistorique = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
doc.system.historique, { relativeTo: this.document })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML( context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
doc.system.notes, { relativeTo: this.document }) doc.system.notes, { relativeTo: this.document })
break break
case "equipement": case "equipement":
context.tab = context.tabs.equipement context.tab = context.tabs.equipement
context.weapons = doc.itemTypes.weapon.sort((a, b) => a.name.localeCompare(b.name))
context.armures = doc.itemTypes.armure.sort((a, b) => a.name.localeCompare(b.name))
context.equipments= doc.itemTypes.equipment.sort((a, b) => a.name.localeCompare(b.name)) context.equipments= doc.itemTypes.equipment.sort((a, b) => a.name.localeCompare(b.name))
break break
} }
@@ -177,6 +219,7 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
static async #onCreateArmure() { static async #onCreateArmure() {
await this.document.createEmbeddedDocuments("Item", [{ await this.document.createEmbeddedDocuments("Item", [{
name: game.i18n.localize("TYPES.Item.armure"), type: "armure", name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
system: { protection: 1, malus: 1 },
}]) }])
} }
@@ -199,6 +242,14 @@ export default class CelestopolCharacterSheet extends CelestopolActorSheet {
await anomaly.update({ "system.usesRemaining": anomaly.system.level }) await anomaly.update({ "system.usesRemaining": anomaly.system.level })
} }
static async #onManageFactionAspects() {
await game.celestopol?.manageFactionAspects(this.document)
}
static async #onResetFactionAspects() {
await game.celestopol?.resetFactionAspects()
}
/** Ouvre un dialogue pour dépenser de l'XP. */ /** Ouvre un dialogue pour dépenser de l'XP. */
static async #onDepenseXp() { static async #onDepenseXp() {
const actor = this.document const actor = this.document
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import CelestopolItemSheet from "./base-item-sheet.mjs" import CelestopolItemSheet from "./base-item-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs" import { SYSTEM } from "../../config/system.mjs"
@@ -97,4 +110,22 @@ export class CelestopolArmureSheet extends CelestopolItemSheet {
this.document.system.description, { async: true }) this.document.system.description, { async: true })
return ctx return ctx
} }
_onRender(context, options) {
super._onRender(context, options)
const protectionInput = this.element.querySelector('[name="system.protection"]')
const malusInput = this.element.querySelector('[name="system.malus"]')
const malusValue = this.element.querySelector('[data-armure-malus-value]')
if (!protectionInput || !malusInput || !malusValue) return
const syncMalus = () => {
malusInput.value = protectionInput.value
malusValue.textContent = protectionInput.value
}
syncMalus()
protectionInput.addEventListener("input", syncMalus)
protectionInput.addEventListener("change", syncMalus)
}
} }
+31 -2
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import CelestopolActorSheet from "./base-actor-sheet.mjs" import CelestopolActorSheet from "./base-actor-sheet.mjs"
import { SYSTEM } from "../../config/system.mjs" import { SYSTEM } from "../../config/system.mjs"
@@ -9,6 +22,7 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
window: { contentClasses: ["npc-content"] }, window: { contentClasses: ["npc-content"] },
actions: { actions: {
createAspect: CelestopolNPCSheet.#onCreateAspect, createAspect: CelestopolNPCSheet.#onCreateAspect,
createEquipment: CelestopolNPCSheet.#onCreateEquipment,
createWeapon: CelestopolNPCSheet.#onCreateWeapon, createWeapon: CelestopolNPCSheet.#onCreateWeapon,
createArmure: CelestopolNPCSheet.#onCreateArmure, createArmure: CelestopolNPCSheet.#onCreateArmure,
rollMoonDie: CelestopolNPCSheet.#onRollMoonDie, rollMoonDie: CelestopolNPCSheet.#onRollMoonDie,
@@ -21,6 +35,7 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
tabs: { template: "templates/generic/tab-navigation.hbs" }, tabs: { template: "templates/generic/tab-navigation.hbs" },
competences: { template: "systems/fvtt-celestopol/templates/npc-competences.hbs" }, competences: { template: "systems/fvtt-celestopol/templates/npc-competences.hbs" },
blessures: { template: "systems/fvtt-celestopol/templates/npc-blessures.hbs" }, blessures: { template: "systems/fvtt-celestopol/templates/npc-blessures.hbs" },
combat: { template: "systems/fvtt-celestopol/templates/npc-combat.hbs" },
equipement: { template: "systems/fvtt-celestopol/templates/npc-equipement.hbs" }, equipement: { template: "systems/fvtt-celestopol/templates/npc-equipement.hbs" },
biographie: { template: "systems/fvtt-celestopol/templates/npc-biographie.hbs" }, biographie: { template: "systems/fvtt-celestopol/templates/npc-biographie.hbs" },
} }
@@ -31,6 +46,7 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
const tabs = { const tabs = {
competences: { id: "competences", group: "sheet", icon: "fa-solid fa-dice-d6", label: "CELESTOPOL.Tab.competences" }, competences: { id: "competences", group: "sheet", icon: "fa-solid fa-dice-d6", label: "CELESTOPOL.Tab.competences" },
blessures: { id: "blessures", group: "sheet", icon: "fa-solid fa-heart-crack", label: "CELESTOPOL.Tab.blessures" }, blessures: { id: "blessures", group: "sheet", icon: "fa-solid fa-heart-crack", label: "CELESTOPOL.Tab.blessures" },
combat: { id: "combat", group: "sheet", icon: "fa-solid fa-khanda", label: "CELESTOPOL.Tab.combat" },
equipement: { id: "equipement", group: "sheet", icon: "fa-solid fa-shield-halved",label: "CELESTOPOL.Tab.equipement" }, equipement: { id: "equipement", group: "sheet", icon: "fa-solid fa-shield-halved",label: "CELESTOPOL.Tab.equipement" },
biographie: { id: "biographie", group: "sheet", icon: "fa-solid fa-book-open", label: "CELESTOPOL.Tab.biographie" }, biographie: { id: "biographie", group: "sheet", icon: "fa-solid fa-book-open", label: "CELESTOPOL.Tab.biographie" },
} }
@@ -54,8 +70,9 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
const sys = this.document.system const sys = this.document.system
context.aspects = this.document.itemTypes.aspect ?? [] context.aspects = this.document.itemTypes.aspect ?? []
context.weapons = this.document.itemTypes.weapon ?? [] context.weapons = this.document.itemTypes.weapon.sort((a, b) => a.name.localeCompare(b.name))
context.armures = this.document.itemTypes.armure ?? [] context.armures = this.document.itemTypes.armure.sort((a, b) => a.name.localeCompare(b.name))
context.equipments = this.document.itemTypes.equipment.sort((a, b) => a.name.localeCompare(b.name))
context.armorMalus = sys.armorMalus ?? 0 context.armorMalus = sys.armorMalus ?? 0
// Label effectif de chaque domaine selon le type de PNJ // Label effectif de chaque domaine selon le type de PNJ
@@ -91,11 +108,16 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
context.system.notes, { relativeTo: this.document } context.system.notes, { relativeTo: this.document }
) )
break break
case "combat":
context.tab = context.tabs.combat
break
case "equipement": case "equipement":
context.tab = context.tabs.equipement context.tab = context.tabs.equipement
break break
case "biographie": case "biographie":
context.tab = context.tabs.biographie context.tab = context.tabs.biographie
context.biographyPortrait = context.system.portraitImage || ""
context.hasBiographyPortrait = !!context.system.portraitImage
context.enrichedHistoire = await foundry.applications.ux.TextEditor.implementation.enrichHTML( context.enrichedHistoire = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
context.system.histoire, { relativeTo: this.document } context.system.histoire, { relativeTo: this.document }
) )
@@ -119,9 +141,16 @@ export default class CelestopolNPCSheet extends CelestopolActorSheet {
}]) }])
} }
static async #onCreateEquipment() {
await this.document.createEmbeddedDocuments("Item", [{
name: game.i18n.localize("TYPES.Item.equipment"), type: "equipment",
}])
}
static async #onCreateArmure() { static async #onCreateArmure() {
await this.document.createEmbeddedDocuments("Item", [{ await this.document.createEmbeddedDocuments("Item", [{
name: game.i18n.localize("TYPES.Item.armure"), type: "armure", name: game.i18n.localize("TYPES.Item.armure"), type: "armure",
system: { protection: 1, malus: 1 },
}]) }])
} }
+77 -16
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
export const SYSTEM_ID = "fvtt-celestopol" export const SYSTEM_ID = "fvtt-celestopol"
export const ASCII = ` export const ASCII = `
@@ -18,28 +31,28 @@ export const STATS = {
/** Domaines groupées par attribut. */ /** Domaines groupées par attribut. */
export const SKILLS = { export const SKILLS = {
ame: { ame: {
artifice: { id: "artifice", label: "CELESTOPOL.Skill.artifice", stat: "ame", resThreshold: 5 }, artifice: { id: "artifice", label: "CELESTOPOL.Skill.artifice", tooltip: "CELESTOPOL.Skill.tooltip.artifice", stat: "ame", resThreshold: 5 },
attraction: { id: "attraction", label: "CELESTOPOL.Skill.attraction", stat: "ame", resThreshold: 2 }, attraction: { id: "attraction", label: "CELESTOPOL.Skill.attraction", tooltip: "CELESTOPOL.Skill.tooltip.attraction", stat: "ame", resThreshold: 2 },
coercition: { id: "coercition", label: "CELESTOPOL.Skill.coercition", stat: "ame", resThreshold: 3 }, coercition: { id: "coercition", label: "CELESTOPOL.Skill.coercition", tooltip: "CELESTOPOL.Skill.tooltip.coercition", stat: "ame", resThreshold: 3 },
faveur: { id: "faveur", label: "CELESTOPOL.Skill.faveur", stat: "ame", resThreshold: 6 }, faveur: { id: "faveur", label: "CELESTOPOL.Skill.faveur", tooltip: "CELESTOPOL.Skill.tooltip.faveur", stat: "ame", resThreshold: 6 },
}, },
corps: { corps: {
echauffouree: { id: "echauffouree", label: "CELESTOPOL.Skill.echauffouree", stat: "corps", resThreshold: 6 }, echauffouree: { id: "echauffouree", label: "CELESTOPOL.Skill.echauffouree", tooltip: "CELESTOPOL.Skill.tooltip.echauffouree", stat: "corps", resThreshold: 6 },
effacement: { id: "effacement", label: "CELESTOPOL.Skill.effacement", stat: "corps", resThreshold: 3 }, effacement: { id: "effacement", label: "CELESTOPOL.Skill.effacement", tooltip: "CELESTOPOL.Skill.tooltip.effacement", stat: "corps", resThreshold: 3 },
mobilite: { id: "mobilite", label: "CELESTOPOL.Skill.mobilite", stat: "corps", resThreshold: 2 }, mobilite: { id: "mobilite", label: "CELESTOPOL.Skill.mobilite", tooltip: "CELESTOPOL.Skill.tooltip.mobilite", stat: "corps", resThreshold: 2 },
prouesse: { id: "prouesse", label: "CELESTOPOL.Skill.prouesse", stat: "corps", resThreshold: 5 }, prouesse: { id: "prouesse", label: "CELESTOPOL.Skill.prouesse", tooltip: "CELESTOPOL.Skill.tooltip.prouesse", stat: "corps", resThreshold: 5 },
}, },
coeur: { coeur: {
appreciation: { id: "appreciation", label: "CELESTOPOL.Skill.appreciation", stat: "coeur", resThreshold: 6 }, appreciation: { id: "appreciation", label: "CELESTOPOL.Skill.appreciation", tooltip: "CELESTOPOL.Skill.tooltip.appreciation", stat: "coeur", resThreshold: 6 },
arts: { id: "arts", label: "CELESTOPOL.Skill.arts", stat: "coeur", resThreshold: 2 }, arts: { id: "arts", label: "CELESTOPOL.Skill.arts", tooltip: "CELESTOPOL.Skill.tooltip.arts", stat: "coeur", resThreshold: 2 },
inspiration: { id: "inspiration", label: "CELESTOPOL.Skill.inspiration", stat: "coeur", resThreshold: 3 }, inspiration: { id: "inspiration", label: "CELESTOPOL.Skill.inspiration", tooltip: "CELESTOPOL.Skill.tooltip.inspiration", stat: "coeur", resThreshold: 3 },
traque: { id: "traque", label: "CELESTOPOL.Skill.traque", stat: "coeur", resThreshold: 5 }, traque: { id: "traque", label: "CELESTOPOL.Skill.traque", tooltip: "CELESTOPOL.Skill.tooltip.traque", stat: "coeur", resThreshold: 5 },
}, },
esprit: { esprit: {
instruction: { id: "instruction", label: "CELESTOPOL.Skill.instruction", stat: "esprit", resThreshold: 2 }, instruction: { id: "instruction", label: "CELESTOPOL.Skill.instruction", tooltip: "CELESTOPOL.Skill.tooltip.instruction", stat: "esprit", resThreshold: 2 },
mtechnologique: { id: "mtechnologique", label: "CELESTOPOL.Skill.mtechnologique", stat: "esprit", resThreshold: 6 }, mtechnologique: { id: "mtechnologique", label: "CELESTOPOL.Skill.mtechnologique", tooltip: "CELESTOPOL.Skill.tooltip.mtechnologique", stat: "esprit", resThreshold: 6 },
raisonnement: { id: "raisonnement", label: "CELESTOPOL.Skill.raisonnement", stat: "esprit", resThreshold: 5 }, raisonnement: { id: "raisonnement", label: "CELESTOPOL.Skill.raisonnement", tooltip: "CELESTOPOL.Skill.tooltip.raisonnement", stat: "esprit", resThreshold: 5 },
traitement: { id: "traitement", label: "CELESTOPOL.Skill.traitement", stat: "esprit", resThreshold: 3 }, traitement: { id: "traitement", label: "CELESTOPOL.Skill.traitement", tooltip: "CELESTOPOL.Skill.tooltip.traitement", stat: "esprit", resThreshold: 3 },
}, },
} }
@@ -88,6 +101,52 @@ export const FACTIONS = {
cour: { id: "cour", label: "CELESTOPOL.Faction.cour" }, cour: { id: "cour", label: "CELESTOPOL.Faction.cour" },
} }
/** Aspects de faction mobilisables au niveau du groupe. */
export const FACTION_ASPECTS = {
bonnesadresses: { id: "bonnesadresses", label: "CELESTOPOL.FactionAspect.bonnesadresses" },
contrebande: { id: "contrebande", label: "CELESTOPOL.FactionAspect.contrebande" },
corruption: { id: "corruption", label: "CELESTOPOL.FactionAspect.corruption" },
diversion: { id: "diversion", label: "CELESTOPOL.FactionAspect.diversion" },
falsification: { id: "falsification", label: "CELESTOPOL.FactionAspect.falsification" },
passedroit: { id: "passedroit", label: "CELESTOPOL.FactionAspect.passedroit" },
renforts: { id: "renforts", label: "CELESTOPOL.FactionAspect.renforts" },
renseignements: { id: "renseignements", label: "CELESTOPOL.FactionAspect.renseignements" },
ressources: { id: "ressources", label: "CELESTOPOL.FactionAspect.ressources" },
surveillance: { id: "surveillance", label: "CELESTOPOL.FactionAspect.surveillance" },
}
/** Tableau p.111 : aspects de faction disponibles selon l'organisation. */
export const FACTION_ASPECTS_BY_FACTION = {
police: [
"diversion", "passedroit", "renforts", "renseignements", "ressources", "surveillance",
],
vorovskoymir: [
"bonnesadresses", "contrebande", "corruption", "diversion", "falsification",
"renforts", "renseignements", "ressources", "surveillance",
],
okhrana: [
"corruption", "diversion", "falsification", "passedroit", "renforts",
"renseignements", "ressources",
],
oto: [
"contrebande", "corruption", "falsification", "renseignements", "surveillance",
],
syndicats: [
"bonnesadresses", "contrebande", "corruption", "falsification", "renseignements", "surveillance",
],
pinkerton: [
"bonnesadresses", "diversion", "falsification", "renforts",
"renseignements", "ressources", "surveillance",
],
cour: [
"bonnesadresses", "contrebande", "diversion", "renforts", "renseignements", "surveillance",
],
lunanovatek: [
"contrebande", "corruption", "falsification", "renforts",
"renseignements", "ressources", "surveillance",
],
}
/** Niveaux de blessures avec leur malus associé. */ /** Niveaux de blessures avec leur malus associé. */
export const WOUND_LEVELS = [ export const WOUND_LEVELS = [
{ id: 0, label: "CELESTOPOL.Wound.none", malus: 0, duration: "" }, { id: 0, label: "CELESTOPOL.Wound.none", malus: 0, duration: "" },
@@ -209,6 +268,8 @@ export const SYSTEM = {
ANOMALY_TYPES, ANOMALY_TYPES,
ANOMALY_DEFINITIONS, ANOMALY_DEFINITIONS,
FACTIONS, FACTIONS,
FACTION_ASPECTS,
FACTION_ASPECTS_BY_FACTION,
NPC_TYPES, NPC_TYPES,
ANTAGONISTE_STATS, ANTAGONISTE_STATS,
WOUND_LEVELS, WOUND_LEVELS,
+13
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
export { default as CelestopolActor } from "./actor.mjs" export { default as CelestopolActor } from "./actor.mjs"
export { default as CelestopolItem } from "./item.mjs" export { default as CelestopolItem } from "./item.mjs"
export { default as CelestopolChatMessage } from "./chat-message.mjs" export { default as CelestopolChatMessage } from "./chat-message.mjs"
+13
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
export default class CelestopolActor extends Actor { export default class CelestopolActor extends Actor {
/** @override */ /** @override */
getRollData() { getRollData() {
+13
View File
@@ -1 +1,14 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
export default class CelestopolChatMessage extends ChatMessage {} export default class CelestopolChatMessage extends ChatMessage {}
+13
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
const SYSTEM_ID = "fvtt-celestopol" const SYSTEM_ID = "fvtt-celestopol"
export default class CelestopolCombat extends Combat { export default class CelestopolCombat extends Combat {
+13
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
export default class CelestopolItem extends Item { export default class CelestopolItem extends Item {
/** @override */ /** @override */
getRollData() { getRollData() {
+219 -26
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import { SYSTEM } from "../config/system.mjs" import { SYSTEM } from "../config/system.mjs"
/** Construit la formule de jet à partir du nombre de dés et du modificateur total. */ /** Construit la formule de jet à partir du nombre de dés et du modificateur total. */
@@ -29,6 +42,58 @@ export class CelestopolRoll extends Roll {
get skillLabel() { return this.options.skillLabel } get skillLabel() { return this.options.skillLabel }
get difficulty() { return this.options.difficulty } get difficulty() { return this.options.difficulty }
/**
* Convertit le niveau de dégâts d'une arme en nombre de blessures de base.
* Règle : une attaque réussie inflige toujours 1 blessure, plus le bonus de dégâts.
* @param {string|number|null} weaponDegats
* @returns {number|null}
*/
static getIncomingWounds(weaponDegats) {
const raw = `${weaponDegats ?? "0"}`
const bonus = Number.parseInt(raw, 10)
if (!Number.isFinite(bonus)) return null
return Math.max(0, 1 + bonus)
}
/**
* Retourne la protection totale de l'armure équipée pour un acteur.
* @param {Actor|null} actor
* @returns {number}
*/
static getActorArmorProtection(actor) {
if (!actor) return 0
if (typeof actor.system?.getArmorMalus === "function") {
return Math.abs(actor.system.getArmorMalus())
}
const derivedArmorMalus = actor.system?.armorMalus
if (Number.isFinite(derivedArmorMalus)) {
return Math.abs(derivedArmorMalus)
}
const armures = actor.itemTypes?.armure ?? []
return armures
.filter(a => a.system.equipped)
.reduce((sum, a) => sum + Math.abs(a.system.protection ?? a.system.malus ?? 0), 0)
}
/**
* Résout un acteur à partir de son UUID si disponible, sinon via son identifiant monde.
* @param {object} ref
* @param {string|null} ref.actorUuid
* @param {string|null} ref.actorId
* @returns {Promise<Actor|null>}
*/
static async resolveActor({ actorUuid = null, actorId = null } = {}) {
if (actorUuid) {
const actorFromUuid = await fromUuid(actorUuid)
if (actorFromUuid) return actorFromUuid
}
if (actorId) return game.actors.get(actorId) ?? null
return null
}
/** /**
* Ouvre le dialogue de configuration du jet via DialogV2 et exécute le jet. * Ouvre le dialogue de configuration du jet via DialogV2 et exécute le jet.
* @param {object} options * @param {object} options
@@ -71,6 +136,8 @@ export class CelestopolRoll extends Roll {
value: m.value, value: m.value,
label: game.i18n.localize(m.label), label: game.i18n.localize(m.label),
})) }))
const factionAspectChoices = game.celestopol?.getFactionAspectSummary(options.actorId ? game.actors.get(options.actorId) : null)
?.availableAspectChoices ?? []
const dialogContext = { const dialogContext = {
actorName: options.actorName, actorName: options.actorName,
@@ -89,6 +156,7 @@ export class CelestopolRoll extends Roll {
aspectChoices, aspectChoices,
situationChoices, situationChoices,
rangedModChoices, rangedModChoices,
factionAspectChoices,
availableTargets, availableTargets,
fortuneValue, fortuneValue,
armorMalus, armorMalus,
@@ -123,7 +191,7 @@ export class CelestopolRoll extends Roll {
function applyTargetSelection() { function applyTargetSelection() {
if (!targetSelect) return if (!targetSelect) return
const selectedOption = targetSelect.options[targetSelect.selectedIndex] const selectedOption = targetSelect.options[targetSelect.selectedIndex]
const val = parseFloat(targetSelect.value) const val = parseFloat(selectedOption?.dataset.corps ?? "")
const corpsPnjInput = wrap.querySelector('#corpsPnj') const corpsPnjInput = wrap.querySelector('#corpsPnj')
if (targetSelect.value && !isNaN(val)) { if (targetSelect.value && !isNaN(val)) {
// Cible sélectionnée : masquer la valeur, afficher le nom // Cible sélectionnée : masquer la valeur, afficher le nom
@@ -155,6 +223,8 @@ export class CelestopolRoll extends Roll {
const autoSucc = rawMod === "auto" const autoSucc = rawMod === "auto"
const modifier = autoSucc ? 0 : (parseInt(rawMod ?? 0) || 0) const modifier = autoSucc ? 0 : (parseInt(rawMod ?? 0) || 0)
const aspectMod = parseInt(wrap.querySelector('#aspectModifier')?.value ?? 0) || 0 const aspectMod = parseInt(wrap.querySelector('#aspectModifier')?.value ?? 0) || 0
const selectedFactionAspect = wrap.querySelector('#factionAspectId')?.selectedOptions?.[0]
const factionAspectBonus = parseInt(selectedFactionAspect?.dataset.value ?? 0) || 0
const situMod = parseInt(wrap.querySelector('#situationMod')?.value ?? 0) || 0 const situMod = parseInt(wrap.querySelector('#situationMod')?.value ?? 0) || 0
const rangedMod = parseInt(wrap.querySelector('#rangedMod')?.value ?? 0) || 0 const rangedMod = parseInt(wrap.querySelector('#rangedMod')?.value ?? 0) || 0
const useDestin = wrap.querySelector('#useDestin')?.checked const useDestin = wrap.querySelector('#useDestin')?.checked
@@ -180,7 +250,7 @@ export class CelestopolRoll extends Roll {
const effSit = puiser ? Math.max(0, situMod) : situMod const effSit = puiser ? Math.max(0, situMod) : situMod
const effArmor = puiser ? 0 : armorMalus const effArmor = puiser ? 0 : armorMalus
const effRanged = puiser ? Math.max(0, rangedMod) : rangedMod const effRanged = puiser ? Math.max(0, rangedMod) : rangedMod
const totalMod = skillValue + effWound + effMod + effAspect + effSit + effArmor + effRanged const totalMod = skillValue + effWound + effMod + effAspect + factionAspectBonus + effSit + effArmor + effRanged
let formula let formula
if (autoSucc) { if (autoSucc) {
@@ -198,7 +268,7 @@ export class CelestopolRoll extends Roll {
if (previewEl) previewEl.textContent = formula if (previewEl) previewEl.textContent = formula
} }
wrap.querySelectorAll('#modifier, #aspectModifier, #situationMod, #rangedMod, #useDestin, #useFortune, #puiserRessources, #corpsPnj') wrap.querySelectorAll('#modifier, #aspectModifier, #factionAspectId, #situationMod, #rangedMod, #useDestin, #useFortune, #puiserRessources, #corpsPnj')
.forEach(el => { .forEach(el => {
el.addEventListener('change', update) el.addEventListener('change', update)
el.addEventListener('input', update) el.addEventListener('input', update)
@@ -224,22 +294,38 @@ export class CelestopolRoll extends Roll {
if (!rollContext) return null if (!rollContext) return null
// En combat : Corps PNJ = seuil direct ; sinon seuil fixe = 11 // En combat : Corps PNJ = seuil direct ; sinon seuil configurable via setting
const corpsPnj = isCombat ? (parseInt(rollContext.corpsPnj ?? 7) || 7) : null const corpsPnj = isCombat ? (parseInt(rollContext.corpsPnj ?? 7) || 7) : null
const difficulty = isCombat ? "combat" : "standard" const difficulty = isCombat ? "combat" : "standard"
const defaultThreshold = game.settings.get(game.celestopol.SYSTEM.id, game.celestopol.DEFAULT_ROLL_THRESHOLD_SETTING)
const diffConfig = isCombat const diffConfig = isCombat
? { value: corpsPnj, label: "CELESTOPOL.Combat.corpsPnj" } ? { value: corpsPnj, label: "CELESTOPOL.Combat.corpsPnj" }
: { value: 11, label: "CELESTOPOL.Roll.threshold" } : { value: defaultThreshold, label: "CELESTOPOL.Roll.threshold" }
const autoSuccess = rollContext.modifier === "auto" const autoSuccess = rollContext.modifier === "auto"
const modifier = autoSuccess ? 0 : (parseInt(rollContext.modifier ?? 0) || 0) const modifier = autoSuccess ? 0 : (parseInt(rollContext.modifier ?? 0) || 0)
const aspectMod = parseInt(rollContext.aspectModifier ?? 0) || 0 const aspectMod = parseInt(rollContext.aspectModifier ?? 0) || 0
const factionAspectId = typeof rollContext.factionAspectId === "string" ? rollContext.factionAspectId : ""
const selectedFactionAspect = factionAspectChoices.find(choice => choice.id === factionAspectId) ?? null
const factionAspectBonus = selectedFactionAspect?.value ?? 0
const factionAspectLabel = selectedFactionAspect?.label ?? ""
const situationMod = parseInt(rollContext.situationMod ?? 0) || 0 const situationMod = parseInt(rollContext.situationMod ?? 0) || 0
const rangedMod = isRangedAttack ? (parseInt(rollContext.rangedMod ?? 0) || 0) : 0 const rangedMod = isRangedAttack ? (parseInt(rollContext.rangedMod ?? 0) || 0) : 0
const isOpposition = !isCombat && !isResistance && (rollContext.isOpposition === true || rollContext.isOpposition === "true") const isOpposition = !isCombat && (rollContext.isOpposition === true || rollContext.isOpposition === "true")
const useDestin = destGaugeFull && (rollContext.useDestin === true || rollContext.useDestin === "true") const useDestin = destGaugeFull && (rollContext.useDestin === true || rollContext.useDestin === "true")
const useFortune = fortuneValue > 0 && (rollContext.useFortune === true || rollContext.useFortune === "true") const useFortune = fortuneValue > 0 && (rollContext.useFortune === true || rollContext.useFortune === "true")
const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true" const puiserRessources = rollContext.puiserRessources === true || rollContext.puiserRessources === "true"
const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "true" const rollMoonDie = rollContext.rollMoonDie === true || rollContext.rollMoonDie === "true"
const selectedCombatTargetRef = typeof rollContext.targetSelect === "string" ? rollContext.targetSelect : ""
const selectedCombatTarget = selectedCombatTargetRef
? availableTargets.find(t => t.uuid === selectedCombatTargetRef || t.id === selectedCombatTargetRef) ?? null
: null
const resolvedWeaponName = (isRangedDefense && selectedCombatTarget?.weaponName) ? selectedCombatTarget.weaponName : weaponName
const resolvedWeaponDegats = (isRangedDefense && selectedCombatTarget?.weaponDegats) ? selectedCombatTarget.weaponDegats : weaponDegats
// Dégâts de l'arme adverse en cas d'échec (arme équipée du PNJ ciblé en mêlée, arme distance en esquive)
const incomingWeaponDegats = selectedCombatTarget?.weaponDegats ?? resolvedWeaponDegats
const targetActorId = selectedCombatTarget?.id || ""
const targetActorUuid = selectedCombatTarget?.uuid || ""
const targetActorName = selectedCombatTarget?.name || ""
// En résistance : forcer puiser=false, lune=false, fortune=false, destin=false // En résistance : forcer puiser=false, lune=false, fortune=false, destin=false
const effectivePuiser = isResistance ? false : puiserRessources const effectivePuiser = isResistance ? false : puiserRessources
@@ -255,7 +341,7 @@ export class CelestopolRoll extends Roll {
// Fortune : 1d8 + 8 ; Destin : 3d8 ; sinon : 2d8 // Fortune : 1d8 + 8 ; Destin : 3d8 ; sinon : 2d8
const nbDice = (!isResistance && useDestin) ? 3 : 2 const nbDice = (!isResistance && useDestin) ? 3 : 2
const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier + effectiveSituationMod + effectiveArmorMalus + effectiveRangedMod const totalModifier = skillValue + effectiveWoundMalus + effectiveAspectMod + effectiveModifier + factionAspectBonus + effectiveSituationMod + effectiveArmorMalus + effectiveRangedMod
const formula = (!isResistance && useFortune) const formula = (!isResistance && useFortune)
? buildFormula(1, totalModifier + 8) ? buildFormula(1, totalModifier + 8)
: buildFormula(nbDice, totalModifier) : buildFormula(nbDice, totalModifier)
@@ -277,6 +363,9 @@ export class CelestopolRoll extends Roll {
difficultyValue: diffConfig.value, difficultyValue: diffConfig.value,
modifier: effectiveModifier, modifier: effectiveModifier,
aspectMod: effectiveAspectMod, aspectMod: effectiveAspectMod,
factionAspectId,
factionAspectLabel,
factionAspectBonus,
situationMod: effectiveSituationMod, situationMod: effectiveSituationMod,
woundMalus: effectiveWoundMalus, woundMalus: effectiveWoundMalus,
autoSuccess, autoSuccess,
@@ -285,15 +374,20 @@ export class CelestopolRoll extends Roll {
isCombat, isCombat,
isRangedDefense, isRangedDefense,
weaponType, weaponType,
weaponName, weaponName: resolvedWeaponName,
weaponDegats, weaponDegats: resolvedWeaponDegats,
incomingWeaponDegats,
targetActorId,
targetActorUuid,
targetActorName,
availableTargets,
rangedMod: effectiveRangedMod, rangedMod: effectiveRangedMod,
useDestin: !isResistance && useDestin, useDestin: !isResistance && useDestin,
useFortune: !isResistance && useFortune, useFortune: !isResistance && useFortune,
puiserRessources: effectivePuiser, puiserRessources: effectivePuiser,
nbDice: (!isResistance && useFortune) ? 1 : nbDice, nbDice: (!isResistance && useFortune) ? 1 : nbDice,
formula, formula,
rollMode: rollContext.visibility ?? "publicroll", rollMode: rollContext.visibility ?? "public",
rollMoonDie: effectiveMoon, rollMoonDie: effectiveMoon,
moonDieResult, moonDieResult,
moonFace, moonFace,
@@ -305,7 +399,10 @@ export class CelestopolRoll extends Roll {
roll.computeResult() roll.computeResult()
// Test de résistance échoué → cocher automatiquement la prochaine case de blessure // Test de résistance échoué → cocher automatiquement la prochaine case de blessure
const actor = game.actors.get(options.actorId) const actor = await this.resolveActor({
actorUuid: options.actorUuid ?? null,
actorId: options.actorId ?? null,
})
if (isResistance && actor && roll.options.resultType === "failure") { if (isResistance && actor && roll.options.resultType === "failure") {
const nextLvl = (actor.system.blessures.lvl ?? 0) + 1 const nextLvl = (actor.system.blessures.lvl ?? 0) + 1
if (nextLvl <= 8) { if (nextLvl <= 8) {
@@ -314,16 +411,25 @@ export class CelestopolRoll extends Roll {
} }
} }
// Mêlée échouée OU défense à distance échouée → joueur prend une blessure // Mêlée échouée OU défense à distance échouée → le protagoniste (PJ uniquement) subit les dégâts de l'arme PNJ
if (isCombat && (weaponType === "melee" || isRangedDefense) && actor && roll.options.resultType === "failure") { if (isCombat && (weaponType === "melee" || isRangedDefense) && actor?.type === "character" && roll.options.resultType === "failure") {
const nextLvl = (actor.system.blessures.lvl ?? 0) + 1 const incomingWounds = this.getIncomingWounds(roll.options.incomingWeaponDegats ?? resolvedWeaponDegats)
if (nextLvl <= 8) { const protection = this.getActorArmorProtection(actor)
const appliedWounds = incomingWounds === null
? 1
: Math.max(0, incomingWounds - protection)
if (appliedWounds > 0) {
const nextLvl = Math.min(8, (actor.system.blessures.lvl ?? 0) + appliedWounds)
await actor.update({ "system.blessures.lvl": nextLvl }) await actor.update({ "system.blessures.lvl": nextLvl })
roll.options.woundTaken = nextLvl roll.options.woundTaken = nextLvl
roll.options.woundTakenCount = appliedWounds
roll.options.incomingWounds = incomingWounds
roll.options.selectedTargetProtection = protection
roll.options.selectedTargetAppliedWounds = appliedWounds
} }
} }
await roll.toMessage({}, { rollMode: rollData.rollMode }) await roll.toMessage({}, { messageMode: rollData.rollMode })
// Batching de toutes les mises à jour de l'acteur en un seul appel réseau // Batching de toutes les mises à jour de l'acteur en un seul appel réseau
if (actor) { if (actor) {
@@ -359,7 +465,7 @@ export class CelestopolRoll extends Roll {
/** /**
* Détermine succès/échec selon la marge (total seuil). * Détermine succès/échec selon la marge (total seuil).
* Seuil : 11 pour les tests normaux, Corps PNJ pour le combat. * Seuil : configurable via setting pour les tests normaux, Corps PNJ pour le combat.
* Pas de succès/échec critique — seul le Dé de la Lune produit des résultats exceptionnels. * Pas de succès/échec critique — seul le Dé de la Lune produit des résultats exceptionnels.
*/ */
computeResult() { computeResult() {
@@ -374,9 +480,10 @@ export class CelestopolRoll extends Roll {
this.options.margin = null this.options.margin = null
return return
} }
const defaultThreshold = game.settings.get(game.celestopol.SYSTEM.id, game.celestopol.DEFAULT_ROLL_THRESHOLD_SETTING)
const threshold = this.options.isCombat const threshold = this.options.isCombat
? (this.options.difficultyValue ?? 0) ? (this.options.difficultyValue ?? 0)
: 11 : defaultThreshold
if (threshold === 0) { if (threshold === 0) {
this.options.resultType = "unknown" this.options.resultType = "unknown"
this.options.margin = null this.options.margin = null
@@ -405,13 +512,16 @@ export class CelestopolRoll extends Roll {
const resultType = this.resultType const resultType = this.resultType
const diceResults = this.dice[0]?.results?.map(r => r.result) ?? [] const diceResults = this.dice[0]?.results?.map(r => r.result) ?? []
const diceSum = diceResults.reduce((a, b) => a + b, 0) const diceSum = diceResults.reduce((a, b) => a + b, 0)
const defaultThreshold = game.settings.get(game.celestopol.SYSTEM.id, game.celestopol.DEFAULT_ROLL_THRESHOLD_SETTING)
const threshold = this.options.isCombat const threshold = this.options.isCombat
? (this.options.difficultyValue ?? 0) ? (this.options.difficultyValue ?? 0)
: 11 : defaultThreshold
const margin = this.options.margin const margin = this.options.margin
const woundMalus = this.options.woundMalus ?? 0 const woundMalus = this.options.woundMalus ?? 0
const armorMalus = this.options.armorMalus ?? 0
const skillValue = this.options.skillValue ?? 0 const skillValue = this.options.skillValue ?? 0
const woundLevelId = this.options.woundLevel ?? 0 const woundLevelId = this.options.woundLevel ?? 0
const weaponDegats = `${this.options.weaponDegats ?? "0"}`
const woundLabel = woundLevelId > 0 const woundLabel = woundLevelId > 0
? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "") ? game.i18n.localize(SYSTEM.WOUND_LEVELS[woundLevelId]?.label ?? "")
: null : null
@@ -430,13 +540,40 @@ export class CelestopolRoll extends Roll {
} }
const isOpposition = this.options.isOpposition ?? false const isOpposition = this.options.isOpposition ?? false
const isWeaponHit = (this.options.isCombat ?? false) && !(this.options.isRangedDefense ?? false) && this.isSuccess
const incomingWounds = isWeaponHit ? this.constructor.getIncomingWounds(weaponDegats) : null
const hasVariableDamage = isWeaponHit && incomingWounds === null
const targetActorId = this.options.targetActorId ?? ""
const targetActorUuid = this.options.targetActorUuid ?? ""
const targetActorName = this.options.targetActorName ?? ""
const availableTargets = (this.options.availableTargets ?? []).map(target => ({
...target,
selected: target.uuid === targetActorUuid || target.id === targetActorId,
}))
const selectedTargetActor = await this.constructor.resolveActor({
actorUuid: targetActorUuid,
actorId: targetActorId,
})
const selectedTargetProtection = selectedTargetActor
? this.constructor.getActorArmorProtection(selectedTargetActor)
: null
const selectedTargetAppliedWounds = (incomingWounds !== null && selectedTargetActor)
? Math.max(0, incomingWounds - selectedTargetProtection)
: null
// Libellé de difficulté : en combat "Corps PNJ : N", en opposition "vs ?", sinon "Seuil : 11" // Type de l'acteur qui lance le jet (character | npc)
const rollingActor = await this.constructor.resolveActor({
actorUuid: this.options.actorUuid ?? null,
actorId: this.options.actorId ?? null,
})
const actorType = rollingActor?.type ?? this.options.actorType ?? null
// Libellé de difficulté : en combat "Corps PNJ : N", en opposition "vs ?", sinon "Seuil : N"
const difficultyLabel = this.options.isCombat const difficultyLabel = this.options.isCombat
? `${game.i18n.localize("CELESTOPOL.Combat.corpsPnj")} : ${threshold}` ? `${game.i18n.localize("CELESTOPOL.Combat.corpsPnj")} : ${threshold}`
: isOpposition : isOpposition
? `${game.i18n.localize("CELESTOPOL.Roll.oppositionVs")}` ? `${game.i18n.localize("CELESTOPOL.Roll.oppositionVs")}`
: `${game.i18n.localize("CELESTOPOL.Roll.threshold")} : 11` : `${game.i18n.localize("CELESTOPOL.Roll.threshold")} : ${threshold}`
return { return {
cssClass: [SYSTEM.id, "dice-roll"].join(" "), cssClass: [SYSTEM.id, "dice-roll"].join(" "),
@@ -464,22 +601,39 @@ export class CelestopolRoll extends Roll {
modifier: this.options.modifier ?? 0, modifier: this.options.modifier ?? 0,
autoSuccess: this.options.autoSuccess ?? false, autoSuccess: this.options.autoSuccess ?? false,
aspectMod: this.options.aspectMod ?? 0, aspectMod: this.options.aspectMod ?? 0,
factionAspectLabel: this.options.factionAspectLabel ?? "",
factionAspectBonus: this.options.factionAspectBonus ?? 0,
skillValue, skillValue,
useDestin: this.options.useDestin ?? false, useDestin: this.options.useDestin ?? false,
useFortune: this.options.useFortune ?? false, useFortune: this.options.useFortune ?? false,
puiserRessources: this.options.puiserRessources ?? false, puiserRessources: this.options.puiserRessources ?? false,
nbDice: this.options.nbDice ?? diceResults.length, nbDice: this.options.nbDice ?? diceResults.length,
woundMalus, woundMalus,
armorMalus,
woundLabel, woundLabel,
isResistance: this.options.isResistance ?? false, isResistance: this.options.isResistance ?? false,
isCombat: this.options.isCombat ?? false, isCombat: this.options.isCombat ?? false,
actorType,
isNpcAttack: actorType === "npc",
weaponName: this.options.weaponName ?? null, weaponName: this.options.weaponName ?? null,
weaponDegats: this.options.weaponDegats ?? null, weaponDegats,
weaponType: this.options.weaponType ?? null, weaponType: this.options.weaponType ?? null,
isRangedDefense: this.options.isRangedDefense ?? false, isRangedDefense: this.options.isRangedDefense ?? false,
woundTaken: this.options.woundTaken ?? null, woundTaken: this.options.woundTaken ?? null,
woundTakenCount: this.options.woundTakenCount ?? null,
situationMod: this.options.situationMod ?? 0, situationMod: this.options.situationMod ?? 0,
rangedMod: this.options.rangedMod ?? 0, rangedMod: this.options.rangedMod ?? 0,
hasDamageSummary: isWeaponHit,
incomingWounds,
incomingWoundsDisplay: incomingWounds ?? "1 + X",
hasVariableDamage,
canApplyWeaponDamage: incomingWounds !== null,
targetActorId,
targetActorUuid,
targetActorName,
selectedTargetProtection,
selectedTargetAppliedWounds,
availableTargets,
// Dé de lune // Dé de lune
hasMoonDie: moonDieResult !== null, hasMoonDie: moonDieResult !== null,
moonDieResult, moonDieResult,
@@ -488,20 +642,55 @@ export class CelestopolRoll extends Roll {
moonResultClass: moonResultType?.cssClass ?? "", moonResultClass: moonResultType?.cssClass ?? "",
moonResultLabel: moonResultType ? game.i18n.localize(moonResultType.label) : "", moonResultLabel: moonResultType ? game.i18n.localize(moonResultType.label) : "",
moonResultDesc: moonResultType ? game.i18n.localize(moonResultType.desc) : "", moonResultDesc: moonResultType ? game.i18n.localize(moonResultType.desc) : "",
moonResultTypeId: moonResultType?.id ?? null,
moonActorId: (actorType === "character") ? (this.options.actorId ?? null) : null,
moonActorUuid: (actorType === "character") ? (this.options.actorUuid ?? null) : null,
moonActorIsCharacter: actorType === "character",
isPrivate, isPrivate,
tooltip: isPrivate ? "" : await this.getTooltip(), tooltip: isPrivate ? "" : await this.getTooltip(),
} }
} }
/** @override */ /** @override */
async toMessage(messageData = {}, { rollMode, create = true } = {}) { async toMessage(messageData = {}, { messageMode, rollMode, create = true } = {}) {
const modernToLegacyRollMode = {
public: CONST.DICE_ROLL_MODES.PUBLIC,
gm: CONST.DICE_ROLL_MODES.PRIVATE,
blind: CONST.DICE_ROLL_MODES.BLIND,
self: CONST.DICE_ROLL_MODES.SELF,
ic: CONST.DICE_ROLL_MODES.PUBLIC,
}
let effectiveRollMode = rollMode ?? messageMode ?? game.settings.get("core", "rollMode") ?? CONST.DICE_ROLL_MODES.PUBLIC
effectiveRollMode = modernToLegacyRollMode[effectiveRollMode] ?? effectiveRollMode
if (!Object.values(CONST.DICE_ROLL_MODES).includes(effectiveRollMode)) {
effectiveRollMode = game.settings.get("core", "rollMode") ?? CONST.DICE_ROLL_MODES.PUBLIC
}
if (!this._evaluated) await this.evaluate({ allowInteractive: effectiveRollMode !== CONST.DICE_ROLL_MODES.BLIND })
const skillLocalized = this.skillLabel ? game.i18n.localize(this.skillLabel) : "" const skillLocalized = this.skillLabel ? game.i18n.localize(this.skillLabel) : ""
const statLocalized = this.options.statLabel const statLocalized = this.options.statLabel
? game.i18n.localize(this.options.statLabel) : "" ? game.i18n.localize(this.options.statLabel) : ""
const flavor = statLocalized const flavor = statLocalized
? `<strong>${statLocalized} ${skillLocalized}</strong>` ? `<strong>${statLocalized} ${skillLocalized}</strong>`
: `<strong>${skillLocalized}</strong>` : `<strong>${skillLocalized}</strong>`
return super.toMessage({ flavor, ...messageData }, { rollMode }) const speakerActor = await this.constructor.resolveActor({
actorUuid: this.options.actorUuid ?? null,
actorId: this.options.actorId ?? null,
})
const content = await this.render({ isPrivate: effectiveRollMode !== CONST.DICE_ROLL_MODES.PUBLIC })
const chatData = foundry.utils.mergeObject({
author: game.user.id,
content,
flavor,
sound: CONFIG.sounds.dice,
rolls: [this],
speaker: speakerActor ? ChatMessage.getSpeaker({ actor: speakerActor }) : undefined,
style: CONST.CHAT_MESSAGE_STYLES.OTHER,
}, messageData)
ChatMessage.applyRollMode(chatData, effectiveRollMode)
if (create) return ChatMessage.create(chatData)
return chatData
} }
/** /**
@@ -516,6 +705,7 @@ export class CelestopolRoll extends Roll {
const resultType = face ? SYSTEM.MOON_RESULT_TYPES[face.result] ?? null : null const resultType = face ? SYSTEM.MOON_RESULT_TYPES[face.result] ?? null : null
const isGoodFortune = result <= 4 const isGoodFortune = result <= 4
const actorIsCharacter = actor?.type === "character"
const templateData = { const templateData = {
result, result,
moonFaceSymbol: face?.symbol ?? "", moonFaceSymbol: face?.symbol ?? "",
@@ -523,8 +713,12 @@ export class CelestopolRoll extends Roll {
moonResultLabel: resultType ? game.i18n.localize(resultType.label) : "", moonResultLabel: resultType ? game.i18n.localize(resultType.label) : "",
moonResultDesc: resultType ? game.i18n.localize(resultType.desc) : "", moonResultDesc: resultType ? game.i18n.localize(resultType.desc) : "",
moonResultClass: resultType?.cssClass ?? "", moonResultClass: resultType?.cssClass ?? "",
moonResultTypeId: resultType?.id ?? null,
isGoodFortune, isGoodFortune,
actorName: actor?.name ?? null, actorName: actor?.name ?? null,
moonActorIsCharacter: actorIsCharacter,
moonActorId: actorIsCharacter ? (actor.id ?? null) : null,
moonActorUuid: actorIsCharacter ? (actor.uuid ?? null) : null,
} }
const content = await foundry.applications.handlebars.renderTemplate( const content = await foundry.applications.handlebars.renderTemplate(
@@ -539,8 +733,7 @@ export class CelestopolRoll extends Roll {
await ChatMessage.create({ await ChatMessage.create({
content, content,
speaker, speaker,
rolls: [roll], style: CONST.CHAT_MESSAGE_STYLES.OTHER,
style: CONST.CHAT_MESSAGE_STYLES?.ROLL ?? 5,
}) })
} }
} }
+13
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
export { default as CelestopolCharacter } from "./character.mjs" export { default as CelestopolCharacter } from "./character.mjs"
export { default as CelestopolNPC } from "./npc.mjs" export { default as CelestopolNPC } from "./npc.mjs"
export { CelestopolAnomaly, CelestopolAspect, CelestopolEquipment, CelestopolWeapon, CelestopolArmure } from "./items.mjs" export { CelestopolAnomaly, CelestopolAspect, CelestopolEquipment, CelestopolWeapon, CelestopolArmure } from "./items.mjs"
+146 -34
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import { SYSTEM } from "../config/system.mjs" import { SYSTEM } from "../config/system.mjs"
export default class CelestopolCharacter extends foundry.abstract.TypeDataModel { export default class CelestopolCharacter extends foundry.abstract.TypeDataModel {
@@ -116,6 +129,8 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
// Description & notes // Description & notes
schema.descriptionPhysique = new fields.HTMLField({ required: true, textSearch: true }) schema.descriptionPhysique = new fields.HTMLField({ required: true, textSearch: true })
schema.descriptionPsychologique = new fields.HTMLField({ required: true, textSearch: true }) schema.descriptionPsychologique = new fields.HTMLField({ required: true, textSearch: true })
schema.historique = new fields.HTMLField({ required: true, initial: "", textSearch: true })
schema.portraitImage = new fields.StringField({ required: true, nullable: false, initial: "" })
schema.notes = new fields.HTMLField({ required: true, textSearch: true }) schema.notes = new fields.HTMLField({ required: true, textSearch: true })
// Données biographiques // Données biographiques
@@ -165,8 +180,21 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
getArmorMalus() { getArmorMalus() {
if (!this.parent) return 0 if (!this.parent) return 0
return -(this.parent.itemTypes.armure return -(this.parent.itemTypes.armure
.filter(a => a.system.equipped && a.system.malus > 0) .filter(a => a.system.equipped && (a.system.protection ?? a.system.malus) > 0)
.reduce((sum, a) => sum + a.system.malus, 0)) .reduce((sum, a) => sum + (a.system.protection ?? a.system.malus), 0))
}
/**
* Retourne le malus d'armure applicable pour un jet PJ.
* Règle : uniquement sur Mobilité et Effacement si l'armure est équipée.
* @param {string} statId
* @param {string|null} skillId
* @returns {number}
*/
getArmorMalusForRoll(statId, skillId = null) {
if (statId !== "corps") return 0
if (!["mobilite", "effacement"].includes(skillId)) return 0
return this.getArmorMalus()
} }
/** /**
@@ -190,6 +218,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
return CelestopolRoll.prompt({ return CelestopolRoll.prompt({
actorId: this.parent.id, actorId: this.parent.id,
actorUuid: this.parent.uuid,
actorName: this.parent.name, actorName: this.parent.name,
actorImage: this.parent.img, actorImage: this.parent.img,
statId, statId,
@@ -198,11 +227,11 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
skillLabel: skill.label, skillLabel: skill.label,
skillValue: skill.value, skillValue: skill.value,
woundMalus: this.getWoundMalus(), woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(), armorMalus: this.getArmorMalusForRoll(statId, skillId),
woundLevel: this.blessures.lvl, woundLevel: this.blessures.lvl,
difficulty: this.prefs.difficulty, difficulty: this.prefs.difficulty,
rollMoonDie: this.prefs.rollMoonDie ?? false, rollMoonDie: this.prefs.rollMoonDie ?? false,
destGaugeFull: this.destin.lvl > 0, destGaugeFull: this.destin.lvl >= 8,
fortuneValue: this.attributs.fortune.value, fortuneValue: this.attributs.fortune.value,
}) })
} }
@@ -221,6 +250,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
return CelestopolRoll.prompt({ return CelestopolRoll.prompt({
actorId: this.parent.id, actorId: this.parent.id,
actorUuid: this.parent.uuid,
actorName: this.parent.name, actorName: this.parent.name,
actorImage: this.parent.img, actorImage: this.parent.img,
statId, statId,
@@ -229,7 +259,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
skillLabel: "CELESTOPOL.Roll.resistanceTest", skillLabel: "CELESTOPOL.Roll.resistanceTest",
skillValue: statData.res, skillValue: statData.res,
woundMalus: this.getWoundMalus(), woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(), armorMalus: 0,
woundLevel: this.blessures.lvl, woundLevel: this.blessures.lvl,
isResistance: true, isResistance: true,
rollMoonDie: false, rollMoonDie: false,
@@ -240,35 +270,48 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
} }
/** /**
* Collecte les tokens PNJs disponibles comme cibles de combat. * Collecte les cibles de combat sur la scène active.
* Priorise le combat tracker, sinon les tokens ciblés par l'utilisateur. * Pour un PJ attaquant, seules les cibles PNJ présentes sur la scène sont proposées.
* @returns {Array<{id:string, name:string, corps:number}>} * @param {object} options
* @param {boolean} [options.onlyRanged=false] - Filtrer sur les PNJ ayant une arme à distance équipée
* @param {boolean} [options.fallbackToAll=false] - Revenir à tous les PNJ si aucune cible trouvée
* @param {boolean} [options.includeMeleeWeapon=false] - Inclure l'arme de mêlée équipée du PNJ (dégâts adverses)
* @returns {Array<{id:string, uuid:string, name:string, corps:number, weaponName?:string, weaponDegats?:string}>}
*/ */
_getCombatTargets() { _getCombatTargets({ onlyRanged = false, fallbackToAll = false, includeMeleeWeapon = false } = {}) {
const toEntry = actor => ({ const getEquippedWeapon = (actor, type) =>
actor.itemTypes?.weapon?.find(item => item.system.type === type && item.system.equipped) ?? null
const toEntry = actor => {
const entry = {
id: actor.id, id: actor.id,
uuid: actor.uuid,
name: actor.name, name: actor.name,
corps: actor.system.stats?.corps?.res ?? 0, corps: actor.system.stats?.corps?.res ?? 0,
})
// Priorité 1 : PNJs dans le combat actif
if (game.combat?.active) {
const list = game.combat.combatants
.filter(c => c.actor?.type === "npc" && c.actorId !== this.parent.id)
.map(c => toEntry(c.actor))
if (list.length) return list
} }
// Priorité 2 : Tokens ciblés par le joueur if (onlyRanged) {
const targeted = [...(game.user?.targets ?? [])] const weapon = getEquippedWeapon(actor, "distance")
.filter(t => t.actor?.type === "npc") if (weapon) {
.map(t => toEntry(t.actor)) entry.weaponName = weapon.name
if (targeted.length) return targeted entry.weaponDegats = weapon.system.degats
// Priorité 3 : Tous les tokens NPC de la scène active }
if (canvas?.tokens?.placeables) { } else if (includeMeleeWeapon) {
return canvas.tokens.placeables const weapon = getEquippedWeapon(actor, "melee")
entry.weaponDegats = weapon ? weapon.system.degats : "0"
}
return entry
}
const sceneTokens = canvas?.scene?.isView ? (canvas.tokens?.placeables ?? []) : []
const targets = [...new Map(sceneTokens
.filter(t => t.actor?.type === "npc" && t.actor.id !== this.parent.id) .filter(t => t.actor?.type === "npc" && t.actor.id !== this.parent.id)
.map(t => toEntry(t.actor)) .filter(t => !onlyRanged || getEquippedWeapon(t.actor, "distance"))
} .map(t => {
return [] const actor = t.actor
return [actor.uuid, toEntry(actor)]
})).values()]
if (!targets.length && onlyRanged && fallbackToAll) return this._getCombatTargets()
return targets
} }
/** /**
@@ -288,6 +331,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
return CelestopolRoll.prompt({ return CelestopolRoll.prompt({
actorId: this.parent.id, actorId: this.parent.id,
actorUuid: this.parent.uuid,
actorName: this.parent.name, actorName: this.parent.name,
actorImage: this.parent.img, actorImage: this.parent.img,
statId: "corps", statId: "corps",
@@ -296,17 +340,50 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label, skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
skillValue: echauffouree.value, skillValue: echauffouree.value,
woundMalus: this.getWoundMalus(), woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(), armorMalus: this.getArmorMalusForRoll("corps", "echauffouree"),
woundLevel: this.blessures.lvl, woundLevel: this.blessures.lvl,
rollMoonDie: this.prefs.rollMoonDie ?? false, rollMoonDie: this.prefs.rollMoonDie ?? false,
destGaugeFull: this.destin.lvl > 0, destGaugeFull: this.destin.lvl >= 8,
fortuneValue: this.attributs.fortune.value, fortuneValue: this.attributs.fortune.value,
isCombat: true, isCombat: true,
isRangedDefense: false, isRangedDefense: false,
weaponType: item.system.type, weaponType: item.system.type,
weaponName: item.name, weaponName: item.name,
weaponDegats: item.system.degats, weaponDegats: item.system.degats,
availableTargets: this._getCombatTargets(), availableTargets: this._getCombatTargets({ includeMeleeWeapon: item.system.type === "melee" }),
})
}
/**
* Lance une attaque à mains nues (Échauffourée sans arme).
*/
async rollUnarmedAttack() {
const { CelestopolRoll } = await import("../documents/roll.mjs")
const echauffouree = this.stats.corps.echauffouree
if (!echauffouree) return null
return CelestopolRoll.prompt({
actorId: this.parent.id,
actorUuid: this.parent.uuid,
actorName: this.parent.name,
actorImage: this.parent.img,
statId: "corps",
skillId: "echauffouree",
statLabel: SYSTEM.STATS.corps.label,
skillLabel: SYSTEM.SKILLS.corps.echauffouree.label,
skillValue: echauffouree.value,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalusForRoll("corps", "echauffouree"),
woundLevel: this.blessures.lvl,
rollMoonDie: this.prefs.rollMoonDie ?? false,
destGaugeFull: this.destin.lvl >= 8,
fortuneValue: this.attributs.fortune.value,
isCombat: true,
isRangedDefense: false,
weaponType: "melee",
weaponName: game.i18n.localize("CELESTOPOL.Combat.unarmedAttack"),
weaponDegats: "0",
availableTargets: this._getCombatTargets({ includeMeleeWeapon: true }),
}) })
} }
@@ -326,6 +403,7 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
return CelestopolRoll.prompt({ return CelestopolRoll.prompt({
actorId: this.parent.id, actorId: this.parent.id,
actorUuid: this.parent.uuid,
actorName: this.parent.name, actorName: this.parent.name,
actorImage: this.parent.img, actorImage: this.parent.img,
statId: "corps", statId: "corps",
@@ -334,17 +412,51 @@ export default class CelestopolCharacter extends foundry.abstract.TypeDataModel
skillLabel: SYSTEM.SKILLS.corps.mobilite.label, skillLabel: SYSTEM.SKILLS.corps.mobilite.label,
skillValue: mobilite.value, skillValue: mobilite.value,
woundMalus: this.getWoundMalus(), woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(), armorMalus: this.getArmorMalusForRoll("corps", "mobilite"),
woundLevel: this.blessures.lvl, woundLevel: this.blessures.lvl,
rollMoonDie: this.prefs.rollMoonDie ?? false, rollMoonDie: this.prefs.rollMoonDie ?? false,
destGaugeFull: this.destin.lvl > 0, destGaugeFull: this.destin.lvl >= 8,
fortuneValue: this.attributs.fortune.value, fortuneValue: this.attributs.fortune.value,
isCombat: true, isCombat: true,
isRangedDefense: true, isRangedDefense: true,
weaponType: "distance", weaponType: "distance",
weaponName: item.name, weaponName: item.name,
weaponDegats: "0", weaponDegats: "0",
availableTargets: this._getCombatTargets(), availableTargets: this._getCombatTargets({ onlyRanged: true, fallbackToAll: true }),
})
}
/**
* Lance une esquive d'attaque à distance sans dépendre d'une arme possédée par le PJ.
* @returns {Promise<import("../documents/roll.mjs").CelestopolRoll|null>}
*/
async rollRangedDefenseBase() {
const { CelestopolRoll } = await import("../documents/roll.mjs")
const mobilite = this.stats.corps.mobilite
if (!mobilite) return null
return CelestopolRoll.prompt({
actorId: this.parent.id,
actorUuid: this.parent.uuid,
actorName: this.parent.name,
actorImage: this.parent.img,
statId: "corps",
skillId: "mobilite",
statLabel: SYSTEM.STATS.corps.label,
skillLabel: SYSTEM.SKILLS.corps.mobilite.label,
skillValue: mobilite.value,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalusForRoll("corps", "mobilite"),
woundLevel: this.blessures.lvl,
rollMoonDie: this.prefs.rollMoonDie ?? false,
destGaugeFull: this.destin.lvl >= 8,
fortuneValue: this.attributs.fortune.value,
isCombat: true,
isRangedDefense: true,
weaponType: "distance",
weaponName: game.i18n.localize("CELESTOPOL.Combat.rangedAttack"),
weaponDegats: "0",
availableTargets: this._getCombatTargets({ onlyRanged: true, fallbackToAll: true }),
}) })
} }
} }
+20 -1
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import { SYSTEM } from "../config/system.mjs" import { SYSTEM } from "../config/system.mjs"
/** Schéma partagé pour les bonus/malus par domaine (utilisé dans anomaly/aspect). */ /** Schéma partagé pour les bonus/malus par domaine (utilisé dans anomaly/aspect). */
@@ -72,6 +85,7 @@ export class CelestopolWeapon extends foundry.abstract.TypeDataModel {
choices: Object.keys(SYSTEM.WEAPON_DAMAGE_TYPES) }), choices: Object.keys(SYSTEM.WEAPON_DAMAGE_TYPES) }),
portee: new fields.StringField({ required: true, nullable: false, initial: "contact", portee: new fields.StringField({ required: true, nullable: false, initial: "contact",
choices: Object.keys(SYSTEM.WEAPON_RANGE_TYPES) }), choices: Object.keys(SYSTEM.WEAPON_RANGE_TYPES) }),
equipped: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ required: true, textSearch: true }), description: new fields.HTMLField({ required: true, textSearch: true }),
} }
} }
@@ -83,9 +97,14 @@ export class CelestopolArmure extends foundry.abstract.TypeDataModel {
const reqInt = { required: true, nullable: false, integer: true } const reqInt = { required: true, nullable: false, integer: true }
return { return {
protection: new fields.NumberField({ ...reqInt, initial: 1, min: 1, max: 2 }), protection: new fields.NumberField({ ...reqInt, initial: 1, min: 1, max: 2 }),
malus: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 2 }), malus: new fields.NumberField({ ...reqInt, initial: 1, min: 0, max: 2 }),
equipped: new fields.BooleanField({ initial: false }), equipped: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({ required: true, textSearch: true }), description: new fields.HTMLField({ required: true, textSearch: true }),
} }
} }
prepareDerivedData() {
super.prepareDerivedData()
this.malus = this.protection
}
} }
+122 -3
View File
@@ -1,3 +1,16 @@
/**
* Célestopol 1922 — Système FoundryVTT
*
* Célestopol 1922 est un jeu de rôle édité par Antre-Monde Éditions.
* Ce système FoundryVTT est une implémentation indépendante et n'est pas
* affilié à Antre-Monde Éditions,
* mais a été réalisé avec l'autorisation d'Antre-Monde Éditions.
*
* @author LeRatierBretonnien
* @copyright 20252026 LeRatierBretonnien
* @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
*/
import { SYSTEM } from "../config/system.mjs" import { SYSTEM } from "../config/system.mjs"
export default class CelestopolNPC extends foundry.abstract.TypeDataModel { export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
@@ -21,7 +34,7 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
// PNJs : 4 domaines uniquement (pas de sous-compétences) // PNJs : 4 domaines uniquement (pas de sous-compétences)
const domainField = (statId) => new fields.SchemaField({ const domainField = (statId) => new fields.SchemaField({
label: new fields.StringField({ required: true, initial: SYSTEM.STATS[statId].label }), label: new fields.StringField({ required: true, initial: SYSTEM.STATS[statId].label }),
res: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 8 }), res: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
actuel: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }), actuel: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
}) })
@@ -38,6 +51,7 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
schema.histoire = new fields.HTMLField({ required: true, textSearch: true }) schema.histoire = new fields.HTMLField({ required: true, textSearch: true })
schema.descriptionPhysique = new fields.HTMLField({ required: true, textSearch: true }) schema.descriptionPhysique = new fields.HTMLField({ required: true, textSearch: true })
schema.portraitImage = new fields.StringField({ required: true, nullable: false, initial: "" })
schema.notes = new fields.HTMLField({ required: true, textSearch: true }) schema.notes = new fields.HTMLField({ required: true, textSearch: true })
return schema return schema
@@ -66,7 +80,20 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
const armures = this.parent?.itemTypes?.armure ?? [] const armures = this.parent?.itemTypes?.armure ?? []
return armures return armures
.filter(a => a.system.equipped) .filter(a => a.system.equipped)
.reduce((sum, a) => sum + (a.system.malus ? -Math.abs(a.system.malus) : 0), 0) .reduce((sum, a) => {
const value = a.system.protection ?? a.system.malus
return sum + (value ? -Math.abs(value) : 0)
}, 0)
}
/**
* Retourne le malus d'armure applicable pour un jet PNJ.
* Règle : sur tous les jets de Corps uniquement.
* @param {string} statId
* @returns {number}
*/
getArmorMalusForRoll(statId) {
return statId === "corps" ? this.getArmorMalus() : 0
} }
/** /**
@@ -85,13 +112,14 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
return CelestopolRoll.prompt({ return CelestopolRoll.prompt({
actorId: this.parent.id, actorId: this.parent.id,
actorUuid: this.parent.uuid,
actorName: this.parent.name, actorName: this.parent.name,
actorImage: this.parent.img, actorImage: this.parent.img,
statId, statId,
skillLabel, skillLabel,
skillValue: statData.res, skillValue: statData.res,
woundMalus: this.getWoundMalus(), woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalus(), armorMalus: this.getArmorMalusForRoll(statId),
woundLevel: this.blessures.lvl, woundLevel: this.blessures.lvl,
}) })
} }
@@ -100,4 +128,95 @@ export default class CelestopolNPC extends foundry.abstract.TypeDataModel {
async rollResistance(statId) { async rollResistance(statId) {
return this.roll(statId) return this.roll(statId)
} }
/**
* Collecte les cibles protagonistes de la scène active pour les jets de combat PNJ.
* @returns {Array<{id:string, name:string, corps:number}>}
*/
_getCombatTargets() {
const toEntry = actor => ({
id: actor.id,
uuid: actor.uuid,
name: actor.name,
corps: actor.system.stats?.corps?.res ?? 0,
})
const sceneTokens = canvas?.scene?.isView ? (canvas.tokens?.placeables ?? []) : []
return [...new Map(sceneTokens
.filter(t => t.actor?.type === "character" && t.actor.id !== this.parent.id)
.map(t => {
const actor = t.actor
return [actor.id, toEntry(actor)]
})).values()]
}
/**
* Lance une attaque PNJ avec une arme.
* Le test utilise le domaine Corps et transmet explicitement les dégâts de l'arme.
* @param {string} itemId
* @returns {Promise<import("../documents/roll.mjs").CelestopolRoll|null>}
*/
async rollAttack(itemId) {
const { CelestopolRoll } = await import("../documents/roll.mjs")
const item = this.parent.items.get(itemId)
if (!item || item.type !== "weapon") return null
return CelestopolRoll.prompt({
actorId: this.parent.id,
actorUuid: this.parent.uuid,
actorName: this.parent.name,
actorImage: this.parent.img,
statId: "corps",
skillId: null,
statLabel: SYSTEM.STATS.corps.label,
skillLabel: "CELESTOPOL.Combat.attack",
skillValue: this.stats.corps.res,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalusForRoll("corps"),
woundLevel: this.blessures.lvl,
rollMoonDie: false,
destGaugeFull: false,
fortuneValue: 0,
isCombat: true,
isRangedDefense: false,
weaponType: item.system.type,
weaponName: item.name,
weaponDegats: item.system.degats,
availableTargets: this._getCombatTargets(),
})
}
/**
* Lance un jet de tir/esquive PNJ avec une arme à distance.
* @param {string} itemId
* @returns {Promise<import("../documents/roll.mjs").CelestopolRoll|null>}
*/
async rollRangedDefense(itemId) {
const { CelestopolRoll } = await import("../documents/roll.mjs")
const item = this.parent.items.get(itemId)
if (!item || item.type !== "weapon" || item.system.type !== "distance") return null
return CelestopolRoll.prompt({
actorId: this.parent.id,
actorUuid: this.parent.uuid,
actorName: this.parent.name,
actorImage: this.parent.img,
statId: "corps",
skillId: null,
statLabel: SYSTEM.STATS.corps.label,
skillLabel: "CELESTOPOL.Combat.rangedDefenseTitle",
skillValue: this.stats.corps.res,
woundMalus: this.getWoundMalus(),
armorMalus: this.getArmorMalusForRoll("corps"),
woundLevel: this.blessures.lvl,
rollMoonDie: false,
destGaugeFull: false,
fortuneValue: 0,
isCombat: true,
isRangedDefense: true,
weaponType: "distance",
weaponName: item.name,
weaponDegats: item.system.degats,
availableTargets: this._getCombatTargets(),
})
}
} }
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
MANIFEST-000110
View File
+7
View File
@@ -0,0 +1,7 @@
2026/05/29-19:25:48.566717 7f386cbff6c0 Recovering log #107
2026/05/29-19:25:48.575874 7f386cbff6c0 Delete type=3 #105
2026/05/29-19:25:48.575904 7f386cbff6c0 Delete type=0 #107
2026/05/29-19:26:15.982905 7f381e7fc6c0 Level-0 table #113: started
2026/05/29-19:26:15.982917 7f381e7fc6c0 Level-0 table #113: 0 bytes OK
2026/05/29-19:26:15.988613 7f381e7fc6c0 Delete type=0 #111
2026/05/29-19:26:15.998513 7f381e7fc6c0 Manual compaction at level-0 from '!journal!eNYstmPK0mMmVJYC' @ 72057594037927935 : 1 .. '!journal.pages!eNYstmPK0mMmVJYC.r9h1ggd3G9hiqYJX' @ 0 : 0; will stop at (end)
+11
View File
@@ -0,0 +1,11 @@
2026/05/29-18:25:23.135570 7f381f7fe6c0 Delete type=3 #1
2026/05/29-18:27:57.110280 7f381e7fc6c0 Level-0 table #108: started
2026/05/29-18:27:57.110317 7f381e7fc6c0 Level-0 table #108: 0 bytes OK
2026/05/29-18:27:57.146936 7f381e7fc6c0 Delete type=0 #106
2026/05/29-18:27:57.291474 7f381e7fc6c0 Manual compaction at level-0 from '!journal!eNYstmPK0mMmVJYC' @ 72057594037927935 : 1 .. '!journal.pages!eNYstmPK0mMmVJYC.r9h1ggd3G9hiqYJX' @ 0 : 0; will stop at '!journal.pages!eNYstmPK0mMmVJYC.r9h1ggd3G9hiqYJX' @ 1 : 1
2026/05/29-18:27:57.291484 7f381e7fc6c0 Compacting 1@0 + 0@1 files
2026/05/29-18:27:57.308314 7f381e7fc6c0 Generated table #109@0: 6 keys, 5441 bytes
2026/05/29-18:27:57.308348 7f381e7fc6c0 Compacted 1@0 + 0@1 files => 5441 bytes
2026/05/29-18:27:57.339343 7f381e7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/29-18:27:57.339668 7f381e7fc6c0 Delete type=2 #88
2026/05/29-18:27:57.450192 7f381e7fc6c0 Manual compaction at level-0 from '!journal.pages!eNYstmPK0mMmVJYC.r9h1ggd3G9hiqYJX' @ 1 : 1 .. '!journal.pages!eNYstmPK0mMmVJYC.r9h1ggd3G9hiqYJX' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000018 MANIFEST-000173
+14 -3
View File
@@ -1,3 +1,14 @@
2026/04/06-17:46:52.532955 7f67ebfff6c0 Recovering log #15 2026/05/29-19:25:48.540511 7f381ffff6c0 Recovering log #170
2026/04/06-17:46:52.543005 7f67ebfff6c0 Delete type=3 #13 2026/05/29-19:25:48.551076 7f381ffff6c0 Delete type=3 #168
2026/04/06-17:46:52.543081 7f67ebfff6c0 Delete type=0 #15 2026/05/29-19:25:48.551131 7f381ffff6c0 Delete type=0 #170
2026/05/29-19:26:15.960995 7f381e7fc6c0 Level-0 table #176: started
2026/05/29-19:26:15.964212 7f381e7fc6c0 Level-0 table #176: 3524 bytes OK
2026/05/29-19:26:15.970133 7f381e7fc6c0 Delete type=0 #174
2026/05/29-19:26:15.988655 7f381e7fc6c0 Manual compaction at level-0 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 145 : 1
2026/05/29-19:26:15.988659 7f381e7fc6c0 Compacting 1@0 + 1@1 files
2026/05/29-19:26:15.991895 7f381e7fc6c0 Generated table #177@0: 9 keys, 6617 bytes
2026/05/29-19:26:15.991901 7f381e7fc6c0 Compacted 1@0 + 1@1 files => 6617 bytes
2026/05/29-19:26:15.998180 7f381e7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/29-19:26:15.998402 7f381e7fc6c0 Delete type=2 #172
2026/05/29-19:26:15.998461 7f381e7fc6c0 Delete type=2 #176
2026/05/29-19:26:16.010680 7f381e7fc6c0 Manual compaction at level-0 from '!items!null' @ 145 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
+12 -15
View File
@@ -1,15 +1,12 @@
2026/04/05-21:02:44.634018 7f8249dff6c0 Recovering log #10 2026/05/29-18:25:23.097516 7f381effd6c0 Delete type=3 #1
2026/04/05-21:02:44.729398 7f8249dff6c0 Delete type=3 #8 2026/05/29-18:27:57.232279 7f381e7fc6c0 Level-0 table #171: started
2026/04/05-21:02:44.729470 7f8249dff6c0 Delete type=0 #10 2026/05/29-18:27:57.253183 7f381e7fc6c0 Level-0 table #171: 3524 bytes OK
2026/04/06-00:09:38.933436 7f82177fe6c0 Level-0 table #16: started 2026/05/29-18:27:57.291326 7f381e7fc6c0 Delete type=0 #169
2026/04/06-00:09:38.937122 7f82177fe6c0 Level-0 table #16: 3525 bytes OK 2026/05/29-18:27:57.404912 7f381e7fc6c0 Manual compaction at level-0 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 141 : 1
2026/04/06-00:09:38.943462 7f82177fe6c0 Delete type=0 #14 2026/05/29-18:27:57.404922 7f381e7fc6c0 Compacting 2@0 + 0@1 files
2026/04/06-00:09:38.943723 7f82177fe6c0 Manual compaction at level-0 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at (end) 2026/05/29-18:27:57.421172 7f381e7fc6c0 Generated table #172@0: 9 keys, 6617 bytes
2026/04/06-00:09:38.966124 7f82177fe6c0 Manual compaction at level-1 from '!items!anomCommMorts001' @ 72057594037927935 : 1 .. '!items!null' @ 0 : 0; will stop at '!items!null' @ 17 : 1 2026/05/29-18:27:57.421194 7f381e7fc6c0 Compacted 2@0 + 0@1 files => 6617 bytes
2026/04/06-00:09:38.966141 7f82177fe6c0 Compacting 1@1 + 1@2 files 2026/05/29-18:27:57.449840 7f381e7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/04/06-00:09:38.969869 7f82177fe6c0 Generated table #17@1: 9 keys, 6617 bytes 2026/05/29-18:27:57.449959 7f381e7fc6c0 Delete type=2 #166
2026/04/06-00:09:38.969906 7f82177fe6c0 Compacted 1@1 + 1@2 files => 6617 bytes 2026/05/29-18:27:57.450112 7f381e7fc6c0 Delete type=2 #171
2026/04/06-00:09:38.976148 7f82177fe6c0 compacted to: files[ 0 0 1 0 0 0 0 ] 2026/05/29-18:27:57.505063 7f381e7fc6c0 Manual compaction at level-0 from '!items!null' @ 141 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
2026/04/06-00:09:38.976266 7f82177fe6c0 Delete type=2 #12
2026/04/06-00:09:38.976457 7f82177fe6c0 Delete type=2 #16
2026/04/06-00:09:38.987710 7f82177fe6c0 Manual compaction at level-1 from '!items!null' @ 17 : 1 .. '!items!null' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
Binary file not shown.
View File
+1
View File
@@ -0,0 +1 @@
MANIFEST-000071
View File
+7
View File
@@ -0,0 +1,7 @@
2026/05/29-19:25:48.553559 7f386cbff6c0 Recovering log #68
2026/05/29-19:25:48.563799 7f386cbff6c0 Delete type=3 #66
2026/05/29-19:25:48.563837 7f386cbff6c0 Delete type=0 #68
2026/05/29-19:26:15.976984 7f381e7fc6c0 Level-0 table #74: started
2026/05/29-19:26:15.976996 7f381e7fc6c0 Level-0 table #74: 0 bytes OK
2026/05/29-19:26:15.982862 7f381e7fc6c0 Delete type=0 #72
2026/05/29-19:26:15.998506 7f381e7fc6c0 Manual compaction at level-0 from '!actors!6RZ6IzJUHm4dB5Ut' @ 72057594037927935 : 1 .. '!folders!MbFQgPdF6Gtbj5AU' @ 0 : 0; will stop at (end)
+11
View File
@@ -0,0 +1,11 @@
2026/05/29-18:25:23.116810 7f381ffff6c0 Delete type=3 #1
2026/05/29-18:27:57.533567 7f381e7fc6c0 Level-0 table #69: started
2026/05/29-18:27:57.533614 7f381e7fc6c0 Level-0 table #69: 0 bytes OK
2026/05/29-18:27:57.568380 7f381e7fc6c0 Delete type=0 #67
2026/05/29-18:27:57.662122 7f381e7fc6c0 Manual compaction at level-0 from '!actors!6RZ6IzJUHm4dB5Ut' @ 72057594037927935 : 1 .. '!folders!MbFQgPdF6Gtbj5AU' @ 0 : 0; will stop at '!folders!MbFQgPdF6Gtbj5AU' @ 37 : 1
2026/05/29-18:27:57.662130 7f381e7fc6c0 Compacting 1@0 + 0@1 files
2026/05/29-18:27:57.681906 7f381e7fc6c0 Generated table #70@0: 36 keys, 35733 bytes
2026/05/29-18:27:57.681939 7f381e7fc6c0 Compacted 1@0 + 0@1 files => 35733 bytes
2026/05/29-18:27:57.724776 7f381e7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/29-18:27:57.724885 7f381e7fc6c0 Delete type=2 #49
2026/05/29-18:27:57.817815 7f381e7fc6c0 Manual compaction at level-0 from '!folders!MbFQgPdF6Gtbj5AU' @ 37 : 1 .. '!folders!MbFQgPdF6Gtbj5AU' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
+1
View File
@@ -0,0 +1 @@
MANIFEST-000113
View File
+7
View File
@@ -0,0 +1,7 @@
2026/05/29-19:25:48.578477 7f381f7fe6c0 Recovering log #110
2026/05/29-19:25:48.589121 7f381f7fe6c0 Delete type=3 #108
2026/05/29-19:25:48.589160 7f381f7fe6c0 Delete type=0 #110
2026/05/29-19:26:15.970220 7f381e7fc6c0 Level-0 table #116: started
2026/05/29-19:26:15.970241 7f381e7fc6c0 Level-0 table #116: 0 bytes OK
2026/05/29-19:26:15.976948 7f381e7fc6c0 Delete type=0 #114
2026/05/29-19:26:15.998499 7f381e7fc6c0 Manual compaction at level-0 from '!scenes!0iGCRqkdJKjmmbl4' @ 72057594037927935 : 1 .. '!scenes.levels!X3XJg7raEXtOFOtj.defaultLevel0000' @ 0 : 0; will stop at (end)
+11
View File
@@ -0,0 +1,11 @@
2026/05/29-18:25:23.154144 7f381ffff6c0 Delete type=3 #1
2026/05/29-18:27:57.197941 7f381e7fc6c0 Level-0 table #111: started
2026/05/29-18:27:57.197975 7f381e7fc6c0 Level-0 table #111: 0 bytes OK
2026/05/29-18:27:57.232075 7f381e7fc6c0 Delete type=0 #109
2026/05/29-18:27:57.339840 7f381e7fc6c0 Manual compaction at level-0 from '!scenes!0iGCRqkdJKjmmbl4' @ 72057594037927935 : 1 .. '!scenes.levels!X3XJg7raEXtOFOtj.defaultLevel0000' @ 0 : 0; will stop at '!scenes.levels!X3XJg7raEXtOFOtj.defaultLevel0000' @ 61 : 1
2026/05/29-18:27:57.339849 7f381e7fc6c0 Compacting 1@0 + 0@1 files
2026/05/29-18:27:57.358860 7f381e7fc6c0 Generated table #112@0: 4 keys, 1746 bytes
2026/05/29-18:27:57.358888 7f381e7fc6c0 Compacted 1@0 + 0@1 files => 1746 bytes
2026/05/29-18:27:57.404742 7f381e7fc6c0 compacted to: files[ 0 1 0 0 0 0 0 ]
2026/05/29-18:27:57.404821 7f381e7fc6c0 Delete type=2 #91
2026/05/29-18:27:57.505039 7f381e7fc6c0 Manual compaction at level-0 from '!scenes.levels!X3XJg7raEXtOFOtj.defaultLevel0000' @ 61 : 1 .. '!scenes.levels!X3XJg7raEXtOFOtj.defaultLevel0000' @ 0 : 0; will stop at (end)

Some files were not shown because too many files have changed in this diff Show More