Ajout d'un onglet "aide" dans la version mobile de l'appli qui reprend les propriétés, les armes et le véhicules
This commit is contained in:
parent
01a77b5fc9
commit
f374d184c4
10 changed files with 1174 additions and 253 deletions
282
front/rules.tsx
282
front/rules.tsx
|
|
@ -9,6 +9,12 @@ import {
|
|||
useLocation,
|
||||
useParams,
|
||||
} from "react-router-dom";
|
||||
import {
|
||||
getLocalizedText,
|
||||
helpProperties,
|
||||
helpVehicles,
|
||||
helpWeapons,
|
||||
} from "../shared/help-catalog";
|
||||
import "./rules.css";
|
||||
|
||||
type Locale = "en" | "fr";
|
||||
|
|
@ -68,20 +74,6 @@ type RulesCopy = {
|
|||
footerNote: string;
|
||||
};
|
||||
|
||||
type PropertyRow = {
|
||||
name: string;
|
||||
price: number;
|
||||
houseCost: number;
|
||||
rent: number;
|
||||
rent1: number;
|
||||
rent2: number;
|
||||
rent3: number;
|
||||
rent4: number;
|
||||
rent5: number;
|
||||
mortgage: number;
|
||||
color: "brown" | "cyan" | "pink" | "green" | "red" | "yellow" | "orange" | "blue";
|
||||
};
|
||||
|
||||
type TabConfig = {
|
||||
id: string;
|
||||
label: { fr: string; en: string };
|
||||
|
|
@ -111,31 +103,6 @@ const tabConfig: TabConfig[] = [
|
|||
},
|
||||
];
|
||||
|
||||
const properties: PropertyRow[] = [
|
||||
{ name: "Négotown", price: 60, houseCost: 50, rent: 2, rent1: 10, rent2: 30, rent3: 90, rent4: 160, rent5: 250, mortgage: 30, color: "brown" },
|
||||
{ name: "Black Arretxea", price: 60, houseCost: 50, rent: 4, rent1: 20, rent2: 60, rent3: 180, rent4: 320, rent5: 450, mortgage: 30, color: "brown" },
|
||||
{ name: "17 rue des patates", price: 100, houseCost: 50, rent: 6, rent1: 30, rent2: 90, rent3: 270, rent4: 400, rent5: 550, mortgage: 50, color: "cyan" },
|
||||
{ name: "69 rue des patates", price: 100, houseCost: 50, rent: 6, rent1: 30, rent2: 90, rent3: 270, rent4: 400, rent5: 550, mortgage: 50, color: "cyan" },
|
||||
{ name: "420 rue des patates", price: 120, houseCost: 50, rent: 8, rent1: 40, rent2: 100, rent3: 300, rent4: 450, rent5: 600, mortgage: 60, color: "cyan" },
|
||||
{ name: "Nuketown", price: 140, houseCost: 100, rent: 10, rent1: 50, rent2: 150, rent3: 450, rent4: 625, rent5: 750, mortgage: 70, color: "pink" },
|
||||
{ name: "Hijacked", price: 140, houseCost: 100, rent: 10, rent1: 50, rent2: 150, rent3: 450, rent4: 625, rent5: 750, mortgage: 70, color: "pink" },
|
||||
{ name: "Bassland", price: 160, houseCost: 100, rent: 12, rent1: 60, rent2: 180, rent3: 500, rent4: 700, rent5: 900, mortgage: 80, color: "pink" },
|
||||
{ name: "Numera Fight Club", price: 180, houseCost: 100, rent: 14, rent1: 70, rent2: 200, rent3: 550, rent4: 750, rent5: 950, mortgage: 90, color: "green" },
|
||||
{ name: "Snoopy's ID", price: 180, houseCost: 100, rent: 14, rent1: 70, rent2: 200, rent3: 550, rent4: 750, rent5: 950, mortgage: 90, color: "green" },
|
||||
{ name: "Jahland Dispensory", price: 200, houseCost: 100, rent: 16, rent1: 80, rent2: 220, rent3: 600, rent4: 800, rent5: 1000, mortgage: 100, color: "green" },
|
||||
{ name: "Pink Hoodie Studio", price: 220, houseCost: 150, rent: 18, rent1: 90, rent2: 250, rent3: 700, rent4: 875, rent5: 1050, mortgage: 110, color: "red" },
|
||||
{ name: "MP7 Studio", price: 220, houseCost: 150, rent: 18, rent1: 90, rent2: 250, rent3: 700, rent4: 875, rent5: 1050, mortgage: 110, color: "red" },
|
||||
{ name: "PCT Studio", price: 240, houseCost: 150, rent: 20, rent1: 100, rent2: 300, rent3: 750, rent4: 925, rent5: 1100, mortgage: 120, color: "red" },
|
||||
{ name: "l'Elysée", price: 260, houseCost: 150, rent: 22, rent1: 110, rent2: 330, rent3: 800, rent4: 975, rent5: 1150, mortgage: 130, color: "yellow" },
|
||||
{ name: "Bar Freak Show", price: 260, houseCost: 150, rent: 22, rent1: 110, rent2: 330, rent3: 800, rent4: 975, rent5: 1150, mortgage: 130, color: "yellow" },
|
||||
{ name: "Garage de Benoir", price: 280, houseCost: 150, rent: 24, rent1: 120, rent2: 360, rent3: 850, rent4: 1025, rent5: 1200, mortgage: 140, color: "yellow" },
|
||||
{ name: "Rue vendredi des noirs", price: 300, houseCost: 200, rent: 26, rent1: 130, rent2: 390, rent3: 900, rent4: 1100, rent5: 1275, mortgage: 150, color: "orange" },
|
||||
{ name: "Domaine de M. P", price: 300, houseCost: 200, rent: 26, rent1: 130, rent2: 390, rent3: 900, rent4: 1100, rent5: 1275, mortgage: 150, color: "orange" },
|
||||
{ name: "Villa du RJ", price: 320, houseCost: 200, rent: 28, rent1: 150, rent2: 450, rent3: 1000, rent4: 1200, rent5: 1400, mortgage: 160, color: "orange" },
|
||||
{ name: "Négoplaza", price: 350, houseCost: 200, rent: 35, rent1: 175, rent2: 500, rent3: 1100, rent4: 1300, rent5: 1500, mortgage: 175, color: "blue" },
|
||||
{ name: "LBTRD Tower", price: 400, houseCost: 200, rent: 50, rent1: 200, rent2: 600, rent3: 1400, rent4: 1700, rent5: 2000, mortgage: 200, color: "blue" },
|
||||
];
|
||||
|
||||
const copy: Record<Locale, RulesCopy> = {
|
||||
fr: {
|
||||
badge: "Manuel du participant",
|
||||
|
|
@ -573,44 +540,7 @@ const copy: Record<Locale, RulesCopy> = {
|
|||
type: "cards",
|
||||
title: "Véhicules disponibles",
|
||||
carousel: true,
|
||||
items: [
|
||||
{
|
||||
title: "Tier 0 — Trottinette au sans-plomb 75",
|
||||
meta: "150₦",
|
||||
text: "Lancer un dé : 1–2 panne (aucun effet). 3–5 avance d’1 case. 6 explosion, recule de 2 cases.",
|
||||
},
|
||||
{
|
||||
title: "Tier 0 — Exosquelette à méga backflips",
|
||||
meta: "150₦",
|
||||
text: "Lancer un dé : 1–2 panne. 3–5 recule d’1 case. 6 explosion, avance de 2 cases.",
|
||||
},
|
||||
{
|
||||
title: "Tier 1 — Automobile",
|
||||
meta: "250₦",
|
||||
text: "Avance d’1 case supplémentaire après le déplacement normal.",
|
||||
},
|
||||
{
|
||||
title: "Tier 2 — Hélicoptère",
|
||||
meta: "400₦",
|
||||
text: "Choisir d’avancer ou reculer de 1 à 3 cases après le déplacement normal.",
|
||||
},
|
||||
{
|
||||
title: "Tier 3 — Tank",
|
||||
meta: "450₦",
|
||||
text:
|
||||
"Avance de 2 ou 3 cases. Protège le joueur des effets négatifs de la case d’arrivée et de la case survolée. Si vous arrivez sur un joueur, il est envoyé à l’Hôpital.",
|
||||
},
|
||||
{
|
||||
title: "Tier 4 — Avion de chasse",
|
||||
meta: "600₦",
|
||||
text: "Choisir d’avancer de 1 à 6 cases après le déplacement normal.",
|
||||
},
|
||||
{
|
||||
title: "Tier 5 — Téléporteur de poche",
|
||||
meta: "800₦",
|
||||
text: "Téléportation immédiate vers n’importe quelle case, sans tenir compte des dés.",
|
||||
},
|
||||
],
|
||||
items: buildVehicleCards("fr"),
|
||||
},
|
||||
{
|
||||
type: "cards",
|
||||
|
|
@ -667,73 +597,7 @@ const copy: Record<Locale, RulesCopy> = {
|
|||
type: "tierGrid",
|
||||
title: "Arsenal",
|
||||
carousel: true,
|
||||
items: [
|
||||
{
|
||||
title: "RPG-7",
|
||||
tier: "Tier 0",
|
||||
meta: "150₦",
|
||||
text:
|
||||
"Stock illimité. Détruit une maison sur la propriété où vous êtes ou adjacente. Jusqu’à 2 tirs par tour. Jet : 1–4 réussite, 5 raté, 6 explosion (direction Hôpital). Sur propriété hypothéquée, un tir réussi la réinitialise.",
|
||||
},
|
||||
{
|
||||
title: "C4 (charges)",
|
||||
tier: "Tier 0",
|
||||
meta: "40₦ la 1re, puis x2 / x3…",
|
||||
text:
|
||||
"Charges illimitées. Pose sur une maison en passant ou en s’arrêtant. Toute charge peut être retirée par n’importe quel joueur en passant (il la récupère). À partir de 2 rounds après la pose, le poseur peut déclencher : maison détruite, joueur sur place → Hôpital, joueur adjacent → Prison (terroriste). Bombe IEM désactive les C4 tant qu’elle est active.",
|
||||
},
|
||||
{
|
||||
title: "Glock 26",
|
||||
tier: "Tier 1",
|
||||
meta: "200₦",
|
||||
text:
|
||||
"Racket d’un joueur sur la même case ou adjacente pour 300₦. Si la cible possède aussi une Glock, jet : 4–6 victoire de l’attaquant, 1–3 défaite. Le perdant est racketté et envoyé à l’Hôpital.",
|
||||
},
|
||||
{
|
||||
title: "AR15",
|
||||
tier: "Tier 2",
|
||||
meta: "400₦",
|
||||
text: "Annule l’effet de n’importe quelle case sur laquelle le joueur se trouve.",
|
||||
},
|
||||
{
|
||||
title: "Mortier",
|
||||
tier: "Tier 3",
|
||||
meta: "400₦",
|
||||
text:
|
||||
"3 tirs. Chaque tir détruit une maison sur une case adjacente ; s’il n’y a pas de maison, la propriété est hypothéquée ; si elle l’est déjà, elle est réinitialisée.",
|
||||
},
|
||||
{
|
||||
title: "Bombe IEM",
|
||||
tier: "Tier 4",
|
||||
meta: "400₦",
|
||||
text:
|
||||
"Pendant un tour : toutes les cases sont désactivées, les actions à distance sont désactivées, les communications privées interdites (sauf même case), toutes les charges de C4 sont désactivées.",
|
||||
},
|
||||
{
|
||||
title: "Pièce d’artillerie",
|
||||
tier: "Tier 5",
|
||||
meta: "600₦",
|
||||
text: "Supprime toutes les maisons d’une propriété appartenant à un joueur.",
|
||||
},
|
||||
{
|
||||
title: "Rods From Gods",
|
||||
tier: "Tier 6",
|
||||
meta: "1500₦",
|
||||
text: "Rase une couleur entière : supprime toutes les maisons et réinitialise les propriétés.",
|
||||
},
|
||||
{
|
||||
title: "Satan2",
|
||||
tier: "Game Ender",
|
||||
meta: "5000₦",
|
||||
text: "Détruit l’intégralité de NegoCity et donne la victoire immédiate au joueur qui l’utilise.",
|
||||
},
|
||||
{
|
||||
title: "La Peste Nègre",
|
||||
tier: "Game Ender",
|
||||
meta: "7000₦",
|
||||
text: "La Peste Nègre (sélection naturelle) élimine tous les joueurs sauf deux. Les deux survivants remportent automatiquement la partie.",
|
||||
},
|
||||
],
|
||||
items: buildWeaponCards("fr"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -1202,44 +1066,7 @@ const copy: Record<Locale, RulesCopy> = {
|
|||
type: "cards",
|
||||
title: "Vehicles",
|
||||
carousel: true,
|
||||
items: [
|
||||
{
|
||||
title: "Tier 0 — Trottinette au sans-plomb 75",
|
||||
meta: "150₦",
|
||||
text: "Roll a die: 1–2 breakdown (no effect). 3–5 move forward 1 space. 6 explosion, move back 2 spaces.",
|
||||
},
|
||||
{
|
||||
title: "Tier 0 — Exosquelette à méga backflips",
|
||||
meta: "150₦",
|
||||
text: "Roll a die: 1–2 breakdown. 3–5 move back 1 space. 6 explosion, move forward 2 spaces.",
|
||||
},
|
||||
{
|
||||
title: "Tier 1 — Automobile",
|
||||
meta: "250₦",
|
||||
text: "Move forward 1 extra space after normal movement.",
|
||||
},
|
||||
{
|
||||
title: "Tier 2 — Hélicoptère",
|
||||
meta: "400₦",
|
||||
text: "Choose to move forward or back by 1 to 3 spaces after normal movement.",
|
||||
},
|
||||
{
|
||||
title: "Tier 3 — Tank",
|
||||
meta: "450₦",
|
||||
text:
|
||||
"Move forward 2 or 3 spaces. Protects you from negative effects of the landing space and the passed-over space. If you land on another player, they go to Hospital.",
|
||||
},
|
||||
{
|
||||
title: "Tier 4 — Avion de chasse",
|
||||
meta: "600₦",
|
||||
text: "Choose to move forward 1 to 6 spaces after normal movement.",
|
||||
},
|
||||
{
|
||||
title: "Tier 5 — Téléporteur de poche",
|
||||
meta: "800₦",
|
||||
text: "Teleport instantly to any space, ignoring dice and intermediate spaces.",
|
||||
},
|
||||
],
|
||||
items: buildVehicleCards("en"),
|
||||
},
|
||||
{
|
||||
type: "cards",
|
||||
|
|
@ -1296,73 +1123,7 @@ const copy: Record<Locale, RulesCopy> = {
|
|||
type: "tierGrid",
|
||||
title: "Arsenal",
|
||||
carousel: true,
|
||||
items: [
|
||||
{
|
||||
title: "RPG-7",
|
||||
tier: "Tier 0",
|
||||
meta: "150₦",
|
||||
text:
|
||||
"Unlimited stock. Destroys a house on your property or an adjacent one. Up to 2 shots per turn. Roll: 1–4 hit, 5 miss, 6 explosion (go to Hospital). On a mortgaged property, a hit resets it.",
|
||||
},
|
||||
{
|
||||
title: "C4 (charges)",
|
||||
tier: "Tier 0",
|
||||
meta: "40₦ first, then x2 / x3…",
|
||||
text:
|
||||
"Unlimited charges. Place on a house by passing over or stopping. Any player passing can remove a charge and keep it. From two rounds after placement, the placer may detonate: destroy house, player on space → Hospital, player adjacent → Prison (terrorist). EMP disables C4 while active.",
|
||||
},
|
||||
{
|
||||
title: "Glock 26",
|
||||
tier: "Tier 1",
|
||||
meta: "200₦",
|
||||
text:
|
||||
"Extort a player on the same or adjacent space for 300₦. If the target also has a Glock, roll: 4–6 attacker wins, 1–3 attacker loses. Loser is extorted and sent to Hospital.",
|
||||
},
|
||||
{
|
||||
title: "AR15",
|
||||
tier: "Tier 2",
|
||||
meta: "400₦",
|
||||
text: "Cancels the effect of any space the player is on.",
|
||||
},
|
||||
{
|
||||
title: "Mortar",
|
||||
tier: "Tier 3",
|
||||
meta: "400₦",
|
||||
text:
|
||||
"3 shots. Each shot destroys a house on an adjacent space; if none, the property is mortgaged; if already mortgaged, the property is reset.",
|
||||
},
|
||||
{
|
||||
title: "EMP Bomb",
|
||||
tier: "Tier 4",
|
||||
meta: "400₦",
|
||||
text:
|
||||
"For one turn: all spaces are disabled, remote actions disabled, private communication forbidden (except same space), all C4 charges disabled.",
|
||||
},
|
||||
{
|
||||
title: "Artillery piece",
|
||||
tier: "Tier 5",
|
||||
meta: "600₦",
|
||||
text: "Removes all houses from a single property.",
|
||||
},
|
||||
{
|
||||
title: "Rods From Gods",
|
||||
tier: "Tier 6",
|
||||
meta: "1500₦",
|
||||
text: "Razes an entire color set: removes all houses and resets properties.",
|
||||
},
|
||||
{
|
||||
title: "Satan2",
|
||||
tier: "Game Ender",
|
||||
meta: "5000₦",
|
||||
text: "Destroys all of NegoCity and grants immediate victory to the user.",
|
||||
},
|
||||
{
|
||||
title: "La Peste Nègre",
|
||||
tier: "Game Ender",
|
||||
meta: "7000₦",
|
||||
text: "La Peste Nègre (natural selection) eliminates all players except two. The two survivors automatically win.",
|
||||
},
|
||||
],
|
||||
items: buildWeaponCards("en"),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -1486,14 +1247,31 @@ function collectSearchLines(section: RuleSection) {
|
|||
return lines;
|
||||
}
|
||||
|
||||
function buildVehicleCards(locale: Locale): RuleCard[] {
|
||||
return helpVehicles.map((vehicle) => ({
|
||||
title: `${getLocalizedText(vehicle.tier, locale)} — ${getLocalizedText(vehicle.name, locale)}`,
|
||||
meta: getLocalizedText(vehicle.price, locale),
|
||||
text: getLocalizedText(vehicle.text, locale),
|
||||
}));
|
||||
}
|
||||
|
||||
function buildWeaponCards(locale: Locale): TierCard[] {
|
||||
return helpWeapons.map((weapon) => ({
|
||||
title: getLocalizedText(weapon.name, locale),
|
||||
tier: getLocalizedText(weapon.tier, locale),
|
||||
meta: getLocalizedText(weapon.price, locale),
|
||||
text: getLocalizedText(weapon.text, locale),
|
||||
}));
|
||||
}
|
||||
|
||||
function buildPropertyRows(locale: Locale) {
|
||||
return properties.map((property) => [
|
||||
return helpProperties.map((property) => [
|
||||
<span
|
||||
key={`${property.name}-color`}
|
||||
key={`${property.id}-color`}
|
||||
className={`property-swatch ${property.color}`}
|
||||
aria-label={property.color}
|
||||
/>,
|
||||
property.name,
|
||||
getLocalizedText(property.name, locale),
|
||||
formatMoney(property.price, locale),
|
||||
formatMoney(property.houseCost, locale),
|
||||
formatMoney(property.rent, locale),
|
||||
|
|
|
|||
15
mobile/metro.config.js
Normal file
15
mobile/metro.config.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
const path = require("node:path");
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
|
||||
const projectRoot = __dirname;
|
||||
const workspaceRoot = path.resolve(projectRoot, "..");
|
||||
|
||||
const config = getDefaultConfig(projectRoot);
|
||||
|
||||
config.watchFolders = [workspaceRoot];
|
||||
config.resolver.nodeModulesPaths = [
|
||||
path.resolve(projectRoot, "node_modules"),
|
||||
path.resolve(workspaceRoot, "node_modules"),
|
||||
];
|
||||
|
||||
module.exports = config;
|
||||
|
|
@ -33,6 +33,7 @@ const scenes = [
|
|||
{ slug: "lobby", fileName: "Lobby.png" },
|
||||
{ slug: "home", fileName: "Home.png" },
|
||||
{ slug: "transfers", fileName: "Transfers.png" },
|
||||
{ slug: "help", fileName: "Help.png" },
|
||||
{ slug: "chat", fileName: "Chat.png" },
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
import type { SessionSnapshot } from "../shared/types";
|
||||
|
||||
export type ScreenshotScene = "start" | "lobby" | "home" | "transfers" | "chat";
|
||||
export type ScreenshotScene =
|
||||
| "start"
|
||||
| "lobby"
|
||||
| "home"
|
||||
| "transfers"
|
||||
| "help"
|
||||
| "chat";
|
||||
|
||||
export type ScreenshotNavigationState = {
|
||||
index: number;
|
||||
|
|
@ -278,6 +284,7 @@ export function normalizeScreenshotScene(value: string | null | undefined): Scre
|
|||
if (normalized === "lobby") return "lobby";
|
||||
if (normalized === "home") return "home";
|
||||
if (normalized === "transfers") return "transfers";
|
||||
if (normalized === "help") return "help";
|
||||
if (normalized === "chat") return "chat";
|
||||
return null;
|
||||
}
|
||||
|
|
@ -352,6 +359,27 @@ export function buildScreenshotFixture(scene: ScreenshotScene): ScreenshotFixtur
|
|||
};
|
||||
}
|
||||
|
||||
if (scene === "help") {
|
||||
return {
|
||||
scene,
|
||||
sessionId: activeSessionId,
|
||||
sessionCode,
|
||||
playerId: meId,
|
||||
session: createActiveSession(),
|
||||
navigationTarget: {
|
||||
root: "PlayerTabs",
|
||||
state: {
|
||||
index: 0,
|
||||
routes: [{ name: "PlayerHelp" }],
|
||||
},
|
||||
followUp: {
|
||||
name: "PlayerTabs",
|
||||
params: { screen: "PlayerHelp" },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
scene,
|
||||
sessionId: activeSessionId,
|
||||
|
|
|
|||
|
|
@ -131,6 +131,22 @@ const translations = {
|
|||
"home.balance": "Balance",
|
||||
"home.recent": "Recent operations",
|
||||
"home.noActivity": "No activity yet.",
|
||||
"help.introTitle": "Quick rules reference",
|
||||
"help.introBody":
|
||||
"Swipe through the main assets and equipment without leaving the game. This tab is a condensed mobile version of the web rules guide.",
|
||||
"help.swipeHint": "Swipe sideways to browse every card.",
|
||||
"help.section.properties": "Real estate",
|
||||
"help.section.vehicles": "Vehicles",
|
||||
"help.section.weapons": "Weapons",
|
||||
"help.field.price": "Price",
|
||||
"help.field.houseCost": "House cost",
|
||||
"help.field.baseRent": "Base rent",
|
||||
"help.field.rent1": "1 house",
|
||||
"help.field.rent2": "2 houses",
|
||||
"help.field.rent3": "3 houses",
|
||||
"help.field.rent4": "4 houses",
|
||||
"help.field.rent5": "5 houses",
|
||||
"help.field.mortgage": "Mortgage",
|
||||
"blackout.title": "EMP",
|
||||
"blackout.defaultReason": "EMP in effect",
|
||||
"blackout.active": "EMP active",
|
||||
|
|
@ -199,6 +215,7 @@ const translations = {
|
|||
"chat.messagePlaceholder": "Message",
|
||||
"tabs.home": "Accounts",
|
||||
"tabs.transfers": "Payments",
|
||||
"tabs.help": "Help",
|
||||
"tabs.chat": "Messages",
|
||||
"tabs.dashboard": "Agency",
|
||||
"tabs.tools": "Control",
|
||||
|
|
@ -343,6 +360,22 @@ const translations = {
|
|||
"home.balance": "Solde",
|
||||
"home.recent": "Opérations récentes",
|
||||
"home.noActivity": "Aucune activité.",
|
||||
"help.introTitle": "Repères rapides",
|
||||
"help.introBody":
|
||||
"Faites défiler les principaux biens et équipements sans quitter la partie. Cet onglet reprend une version condensée du guide web.",
|
||||
"help.swipeHint": "Faites glisser horizontalement pour voir toutes les cartes.",
|
||||
"help.section.properties": "Immobilier",
|
||||
"help.section.vehicles": "Véhicules",
|
||||
"help.section.weapons": "Armes",
|
||||
"help.field.price": "Prix",
|
||||
"help.field.houseCost": "Coût maison",
|
||||
"help.field.baseRent": "Loyer",
|
||||
"help.field.rent1": "1 maison",
|
||||
"help.field.rent2": "2 maisons",
|
||||
"help.field.rent3": "3 maisons",
|
||||
"help.field.rent4": "4 maisons",
|
||||
"help.field.rent5": "5 maisons",
|
||||
"help.field.mortgage": "Hypothèque",
|
||||
"blackout.title": "EMP",
|
||||
"blackout.defaultReason": "EMP en cours",
|
||||
"blackout.active": "EMP actif",
|
||||
|
|
@ -411,6 +444,7 @@ const translations = {
|
|||
"chat.messagePlaceholder": "Message",
|
||||
"tabs.home": "Comptes",
|
||||
"tabs.transfers": "Paiements",
|
||||
"tabs.help": "Aide",
|
||||
"tabs.chat": "Messages",
|
||||
"tabs.dashboard": "Agence",
|
||||
"tabs.tools": "Pilotage",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import AgencyCreateScreen from "../screens/AgencyCreateScreen";
|
|||
import LobbyScreen from "../screens/LobbyScreen";
|
||||
import PlayerHomeScreen from "../screens/PlayerHomeScreen";
|
||||
import PlayerTransfersScreen from "../screens/PlayerTransfersScreen";
|
||||
import PlayerHelpScreen from "../screens/PlayerHelpScreen";
|
||||
import BankerDashboardScreen from "../screens/BankerDashboardScreen";
|
||||
import BankerToolsScreen from "../screens/BankerToolsScreen";
|
||||
import ChatListScreen from "../screens/chat/ChatListScreen";
|
||||
|
|
@ -108,6 +109,17 @@ export function PlayerTabsNavigator() {
|
|||
),
|
||||
}}
|
||||
/>
|
||||
<PlayerTabs.Screen
|
||||
name="PlayerHelp"
|
||||
component={PlayerHelpScreen}
|
||||
options={{
|
||||
title: t("tabs.help"),
|
||||
headerTitle: buildHeaderTitle(t("tabs.help")),
|
||||
tabBarIcon: ({ color, size }) => (
|
||||
<Ionicons name="help-circle-outline" size={size} color={color} />
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<PlayerTabs.Screen
|
||||
name="PlayerChat"
|
||||
component={ChatStackNavigator}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export type ChatStackParamList = {
|
|||
export type PlayerTabsParamList = {
|
||||
PlayerHome: undefined;
|
||||
PlayerTransfers: undefined;
|
||||
PlayerHelp: undefined;
|
||||
PlayerChat: undefined;
|
||||
};
|
||||
|
||||
|
|
|
|||
451
mobile/src/screens/PlayerHelpScreen.tsx
Normal file
451
mobile/src/screens/PlayerHelpScreen.tsx
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
import React, { useMemo } from "react";
|
||||
import {
|
||||
FlatList,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
useWindowDimensions,
|
||||
} from "react-native";
|
||||
import {
|
||||
getLocalizedText,
|
||||
helpProperties,
|
||||
helpVehicles,
|
||||
helpWeapons,
|
||||
type HelpLocale,
|
||||
type HelpProperty,
|
||||
type HelpPropertyColor,
|
||||
type HelpVehicle,
|
||||
type HelpWeapon,
|
||||
} from "../../../shared/help-catalog";
|
||||
import EmpOverlay from "../components/EmpOverlay";
|
||||
import { useI18n } from "../i18n";
|
||||
import { useSession } from "../state/session-context";
|
||||
import { useTheme } from "../theme";
|
||||
import type { AppTheme } from "../theme";
|
||||
|
||||
const PROPERTY_SWATCHES: Record<HelpPropertyColor, string> = {
|
||||
brown: "#714826",
|
||||
cyan: "#87d8d9",
|
||||
pink: "#bc46ca",
|
||||
green: "#5b933c",
|
||||
red: "#ab3d31",
|
||||
yellow: "#f8ff72",
|
||||
orange: "#e9b054",
|
||||
blue: "#0e08a2",
|
||||
};
|
||||
|
||||
const PROPERTY_LIGHT_TEXT: Record<HelpPropertyColor, boolean> = {
|
||||
brown: true,
|
||||
cyan: false,
|
||||
pink: true,
|
||||
green: true,
|
||||
red: true,
|
||||
yellow: false,
|
||||
orange: false,
|
||||
blue: true,
|
||||
};
|
||||
|
||||
const SCREEN_PADDING = 20;
|
||||
const CARD_GAP = 12;
|
||||
|
||||
function formatMoney(amount: number, locale: HelpLocale) {
|
||||
const intlLocale = locale === "fr" ? "fr-FR" : "en-US";
|
||||
return `₦${new Intl.NumberFormat(intlLocale, {
|
||||
maximumFractionDigits: 0,
|
||||
}).format(amount)}`;
|
||||
}
|
||||
|
||||
function PropertyCard({
|
||||
property,
|
||||
locale,
|
||||
cardWidth,
|
||||
theme,
|
||||
t,
|
||||
}: {
|
||||
property: HelpProperty;
|
||||
locale: HelpLocale;
|
||||
cardWidth: number;
|
||||
theme: AppTheme;
|
||||
t: ReturnType<typeof useI18n>["t"];
|
||||
}) {
|
||||
const styles = useMemo(() => createStyles(theme), [theme]);
|
||||
const propertyName = getLocalizedText(property.name, locale);
|
||||
const swatchColor = PROPERTY_SWATCHES[property.color];
|
||||
const swatchTextColor = PROPERTY_LIGHT_TEXT[property.color] ? "#fff8ee" : "#241a08";
|
||||
const stats = [
|
||||
{ label: t("help.field.price"), value: formatMoney(property.price, locale) },
|
||||
{ label: t("help.field.houseCost"), value: formatMoney(property.houseCost, locale) },
|
||||
{ label: t("help.field.baseRent"), value: formatMoney(property.rent, locale) },
|
||||
{ label: t("help.field.rent1"), value: formatMoney(property.rent1, locale) },
|
||||
{ label: t("help.field.rent2"), value: formatMoney(property.rent2, locale) },
|
||||
{ label: t("help.field.rent3"), value: formatMoney(property.rent3, locale) },
|
||||
{ label: t("help.field.rent4"), value: formatMoney(property.rent4, locale) },
|
||||
{ label: t("help.field.rent5"), value: formatMoney(property.rent5, locale) },
|
||||
{ label: t("help.field.mortgage"), value: formatMoney(property.mortgage, locale) },
|
||||
];
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.propertyCard,
|
||||
{
|
||||
width: cardWidth,
|
||||
borderColor: swatchColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<View style={styles.cardHeader}>
|
||||
<View
|
||||
style={[
|
||||
styles.colorBadge,
|
||||
{
|
||||
backgroundColor: swatchColor,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={[styles.colorBadgeText, { color: swatchTextColor }]}>
|
||||
{property.color.toUpperCase()}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.propertyTitle}>{propertyName}</Text>
|
||||
</View>
|
||||
<View style={styles.statsList}>
|
||||
{stats.map((stat) => (
|
||||
<View key={`${property.id}-${stat.label}`} style={styles.statRow}>
|
||||
<Text style={styles.statLabel}>{stat.label}</Text>
|
||||
<Text style={styles.statValue}>{stat.value}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function AssetCard({
|
||||
item,
|
||||
locale,
|
||||
cardWidth,
|
||||
theme,
|
||||
}: {
|
||||
item: HelpVehicle | HelpWeapon;
|
||||
locale: HelpLocale;
|
||||
cardWidth: number;
|
||||
theme: AppTheme;
|
||||
}) {
|
||||
const styles = useMemo(() => createStyles(theme), [theme]);
|
||||
|
||||
return (
|
||||
<View style={[styles.assetCard, { width: cardWidth }]}>
|
||||
<View style={styles.cardHeader}>
|
||||
<View style={styles.assetPills}>
|
||||
<View style={styles.tierPill}>
|
||||
<Text style={styles.tierPillText}>{getLocalizedText(item.tier, locale)}</Text>
|
||||
</View>
|
||||
<View style={styles.pricePill}>
|
||||
<Text style={styles.pricePillText}>{getLocalizedText(item.price, locale)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.assetTitle}>{getLocalizedText(item.name, locale)}</Text>
|
||||
</View>
|
||||
<Text style={styles.assetBody}>{getLocalizedText(item.text, locale)}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function CarouselSection({
|
||||
title,
|
||||
hint,
|
||||
styles,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
hint: string;
|
||||
styles: ReturnType<typeof createStyles>;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<View>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={styles.sectionTitle}>{title}</Text>
|
||||
<Text style={styles.sectionHint}>{hint}</Text>
|
||||
</View>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export default function PlayerHelpScreen() {
|
||||
const manager = useSession();
|
||||
const { t, locale } = useI18n();
|
||||
const theme = useTheme();
|
||||
const styles = useMemo(() => createStyles(theme), [theme]);
|
||||
const { width } = useWindowDimensions();
|
||||
const cardWidth = Math.max(280, Math.min(width - SCREEN_PADDING * 2 - 18, 420));
|
||||
const snapInterval = cardWidth + CARD_GAP;
|
||||
const showEmp = Boolean(manager.session?.blackoutActive) && !manager.isBanker;
|
||||
|
||||
if (!manager.session || !manager.me) {
|
||||
return (
|
||||
<View style={styles.loadingContainer}>
|
||||
<Text style={styles.helper}>{t("common.loading")}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.wrapper}>
|
||||
<ScrollView
|
||||
style={styles.scroll}
|
||||
contentContainerStyle={styles.content}
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
<View style={styles.heroCard}>
|
||||
<Text style={styles.heroTitle}>{t("help.introTitle")}</Text>
|
||||
<Text style={styles.heroBody}>{t("help.introBody")}</Text>
|
||||
</View>
|
||||
|
||||
<CarouselSection
|
||||
title={t("help.section.properties")}
|
||||
hint={t("help.swipeHint")}
|
||||
styles={styles}
|
||||
>
|
||||
<FlatList
|
||||
data={helpProperties}
|
||||
keyExtractor={(item) => item.id}
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
decelerationRate="fast"
|
||||
snapToInterval={snapInterval}
|
||||
snapToAlignment="start"
|
||||
disableIntervalMomentum
|
||||
contentContainerStyle={styles.carouselContent}
|
||||
ItemSeparatorComponent={() => <View style={styles.carouselGap} />}
|
||||
renderItem={({ item }) => (
|
||||
<PropertyCard
|
||||
property={item}
|
||||
locale={locale}
|
||||
cardWidth={cardWidth}
|
||||
theme={theme}
|
||||
t={t}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</CarouselSection>
|
||||
|
||||
<CarouselSection
|
||||
title={t("help.section.vehicles")}
|
||||
hint={t("help.swipeHint")}
|
||||
styles={styles}
|
||||
>
|
||||
<FlatList
|
||||
data={helpVehicles}
|
||||
keyExtractor={(item) => item.id}
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
decelerationRate="fast"
|
||||
snapToInterval={snapInterval}
|
||||
snapToAlignment="start"
|
||||
disableIntervalMomentum
|
||||
contentContainerStyle={styles.carouselContent}
|
||||
ItemSeparatorComponent={() => <View style={styles.carouselGap} />}
|
||||
renderItem={({ item }) => (
|
||||
<AssetCard
|
||||
item={item}
|
||||
locale={locale}
|
||||
cardWidth={cardWidth}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</CarouselSection>
|
||||
|
||||
<CarouselSection
|
||||
title={t("help.section.weapons")}
|
||||
hint={t("help.swipeHint")}
|
||||
styles={styles}
|
||||
>
|
||||
<FlatList
|
||||
data={helpWeapons}
|
||||
keyExtractor={(item) => item.id}
|
||||
horizontal
|
||||
nestedScrollEnabled
|
||||
showsHorizontalScrollIndicator={false}
|
||||
decelerationRate="fast"
|
||||
snapToInterval={snapInterval}
|
||||
snapToAlignment="start"
|
||||
disableIntervalMomentum
|
||||
contentContainerStyle={styles.carouselContent}
|
||||
ItemSeparatorComponent={() => <View style={styles.carouselGap} />}
|
||||
renderItem={({ item }) => (
|
||||
<AssetCard
|
||||
item={item}
|
||||
locale={locale}
|
||||
cardWidth={cardWidth}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</CarouselSection>
|
||||
</ScrollView>
|
||||
<EmpOverlay visible={showEmp} reason={manager.session.blackoutReason} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const createStyles = (theme: AppTheme) =>
|
||||
StyleSheet.create({
|
||||
wrapper: {
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
scroll: {
|
||||
flex: 1,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
content: {
|
||||
padding: SCREEN_PADDING,
|
||||
gap: 24,
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
padding: SCREEN_PADDING,
|
||||
justifyContent: "center",
|
||||
backgroundColor: theme.colors.background,
|
||||
},
|
||||
helper: {
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
heroCard: {
|
||||
backgroundColor: theme.colors.surface,
|
||||
borderRadius: 18,
|
||||
padding: 18,
|
||||
gap: 8,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.borderMuted,
|
||||
},
|
||||
heroTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: "700",
|
||||
color: theme.colors.text,
|
||||
},
|
||||
heroBody: {
|
||||
color: theme.colors.textMuted,
|
||||
lineHeight: 20,
|
||||
},
|
||||
sectionHeader: {
|
||||
gap: 4,
|
||||
marginBottom: 12,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: "700",
|
||||
color: theme.colors.text,
|
||||
},
|
||||
sectionHint: {
|
||||
fontSize: 13,
|
||||
color: theme.colors.textMuted,
|
||||
},
|
||||
carouselContent: {
|
||||
paddingRight: SCREEN_PADDING,
|
||||
},
|
||||
carouselGap: {
|
||||
width: CARD_GAP,
|
||||
},
|
||||
propertyCard: {
|
||||
backgroundColor: theme.colors.surface,
|
||||
borderRadius: 18,
|
||||
padding: 16,
|
||||
borderWidth: 1,
|
||||
gap: 16,
|
||||
},
|
||||
assetCard: {
|
||||
backgroundColor: theme.colors.surface,
|
||||
borderRadius: 18,
|
||||
padding: 16,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
gap: 14,
|
||||
},
|
||||
cardHeader: {
|
||||
gap: 10,
|
||||
},
|
||||
colorBadge: {
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 999,
|
||||
},
|
||||
colorBadgeText: {
|
||||
fontSize: 11,
|
||||
fontWeight: "800",
|
||||
letterSpacing: 0.6,
|
||||
},
|
||||
propertyTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: "700",
|
||||
color: theme.colors.text,
|
||||
},
|
||||
statsList: {
|
||||
gap: 10,
|
||||
},
|
||||
statRow: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
paddingBottom: 10,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: theme.colors.borderMuted,
|
||||
},
|
||||
statLabel: {
|
||||
flex: 1,
|
||||
color: theme.colors.textMuted,
|
||||
fontSize: 13,
|
||||
},
|
||||
statValue: {
|
||||
color: theme.colors.text,
|
||||
fontWeight: "700",
|
||||
fontSize: 13,
|
||||
},
|
||||
assetPills: {
|
||||
flexDirection: "row",
|
||||
gap: 8,
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
tierPill: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 999,
|
||||
backgroundColor: theme.colors.brandSurface,
|
||||
},
|
||||
tierPillText: {
|
||||
color: theme.colors.brandText,
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
},
|
||||
pricePill: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 999,
|
||||
backgroundColor: theme.colors.accentSurface,
|
||||
},
|
||||
pricePillText: {
|
||||
color: theme.colors.accentText,
|
||||
fontSize: 12,
|
||||
fontWeight: "700",
|
||||
},
|
||||
assetTitle: {
|
||||
fontSize: 20,
|
||||
fontWeight: "700",
|
||||
color: theme.colors.text,
|
||||
},
|
||||
assetBody: {
|
||||
color: theme.colors.textMuted,
|
||||
lineHeight: 20,
|
||||
},
|
||||
});
|
||||
62
shared/help-catalog.test.ts
Normal file
62
shared/help-catalog.test.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { describe, expect, it } from "bun:test";
|
||||
import { helpProperties, helpVehicles, helpWeapons } from "./help-catalog";
|
||||
|
||||
describe("help catalog", () => {
|
||||
it("exports the expected item counts", () => {
|
||||
expect(helpProperties).toHaveLength(22);
|
||||
expect(helpVehicles).toHaveLength(7);
|
||||
expect(helpWeapons).toHaveLength(10);
|
||||
});
|
||||
|
||||
it("provides both locales for every exported item", () => {
|
||||
helpProperties.forEach((property) => {
|
||||
expect(property.name.en.length).toBeGreaterThan(0);
|
||||
expect(property.name.fr.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
helpVehicles.forEach((vehicle) => {
|
||||
expect(vehicle.name.en.length).toBeGreaterThan(0);
|
||||
expect(vehicle.name.fr.length).toBeGreaterThan(0);
|
||||
expect(vehicle.tier.en.length).toBeGreaterThan(0);
|
||||
expect(vehicle.tier.fr.length).toBeGreaterThan(0);
|
||||
expect(vehicle.price.en.length).toBeGreaterThan(0);
|
||||
expect(vehicle.price.fr.length).toBeGreaterThan(0);
|
||||
expect(vehicle.text.en.length).toBeGreaterThan(0);
|
||||
expect(vehicle.text.fr.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
helpWeapons.forEach((weapon) => {
|
||||
expect(weapon.name.en.length).toBeGreaterThan(0);
|
||||
expect(weapon.name.fr.length).toBeGreaterThan(0);
|
||||
expect(weapon.tier.en.length).toBeGreaterThan(0);
|
||||
expect(weapon.tier.fr.length).toBeGreaterThan(0);
|
||||
expect(weapon.price.en.length).toBeGreaterThan(0);
|
||||
expect(weapon.price.fr.length).toBeGreaterThan(0);
|
||||
expect(weapon.text.en.length).toBeGreaterThan(0);
|
||||
expect(weapon.text.fr.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("matches key spot values from the rules data", () => {
|
||||
expect(helpProperties[0]).toMatchObject({
|
||||
id: "negotown",
|
||||
price: 60,
|
||||
houseCost: 50,
|
||||
mortgage: 30,
|
||||
color: "brown",
|
||||
});
|
||||
expect(helpProperties[21]).toMatchObject({
|
||||
id: "lbtrd-tower",
|
||||
rent5: 2000,
|
||||
color: "blue",
|
||||
});
|
||||
expect(helpVehicles[6]).toMatchObject({
|
||||
id: "teleporteur-de-poche",
|
||||
price: { en: "800₦", fr: "800₦" },
|
||||
});
|
||||
expect(helpWeapons[5]).toMatchObject({
|
||||
id: "emp-bomb",
|
||||
name: { en: "EMP Bomb", fr: "Bombe IEM" },
|
||||
});
|
||||
});
|
||||
});
|
||||
539
shared/help-catalog.ts
Normal file
539
shared/help-catalog.ts
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
export type HelpLocale = "en" | "fr";
|
||||
|
||||
export type LocalizedText = {
|
||||
en: string;
|
||||
fr: string;
|
||||
};
|
||||
|
||||
export type HelpPropertyColor = "brown" | "cyan" | "pink" | "green" | "red" | "yellow" | "orange" | "blue";
|
||||
|
||||
export type HelpProperty = {
|
||||
id: string;
|
||||
color: HelpPropertyColor;
|
||||
name: LocalizedText;
|
||||
price: number;
|
||||
houseCost: number;
|
||||
rent: number;
|
||||
rent1: number;
|
||||
rent2: number;
|
||||
rent3: number;
|
||||
rent4: number;
|
||||
rent5: number;
|
||||
mortgage: number;
|
||||
};
|
||||
|
||||
export type HelpVehicle = {
|
||||
id: string;
|
||||
name: LocalizedText;
|
||||
tier: LocalizedText;
|
||||
price: LocalizedText;
|
||||
text: LocalizedText;
|
||||
};
|
||||
|
||||
export type HelpWeapon = {
|
||||
id: string;
|
||||
name: LocalizedText;
|
||||
tier: LocalizedText;
|
||||
price: LocalizedText;
|
||||
text: LocalizedText;
|
||||
};
|
||||
|
||||
export function getLocalizedText(value: LocalizedText, locale: HelpLocale): string {
|
||||
return value[locale];
|
||||
}
|
||||
|
||||
export const helpProperties: HelpProperty[] = [
|
||||
{
|
||||
id: "negotown",
|
||||
name: { en: "Negotown", fr: "Négotown" },
|
||||
price: 60,
|
||||
houseCost: 50,
|
||||
rent: 2,
|
||||
rent1: 10,
|
||||
rent2: 30,
|
||||
rent3: 90,
|
||||
rent4: 160,
|
||||
rent5: 250,
|
||||
mortgage: 30,
|
||||
color: "brown",
|
||||
},
|
||||
{
|
||||
id: "black-arretxea",
|
||||
name: { en: "Black Arretxea", fr: "Black Arretxea" },
|
||||
price: 60,
|
||||
houseCost: 50,
|
||||
rent: 4,
|
||||
rent1: 20,
|
||||
rent2: 60,
|
||||
rent3: 180,
|
||||
rent4: 320,
|
||||
rent5: 450,
|
||||
mortgage: 30,
|
||||
color: "brown",
|
||||
},
|
||||
{
|
||||
id: "17-rue-des-patates",
|
||||
name: { en: "17 rue des patates", fr: "17 rue des patates" },
|
||||
price: 100,
|
||||
houseCost: 50,
|
||||
rent: 6,
|
||||
rent1: 30,
|
||||
rent2: 90,
|
||||
rent3: 270,
|
||||
rent4: 400,
|
||||
rent5: 550,
|
||||
mortgage: 50,
|
||||
color: "cyan",
|
||||
},
|
||||
{
|
||||
id: "69-rue-des-patates",
|
||||
name: { en: "69 rue des patates", fr: "69 rue des patates" },
|
||||
price: 100,
|
||||
houseCost: 50,
|
||||
rent: 6,
|
||||
rent1: 30,
|
||||
rent2: 90,
|
||||
rent3: 270,
|
||||
rent4: 400,
|
||||
rent5: 550,
|
||||
mortgage: 50,
|
||||
color: "cyan",
|
||||
},
|
||||
{
|
||||
id: "420-rue-des-patates",
|
||||
name: { en: "420 rue des patates", fr: "420 rue des patates" },
|
||||
price: 120,
|
||||
houseCost: 50,
|
||||
rent: 8,
|
||||
rent1: 40,
|
||||
rent2: 100,
|
||||
rent3: 300,
|
||||
rent4: 450,
|
||||
rent5: 600,
|
||||
mortgage: 60,
|
||||
color: "cyan",
|
||||
},
|
||||
{
|
||||
id: "nuketown",
|
||||
name: { en: "Nuketown", fr: "Nuketown" },
|
||||
price: 140,
|
||||
houseCost: 100,
|
||||
rent: 10,
|
||||
rent1: 50,
|
||||
rent2: 150,
|
||||
rent3: 450,
|
||||
rent4: 625,
|
||||
rent5: 750,
|
||||
mortgage: 70,
|
||||
color: "pink",
|
||||
},
|
||||
{
|
||||
id: "hijacked",
|
||||
name: { en: "Hijacked", fr: "Hijacked" },
|
||||
price: 140,
|
||||
houseCost: 100,
|
||||
rent: 10,
|
||||
rent1: 50,
|
||||
rent2: 150,
|
||||
rent3: 450,
|
||||
rent4: 625,
|
||||
rent5: 750,
|
||||
mortgage: 70,
|
||||
color: "pink",
|
||||
},
|
||||
{
|
||||
id: "bassland",
|
||||
name: { en: "Bassland", fr: "Bassland" },
|
||||
price: 160,
|
||||
houseCost: 100,
|
||||
rent: 12,
|
||||
rent1: 60,
|
||||
rent2: 180,
|
||||
rent3: 500,
|
||||
rent4: 700,
|
||||
rent5: 900,
|
||||
mortgage: 80,
|
||||
color: "pink",
|
||||
},
|
||||
{
|
||||
id: "numera-fight-club",
|
||||
name: { en: "Numera Fight Club", fr: "Numera Fight Club" },
|
||||
price: 180,
|
||||
houseCost: 100,
|
||||
rent: 14,
|
||||
rent1: 70,
|
||||
rent2: 200,
|
||||
rent3: 550,
|
||||
rent4: 750,
|
||||
rent5: 950,
|
||||
mortgage: 90,
|
||||
color: "green",
|
||||
},
|
||||
{
|
||||
id: "snoopys-id",
|
||||
name: { en: "Snoopy's ID", fr: "Snoopy's ID" },
|
||||
price: 180,
|
||||
houseCost: 100,
|
||||
rent: 14,
|
||||
rent1: 70,
|
||||
rent2: 200,
|
||||
rent3: 550,
|
||||
rent4: 750,
|
||||
rent5: 950,
|
||||
mortgage: 90,
|
||||
color: "green",
|
||||
},
|
||||
{
|
||||
id: "jahland-dispensory",
|
||||
name: { en: "Jahland Dispensory", fr: "Jahland Dispensory" },
|
||||
price: 200,
|
||||
houseCost: 100,
|
||||
rent: 16,
|
||||
rent1: 80,
|
||||
rent2: 220,
|
||||
rent3: 600,
|
||||
rent4: 800,
|
||||
rent5: 1000,
|
||||
mortgage: 100,
|
||||
color: "green",
|
||||
},
|
||||
{
|
||||
id: "pink-hoodie-studio",
|
||||
name: { en: "Pink Hoodie Studio", fr: "Pink Hoodie Studio" },
|
||||
price: 220,
|
||||
houseCost: 150,
|
||||
rent: 18,
|
||||
rent1: 90,
|
||||
rent2: 250,
|
||||
rent3: 700,
|
||||
rent4: 875,
|
||||
rent5: 1050,
|
||||
mortgage: 110,
|
||||
color: "red",
|
||||
},
|
||||
{
|
||||
id: "mp7-studio",
|
||||
name: { en: "MP7 Studio", fr: "MP7 Studio" },
|
||||
price: 220,
|
||||
houseCost: 150,
|
||||
rent: 18,
|
||||
rent1: 90,
|
||||
rent2: 250,
|
||||
rent3: 700,
|
||||
rent4: 875,
|
||||
rent5: 1050,
|
||||
mortgage: 110,
|
||||
color: "red",
|
||||
},
|
||||
{
|
||||
id: "pct-studio",
|
||||
name: { en: "PCT Studio", fr: "PCT Studio" },
|
||||
price: 240,
|
||||
houseCost: 150,
|
||||
rent: 20,
|
||||
rent1: 100,
|
||||
rent2: 300,
|
||||
rent3: 750,
|
||||
rent4: 925,
|
||||
rent5: 1100,
|
||||
mortgage: 120,
|
||||
color: "red",
|
||||
},
|
||||
{
|
||||
id: "elysee",
|
||||
name: { en: "l'Elysée", fr: "l'Elysée" },
|
||||
price: 260,
|
||||
houseCost: 150,
|
||||
rent: 22,
|
||||
rent1: 110,
|
||||
rent2: 330,
|
||||
rent3: 800,
|
||||
rent4: 975,
|
||||
rent5: 1150,
|
||||
mortgage: 130,
|
||||
color: "yellow",
|
||||
},
|
||||
{
|
||||
id: "bar-freak-show",
|
||||
name: { en: "Bar Freak Show", fr: "Bar Freak Show" },
|
||||
price: 260,
|
||||
houseCost: 150,
|
||||
rent: 22,
|
||||
rent1: 110,
|
||||
rent2: 330,
|
||||
rent3: 800,
|
||||
rent4: 975,
|
||||
rent5: 1150,
|
||||
mortgage: 130,
|
||||
color: "yellow",
|
||||
},
|
||||
{
|
||||
id: "garage-de-benoir",
|
||||
name: { en: "Garage de Benoir", fr: "Garage de Benoir" },
|
||||
price: 280,
|
||||
houseCost: 150,
|
||||
rent: 24,
|
||||
rent1: 120,
|
||||
rent2: 360,
|
||||
rent3: 850,
|
||||
rent4: 1025,
|
||||
rent5: 1200,
|
||||
mortgage: 140,
|
||||
color: "yellow",
|
||||
},
|
||||
{
|
||||
id: "rue-vendredi-des-noirs",
|
||||
name: { en: "Rue vendredi des noirs", fr: "Rue vendredi des noirs" },
|
||||
price: 300,
|
||||
houseCost: 200,
|
||||
rent: 26,
|
||||
rent1: 130,
|
||||
rent2: 390,
|
||||
rent3: 900,
|
||||
rent4: 1100,
|
||||
rent5: 1275,
|
||||
mortgage: 150,
|
||||
color: "orange",
|
||||
},
|
||||
{
|
||||
id: "domaine-de-m-p",
|
||||
name: { en: "Domaine de M. P", fr: "Domaine de M. P" },
|
||||
price: 300,
|
||||
houseCost: 200,
|
||||
rent: 26,
|
||||
rent1: 130,
|
||||
rent2: 390,
|
||||
rent3: 900,
|
||||
rent4: 1100,
|
||||
rent5: 1275,
|
||||
mortgage: 150,
|
||||
color: "orange",
|
||||
},
|
||||
{
|
||||
id: "villa-du-rj",
|
||||
name: { en: "Villa du RJ", fr: "Villa du RJ" },
|
||||
price: 320,
|
||||
houseCost: 200,
|
||||
rent: 28,
|
||||
rent1: 150,
|
||||
rent2: 450,
|
||||
rent3: 1000,
|
||||
rent4: 1200,
|
||||
rent5: 1400,
|
||||
mortgage: 160,
|
||||
color: "orange",
|
||||
},
|
||||
{
|
||||
id: "negoplaza",
|
||||
name: { en: "Negoplaza", fr: "Négoplaza" },
|
||||
price: 350,
|
||||
houseCost: 200,
|
||||
rent: 35,
|
||||
rent1: 175,
|
||||
rent2: 500,
|
||||
rent3: 1100,
|
||||
rent4: 1300,
|
||||
rent5: 1500,
|
||||
mortgage: 175,
|
||||
color: "blue",
|
||||
},
|
||||
{
|
||||
id: "lbtrd-tower",
|
||||
name: { en: "LBTRD Tower", fr: "LBTRD Tower" },
|
||||
price: 400,
|
||||
houseCost: 200,
|
||||
rent: 50,
|
||||
rent1: 200,
|
||||
rent2: 600,
|
||||
rent3: 1400,
|
||||
rent4: 1700,
|
||||
rent5: 2000,
|
||||
mortgage: 200,
|
||||
color: "blue",
|
||||
},
|
||||
];
|
||||
|
||||
export const helpVehicles: HelpVehicle[] = [
|
||||
{
|
||||
id: "trottinette-sans-plomb-75",
|
||||
name: {
|
||||
en: "Trottinette au sans-plomb 75",
|
||||
fr: "Trottinette au sans-plomb 75",
|
||||
},
|
||||
tier: { en: "Tier 0", fr: "Tier 0" },
|
||||
price: { en: "150₦", fr: "150₦" },
|
||||
text: {
|
||||
en: "Roll a die: 1–2 breakdown (no effect). 3–5 move forward 1 space. 6 explosion, move back 2 spaces.",
|
||||
fr: "Lancer un dé : 1–2 panne (aucun effet). 3–5 avance d’1 case. 6 explosion, recule de 2 cases.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "exosquelette-backflips",
|
||||
name: {
|
||||
en: "Exosquelette à méga backflips",
|
||||
fr: "Exosquelette à méga backflips",
|
||||
},
|
||||
tier: { en: "Tier 0", fr: "Tier 0" },
|
||||
price: { en: "150₦", fr: "150₦" },
|
||||
text: {
|
||||
en: "Roll a die: 1–2 breakdown. 3–5 move back 1 space. 6 explosion, move forward 2 spaces.",
|
||||
fr: "Lancer un dé : 1–2 panne. 3–5 recule d’1 case. 6 explosion, avance de 2 cases.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "automobile",
|
||||
name: { en: "Automobile", fr: "Automobile" },
|
||||
tier: { en: "Tier 1", fr: "Tier 1" },
|
||||
price: { en: "250₦", fr: "250₦" },
|
||||
text: {
|
||||
en: "Move forward 1 extra space after normal movement.",
|
||||
fr: "Avance d’1 case supplémentaire après le déplacement normal.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "helicoptere",
|
||||
name: { en: "Hélicoptère", fr: "Hélicoptère" },
|
||||
tier: { en: "Tier 2", fr: "Tier 2" },
|
||||
price: { en: "400₦", fr: "400₦" },
|
||||
text: {
|
||||
en: "Choose to move forward or back by 1 to 3 spaces after normal movement.",
|
||||
fr: "Choisir d’avancer ou reculer de 1 à 3 cases après le déplacement normal.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "tank",
|
||||
name: { en: "Tank", fr: "Tank" },
|
||||
tier: { en: "Tier 3", fr: "Tier 3" },
|
||||
price: { en: "450₦", fr: "450₦" },
|
||||
text: {
|
||||
en: "Move forward 2 or 3 spaces. Protects you from negative effects of the landing space and the passed-over space. If you land on another player, they go to Hospital.",
|
||||
fr: "Avance de 2 ou 3 cases. Protège le joueur des effets négatifs de la case d’arrivée et de la case survolée. Si vous arrivez sur un joueur, il est envoyé à l’Hôpital.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "avion-de-chasse",
|
||||
name: { en: "Avion de chasse", fr: "Avion de chasse" },
|
||||
tier: { en: "Tier 4", fr: "Tier 4" },
|
||||
price: { en: "600₦", fr: "600₦" },
|
||||
text: {
|
||||
en: "Choose to move forward 1 to 6 spaces after normal movement.",
|
||||
fr: "Choisir d’avancer de 1 à 6 cases après le déplacement normal.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "teleporteur-de-poche",
|
||||
name: { en: "Téléporteur de poche", fr: "Téléporteur de poche" },
|
||||
tier: { en: "Tier 5", fr: "Tier 5" },
|
||||
price: { en: "800₦", fr: "800₦" },
|
||||
text: {
|
||||
en: "Teleport instantly to any space, ignoring dice and intermediate spaces.",
|
||||
fr: "Téléportation immédiate vers n’importe quelle case, sans tenir compte des dés.",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const helpWeapons: HelpWeapon[] = [
|
||||
{
|
||||
id: "rpg-7",
|
||||
name: { en: "RPG-7", fr: "RPG-7" },
|
||||
tier: { en: "Tier 0", fr: "Tier 0" },
|
||||
price: { en: "150₦", fr: "150₦" },
|
||||
text: {
|
||||
en: "Unlimited stock. Destroys a house on your property or an adjacent one. Up to 2 shots per turn. Roll: 1–4 hit, 5 miss, 6 explosion (go to Hospital). On a mortgaged property, a hit resets it.",
|
||||
fr: "Stock illimité. Détruit une maison sur la propriété où vous êtes ou adjacente. Jusqu’à 2 tirs par tour. Jet : 1–4 réussite, 5 raté, 6 explosion (direction Hôpital). Sur propriété hypothéquée, un tir réussi la réinitialise.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "c4",
|
||||
name: { en: "C4 (charges)", fr: "C4 (charges)" },
|
||||
tier: { en: "Tier 0", fr: "Tier 0" },
|
||||
price: {
|
||||
en: "40₦ first, then x2 / x3…",
|
||||
fr: "40₦ la 1re, puis x2 / x3…",
|
||||
},
|
||||
text: {
|
||||
en: "Unlimited charges. Place on a house by passing over or stopping. Any player passing can remove a charge and keep it. From two rounds after placement, the placer may detonate: destroy house, player on space → Hospital, player adjacent → Prison (terrorist). EMP disables C4 while active.",
|
||||
fr: "Charges illimitées. Pose sur une maison en passant ou en s’arrêtant. Toute charge peut être retirée par n’importe quel joueur en passant (il la récupère). À partir de 2 rounds après la pose, le poseur peut déclencher : maison détruite, joueur sur place → Hôpital, joueur adjacent → Prison (terroriste). Bombe IEM désactive les C4 tant qu’elle est active.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "glock-26",
|
||||
name: { en: "Glock 26", fr: "Glock 26" },
|
||||
tier: { en: "Tier 1", fr: "Tier 1" },
|
||||
price: { en: "200₦", fr: "200₦" },
|
||||
text: {
|
||||
en: "Extort a player on the same or adjacent space for 300₦. If the target also has a Glock, roll: 4–6 attacker wins, 1–3 attacker loses. Loser is extorted and sent to Hospital.",
|
||||
fr: "Racket d’un joueur sur la même case ou adjacente pour 300₦. Si la cible possède aussi une Glock, jet : 4–6 victoire de l’attaquant, 1–3 défaite. Le perdant est racketté et envoyé à l’Hôpital.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "ar15",
|
||||
name: { en: "AR15", fr: "AR15" },
|
||||
tier: { en: "Tier 2", fr: "Tier 2" },
|
||||
price: { en: "400₦", fr: "400₦" },
|
||||
text: {
|
||||
en: "Cancels the effect of any space the player is on.",
|
||||
fr: "Annule l’effet de n’importe quelle case sur laquelle le joueur se trouve.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "mortar",
|
||||
name: { en: "Mortar", fr: "Mortier" },
|
||||
tier: { en: "Tier 3", fr: "Tier 3" },
|
||||
price: { en: "400₦", fr: "400₦" },
|
||||
text: {
|
||||
en: "3 shots. Each shot destroys a house on an adjacent space; if none, the property is mortgaged; if already mortgaged, the property is reset.",
|
||||
fr: "3 tirs. Chaque tir détruit une maison sur une case adjacente ; s’il n’y a pas de maison, la propriété est hypothéquée ; si elle l’est déjà, elle est réinitialisée.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "emp-bomb",
|
||||
name: { en: "EMP Bomb", fr: "Bombe IEM" },
|
||||
tier: { en: "Tier 4", fr: "Tier 4" },
|
||||
price: { en: "400₦", fr: "400₦" },
|
||||
text: {
|
||||
en: "For one turn: all spaces are disabled, remote actions disabled, private communication forbidden (except same space), all C4 charges disabled.",
|
||||
fr: "Pendant un tour : toutes les cases sont désactivées, les actions à distance sont désactivées, les communications privées interdites (sauf même case), toutes les charges de C4 sont désactivées.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "artillery-piece",
|
||||
name: { en: "Artillery piece", fr: "Pièce d’artillerie" },
|
||||
tier: { en: "Tier 5", fr: "Tier 5" },
|
||||
price: { en: "600₦", fr: "600₦" },
|
||||
text: {
|
||||
en: "Removes all houses from a single property.",
|
||||
fr: "Supprime toutes les maisons d’une propriété appartenant à un joueur.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "rods-from-gods",
|
||||
name: { en: "Rods From Gods", fr: "Rods From Gods" },
|
||||
tier: { en: "Tier 6", fr: "Tier 6" },
|
||||
price: { en: "1500₦", fr: "1500₦" },
|
||||
text: {
|
||||
en: "Razes an entire color set: removes all houses and resets properties.",
|
||||
fr: "Rase une couleur entière : supprime toutes les maisons et réinitialise les propriétés.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "satan2",
|
||||
name: { en: "Satan2", fr: "Satan2" },
|
||||
tier: { en: "Game Ender", fr: "Game Ender" },
|
||||
price: { en: "5000₦", fr: "5000₦" },
|
||||
text: {
|
||||
en: "Destroys all of NegoCity and grants immediate victory to the user.",
|
||||
fr: "Détruit l’intégralité de NegoCity et donne la victoire immédiate au joueur qui l’utilise.",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "la-peste-negre",
|
||||
name: { en: "La Peste Nègre", fr: "La Peste Nègre" },
|
||||
tier: { en: "Game Ender", fr: "Game Ender" },
|
||||
price: { en: "7000₦", fr: "7000₦" },
|
||||
text: {
|
||||
en: "La Peste Nègre (natural selection) eliminates all players except two. The two survivors automatically win.",
|
||||
fr: "La Peste Nègre (sélection naturelle) élimine tous les joueurs sauf deux. Les deux survivants remportent automatiquement la partie.",
|
||||
},
|
||||
},
|
||||
];
|
||||
Loading…
Add table
Reference in a new issue