feat: zones sûres + commerce PNJ-à-PNJ + raids factions

encounter_tables.json:
  safe_zones: [independance, nola_vieux_carre, baton_rouge, laplace]
  Zone safe: zone_type=safe, tier_weights sans lambda/group (actif+boss seulement)
  raid_chance: probabilité séparée par zone (0.4-0.8%) indépendante du roll normal
  entity_pools.raid_faction: commando éclaireur + milice faction hostile
  urban_trade_roles: rôles autorisés à faire du commerce en ville
  orientation_reactions: ajout réaction 'trade' pour marchands/doc/tech/passeur/scavenger
  outcomes.trade: success (+caps, stress-1) / failed (aucun effet)

encounter.py:
  _is_safe_zone(): vérifie si zone est dans safe_zones
  should_trigger_raid(): roll séparé par zone pour déclencher un raid
  generate_entity_trade(): cherche un PNJ compatible dans la même zone pour échange
  generate_entity_raid(): génère un commando de faction ennemie avec contexte faction
  _trade_roll(): jet CHA+LCK pour conclure une vente, caps_gain 10-60
  resolve_encounter(): branche 'trade' → caps gagnés si succès
  process_encounters_tick():
    - Roll de raid une fois par zone sûre (pas par PNJ)
    - PNJ en zone raidée → encounter_raid, skip encounter normal ce tick
    - Zone safe + rôle commerçant → priorité generate_entity_trade sur actif
    - Fallback boss en zone safe → None (pas de lambda)

Logique résultante:
  Pearl River (dangerous): alligators, goules, raiders → inchangé
  Independance (safe): un marchand croise un autre PNJ → vente possible
  Raid rare sur independance: commando Union/Ecumeurs → combat ou fuite
  Bêtes en ville: impossible
This commit is contained in:
Corback
2026-06-16 08:10:05 +00:00
parent 1bf0f921b6
commit dd985c868c
2 changed files with 286 additions and 46 deletions
+87 -35
View File
@@ -1,56 +1,72 @@
{
"_comment": "Tables d'encounters par zone. chance_per_tick = probabilité par tick. tier_weights = distribution des tiers d'encounter (lambda/group/actif/boss). Somme des weights doit = 1.0",
"_comment": "Tables d'encounters par zone. chance_per_tick = probabilité par tick. tier_weights = distribution des tiers. zone_type: safe = pas de bêtes/raiders, seulement PNJ-à-PNJ et raids rares. raid_chance = probabilité séparée d'un raid faction (ignorée en zone dangereuse).",
"safe_zones": ["independance", "nola_vieux_carre", "baton_rouge", "laplace"],
"zone_profiles": {
"independance": {
"zone_type": "safe",
"chance_per_tick": 0.15,
"raid_chance": 0.004,
"tier_weights": {"actif": 0.92, "boss": 0.08},
"flavor": "Zone protégée par l'Union, relativement sûre. Marché actif, patrouilles régulières."
},
"nola_vieux_carre": {
"zone_type": "safe",
"chance_per_tick": 0.12,
"raid_chance": 0.005,
"tier_weights": {"actif": 0.94, "boss": 0.06},
"flavor": "Zone tenue par la Régie, murée, patrouilles régulières. Quartier commerçant fortifié."
},
"baton_rouge": {
"zone_type": "safe",
"chance_per_tick": 0.10,
"raid_chance": 0.008,
"tier_weights": {"actif": 0.90, "boss": 0.10},
"flavor": "Ville sous tension Syndicat du Capitole. Sûre mais surveillance constante."
},
"laplace": {
"zone_type": "safe",
"chance_per_tick": 0.13,
"raid_chance": 0.006,
"tier_weights": {"actif": 0.93, "boss": 0.07},
"flavor": "Carrefour commercial, passage constant, relativement neutre. Beaucoup d'échanges."
},
"nola_cbd": {
"zone_type": "dangerous",
"chance_per_tick": 0.12,
"tier_weights": {"lambda": 0.45, "group": 0.38, "actif": 0.15, "boss": 0.02},
"flavor": "Gratte-ciels inondés, ruines industrielles, territoire Super Mutants"
},
"pearl_river": {
"zone_type": "dangerous",
"chance_per_tick": 0.09,
"tier_weights": {"lambda": 0.55, "group": 0.20, "actif": 0.22, "boss": 0.03},
"flavor": "Bayou dense, territoire Écumeurs, créatures des marais"
},
"la_paroisse": {
"zone_type": "dangerous",
"chance_per_tick": 0.10,
"tier_weights": {"lambda": 0.50, "group": 0.20, "actif": 0.25, "boss": 0.05},
"flavor": "Zone radioactive, territoire Grand Krewe, goules partout"
},
"donaldsonville": {
"zone_type": "dangerous",
"chance_per_tick": 0.06,
"tier_weights": {"lambda": 0.60, "group": 0.28, "actif": 0.10, "boss": 0.02},
"flavor": "Ville intermédiaire, sans faction, passage obligé"
"flavor": "Ville intermédiaire, sans faction, passage obligé. Pas de protection réelle."
},
"oak_plantation": {
"zone_type": "dangerous",
"chance_per_tick": 0.05,
"tier_weights": {"lambda": 0.50, "group": 0.20, "actif": 0.28, "boss": 0.02},
"flavor": "Plantation gardée, esclaves en fuite, patrouilles Dynaste"
},
"baton_rouge": {
"chance_per_tick": 0.04,
"tier_weights": {"lambda": 0.55, "group": 0.25, "actif": 0.18, "boss": 0.02},
"flavor": "Ville sous tension, présence Syndicat du Capitole"
},
"nola_fleuve": {
"zone_type": "dangerous",
"chance_per_tick": 0.05,
"tier_weights": {"lambda": 0.45, "group": 0.30, "actif": 0.22, "boss": 0.03},
"flavor": "Port fluvial, passage de bateaux, CdA patrouille"
},
"independance": {
"chance_per_tick": 0.02,
"tier_weights": {"lambda": 0.80, "group": 0.15, "actif": 0.04, "boss": 0.01},
"flavor": "Zone protégée par l'Union, relativement sûre"
},
"laplace": {
"chance_per_tick": 0.03,
"tier_weights": {"lambda": 0.65, "group": 0.25, "actif": 0.09, "boss": 0.01},
"flavor": "Carrefour commercial, passage constant, relativement neutre"
},
"nola_vieux_carre": {
"chance_per_tick": 0.025,
"tier_weights": {"lambda": 0.70, "group": 0.20, "actif": 0.09, "boss": 0.01},
"flavor": "Zone tenue par la Régie, murée, patrouilles régulières"
}
},
@@ -202,23 +218,55 @@
"can_negotiate": false,
"immune_radiation": true
}
],
"raid_faction": [
{
"key": "raid_eclaireurs",
"name": "Commando éclaireur",
"description": "Petit groupe armé d'une faction ennemie qui teste les défenses de la zone",
"count_range": [2, 4],
"stats_base": {"hp": 12, "strength": 7, "agility": 7, "endurance": 6, "perception": 6, "luck": 4},
"weapon": {"name": "Fusil automatique", "damage_dice": 2, "damage_bonus": 1, "damage_type": "physical", "range": "medium"},
"armor_rating": 1,
"caps_loot": [40, 180],
"item_loot": ["cram", "stimpak", "eau_purifiee"],
"xp": 6,
"can_negotiate": false
},
{
"key": "raid_milice",
"name": "Milice faction hostile",
"description": "Force paramilitaire d'une faction cherchant à prendre le contrôle du quartier",
"count_range": [3, 6],
"stats_base": {"hp": 14, "strength": 7, "agility": 6, "endurance": 7, "perception": 6, "luck": 4},
"weapon": {"name": "Fusil de combat", "damage_dice": 3, "damage_bonus": 0, "damage_type": "physical", "range": "medium"},
"armor_rating": 2,
"caps_loot": [60, 250],
"item_loot": ["stimpak", "eau_purifiee", "couteau_combat"],
"xp": 8,
"can_negotiate": true,
"negotiate_cost_caps": [100, 400]
}
]
},
"urban_trade_roles": ["marchand", "docteur", "technicien", "passeur", "scavenger"],
"orientation_reactions": {
"_comment": "Réaction par défaut selon le rôle du PNJ face à chaque tier d'encounter",
"garde": {"lambda": "combat", "group": "combat", "actif": "combat", "boss": "fuite_ou_combat"},
"eclaireur": {"lambda": "evitement", "group": "fuite", "actif": "fuite", "boss": "fuite"},
"marchand": {"lambda": "fuite", "group": "negociation","actif": "negociation","boss": "fuite"},
"docteur": {"lambda": "fuite", "group": "fuite", "actif": "negociation","boss": "fuite"},
"technicien": {"lambda": "fuite", "group": "fuite", "actif": "fuite", "boss": "fuite"},
"pillard": {"lambda": "combat", "group": "negociation","actif": "combat", "boss": "fuite"},
"raider_boss": {"lambda": "combat", "group": "combat", "actif": "combat", "boss": "combat"},
"lieutenant": {"lambda": "combat", "group": "combat", "actif": "combat", "boss": "fuite_ou_combat"},
"chasseur_primes": {"lambda": "combat", "group": "combat", "actif": "negociation","boss": "fuite"},
"passeur": {"lambda": "evitement", "group": "evitement", "actif": "fuite", "boss": "fuite"},
"scavenger": {"lambda": "fuite", "group": "fuite", "actif": "fuite", "boss": "fuite"},
"survivant_generique":{"lambda":"fuite", "group": "fuite", "actif": "negociation","boss": "fuite"}
"_comment": "Réaction par défaut selon le rôle du PNJ face à chaque tier d'encounter. 'trade' = interaction commerciale PNJ-à-PNJ en zone safe.",
"garde": {"lambda": "combat", "group": "combat", "actif": "combat", "boss": "fuite_ou_combat", "raid": "combat"},
"eclaireur": {"lambda": "evitement", "group": "fuite", "actif": "fuite", "boss": "fuite", "raid": "fuite"},
"marchand": {"lambda": "fuite", "group": "negociation","actif": "trade", "boss": "fuite", "raid": "fuite"},
"docteur": {"lambda": "fuite", "group": "fuite", "actif": "trade", "boss": "fuite", "raid": "fuite"},
"technicien": {"lambda": "fuite", "group": "fuite", "actif": "trade", "boss": "fuite", "raid": "fuite"},
"pillard": {"lambda": "combat", "group": "negociation","actif": "combat", "boss": "fuite", "raid": "fuite"},
"raider_boss": {"lambda": "combat", "group": "combat", "actif": "combat", "boss": "combat", "raid": "combat"},
"lieutenant": {"lambda": "combat", "group": "combat", "actif": "combat", "boss": "fuite_ou_combat", "raid": "combat"},
"chasseur_primes": {"lambda": "combat", "group": "combat", "actif": "negociation", "boss": "fuite", "raid": "fuite"},
"passeur": {"lambda": "evitement", "group": "evitement", "actif": "trade", "boss": "fuite", "raid": "fuite"},
"scavenger": {"lambda": "fuite", "group": "fuite", "actif": "trade", "boss": "fuite", "raid": "fuite"},
"survivant_generique":{"lambda": "fuite", "group": "fuite", "actif": "negociation", "boss": "fuite", "raid": "fuite"}
},
"outcomes": {
@@ -238,6 +286,10 @@
"negociation": {
"success": {"loot": false, "xp": true, "stress_delta": 1, "caps_cost": true, "description": "Négociation réussie"},
"failed": {"loot": false, "xp": false, "stress_delta": 4, "hp_loss_pct": 0.15, "description": "Négociation échouée, attaqué"}
},
"trade": {
"success": {"loot": false, "xp": true, "stress_delta": -1, "caps_gain": true, "description": "Vente conclue"},
"failed": {"loot": false, "xp": false, "stress_delta": 0, "description": "Pas d'accord, chacun repart"}
}
}
}